diff --git a/package.json b/package.json index dffd9cee..95f4a719 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,12 @@ { "name": "emp3-web-sdk", "version": "2.6.0", - "keywords": ["extensible", "map", "mapping", "platform"], + "keywords": [ + "extensible", + "map", + "mapping", + "platform" + ], "description": "A library for embedding and manipulationg maps in web applications.", "bugs": { "url": "https://github.com/missioncommand/emp3-web/issues" @@ -33,6 +38,7 @@ "grunt": "^0.4.5", "grunt-concurrent": "^2.3.1", "grunt-contrib-compress": "^1.3.0", + "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "", "grunt-contrib-cssmin": "", "grunt-contrib-jshint": "^0.10.0", diff --git a/src/mapengine/cesium/js/lib/cesium/Cesium.js b/src/mapengine/cesium/js/lib/cesium/Cesium.js index 233742cb..41320ab4 100644 --- a/src/mapengine/cesium/js/lib/cesium/Cesium.js +++ b/src/mapengine/cesium/js/lib/cesium/Cesium.js @@ -456,7 +456,6 @@ var requirejs, require, define; }; }()); -/*global define*/ define('Core/appendForwardSlash',[],function() { 'use strict'; @@ -473,7 +472,6 @@ define('Core/appendForwardSlash',[],function() { return appendForwardSlash; }); -/*global define*/ define('Core/defined',[],function() { 'use strict'; @@ -497,7 +495,6 @@ define('Core/defined',[],function() { return defined; }); -/*global define*/ define('Core/DeveloperError',[ './defined' ], function( @@ -578,7 +575,6 @@ define('Core/DeveloperError',[ return DeveloperError; }); -/*global define*/ define('Core/Check',[ './defined', './DeveloperError' @@ -745,10 +741,26 @@ define('Core/Check',[ } }; + /** + * Throws if test1 and test2 is not typeof 'number' and not equal in value + * + * @param {String} name1 The name of the first variable being tested + * @param {String} name2 The name of the second variable being tested against + * @param {*} test1 The value to test + * @param {*} test2 The value to test against + * @exception {DeveloperError} test1 and test2 should be type of 'number' and be equal in value + */ + Check.typeOf.number.equals = function (name1, name2, test1, test2) { + Check.typeOf.number(name1, test1); + Check.typeOf.number(name2, test2); + if (test1 !== test2) { + throw new DeveloperError(name1 + ' must be equal to ' + name2 + ', the actual values are ' + test1 + ' and ' + test2); + } + }; + return Check; }); -/*global define*/ define('Core/freezeObject',[ './defined' ], function( @@ -774,7 +786,6 @@ define('Core/freezeObject',[ return freezeObject; }); -/*global define*/ define('Core/defaultValue',[ './freezeObject' ], function( @@ -795,7 +806,7 @@ define('Core/defaultValue',[ * param = Cesium.defaultValue(param, 'default'); */ function defaultValue(a, b) { - if (a !== undefined) { + if (a !== undefined && a !== null) { return a; } return b; @@ -810,7 +821,6 @@ define('Core/defaultValue',[ return defaultValue; }); -/*global define*/ define('Core/arrayFill',[ './Check', './defaultValue', @@ -1061,7 +1071,6 @@ MersenneTwister.prototype.random = function() { return MersenneTwister; }); -/*global define*/ define('Core/Math',[ '../ThirdParty/mersenne-twister', './defaultValue', @@ -1715,7 +1724,7 @@ define('Core/Math',[ }; /** - * Generates a random number in the range of [0.0, 1.0) + * Generates a random floating point 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). @@ -1727,8 +1736,20 @@ define('Core/Math',[ return randomNumberGenerator.random(); }; + + /** + * Generates a random number between two numbers. + * + * @param {Number} min The minimum value. + * @param {Number} max The maximum value. + * @returns {Number} A random number between the min and max. + */ + CesiumMath.randomBetween = function(min, max) { + return CesiumMath.nextRandomNumber() * (max - min) + min; + }; + /** - * Computes Math.acos(value), but first clamps value to the range [-1.0, 1.0] + * 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. @@ -1740,7 +1761,7 @@ define('Core/Math',[ }; /** - * Computes Math.asin(value), but first clamps value to the range [-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. @@ -1784,7 +1805,6 @@ define('Core/Math',[ return CesiumMath; }); -/*global define*/ define('Core/arrayRemoveDuplicates',[ './Check', './defaultValue', @@ -1881,7 +1901,6 @@ define('Core/arrayRemoveDuplicates',[ return arrayRemoveDuplicates; }); -/*global define*/ define('Core/defineProperties',[ './defined' ], function( @@ -1916,7 +1935,6 @@ define('Core/defineProperties',[ return defineProperties; }); -/*global define*/ define('Core/AssociativeArray',[ './defined', './defineProperties', @@ -2034,7 +2052,6 @@ define('Core/AssociativeArray',[ return AssociativeArray; }); -/*global define*/ define('Core/Cartesian2',[ './Check', './defaultValue', @@ -2678,14 +2695,13 @@ define('Core/Cartesian2',[ return Cartesian2; }); -/*global define*/ define('Core/Cartesian3',[ - './Check', - './defaultValue', - './defined', - './DeveloperError', - './freezeObject', - './Math' + './Check', + './defaultValue', + './defined', + './DeveloperError', + './freezeObject', + './Math' ], function( Check, defaultValue, @@ -3242,12 +3258,10 @@ define('Core/Cartesian3',[ } else { result = Cartesian3.clone(Cartesian3.UNIT_Z, result); } + } else if (f.y <= f.z) { + result = Cartesian3.clone(Cartesian3.UNIT_Y, result); } else { - if (f.y <= f.z) { - result = Cartesian3.clone(Cartesian3.UNIT_Y, result); - } else { - result = Cartesian3.clone(Cartesian3.UNIT_Z, result); - } + result = Cartesian3.clone(Cartesian3.UNIT_Z, result); } return result; @@ -3586,7 +3600,6 @@ define('Core/Cartesian3',[ return Cartesian3; }); -/*global define*/ define('Core/AttributeCompression',[ './Cartesian2', './Cartesian3', @@ -3830,7 +3843,6 @@ define('Core/AttributeCompression',[ return AttributeCompression; }); -/*global define*/ define('Core/Intersect',[ './freezeObject' ], function( @@ -3874,7 +3886,6 @@ define('Core/Intersect',[ return freezeObject(Intersect); }); -/*global define*/ define('Core/AxisAlignedBoundingBox',[ './Cartesian3', './Check', @@ -4101,7 +4112,6 @@ define('Core/AxisAlignedBoundingBox',[ return AxisAlignedBoundingBox; }); -/*global define*/ define('Core/barycentricCoordinates',[ './Cartesian2', './Cartesian3', @@ -4111,7 +4121,7 @@ define('Core/barycentricCoordinates',[ Cartesian2, Cartesian3, Check, - defined ) { + defined) { 'use strict'; var scratchCartesian1 = new Cartesian3(); @@ -4181,7 +4191,6 @@ define('Core/barycentricCoordinates',[ return barycentricCoordinates; }); -/*global define*/ define('Core/binarySearch',[ './Check', './defined' @@ -4254,7 +4263,6 @@ define('Core/binarySearch',[ return binarySearch; }); -/*global define*/ define('Core/Credit',[ './defined', './defineProperties', @@ -4408,7 +4416,6 @@ define('Core/Credit',[ return Credit; }); -/*global define*/ define('Core/BingMapsApi',[ './Credit', './defined' @@ -4452,7 +4459,7 @@ define('Core/BingMapsApi',[ console.log(errorString); printedBingWarning = true; } - return 'AihaXS6TtE_olKOVdtkMenAMq1L5nDlnU69mRtNisz1vZavr1HhdqGRNkB2Bcqvs'; + return 'Aig5SkZ4pNMN8b4rX-RUH2c_95mK-wjb4WL9k50K51faErEGnNsxgpWHXiqS3Rhe'; } return BingMapsApi.defaultKey; @@ -4497,7 +4504,6 @@ define('Core/BingMapsApi',[ * limitations under the License. * */ -/*global define*/ define('ThirdParty/Uri',[],function() { /** @@ -5499,7 +5505,6 @@ define('ThirdParty/when',[],function () { // Boilerplate for AMD, Node, and browser global ); -/*global define*/ define('Core/combine',[ './defaultValue', './defined' @@ -5580,7 +5585,6 @@ define('Core/combine',[ return combine; }); -/*global define*/ define('Core/isArray',[ './defined' ], function( @@ -5604,7 +5608,6 @@ define('Core/isArray',[ return isArray; }); -/*global define*/ define('Core/objectToQuery',[ './defined', './DeveloperError', @@ -5631,7 +5634,7 @@ define('Core/objectToQuery',[ * key2 : 'a/b', * key3 : ['x', 'y'] * }); - * + * * @see queryToObject * // str will be: * // 'key1=some%20value&key2=a%2Fb&key3=x&key3=y' @@ -5667,7 +5670,6 @@ define('Core/objectToQuery',[ return objectToQuery; }); -/*global define*/ define('Core/queryToObject',[ './defined', './DeveloperError', @@ -5696,7 +5698,7 @@ define('Core/queryToObject',[ * // key2 : 'a/b', * // key3 : ['x', 'y'] * // } - * + * * @see objectToQuery */ function queryToObject(queryString) { @@ -5733,7 +5735,1024 @@ define('Core/queryToObject',[ return queryToObject; }); -/*global define*/ +define('Core/RequestState',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * State of the request. + * + * @exports RequestState + */ + var RequestState = { + /** + * Initial unissued state. + * + * @type Number + * @constant + */ + UNISSUED : 0, + + /** + * Issued but not yet active. Will become active when open slots are available. + * + * @type Number + * @constant + */ + ISSUED : 1, + + /** + * Actual http request has been sent. + * + * @type Number + * @constant + */ + ACTIVE : 2, + + /** + * Request completed successfully. + * + * @type Number + * @constant + */ + RECEIVED : 3, + + /** + * Request was cancelled, either explicitly or automatically because of low priority. + * + * @type Number + * @constant + */ + CANCELLED : 4, + + /** + * Request failed. + * + * @type Number + * @constant + */ + FAILED : 5 + }; + + return freezeObject(RequestState); +}); + +define('Core/RequestType',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * An enum identifying the type of request. Used for finer grained logging and priority sorting. + * + * @exports RequestType + */ + var RequestType = { + /** + * Terrain request. + * + * @type Number + * @constant + */ + TERRAIN : 0, + + /** + * Imagery request. + * + * @type Number + * @constant + */ + IMAGERY : 1, + + /** + * 3D Tiles request. + * + * @type Number + * @constant + */ + TILES3D : 2, + + /** + * Other request. + * + * @type Number + * @constant + */ + OTHER : 3 + }; + + return freezeObject(RequestType); +}); + +define('Core/Request',[ + './defaultValue', + './defined', + './defineProperties', + './RequestState', + './RequestType' + ], function( + defaultValue, + defined, + defineProperties, + RequestState, + RequestType) { + 'use strict'; + + /** + * Stores information for making a request. In general this does not need to be constructed directly. + * + * @alias Request + * @constructor + * + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.url] The url to request. + * @param {Request~RequestCallback} [options.requestFunction] The function that makes the actual data request. + * @param {Request~CancelCallback} [options.cancelFunction] The function that is called when the request is cancelled. + * @param {Request~PriorityCallback} [options.priorityFunction] The function that is called to update the request's priority, which occurs once per frame. + * @param {Number} [options.priority=0.0] The initial priority of the request. + * @param {Boolean} [options.throttle=false] Whether to throttle and prioritize the request. If false, the request will be sent immediately. If true, the request will be throttled and sent based on priority. + * @param {Boolean} [options.throttleByServer=false] Whether to throttle the request by server. + * @param {RequestType} [options.type=RequestType.OTHER] The type of request. + */ + function Request(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var throttleByServer = defaultValue(options.throttleByServer, false); + var throttle = throttleByServer || defaultValue(options.throttle, false); + + /** + * The URL to request. + * + * @type {String} + */ + this.url = options.url; + + /** + * The function that makes the actual data request. + * + * @type {Request~RequestCallback} + */ + this.requestFunction = options.requestFunction; + + /** + * The function that is called when the request is cancelled. + * + * @type {Request~CancelCallback} + */ + this.cancelFunction = options.cancelFunction; + + /** + * The function that is called to update the request's priority, which occurs once per frame. + * + * @type {Request~PriorityCallback} + */ + this.priorityFunction = options.priorityFunction; + + /** + * Priority is a unit-less value where lower values represent higher priority. + * For world-based objects, this is usually the distance from the camera. + * A request that does not have a priority function defaults to a priority of 0. + * + * If priorityFunction is defined, this value is updated every frame with the result of that call. + * + * @type {Number} + * @default 0.0 + */ + this.priority = defaultValue(options.priority, 0.0); + + /** + * Whether to throttle and prioritize the request. If false, the request will be sent immediately. If true, the + * request will be throttled and sent based on priority. + * + * @type {Boolean} + * @readonly + * + * @default false + */ + this.throttle = throttle; + + /** + * Whether to throttle the request by server. Browsers typically support about 6-8 parallel connections + * for HTTP/1 servers, and an unlimited amount of connections for HTTP/2 servers. Setting this value + * to true is preferable for requests going through HTTP/1 servers. + * + * @type {Boolean} + * @readonly + * + * @default false + */ + this.throttleByServer = throttleByServer; + + /** + * Type of request. + * + * @type {RequestType} + * @readonly + * + * @default RequestType.OTHER + */ + this.type = defaultValue(options.type, RequestType.OTHER); + + /** + * A key used to identify the server that a request is going to. It is derived from the url's authority and scheme. + * + * @type {String} + * + * @private + */ + this.serverKey = undefined; + + /** + * The current state of the request. + * + * @type {RequestState} + * @readonly + */ + this.state = RequestState.UNISSUED; + + /** + * The requests's deferred promise. + * + * @type {Object} + * + * @private + */ + this.deferred = undefined; + + /** + * Whether the request was explicitly cancelled. + * + * @type {Boolean} + * + * @private + */ + this.cancelled = false; + } + + /** + * Mark the request as cancelled. + * + * @private + */ + Request.prototype.cancel = function() { + this.cancelled = true; + }; + + /** + * The function that makes the actual data request. + * @callback Request~RequestCallback + * @returns {Promise} A promise for the requested data. + */ + + /** + * The function that is called when the request is cancelled. + * @callback Request~CancelCallback + */ + + /** + * The function that is called to update the request's priority, which occurs once per frame. + * @callback Request~PriorityCallback + * @returns {Number} The updated priority value. + */ + + return Request; +}); + +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; +}); + +define('Core/Heap',[ + './Check', + './defaultValue', + './defined', + './defineProperties' + ], function( + Check, + defaultValue, + defined, + defineProperties) { + 'use strict'; + + /** + * Array implementation of a heap. + * + * @alias Heap + * @constructor + * @private + * + * @param {Object} options Object with the following properties: + * @param {Heap~ComparatorCallback} options.comparator The comparator to use for the heap. If comparator(a, b) is less than 0, sort a to a lower index than b, otherwise sort to a higher index. + */ + function Heap(options) { + + this._comparator = options.comparator; + this._array = []; + this._length = 0; + this._maximumLength = undefined; + } + + defineProperties(Heap.prototype, { + /** + * Gets the length of the heap. + * + * @memberof Heap.prototype + * + * @type {Number} + * @readonly + */ + length : { + get : function() { + return this._length; + } + }, + + /** + * Gets the internal array. + * + * @memberof Heap.prototype + * + * @type {Array} + * @readonly + */ + internalArray : { + get : function() { + return this._array; + } + }, + + /** + * Gets and sets the maximum length of the heap. + * + * @memberof Heap.prototype + * + * @type {Number} + */ + maximumLength : { + get : function() { + return this._maximumLength; + }, + set : function(value) { + this._maximumLength = value; + if (this._length > value && value > 0) { + this._length = value; + this._array.length = value; + } + } + }, + + /** + * The comparator to use for the heap. If comparator(a, b) is less than 0, sort a to a lower index than b, otherwise sort to a higher index. + * + * @memberof Heap.prototype + * + * @type {Heap~ComparatorCallback} + */ + comparator : { + get : function() { + return this._comparator; + } + } + }); + + function swap(array, a, b) { + var temp = array[a]; + array[a] = array[b]; + array[b] = temp; + } + + /** + * Resizes the internal array of the heap. + * + * @param {Number} [length] The length to resize internal array to. Defaults to the current length of the heap. + */ + Heap.prototype.reserve = function(length) { + length = defaultValue(length, this._length); + this._array.length = length; + }; + + /** + * Update the heap so that index and all descendants satisfy the heap property. + * + * @param {Number} [index=0] The starting index to heapify from. + */ + Heap.prototype.heapify = function(index) { + index = defaultValue(index, 0); + var length = this._length; + var comparator = this._comparator; + var array = this._array; + var candidate = -1; + var inserting = true; + + while (inserting) { + var right = 2 * (index + 1); + var left = right - 1; + + if (left < length && comparator(array[left], array[index]) < 0) { + candidate = left; + } else { + candidate = index; + } + + if (right < length && comparator(array[right], array[candidate]) < 0) { + candidate = right; + } + if (candidate !== index) { + swap(array, candidate, index); + index = candidate; + } else { + inserting = false; + } + } + }; + + /** + * Resort the heap. + */ + Heap.prototype.resort = function() { + var length = this._length; + for (var i = Math.ceil(length / 2); i >= 0; --i) { + this.heapify(i); + } + }; + + /** + * Insert an element into the heap. If the length would grow greater than maximumLength + * of the heap, extra elements are removed. + * + * @param {*} element The element to insert + * + * @return {*} The element that was removed from the heap if the heap is at full capacity. + */ + Heap.prototype.insert = function(element) { + + var array = this._array; + var comparator = this._comparator; + var maximumLength = this._maximumLength; + + var index = this._length++; + if (index < array.length) { + array[index] = element; + } else { + array.push(element); + } + + while (index !== 0) { + var parent = Math.floor((index - 1) / 2); + if (comparator(array[index], array[parent]) < 0) { + swap(array, index, parent); + index = parent; + } else { + break; + } + } + + var removedElement; + + if (defined(maximumLength) && (this._length > maximumLength)) { + removedElement = array[maximumLength]; + this._length = maximumLength; + } + + return removedElement; + }; + + /** + * Remove the element specified by index from the heap and return it. + * + * @param {Number} [index=0] The index to remove. + * @returns {*} The specified element of the heap. + */ + Heap.prototype.pop = function(index) { + index = defaultValue(index, 0); + if (this._length === 0) { + return undefined; + } + + var array = this._array; + var root = array[index]; + swap(array, index, --this._length); + this.heapify(index); + return root; + }; + + /** + * The comparator to use for the heap. + * @callback Heap~ComparatorCallback + * @param {*} a An element in the heap. + * @param {*} b An element in the heap. + * @returns {Number} If the result of the comparison is less than 0, sort a to a lower index than b, otherwise sort to a higher index. + */ + + return Heap; +}); + +define('Core/isBlobUri',[ + './Check' + ], function( + Check) { + 'use strict'; + + var blobUriRegex = /^blob:/i; + + /** + * Determines if the specified uri is a blob uri. + * + * @exports isBlobUri + * + * @param {String} uri The uri to test. + * @returns {Boolean} true when the uri is a blob uri; otherwise, false. + * + * @private + */ + function isBlobUri(uri) { + + return blobUriRegex.test(uri); + } + + return isBlobUri; +}); + +define('Core/isDataUri',[ + './Check' + ], function( + Check) { + 'use strict'; + + var dataUriRegex = /^data:/i; + + /** + * Determines if the specified uri is a data uri. + * + * @exports isDataUri + * + * @param {String} uri The uri to test. + * @returns {Boolean} true when the uri is a data uri; otherwise, false. + * + * @private + */ + function isDataUri(uri) { + + return dataUriRegex.test(uri); + } + + return isDataUri; +}); + +define('Core/RequestScheduler',[ + '../ThirdParty/Uri', + '../ThirdParty/when', + './Check', + './clone', + './defined', + './defineProperties', + './Heap', + './isBlobUri', + './isDataUri', + './RequestState' + ], function( + Uri, + when, + Check, + clone, + defined, + defineProperties, + Heap, + isBlobUri, + isDataUri, + RequestState) { + 'use strict'; + + function sortRequests(a, b) { + return a.priority - b.priority; + } + + var statistics = { + numberOfAttemptedRequests : 0, + numberOfActiveRequests : 0, + numberOfCancelledRequests : 0, + numberOfCancelledActiveRequests : 0, + numberOfFailedRequests : 0, + numberOfActiveRequestsEver : 0 + }; + + var priorityHeapLength = 20; + var requestHeap = new Heap({ + comparator : sortRequests + }); + requestHeap.maximumLength = priorityHeapLength; + requestHeap.reserve(priorityHeapLength); + + var activeRequests = []; + var numberOfActiveRequestsByServer = {}; + + var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri(); + + /** + * Tracks the number of active requests and prioritizes incoming requests. + * + * @exports RequestScheduler + * + * @private + */ + function RequestScheduler() { + } + + /** + * The maximum number of simultaneous active requests. Un-throttled requests do not observe this limit. + * @type {Number} + * @default 50 + */ + RequestScheduler.maximumRequests = 50; + + /** + * The maximum number of simultaneous active requests per server. Un-throttled requests do not observe this limit. + * @type {Number} + * @default 6 + */ + RequestScheduler.maximumRequestsPerServer = 6; + + /** + * Specifies if the request scheduler should throttle incoming requests, or let the browser queue requests under its control. + * @type {Boolean} + * @default true + */ + RequestScheduler.throttleRequests = true; + + /** + * When true, log statistics to the console every frame + * @type {Boolean} + * @default false + */ + RequestScheduler.debugShowStatistics = false; + + defineProperties(RequestScheduler, { + /** + * Returns the statistics used by the request scheduler. + * + * @memberof RequestScheduler + * + * @type Object + * @readonly + */ + statistics : { + get : function() { + return statistics; + } + }, + + /** + * The maximum size of the priority heap. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active. + * + * @memberof RequestScheduler + * + * @type {Number} + * @default 20 + */ + priorityHeapLength : { + get : function() { + return priorityHeapLength; + }, + set : function(value) { + // If the new length shrinks the heap, need to cancel some of the requests. + // Since this value is not intended to be tweaked regularly it is fine to just cancel the high priority requests. + if (value < priorityHeapLength) { + while (requestHeap.length > value) { + var request = requestHeap.pop(); + cancelRequest(request); + } + } + priorityHeapLength = value; + requestHeap.maximumLength = value; + requestHeap.reserve(value); + } + } + }); + + function updatePriority(request) { + if (defined(request.priorityFunction)) { + request.priority = request.priorityFunction(); + } + } + + function serverHasOpenSlots(serverKey) { + return numberOfActiveRequestsByServer[serverKey] < RequestScheduler.maximumRequestsPerServer; + } + + function issueRequest(request) { + if (request.state === RequestState.UNISSUED) { + request.state = RequestState.ISSUED; + request.deferred = when.defer(); + } + return request.deferred.promise; + } + + function getRequestReceivedFunction(request) { + return function(results) { + if (request.state === RequestState.CANCELLED) { + // If the data request comes back but the request is cancelled, ignore it. + return; + } + --statistics.numberOfActiveRequests; + --numberOfActiveRequestsByServer[request.serverKey]; + request.state = RequestState.RECEIVED; + request.deferred.resolve(results); + }; + } + + function getRequestFailedFunction(request) { + return function(error) { + if (request.state === RequestState.CANCELLED) { + // If the data request comes back but the request is cancelled, ignore it. + return; + } + ++statistics.numberOfFailedRequests; + --statistics.numberOfActiveRequests; + --numberOfActiveRequestsByServer[request.serverKey]; + request.state = RequestState.FAILED; + request.deferred.reject(error); + }; + } + + function startRequest(request) { + var promise = issueRequest(request); + request.state = RequestState.ACTIVE; + activeRequests.push(request); + ++statistics.numberOfActiveRequests; + ++statistics.numberOfActiveRequestsEver; + ++numberOfActiveRequestsByServer[request.serverKey]; + request.requestFunction().then(getRequestReceivedFunction(request)).otherwise(getRequestFailedFunction(request)); + return promise; + } + + function cancelRequest(request) { + var active = request.state === RequestState.ACTIVE; + request.state = RequestState.CANCELLED; + ++statistics.numberOfCancelledRequests; + request.deferred.reject(); + + if (active) { + --statistics.numberOfActiveRequests; + --numberOfActiveRequestsByServer[request.serverKey]; + ++statistics.numberOfCancelledActiveRequests; + } + + if (defined(request.cancelFunction)) { + request.cancelFunction(); + } + } + + /** + * Sort requests by priority and start requests. + */ + RequestScheduler.update = function() { + var i; + var request; + + // Loop over all active requests. Cancelled, failed, or received requests are removed from the array to make room for new requests. + var removeCount = 0; + var activeLength = activeRequests.length; + for (i = 0; i < activeLength; ++i) { + request = activeRequests[i]; + if (request.cancelled) { + // Request was explicitly cancelled + cancelRequest(request); + } + if (request.state !== RequestState.ACTIVE) { + // Request is no longer active, remove from array + ++removeCount; + continue; + } + if (removeCount > 0) { + // Shift back to fill in vacated slots from completed requests + activeRequests[i - removeCount] = request; + } + } + activeRequests.length -= removeCount; + + // Update priority of issued requests and resort the heap + var issuedRequests = requestHeap.internalArray; + var issuedLength = requestHeap.length; + for (i = 0; i < issuedLength; ++i) { + updatePriority(issuedRequests[i]); + } + requestHeap.resort(); + + // Get the number of open slots and fill with the highest priority requests. + // Un-throttled requests are automatically added to activeRequests, so activeRequests.length may exceed maximumRequests + var openSlots = Math.max(RequestScheduler.maximumRequests - activeRequests.length, 0); + var filledSlots = 0; + while (filledSlots < openSlots && requestHeap.length > 0) { + // Loop until all open slots are filled or the heap becomes empty + request = requestHeap.pop(); + if (request.cancelled) { + // Request was explicitly cancelled + cancelRequest(request); + continue; + } + + if (request.throttleByServer && !serverHasOpenSlots(request.serverKey)) { + // Open slots are available, but the request is throttled by its server. Cancel and try again later. + cancelRequest(request); + continue; + } + + startRequest(request); + ++filledSlots; + } + + updateStatistics(); + }; + + /** + * Get the server key from a given url. + * + * @param {String} url The url. + * @returns {String} The server key. + */ + RequestScheduler.getServerKey = function(url) { + + var uri = new Uri(url).resolve(pageUri); + uri.normalize(); + var serverKey = uri.authority; + if (!/:/.test(serverKey)) { + // If the authority does not contain a port number, add port 443 for https or port 80 for http + serverKey = serverKey + ':' + (uri.scheme === 'https' ? '443' : '80'); + } + + var length = numberOfActiveRequestsByServer[serverKey]; + if (!defined(length)) { + numberOfActiveRequestsByServer[serverKey] = 0; + } + + return serverKey; + }; + + /** + * Issue a request. If request.throttle is false, the request is sent immediately. Otherwise the request will be + * queued and sorted by priority before being sent. + * + * @param {Request} request The request object. + * + * @returns {Promise|undefined} A Promise for the requested data, or undefined if this request does not have high enough priority to be issued. + */ + RequestScheduler.request = function(request) { + + if (isDataUri(request.url) || isBlobUri(request.url)) { + request.state = RequestState.RECEIVED; + return request.requestFunction(); + } + + ++statistics.numberOfAttemptedRequests; + + if (!defined(request.serverKey)) { + request.serverKey = RequestScheduler.getServerKey(request.url); + } + + if (!RequestScheduler.throttleRequests || !request.throttle) { + return startRequest(request); + } + + if (activeRequests.length >= RequestScheduler.maximumRequests) { + // Active requests are saturated. Try again later. + return undefined; + } + + if (request.throttleByServer && !serverHasOpenSlots(request.serverKey)) { + // Server is saturated. Try again later. + return undefined; + } + + // Insert into the priority heap and see if a request was bumped off. If this request is the lowest + // priority it will be returned. + updatePriority(request); + var removedRequest = requestHeap.insert(request); + + if (defined(removedRequest)) { + if (removedRequest === request) { + // Request does not have high enough priority to be issued + return undefined; + } + // A previously issued request has been bumped off the priority heap, so cancel it + cancelRequest(removedRequest); + } + + return issueRequest(request); + }; + + function clearStatistics() { + statistics.numberOfAttemptedRequests = 0; + statistics.numberOfCancelledRequests = 0; + statistics.numberOfCancelledActiveRequests = 0; + } + + function updateStatistics() { + if (!RequestScheduler.debugShowStatistics) { + return; + } + + if (statistics.numberOfAttemptedRequests > 0) { + console.log('Number of attempted requests: ' + statistics.numberOfAttemptedRequests); + } + if (statistics.numberOfActiveRequests > 0) { + console.log('Number of active requests: ' + statistics.numberOfActiveRequests); + } + if (statistics.numberOfCancelledRequests > 0) { + console.log('Number of cancelled requests: ' + statistics.numberOfCancelledRequests); + } + if (statistics.numberOfCancelledActiveRequests > 0) { + console.log('Number of cancelled active requests: ' + statistics.numberOfCancelledActiveRequests); + } + if (statistics.numberOfFailedRequests > 0) { + console.log('Number of failed requests: ' + statistics.numberOfFailedRequests); + } + + clearStatistics(); + } + + /** + * For testing only. Clears any requests that may not have completed from previous tests. + * + * @private + */ + RequestScheduler.clearForSpecs = function() { + while (requestHeap.length > 0) { + var request = requestHeap.pop(); + cancelRequest(request); + } + var length = activeRequests.length; + for (var i = 0; i < length; ++i) { + cancelRequest(activeRequests[i]); + } + activeRequests.length = 0; + numberOfActiveRequestsByServer = {}; + + // Clear stats + statistics.numberOfAttemptedRequests = 0; + statistics.numberOfActiveRequests = 0; + statistics.numberOfCancelledRequests = 0; + statistics.numberOfCancelledActiveRequests = 0; + statistics.numberOfFailedRequests = 0; + statistics.numberOfActiveRequestsEver = 0; + }; + + /** + * For testing only. + * + * @private + */ + RequestScheduler.numberOfActiveRequestsByServer = function(serverKey) { + return numberOfActiveRequestsByServer[serverKey]; + }; + + /** + * For testing only. + * + * @private + */ + RequestScheduler.requestHeap = requestHeap; + + return RequestScheduler; +}); + define('Core/loadJsonp',[ '../ThirdParty/Uri', '../ThirdParty/when', @@ -5742,7 +6761,9 @@ define('Core/loadJsonp',[ './defined', './DeveloperError', './objectToQuery', - './queryToObject' + './queryToObject', + './Request', + './RequestScheduler' ], function( Uri, when, @@ -5751,7 +6772,9 @@ define('Core/loadJsonp',[ defined, DeveloperError, objectToQuery, - queryToObject) { + queryToObject, + Request, + RequestScheduler) { 'use strict'; /** @@ -5764,7 +6787,8 @@ define('Core/loadJsonp',[ * @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. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * * * @example @@ -5774,10 +6798,10 @@ define('Core/loadJsonp',[ * }).otherwise(function(error) { * // an error occurred * }); - * + * * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ - function loadJsonp(url, options) { + function loadJsonp(url, options, request) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -5787,19 +6811,6 @@ define('Core/loadJsonp',[ 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, '')); @@ -5820,9 +6831,27 @@ define('Core/loadJsonp',[ url = proxy.getURL(url); } - loadJsonp.loadAndExecuteScript(url, functionName, deferred); + request = defined(request) ? request : new Request(); + request.url = url; + request.requestFunction = function() { + var deferred = when.defer(); - return deferred.promise; + //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; + } + }; + + loadJsonp.loadAndExecuteScript(url, functionName, deferred); + return deferred.promise; + }; + + return RequestScheduler.request(request); } // This is broken out into a separate function so that it can be mocked for testing purposes. @@ -5848,7 +6877,6 @@ define('Core/loadJsonp',[ return loadJsonp; }); -/*global define*/ define('Core/scaleToGeodeticSurface',[ './Cartesian3', './defined', @@ -5971,7 +6999,6 @@ define('Core/scaleToGeodeticSurface',[ return scaleToGeodeticSurface; }); -/*global define*/ define('Core/Cartographic',[ './Cartesian3', './Check', @@ -6223,10 +7250,10 @@ define('Core/Cartographic',[ return Cartographic; }); -/*global define*/ define('Core/Ellipsoid',[ './Cartesian3', './Cartographic', + './Check', './defaultValue', './defined', './defineProperties', @@ -6237,6 +7264,7 @@ define('Core/Ellipsoid',[ ], function( Cartesian3, Cartographic, + Check, defaultValue, defined, defineProperties, @@ -6277,7 +7305,7 @@ define('Core/Ellipsoid',[ ellipsoid._centerToleranceSquared = CesiumMath.EPSILON1; if (ellipsoid._radiiSquared.z !== 0) { - ellipsoid._sqauredXOverSquaredZ = ellipsoid._radiiSquared.x / ellipsoid._radiiSquared.z; + ellipsoid._squaredXOverSquaredZ = ellipsoid._radiiSquared.x / ellipsoid._radiiSquared.z; } } @@ -6310,7 +7338,7 @@ define('Core/Ellipsoid',[ this._minimumRadius = undefined; this._maximumRadius = undefined; this._centerToleranceSquared = undefined; - this._sqauredXOverSquaredZ = undefined; + this._squaredXOverSquaredZ = undefined; initialize(this, x, y, z); } @@ -6810,9 +7838,9 @@ define('Core/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 + * @param {Cartesian3} [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 + * @returns {Cartesian3 | 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). @@ -6822,7 +7850,7 @@ define('Core/Ellipsoid',[ buffer = defaultValue(buffer, 0.0); - var sqauredXOverSquaredZ = this._sqauredXOverSquaredZ; + var squaredXOverSquaredZ = this._squaredXOverSquaredZ; if (!defined(result)) { result = new Cartesian3(); @@ -6830,7 +7858,7 @@ define('Core/Ellipsoid',[ result.x = 0.0; result.y = 0.0; - result.z = position.z * (1 - sqauredXOverSquaredZ); + result.z = position.z * (1 - squaredXOverSquaredZ); if (Math.abs(result.z) >= this._radii.z - buffer) { return undefined; @@ -6842,7 +7870,6 @@ define('Core/Ellipsoid',[ return Ellipsoid; }); -/*global define*/ define('Core/Rectangle',[ './Cartographic', './Check', @@ -7118,13 +8145,14 @@ define('Core/Rectangle',[ /** * Creates the smallest possible Rectangle that encloses all positions in the provided array. * - * @param {Cartesian[]} cartesians The list of Cartesian instances. + * @param {Cartesian3[]} 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) { - + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + var west = Number.MAX_VALUE; var east = -Number.MAX_VALUE; var westOverIDL = Number.MAX_VALUE; @@ -7168,7 +8196,7 @@ define('Core/Rectangle',[ }; /** - * Duplicates an Rectangle. + * Duplicates a 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. @@ -7248,7 +8276,7 @@ define('Core/Rectangle',[ }; /** - * Checks an Rectangle's properties and throws if they are not in valid ranges. + * Checks a Rectangle's properties and throws if they are not in valid ranges. * * @param {Rectangle} rectangle The rectangle to validate * @@ -7628,23 +8656,22 @@ define('Core/Rectangle',[ return Rectangle; }); -/*global define*/ define('Core/BingMapsGeocoderService',[ - './BingMapsApi', - './Check', - './defaultValue', - './defined', - './defineProperties', - './loadJsonp', - './Rectangle' -], function( - BingMapsApi, - Check, - defaultValue, - defined, - defineProperties, - loadJsonp, - Rectangle) { + './BingMapsApi', + './Check', + './defaultValue', + './defined', + './defineProperties', + './loadJsonp', + './Rectangle' + ], function( + BingMapsApi, + Check, + defaultValue, + defined, + defineProperties, + loadJsonp, + Rectangle) { 'use strict'; var url = 'https://dev.virtualearth.net/REST/v1/Locations'; @@ -7739,7 +8766,6 @@ define('Core/BingMapsGeocoderService',[ return BingMapsGeocoderService; }); -/*global define*/ define('Core/GeographicProjection',[ './Cartesian3', './Cartographic', @@ -7854,7 +8880,6 @@ define('Core/GeographicProjection',[ return GeographicProjection; }); -/*global define*/ define('Core/BoundingRectangle',[ './Cartesian2', './Cartographic', @@ -8208,7 +9233,6 @@ define('Core/BoundingRectangle',[ return BoundingRectangle; }); -/*global define*/ define('Core/Interval',[ './defaultValue' ], function( @@ -8241,7 +9265,6 @@ define('Core/Interval',[ return Interval; }); -/*global define*/ define('Core/Matrix3',[ './Cartesian3', './Check', @@ -9601,7 +10624,6 @@ define('Core/Matrix3',[ return Matrix3; }); -/*global define*/ define('Core/Cartesian4',[ './Check', './defaultValue', @@ -10321,7 +11343,6 @@ define('Core/Cartesian4',[ return Cartesian4; }); -/*global define*/ define('Core/RuntimeError',[ './defined' ], function( @@ -10394,7 +11415,6 @@ define('Core/RuntimeError',[ return RuntimeError; }); -/*global define*/ define('Core/Matrix4',[ './Cartesian3', './Cartesian4', @@ -12780,7 +13800,6 @@ define('Core/Matrix4',[ return Matrix4; }); -/*global define*/ define('Core/BoundingSphere',[ './Cartesian3', './Cartographic', @@ -12883,7 +13902,8 @@ define('Core/BoundingSphere',[ var zMax = Cartesian3.clone(currentPos, fromPointsZMax); var numPositions = positions.length; - for (var i = 1; i < numPositions; i++) { + var i; + for (i = 1; i < numPositions; i++) { Cartesian3.clone(positions[i], currentPos); var x = currentPos.x; @@ -13144,7 +14164,8 @@ define('Core/BoundingSphere',[ var zMax = Cartesian3.clone(currentPos, fromPointsZMax); var numElements = positions.length; - for (var i = 0; i < numElements; i += stride) { + var i; + for (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; @@ -13301,7 +14322,8 @@ define('Core/BoundingSphere',[ var zMax = Cartesian3.clone(currentPos, fromPointsZMax); var numElements = positionsHigh.length; - for (var i = 0; i < numElements; i += 3) { + var i; + for (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]; @@ -13495,7 +14517,8 @@ define('Core/BoundingSphere',[ } var positions = []; - for (var i = 0; i < length; i++) { + var i; + for (i = 0; i < length; i++) { positions.push(boundingSpheres[i].center); } @@ -14022,7 +15045,6 @@ define('Core/BoundingSphere',[ return BoundingSphere; }); -/*global define*/ define('Core/Fullscreen',[ './defined', './defineProperties' @@ -14278,7 +15300,6 @@ define('Core/Fullscreen',[ return Fullscreen; }); -/*global define*/ define('Core/FeatureDetection',[ './defaultValue', './defined', @@ -14541,7 +15562,6 @@ define('Core/FeatureDetection',[ return FeatureDetection; }); -/*global define*/ define('Core/WebGLConstants',[ './freezeObject' ], function( @@ -15156,7 +16176,6 @@ define('Core/WebGLConstants',[ return freezeObject(WebGLConstants); }); -/*global define*/ define('Core/ComponentDatatype',[ './defaultValue', './defined', @@ -15460,7 +16479,6 @@ define('Core/ComponentDatatype',[ return freezeObject(ComponentDatatype); }); -/*global define*/ define('Core/GeometryType',[ './freezeObject' ], function( @@ -15480,7 +16498,6 @@ define('Core/GeometryType',[ return freezeObject(GeometryType); }); -/*global define*/ define('Core/PrimitiveType',[ './freezeObject', './WebGLConstants' @@ -15572,14 +16589,15 @@ define('Core/PrimitiveType',[ return freezeObject(PrimitiveType); }); -/*global define*/ define('Core/Geometry',[ + './Check', './defaultValue', './defined', './DeveloperError', './GeometryType', './PrimitiveType' ], function( + Check, defaultValue, defined, DeveloperError, @@ -15760,7 +16778,6 @@ define('Core/Geometry',[ return Geometry; }); -/*global define*/ define('Core/GeometryAttribute',[ './defaultValue', './defined', @@ -15803,7 +16820,7 @@ define('Core/GeometryAttribute',[ * }, * primitiveType : Cesium.PrimitiveType.LINE_LOOP * }); - * + * * @see Geometry */ function GeometryAttribute(options) { @@ -15889,7 +16906,6 @@ define('Core/GeometryAttribute',[ return GeometryAttribute; }); -/*global define*/ define('Core/GeometryAttributes',[ './defaultValue' ], function( @@ -15985,7 +17001,6 @@ define('Core/GeometryAttributes',[ return GeometryAttributes; }); -/*global define*/ define('Core/VertexFormat',[ './defaultValue', './defined', @@ -16226,7 +17241,7 @@ define('Core/VertexFormat',[ array[startingIndex++] = value.st ? 1.0 : 0.0; array[startingIndex++] = value.tangent ? 1.0 : 0.0; array[startingIndex++] = value.bitangent ? 1.0 : 0.0; - array[startingIndex++] = value.color ? 1.0 : 0.0; + array[startingIndex] = value.color ? 1.0 : 0.0; return array; }; @@ -16252,7 +17267,7 @@ define('Core/VertexFormat',[ result.st = array[startingIndex++] === 1.0; result.tangent = array[startingIndex++] === 1.0; result.bitangent = array[startingIndex++] === 1.0; - result.color = array[startingIndex++] === 1.0; + result.color = array[startingIndex] === 1.0; return result; }; @@ -16283,7 +17298,6 @@ define('Core/VertexFormat',[ return VertexFormat; }); -/*global define*/ define('Core/BoxGeometry',[ './BoundingSphere', './Cartesian3', @@ -17095,7 +18109,6 @@ define('Core/BoxGeometry',[ return BoxGeometry; }); -/*global define*/ define('Core/BoxOutlineGeometry',[ './BoundingSphere', './Cartesian3', @@ -17366,7 +18379,6 @@ define('Core/BoxOutlineGeometry',[ return BoxOutlineGeometry; }); -/*global define*/ define('Core/getAbsoluteUri',[ '../ThirdParty/Uri', './defaultValue', @@ -17401,7 +18413,6 @@ define('Core/getAbsoluteUri',[ return getAbsoluteUri; }); -/*global define*/ define('Core/joinUrls',[ '../ThirdParty/Uri', './defaultValue', @@ -17512,7 +18523,6 @@ define('Core/joinUrls',[ return joinUrls; }); -/*global define*/ define('Core/buildModuleUrl',[ '../ThirdParty/Uri', './defined', @@ -17617,7 +18627,6 @@ define('Core/buildModuleUrl',[ return buildModuleUrl; }); -/*global define*/ define('Core/cancelAnimationFrame',[ './defined' ], function( @@ -17669,21 +18678,20 @@ define('Core/cancelAnimationFrame',[ return cancelAnimationFrame; }); -/*global define*/ define('Core/CartographicGeocoderService',[ - './Cartesian3', - './Check', - './defaultValue', - './defineProperties', - './defined', - '../ThirdParty/when' -], function( - Cartesian3, - Check, - defaultValue, - defineProperties, - defined, - when) { + '../ThirdParty/when', + './Cartesian3', + './Check', + './defaultValue', + './defined', + './defineProperties' + ], function( + when, + Cartesian3, + Check, + defaultValue, + defined, + defineProperties) { 'use strict'; /** @@ -17710,6 +18718,20 @@ define('Core/CartographicGeocoderService',[ var latitude = +splitQuery[1]; var height = (splitQuery.length === 3) ? +splitQuery[2] : 300.0; + if (isNaN(longitude) && isNaN(latitude)) { + var coordTest = /^(\d+.?\d*)([nsew])/i; + for (var i = 0; i < splitQuery.length; ++i) { + var splitCoord = splitQuery[i].match(coordTest); + if (coordTest.test(splitQuery[i]) && splitCoord.length === 3) { + if (/^[ns]/i.test(splitCoord[2])) { + latitude = (/^[n]/i.test(splitCoord[2])) ? +splitCoord[1] : -splitCoord[1]; + } else if (/^[ew]/i.test(splitCoord[2])) { + longitude = (/^[e]/i.test(splitCoord[2])) ? +splitCoord[1] : -splitCoord[1]; + } + } + } + } + if (!isNaN(longitude) && !isNaN(latitude) && !isNaN(height)) { var result = { displayName: query, @@ -17724,7 +18746,6 @@ define('Core/CartographicGeocoderService',[ return CartographicGeocoderService; }); -/*global define*/ define('Core/Spline',[ './defaultValue', './defined', @@ -17839,7 +18860,6 @@ define('Core/Spline',[ return Spline; }); -/*global define*/ define('Core/LinearSpline',[ './Cartesian3', './defaultValue', @@ -17885,10 +18905,11 @@ define('Core/LinearSpline',[ * }); * * var p0 = spline.evaluate(times[0]); - * + * * @see HermiteSpline * @see CatmullRomSpline * @see QuaternionSpline + * @see WeightSpline */ function LinearSpline(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -17975,7 +18996,6 @@ define('Core/LinearSpline',[ return LinearSpline; }); -/*global define*/ define('Core/TridiagonalSystemSolver',[ './Cartesian3', './defined', @@ -18062,7 +19082,6 @@ define('Core/TridiagonalSystemSolver',[ return TridiagonalSystemSolver; }); -/*global define*/ define('Core/HermiteSpline',[ './Cartesian3', './Cartesian4', @@ -18237,10 +19256,11 @@ define('Core/HermiteSpline',[ * }); * * var p0 = spline.evaluate(times[0]); - * + * * @see CatmullRomSpline * @see LinearSpline * @see QuaternionSpline + * @see WeightSpline */ function HermiteSpline(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -18550,24 +19570,23 @@ define('Core/HermiteSpline',[ return HermiteSpline; }); -/*global define*/ define('Core/CatmullRomSpline',[ './Cartesian3', './Cartesian4', + './Check', './defaultValue', './defined', './defineProperties', - './DeveloperError', './HermiteSpline', './Matrix4', './Spline' ], function( Cartesian3, Cartesian4, + Check, defaultValue, defined, defineProperties, - DeveloperError, HermiteSpline, Matrix4, Spline) { @@ -18690,10 +19709,11 @@ define('Core/CatmullRomSpline',[ * * 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 + * @see WeightSpline */ function CatmullRomSpline(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -18831,15 +19851,14 @@ define('Core/CatmullRomSpline',[ return CatmullRomSpline; }); -/*global define*/ define('Core/Event',[ + './Check', './defined', - './defineProperties', - './DeveloperError' + './defineProperties' ], function( + Check, defined, - defineProperties, - DeveloperError) { + defineProperties) { 'use strict'; /** @@ -18948,6 +19967,10 @@ define('Core/Event',[ return false; }; + function compareNumber(a,b) { + return b - a; + } + /** * Raises the event by calling each registered listener with all supplied arguments. * @@ -18974,12 +19997,15 @@ define('Core/Event',[ //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); + if (length > 0) { + toRemove.sort(compareNumber); + for (i = 0; i < length; i++) { + var index = toRemove[i]; + listeners.splice(index, 1); + scopes.splice(index, 1); + } + toRemove.length = 0; } - toRemove.length = 0; this._insideRaiseEvent = false; }; @@ -18992,23 +20018,22 @@ define('Core/Event',[ return Event; }); -/*global define*/ define('Core/GeographicTilingScheme',[ './Cartesian2', + './Check', './defaultValue', './defined', './defineProperties', - './DeveloperError', './Ellipsoid', './GeographicProjection', './Math', './Rectangle' ], function( Cartesian2, + Check, defaultValue, defined, defineProperties, - DeveloperError, Ellipsoid, GeographicProjection, CesiumMath, @@ -19233,22 +20258,21 @@ define('Core/GeographicTilingScheme',[ return GeographicTilingScheme; }); -/*global define*/ define('Core/EllipsoidalOccluder',[ './BoundingSphere', './Cartesian3', + './Check', './defaultValue', './defined', './defineProperties', - './DeveloperError', './Rectangle' ], function( BoundingSphere, Cartesian3, + Check, defaultValue, defined, defineProperties, - DeveloperError, Rectangle) { 'use strict'; @@ -19522,7 +20546,6 @@ define('Core/EllipsoidalOccluder',[ return EllipsoidalOccluder; }); -/*global define*/ define('Core/QuadraticRealPolynomial',[ './DeveloperError', './Math' @@ -19640,7 +20663,6 @@ define('Core/QuadraticRealPolynomial',[ return QuadraticRealPolynomial; }); -/*global define*/ define('Core/CubicRealPolynomial',[ './DeveloperError', './QuadraticRealPolynomial' @@ -19853,7 +20875,6 @@ define('Core/CubicRealPolynomial',[ return CubicRealPolynomial; }); -/*global define*/ define('Core/QuarticRealPolynomial',[ './CubicRealPolynomial', './DeveloperError', @@ -20069,9 +21090,8 @@ define('Core/QuarticRealPolynomial',[ 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[0], roots2[0], roots1[1], roots2[1]]; } return roots1; } @@ -20148,7 +21168,6 @@ define('Core/QuarticRealPolynomial',[ return QuarticRealPolynomial; }); -/*global define*/ define('Core/Ray',[ './Cartesian3', './defaultValue', @@ -20216,7 +21235,6 @@ define('Core/Ray',[ return Ray; }); -/*global define*/ define('Core/IntersectionTests',[ './Cartesian3', './Cartographic', @@ -20601,11 +21619,10 @@ define('Core/IntersectionTests',[ start : root1, stop : root0 }; - } else { - // qw2 == product. Repeated roots (2 intersections). - var root = Math.sqrt(difference / w2); - return new Interval(root, root); } + // qw2 == product. Repeated roots (2 intersections). + var root = Math.sqrt(difference / w2); + return new Interval(root, root); } else if (q2 < 1.0) { // Inside ellipsoid (2 intersections). difference = q2 - 1.0; // Negatively valued. @@ -20615,17 +21632,16 @@ define('Core/IntersectionTests',[ discriminant = qw * qw - product; temp = -qw + Math.sqrt(discriminant); // Positively valued. return new Interval(0.0, temp / w2); - } else { - // q2 == 1.0. On ellipsoid. - if (qw < 0.0) { - // Looking inward. - w2 = Cartesian3.magnitudeSquared(w); - return new Interval(0.0, -qw / w2); - } - - // qw >= 0.0. Looking outward or tangent. - return undefined; } + // q2 == 1.0. On ellipsoid. + if (qw < 0.0) { + // Looking inward. + w2 = Cartesian3.magnitudeSquared(w); + return new Interval(0.0, -qw / w2); + } + + // qw >= 0.0. Looking outward or tangent. + return undefined; }; function addWithCancellationCheck(left, right, tolerance) { @@ -21034,19 +22050,18 @@ define('Core/IntersectionTests',[ return IntersectionTests; }); -/*global define*/ define('Core/Plane',[ './Cartesian3', './defined', './DeveloperError', - './Math', - './freezeObject' + './freezeObject', + './Math' ], function( Cartesian3, defined, DeveloperError, - CesiumMath, - freezeObject) { + freezeObject, + CesiumMath) { 'use strict'; /** @@ -21141,11 +22156,10 @@ define('Core/Plane',[ if (!defined(result)) { return new Plane(normal, distance); - } else { - Cartesian3.clone(normal, result.normal); - result.distance = distance; - return result; } + Cartesian3.clone(normal, result.normal); + result.distance = distance; + return result; }; /** @@ -21191,113 +22205,6 @@ define('Core/Plane',[ 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) { - - oneTimeWarning(identifier, message); - } - - return deprecationWarning; -}); - -/*global define*/ define('Core/EarthOrientationParametersSample',[],function() { 'use strict'; @@ -21477,7 +22384,6 @@ 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 () { @@ -21671,7 +22577,6 @@ function sprintf () { return sprintf; }); -/*global define*/ define('Core/GregorianDate',[],function() { 'use strict'; @@ -21729,7 +22634,6 @@ define('Core/GregorianDate',[],function() { return GregorianDate; }); -/*global define*/ define('Core/isLeapYear',[ './DeveloperError' ], function( @@ -21755,7 +22659,6 @@ define('Core/isLeapYear',[ return isLeapYear; }); -/*global define*/ define('Core/LeapSecond',[],function() { 'use strict'; @@ -21786,7 +22689,6 @@ define('Core/LeapSecond',[],function() { return LeapSecond; }); -/*global define*/ define('Core/TimeConstants',[ './freezeObject' ], function( @@ -21879,7 +22781,6 @@ define('Core/TimeConstants',[ return freezeObject(TimeConstants); }); -/*global define*/ define('Core/TimeStandard',[ './freezeObject' ], function( @@ -21901,12 +22802,17 @@ define('Core/TimeStandard',[ * UTC = TAI - deltaT where deltaT is the number of leap * seconds which have been introduced as of the time in TAI. * + * @type {Number} + * @constant */ UTC : 0, /** * Represents the International Atomic Time (TAI) time standard. * TAI is the principal time standard to which the other time standards are related. + * + * @type {Number} + * @constant */ TAI : 1 }; @@ -21914,7 +22820,6 @@ define('Core/TimeStandard',[ return freezeObject(TimeStandard); }); -/*global define*/ define('Core/JulianDate',[ '../ThirdParty/sprintf', './binarySearch', @@ -22118,6 +23023,26 @@ define('Core/JulianDate',[ } } + /** + * Creates a new instance from a GregorianDate. + * + * @param {GregorianDate} date A GregorianDate. + * @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 GregorianDate. + */ + JulianDate.fromGregorianDate = function(date, result) { + + var components = computeJulianDateComponents(date.year, date.month, date.day, date.hour, date.minute, date.second, date.millisecond); + 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 JavaScript Date. * @@ -22225,7 +23150,8 @@ define('Core/JulianDate',[ //Now that we have all of the date components, validate them to make sure nothing is out of range. inLeapYear = isLeapYear(year); - //Not move onto the time string, which is much simpler. + //Now move onto the time string, which is much simpler. + //If no time is specified, it is considered the beginning of the day, UTC to match Javascript's implementation. var offsetIndex; if (defined(time)) { tokens = time.match(matchHoursMinutesSeconds); @@ -22277,9 +23203,6 @@ define('Core/JulianDate',[ 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. @@ -22471,23 +23394,23 @@ define('Core/JulianDate',[ */ JulianDate.toIso8601 = function(julianDate, precision) { - var gDate = JulianDate.toGregorianDate(julianDate, gDate); + var gDate = JulianDate.toGregorianDate(julianDate, gregorianDateScratch); 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); + 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); + 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); + return sprintf('%04d-%02d-%02dT%02d:%02d:%02d.%sZ', gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second, millisecondStr); }; /** @@ -22797,47 +23720,6 @@ define('Core/JulianDate',[ 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'; @@ -22851,7 +23733,7 @@ define('Core/parseResponseHeaders',[], function() { * 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) { @@ -22881,7 +23763,6 @@ define('Core/parseResponseHeaders',[], function() { return parseResponseHeaders; }); -/*global define*/ define('Core/RequestErrorEvent',[ './defined', './parseResponseHeaders' @@ -22948,7 +23829,6 @@ define('Core/RequestErrorEvent',[ return RequestErrorEvent; }); -/*global define*/ define('Core/TrustedServers',[ '../ThirdParty/Uri', './defined', @@ -22958,7 +23838,7 @@ define('Core/TrustedServers',[ 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. @@ -23078,25 +23958,30 @@ define('Core/TrustedServers',[ TrustedServers.clear = function() { _servers = {}; }; - + return TrustedServers; }); -/*global define*/ define('Core/loadWithXhr',[ '../ThirdParty/when', + './Check', './defaultValue', './defined', './DeveloperError', + './Request', './RequestErrorEvent', + './RequestScheduler', './RuntimeError', './TrustedServers' ], function( when, + Check, defaultValue, defined, DeveloperError, + Request, RequestErrorEvent, + RequestScheduler, RuntimeError, TrustedServers) { 'use strict'; @@ -23110,13 +23995,14 @@ define('Core/loadWithXhr',[ * @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.url The URL of the data. * @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. + * @param {Request} [options.request] The request object. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * * * @example @@ -23141,19 +24027,29 @@ define('Core/loadWithXhr',[ options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var url = options.url; + var responseType = options.responseType; var method = defaultValue(options.method, 'GET'); var data = options.data; var headers = options.headers; var overrideMimeType = options.overrideMimeType; + url = defaultValue(url, options.url); - return when(options.url, function(url) { + var request = defined(options.request) ? options.request : new Request(); + request.url = url; + request.requestFunction = function() { var deferred = when.defer(); - - loadWithXhr.load(url, responseType, method, data, headers, deferred, overrideMimeType); - + var xhr = loadWithXhr.load(url, responseType, method, data, headers, deferred, overrideMimeType); + if (defined(xhr) && defined(xhr.abort)) { + request.cancelFunction = function() { + xhr.abort(); + }; + } return deferred.promise; - }); + }; + + return RequestScheduler.request(request); } var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/; @@ -23268,6 +24164,8 @@ define('Core/loadWithXhr',[ }; xhr.send(data); + + return xhr; }; loadWithXhr.defaultLoad = loadWithXhr.load; @@ -23275,7 +24173,6 @@ define('Core/loadWithXhr',[ return loadWithXhr; }); -/*global define*/ define('Core/loadText',[ './loadWithXhr' ], function( @@ -23290,9 +24187,10 @@ define('Core/loadText',[ * * @exports loadText * - * @param {String|Promise.} url The URL to request, or a promise for the URL. + * @param {String} url The URL to request. * @param {Object} [headers] HTTP headers to send with the request. - * @returns {Promise.} a promise that will resolve to the requested data when loaded. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * * * @example @@ -23304,22 +24202,22 @@ define('Core/loadText',[ * }).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) { + function loadText(url, headers, request) { return loadWithXhr({ url : url, - headers : headers + headers : headers, + request : request }); } return loadText; }); -/*global define*/ define('Core/loadJson',[ './clone', './defined', @@ -23347,11 +24245,12 @@ define('Core/loadJson',[ * * @exports loadJson * - * @param {String|Promise.} url The URL to request, or a promise for the URL. + * @param {String} url The URL to request. * @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. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * * * @example @@ -23360,12 +24259,12 @@ define('Core/loadJson',[ * }).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) { + function loadJson(url, headers, request) { if (!defined(headers)) { headers = defaultHeaders; @@ -23375,7 +24274,12 @@ define('Core/loadJson',[ headers.Accept = defaultHeaders.Accept; } - return loadText(url, headers).then(function(value) { + var textPromise = loadText(url, headers, request); + if (!defined(textPromise)) { + return undefined; + } + + return textPromise.then(function(value) { return JSON.parse(value); }); } @@ -23383,7 +24287,6 @@ define('Core/loadJson',[ return loadJson; }); -/*global define*/ define('Core/EarthOrientationParameters',[ '../ThirdParty/when', './binarySearch', @@ -23767,7 +24670,6 @@ define('Core/EarthOrientationParameters',[ return EarthOrientationParameters; }); -/*global define*/ define('Core/Iau2006XysSample',[],function() { 'use strict'; @@ -23806,7 +24708,6 @@ define('Core/Iau2006XysSample',[],function() { return Iau2006XysSample; }); -/*global define*/ define('Core/Iau2006XysData',[ '../ThirdParty/when', './buildModuleUrl', @@ -24072,7 +24973,6 @@ define('Core/Iau2006XysData',[ return Iau2006XysData; }); -/*global define*/ define('Core/HeadingPitchRoll',[ './defaultValue', './defined', @@ -24083,7 +24983,7 @@ define('Core/HeadingPitchRoll',[ defined, DeveloperError, CesiumMath) { - "use strict"; + 'use strict'; /** * A rotation expressed as a heading, pitch, and roll. Heading is the rotation about the @@ -24247,13 +25147,11 @@ define('Core/HeadingPitchRoll',[ return HeadingPitchRoll; }); -/*global define*/ define('Core/Quaternion',[ './Cartesian3', './Check', './defaultValue', './defined', - './deprecationWarning', './FeatureDetection', './freezeObject', './HeadingPitchRoll', @@ -24264,7 +25162,6 @@ define('Core/Quaternion',[ Check, defaultValue, defined, - deprecationWarning, FeatureDetection, freezeObject, HeadingPitchRoll, @@ -24429,19 +25326,12 @@ define('Core/Quaternion',[ * @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(headingOrHeadingPitchRoll, pitchOrResult, roll, result) { - var hpr; - if (headingOrHeadingPitchRoll instanceof HeadingPitchRoll) { - hpr = headingOrHeadingPitchRoll; - result = pitchOrResult; - } else { - deprecationWarning('Quaternion.fromHeadingPitchRoll(heading, pitch, roll,result)', 'The method was deprecated in Cesium 1.32 and will be removed in version 1.33. ' + 'Use Quaternion.fromHeadingPitchRoll(hpr,result) where hpr is a HeadingPitchRoll'); - hpr = new HeadingPitchRoll(headingOrHeadingPitchRoll, pitchOrResult, roll); - } - scratchRollQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_X, hpr.roll, scratchHPRQuaternion); - scratchPitchQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -hpr.pitch, result); + Quaternion.fromHeadingPitchRoll = function(headingPitchRoll, result) { + + scratchRollQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_X, headingPitchRoll.roll, scratchHPRQuaternion); + scratchPitchQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -headingPitchRoll.pitch, result); result = Quaternion.multiply(scratchPitchQuaternion, scratchRollQuaternion, scratchPitchQuaternion); - scratchHeadingQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -hpr.heading, scratchHPRQuaternion); + scratchHeadingQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -headingPitchRoll.heading, scratchHPRQuaternion); return Quaternion.multiply(scratchHeadingQuaternion, result, result); }; @@ -25174,7 +26064,6 @@ define('Core/Quaternion',[ return Quaternion; }); -/*global define*/ define('Core/Transforms',[ '../ThirdParty/when', './Cartesian2', @@ -25184,7 +26073,6 @@ define('Core/Transforms',[ './Check', './defaultValue', './defined', - './deprecationWarning', './DeveloperError', './EarthOrientationParameters', './EarthOrientationParametersSample', @@ -25206,7 +26094,6 @@ define('Core/Transforms',[ Check, defaultValue, defined, - deprecationWarning, DeveloperError, EarthOrientationParameters, EarthOrientationParametersSample, @@ -25394,6 +26281,7 @@ define('Core/Transforms',[ *
  • The z axis points in the direction of the ellipsoid surface normal which passes through the position.
  • * * + * @function * @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. @@ -25416,6 +26304,7 @@ define('Core/Transforms',[ *
  • The z axis points in the opposite direction of the ellipsoid surface normal which passes through the position.
  • * * + * @function * @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. @@ -25438,6 +26327,7 @@ define('Core/Transforms',[ *
  • The z axis points in the local east direction.
  • * * + * @function * @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. @@ -25451,26 +26341,27 @@ define('Core/Transforms',[ Transforms.northUpEastToFixedFrame = Transforms.localFrameToFixedFrameGenerator('north','up'); /** - * 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 = Transforms.localFrameToFixedFrameGenerator('north','west'); + * 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.
    • + *
    + * + * @function + * @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 = Transforms.localFrameToFixedFrameGenerator('north','west'); var scratchHPRQuaternion = new Quaternion(); var scratchScale = new Cartesian3(1.0, 1.0, 1.0); @@ -25485,7 +26376,7 @@ define('Core/Transforms',[ * @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 {Transforms~LocalFrameToFixedFrame} [fixedFrameTransformOrResult=Transforms.eastNorthUpToFixedFrame] A 4x4 transformation + * @param {Transforms~LocalFrameToFixedFrame} [fixedFrameTransform=Transforms.eastNorthUpToFixedFrame] A 4x4 transformation * matrix from a reference frame to the provided ellipsoid's fixed reference frame * @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. @@ -25499,18 +26390,12 @@ define('Core/Transforms',[ * var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll); * var transform = Cesium.Transforms.headingPitchRollToFixedFrame(center, hpr); */ - Transforms.headingPitchRollToFixedFrame = function(origin, headingPitchRoll, ellipsoid, fixedFrameTransformOrResult, result) { + Transforms.headingPitchRollToFixedFrame = function(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result) { - // checks for required parameters happen in the called functions - if(fixedFrameTransformOrResult instanceof Matrix4){ - result = fixedFrameTransformOrResult; - fixedFrameTransformOrResult = undefined; - deprecationWarning('Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, result)', 'The method was deprecated in Cesium 1.31 and will be removed in version 1.33. Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result) where fixedFrameTransform is a a 4x4 transformation matrix (see Transforms.localFrameToFixedFrameGenerator)'); - } - fixedFrameTransformOrResult = defaultValue(fixedFrameTransformOrResult,Transforms.eastNorthUpToFixedFrame); + fixedFrameTransform = defaultValue(fixedFrameTransform, Transforms.eastNorthUpToFixedFrame); var hprQuaternion = Quaternion.fromHeadingPitchRoll(headingPitchRoll, scratchHPRQuaternion); var hprMatrix = Matrix4.fromTranslationQuaternionRotationScale(Cartesian3.ZERO, hprQuaternion, scratchScale, scratchHPRMatrix4); - result = fixedFrameTransformOrResult(origin, ellipsoid, result); + result = fixedFrameTransform(origin, ellipsoid, result); return Matrix4.multiply(result, hprMatrix, result); }; @@ -25526,7 +26411,7 @@ define('Core/Transforms',[ * @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 {Transforms~LocalFrameToFixedFrame} [fixedFrameTransformOrResult=Transforms.eastNorthUpToFixedFrame] A 4x4 transformation + * @param {Transforms~LocalFrameToFixedFrame} [fixedFrameTransform=Transforms.eastNorthUpToFixedFrame] A 4x4 transformation * matrix from a reference frame to the provided ellipsoid's fixed reference frame * @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. @@ -25540,13 +26425,9 @@ define('Core/Transforms',[ * var hpr = new HeadingPitchRoll(heading, pitch, roll); * var quaternion = Cesium.Transforms.headingPitchRollQuaternion(center, hpr); */ - Transforms.headingPitchRollQuaternion = function(origin, headingPitchRoll, ellipsoid, fixedFrameTransformOrResult, result) { - if (fixedFrameTransformOrResult instanceof Quaternion) { - result = fixedFrameTransformOrResult; - fixedFrameTransformOrResult = undefined; - deprecationWarning('Transforms.headingPitchRollQuaternion(origin, headingPitchRoll, ellipsoid, result)', 'The method was deprecated in Cesium 1.31 and will be removed in version 1.33. Transforms.headingPitchRollQuaternion(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result) where fixedFrameTransform is a a 4x4 transformation matrix (see Transforms.localFrameToFixedFrameGenerator)'); - } - var transform = Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid,fixedFrameTransformOrResult, scratchENUMatrix4); + Transforms.headingPitchRollQuaternion = function(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result) { + + var transform = Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, scratchENUMatrix4); var rotation = Matrix4.getRotation(transform, scratchHPRMatrix3); return Quaternion.fromRotationMatrix(rotation, result); }; @@ -25984,7 +26865,6 @@ define('Core/Transforms',[ return Transforms; }); -/*global define*/ define('Core/EllipsoidTangentPlane',[ './AxisAlignedBoundingBox', './Cartesian2', @@ -26294,7 +27174,6 @@ define('Core/EllipsoidTangentPlane',[ return EllipsoidTangentPlane; }); -/*global define*/ define('Core/OrientedBoundingBox',[ './BoundingSphere', './Cartesian2', @@ -26975,7 +27854,6 @@ define('Core/OrientedBoundingBox',[ return OrientedBoundingBox; }); -/*global define*/ define('Core/TerrainQuantization',[ './freezeObject' ], function( @@ -27010,7 +27888,6 @@ define('Core/TerrainQuantization',[ return freezeObject(TerrainQuantization); }); -/*global define*/ define('Core/TerrainEncoding',[ './AttributeCompression', './Cartesian2', @@ -27366,22 +28243,20 @@ define('Core/TerrainEncoding',[ offsetInBytes : numCompressed0 * sizeInBytes, strideInBytes : stride }]; - } else { - return [{ - index : attributes.compressed0, - vertexBuffer : buffer, - componentDatatype : datatype, - componentsPerAttribute : numCompressed0 - }]; } + return [{ + index : attributes.compressed0, + vertexBuffer : buffer, + componentDatatype : datatype, + componentsPerAttribute : numCompressed0 + }]; }; TerrainEncoding.prototype.getAttributeLocations = function() { if (this.quantization === TerrainQuantization.NONE) { return attributesNone; - } else { - return attributes; } + return attributes; }; TerrainEncoding.clone = function(encoding, result) { @@ -27404,7 +28279,6 @@ define('Core/TerrainEncoding',[ return TerrainEncoding; }); -/*global define*/ define('Core/WebMercatorProjection',[ './Cartesian3', './Cartographic', @@ -27560,7 +28434,6 @@ define('Core/WebMercatorProjection',[ return WebMercatorProjection; }); -/*global define*/ define('Core/HeightmapTessellator',[ './AxisAlignedBoundingBox', './BoundingSphere', @@ -27986,7 +28859,6 @@ define('Core/HeightmapTessellator',[ return HeightmapTessellator; }); -/*global define*/ define('Core/destroyObject',[ './defaultValue', './DeveloperError' @@ -28023,7 +28895,7 @@ define('Core/destroyObject',[ * _gl.deleteTexture(_texture); * return Cesium.destroyObject(this); * }; - * + * * @see DeveloperError */ function destroyObject(object, message) { @@ -28046,7 +28918,6 @@ define('Core/destroyObject',[ return destroyObject; }); -/*global define*/ define('Core/isCrossOriginUrl',[ './defined' ], function( @@ -28082,7 +28953,6 @@ define('Core/isCrossOriginUrl',[ return isCrossOriginUrl; }); -/*global define*/ define('Core/TaskProcessor',[ '../ThirdParty/when', './buildModuleUrl', @@ -28362,7 +29232,6 @@ define('Core/TaskProcessor',[ return TaskProcessor; }); -/*global define*/ define('Core/TerrainMesh',[ './defaultValue' ], function( @@ -28474,7 +29343,6 @@ define('Core/TerrainMesh',[ return TerrainMesh; }); -/*global define*/ define('Core/TerrainProvider',[ './defined', './defineProperties', @@ -28497,6 +29365,8 @@ define('Core/TerrainProvider',[ * * @see EllipsoidTerrainProvider * @see CesiumTerrainProvider + * @see VRTheWorldTerrainProvider + * @see GoogleEarthEnterpriseTerrainProvider */ function TerrainProvider() { DeveloperError.throwInstantiationError(); @@ -28668,9 +29538,8 @@ define('Core/TerrainProvider',[ * @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. + * @param {Request} [request] The request object. Intended for internal use only. + * * @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. @@ -28701,7 +29570,6 @@ define('Core/TerrainProvider',[ return TerrainProvider; }); -/*global define*/ define('Core/HeightmapTerrainData',[ '../ThirdParty/when', './defaultValue', @@ -29205,7 +30073,6 @@ define('Core/HeightmapTerrainData',[ return HeightmapTerrainData; }); -/*global define*/ define('Core/IndexDatatype',[ './defined', './DeveloperError', @@ -29338,7 +30205,6 @@ define('Core/IndexDatatype',[ return freezeObject(IndexDatatype); }); -/*global define*/ define('Core/loadArrayBuffer',[ './loadWithXhr' ], function( @@ -29353,10 +30219,10 @@ define('Core/loadArrayBuffer',[ * * @exports loadArrayBuffer * - * @param {String|Promise.} url The URL of the binary data, or a promise for the URL. + * @param {String} url The URL of the binary data. * @param {Object} [headers] HTTP headers to send with the requests. - * @returns {Promise.} a promise that will resolve to the requested data when loaded. - * + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * * @example * // load a single URL asynchronously @@ -29365,22 +30231,22 @@ define('Core/loadArrayBuffer',[ * }).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) { + function loadArrayBuffer(url, headers, request) { return loadWithXhr({ url : url, responseType : 'arraybuffer', - headers : headers + headers : headers, + request : request }); } return loadArrayBuffer; }); -/*global define*/ define('Core/Intersections2D',[ './Cartesian3', './defined', @@ -29614,15 +30480,13 @@ define('Core/Intersections2D',[ result.y = l2; result.z = l3; return result; - } else { - return new Cartesian3(l1, l2, l3); } + return new Cartesian3(l1, l2, l3); }; return Intersections2D; }); -/*global define*/ define('Core/QuantizedMeshTerrainData',[ '../ThirdParty/when', './BoundingSphere', @@ -29816,9 +30680,8 @@ define('Core/QuantizedMeshTerrainData',[ if (needsSort) { arrayScratch.sort(sortFunction); return IndexDatatype.createTypedArray(vertexCount, arrayScratch); - } else { - return indices; } + return indices; } var createMeshTaskProcessor = new TaskProcessor('createVerticesFromQuantizedTerrainMesh'); @@ -30138,119 +31001,22 @@ define('Core/QuantizedMeshTerrainData',[ return QuantizedMeshTerrainData; }); -/*global define*/ -define('Core/throttleRequestByServer',[ - '../ThirdParty/Uri', - '../ThirdParty/when', - './defaultValue' +define('Core/TileAvailability',[ + './binarySearch', + './Cartographic', + './defined', + './defineProperties', + './DeveloperError', + './Rectangle' ], function( - Uri, - when, - defaultValue) { + binarySearch, + Cartographic, + defined, + defineProperties, + DeveloperError, + Rectangle) { '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/TileAvailability',[ - './binarySearch', - './Cartographic', - './defined', - './defineProperties', - './DeveloperError', - './Rectangle' -], function( - binarySearch, - Cartographic, - defined, - defineProperties, - DeveloperError, - Rectangle) { - "use strict"; - /** * Reports the availability of tiles in a {@link TilingScheme}. * @@ -30663,7 +31429,6 @@ define('Core/TileAvailability',[ return TileAvailability; }); -/*global define*/ define('Core/formatError',[ './defined' ], function( @@ -30701,7 +31466,6 @@ define('Core/formatError',[ return formatError; }); -/*global define*/ define('Core/TileProviderError',[ './defaultValue', './defined', @@ -30860,7 +31624,6 @@ define('Core/TileProviderError',[ return TileProviderError; }); -/*global define*/ define('Core/CesiumTerrainProvider',[ '../ThirdParty/Uri', '../ThirdParty/when', @@ -30881,8 +31644,9 @@ define('Core/CesiumTerrainProvider',[ './Math', './OrientedBoundingBox', './QuantizedMeshTerrainData', + './Request', + './RequestType', './TerrainProvider', - './throttleRequestByServer', './TileAvailability', './TileProviderError' ], function( @@ -30905,14 +31669,15 @@ define('Core/CesiumTerrainProvider',[ CesiumMath, OrientedBoundingBox, QuantizedMeshTerrainData, + Request, + RequestType, TerrainProvider, - throttleRequestByServer, TileAvailability, TileProviderError) { 'use strict'; /** - * A {@link TerrainProvider} that access terrain data in a Cesium terrain format. + * A {@link TerrainProvider} that accesses 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}. * @@ -31157,12 +31922,11 @@ define('Core/CesiumTerrainProvider',[ 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' - }; } + 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) { @@ -31346,9 +32110,8 @@ define('Core/CesiumTerrainProvider',[ * @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. + * @param {Request} [request] The request object. Intended for internal use only. + * * @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. @@ -31356,7 +32119,7 @@ define('Core/CesiumTerrainProvider',[ * @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready} * returns true. */ - CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { var urlTemplates = this._tileUrlTemplates; if (urlTemplates.length === 0) { @@ -31374,8 +32137,6 @@ define('Core/CesiumTerrainProvider',[ url = proxy.getURL(url); } - var promise; - var extensionList = []; if (this._requestVertexNormals && this._hasVertexNormals) { extensionList.push(this._littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals'); @@ -31384,26 +32145,18 @@ define('Core/CesiumTerrainProvider',[ 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 promise = loadArrayBuffer(url, getRequestHeader(extensionList), request); + + if (!defined(promise)) { + return undefined; } 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); } + return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY); }); }; @@ -31570,7 +32323,6 @@ define('Core/CesiumTerrainProvider',[ return CesiumTerrainProvider; }); -/*global define*/ define('Core/EllipseGeometryLibrary',[ './Cartesian3', './Math', @@ -31844,7 +32596,6 @@ define('Core/EllipseGeometryLibrary',[ return EllipseGeometryLibrary; }); -/*global define*/ define('Core/GeometryInstance',[ './defaultValue', './defined', @@ -31899,7 +32650,7 @@ define('Core/GeometryInstance',[ * }, * id : 'top' * }); - * + * * @see Geometry */ function GeometryInstance(options) { @@ -31969,15 +32720,14 @@ define('Core/GeometryInstance',[ return GeometryInstance; }); -/*global define*/ define('Core/EncodedCartesian3',[ './Cartesian3', - './defined', - './DeveloperError' + './Check', + './defined' ], function( Cartesian3, - defined, - DeveloperError) { + Check, + defined) { 'use strict'; /** @@ -32138,7 +32888,6 @@ define('Core/EncodedCartesian3',[ return EncodedCartesian3; }); -/*global define*/ define('Core/Tipsify',[ './defaultValue', './defined', @@ -32327,7 +33076,8 @@ define('Core/Tipsify',[ // Vertices var vertices = []; - for ( var i = 0; i < maximumIndexPlusOne; i++) { + var i; + for (i = 0; i < maximumIndexPlusOne; i++) { vertices[i] = { numLiveTriangles : 0, timeStamp : 0, @@ -32407,7 +33157,6 @@ define('Core/Tipsify',[ return Tipsify; }); -/*global define*/ define('Core/GeometryPipeline',[ './AttributeCompression', './barycentricCoordinates', @@ -32763,8 +33512,8 @@ define('Core/GeometryPipeline',[ while (intoElementsIn < numVertices) { var temp = indexCrossReferenceOldToNew[intoElementsIn]; if (temp !== -1) { - for (i = 0; i < numComponents; i++) { - elementsOut[numComponents * temp + i] = elementsIn[numComponents * intoElementsIn + i]; + for (var j = 0; j < numComponents; j++) { + elementsOut[numComponents * temp + j] = elementsIn[numComponents * intoElementsIn + j]; } } ++intoElementsIn; @@ -33380,8 +34129,8 @@ define('Core/GeometryPipeline',[ var normalsPerVertex = new Array(numVertices); var normalsPerTriangle = new Array(numIndices / 3); var normalIndices = new Array(numIndices); - - for ( var i = 0; i < numVertices; i++) { + var i; + for ( i = 0; i < numVertices; i++) { normalsPerVertex[i] = { indexOffset : 0, count : 0, @@ -33518,7 +34267,8 @@ define('Core/GeometryPipeline',[ var numIndices = indices.length; var tan1 = new Array(numVertices * 3); - for ( var i = 0; i < tan1.length; i++) { + var i; + for ( i = 0; i < tan1.length; i++) { tan1[i] = 0; } @@ -34769,7 +35519,6 @@ define('Core/GeometryPipeline',[ return GeometryPipeline; }); -/*global define*/ define('Core/EllipseGeometry',[ './BoundingSphere', './Cartesian2', @@ -35436,7 +36185,8 @@ define('Core/EllipseGeometry',[ Matrix4.inverseTransformation(scratchEnuToFixedMatrix, scratchFixedToEnuMatrix); // Find the 4 extreme points of the ellipse in ENU - for (var i = 0; i < 4; ++i) { + var i; + for (i = 0; i < 4; ++i) { Cartesian3.clone(Cartesian3.ZERO, scratchRectanglePoints[i]); } scratchRectanglePoints[0].x += semiMajorAxis; @@ -35729,7 +36479,6 @@ define('Core/EllipseGeometry',[ return EllipseGeometry; }); -/*global define*/ define('Core/CircleGeometry',[ './Cartesian3', './Check', @@ -35916,7 +36665,6 @@ define('Core/CircleGeometry',[ return CircleGeometry; }); -/*global define*/ define('Core/EllipseOutlineGeometry',[ './BoundingSphere', './Cartesian3', @@ -36248,7 +36996,6 @@ define('Core/EllipseOutlineGeometry',[ return EllipseOutlineGeometry; }); -/*global define*/ define('Core/CircleOutlineGeometry',[ './Cartesian3', './Check', @@ -36390,7 +37137,6 @@ define('Core/CircleOutlineGeometry',[ return CircleOutlineGeometry; }); -/*global define*/ define('Core/ClockRange',[ './freezeObject' ], function( @@ -36439,7 +37185,6 @@ define('Core/ClockRange',[ return freezeObject(ClockRange); }); -/*global define*/ define('Core/ClockStep',[ './freezeObject' ], function( @@ -36487,7 +37232,6 @@ define('Core/ClockStep',[ return freezeObject(ClockStep); }); -/*global define*/ define('Core/getTimestamp',[ './defined' ], function( @@ -36520,7 +37264,6 @@ define('Core/getTimestamp',[ return getTimestamp; }); -/*global define*/ define('Core/Clock',[ './ClockRange', './ClockStep', @@ -36830,18 +37573,17 @@ define('Core/Clock',[ return Clock; }); -/*global define*/ define('Core/Color',[ + './Check', './defaultValue', './defined', - './DeveloperError', './FeatureDetection', './freezeObject', './Math' ], function( + Check, defaultValue, defined, - DeveloperError, FeatureDetection, freezeObject, CesiumMath) { @@ -37115,8 +37857,7 @@ define('Core/Color',[ var minimumGreen = defaultValue(options.minimumGreen, 0); var maximumGreen = defaultValue(options.maximumGreen, 1.0); - - green = minimumGreen + (CesiumMath.nextRandomNumber() * (maximumGreen - minimumGreen)); + green = minimumGreen + (CesiumMath.nextRandomNumber() * (maximumGreen - minimumGreen)); } var blue = options.blue; @@ -38943,7 +39684,6 @@ define('Core/Color',[ return Color; }); -/*global define*/ define('Core/ColorGeometryInstanceAttribute',[ './Color', './ComponentDatatype', @@ -38984,7 +39724,7 @@ define('Core/ColorGeometryInstanceAttribute',[ * color : new Cesium.ColorGeometryInstanceAttribute(red, green, blue, alpha) * } * }); - * + * * @see GeometryInstance * @see GeometryInstanceAttribute */ @@ -39122,14 +39862,12 @@ define('Core/ColorGeometryInstanceAttribute',[ return ColorGeometryInstanceAttribute; }); -/*global define*/ define('Core/CompressedTextureBuffer',[ './defined', './defineProperties' ], function( defined, - defineProperties - ) { + defineProperties) { 'use strict'; /** @@ -39215,7 +39953,7 @@ define('Core/CompressedTextureBuffer',[ return CompressedTextureBuffer; }); -/*global define*/ + define('Core/CornerType',[ './freezeObject' ], function( @@ -39262,23 +40000,22 @@ define('Core/CornerType',[ return freezeObject(CornerType); }); -/*global define*/ define('Core/EllipsoidGeodesic',[ './Cartesian3', './Cartographic', + './Check', './defaultValue', './defined', './defineProperties', - './DeveloperError', './Ellipsoid', './Math' ], function( Cartesian3, Cartographic, + Check, defaultValue, defined, defineProperties, - DeveloperError, Ellipsoid, CesiumMath) { 'use strict'; @@ -39436,6 +40173,8 @@ define('Core/EllipsoidGeodesic',[ ellipsoidGeodesic._uSquared = uSquared; } + var scratchCart1 = new Cartesian3(); + var scratchCart2 = new Cartesian3(); function computeProperties(ellipsoidGeodesic, start, end, ellipsoid) { var firstCartesian = Cartesian3.normalize(ellipsoid.cartographicToCartesian(start, scratchCart2), scratchCart1); var lastCartesian = Cartesian3.normalize(ellipsoid.cartographicToCartesian(end, scratchCart2), scratchCart2); @@ -39452,8 +40191,6 @@ define('Core/EllipsoidGeodesic',[ setConstants(ellipsoidGeodesic); } - var scratchCart1 = new Cartesian3(); - var scratchCart2 = new Cartesian3(); /** * Initializes a geodesic on the ellipsoid connecting the two provided planetodetic points. * @@ -39653,7 +40390,6 @@ define('Core/EllipsoidGeodesic',[ return EllipsoidGeodesic; }); -/*global define*/ define('Core/PolylinePipeline',[ './Cartesian3', './Cartographic', @@ -39969,7 +40705,6 @@ define('Core/PolylinePipeline',[ return PolylinePipeline; }); -/*global define*/ define('Core/PolylineVolumeGeometryLibrary',[ './Cartesian2', './Cartesian3', @@ -40057,6 +40792,9 @@ define('Core/PolylineVolumeGeometryLibrary',[ return heights; } + var nextScratch = new Cartesian3(); + var prevScratch = new Cartesian3(); + function computeRotationAngle(start, end, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); @@ -40223,8 +40961,6 @@ define('Core/PolylineVolumeGeometryLibrary',[ 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); @@ -40382,7 +41118,6 @@ define('Core/PolylineVolumeGeometryLibrary',[ return PolylineVolumeGeometryLibrary; }); -/*global define*/ define('Core/CorridorGeometryLibrary',[ './Cartesian3', './CornerType', @@ -40535,13 +41270,6 @@ define('Core/CorridorGeometryLibrary',[ } }; - 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(); @@ -40552,7 +41280,6 @@ define('Core/CorridorGeometryLibrary',[ 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; @@ -40705,7 +41432,6 @@ define('Core/CorridorGeometryLibrary',[ return CorridorGeometryLibrary; }); -/*global define*/ define('ThirdParty/earcut-2.1.1',[], function() { 'use strict'; @@ -41353,7 +42079,6 @@ earcut.flatten = function (data) { return earcut; }); -/*global define*/ define('Core/WindingOrder',[ './freezeObject', './WebGLConstants' @@ -41396,15 +42121,14 @@ define('Core/WindingOrder',[ return freezeObject(WindingOrder); }); -/*global define*/ define('Core/PolygonPipeline',[ '../ThirdParty/earcut-2.1.1', './Cartesian2', './Cartesian3', + './Check', './ComponentDatatype', './defaultValue', './defined', - './DeveloperError', './Ellipsoid', './Geometry', './GeometryAttribute', @@ -41415,10 +42139,10 @@ define('Core/PolygonPipeline',[ earcut, Cartesian2, Cartesian3, + Check, ComponentDatatype, defaultValue, defined, - DeveloperError, Ellipsoid, Geometry, GeometryAttribute, @@ -41658,7 +42382,6 @@ define('Core/PolygonPipeline',[ return PolygonPipeline; }); -/*global define*/ define('Core/CorridorGeometry',[ './arrayRemoveDuplicates', './BoundingSphere', @@ -41715,6 +42438,13 @@ define('Core/CorridorGeometry',[ var scratch1 = new Cartesian3(); var scratch2 = new Cartesian3(); + function scaleToSurface(positions, ellipsoid) { + for (var i = 0; i < positions.length; i++) { + positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); + } + return positions; + } + function addNormals(attr, normal, left, front, back, vertexFormat) { var normals = attr.normals; var tangents = attr.tangents; @@ -42251,6 +42981,7 @@ define('Core/CorridorGeometry',[ attributes.position.values = newPositions; attributes = extrudedAttributes(attributes, vertexFormat); + var i; var size = length / 3; if (params.shadowVolume) { var topNormals = attributes.normal.values; @@ -42273,7 +43004,6 @@ define('Core/CorridorGeometry',[ } } - var i; var iLength = indices.length; var twoSize = size + size; var newIndices = IndexDatatype.createTypedArray(newPositions.length / 3, iLength * 2 + twoSize * 3); @@ -42359,6 +43089,7 @@ define('Core/CorridorGeometry',[ var scratchCartographicMax = new Cartographic(); function computeRectangle(positions, ellipsoid, width, cornerType) { + positions = scaleToSurface(positions, ellipsoid); var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); var length = cleanPositions.length; if (length < 2 || width <= 0) { @@ -42604,14 +43335,15 @@ define('Core/CorridorGeometry',[ var width = corridorGeometry._width; var extrudedHeight = corridorGeometry._extrudedHeight; var extrude = (height !== extrudedHeight); + var ellipsoid = corridorGeometry._ellipsoid; + positions = scaleToSurface(positions, ellipsoid); 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, @@ -42686,17 +43418,16 @@ define('Core/CorridorGeometry',[ return CorridorGeometry; }); -/*global define*/ define('Core/CorridorOutlineGeometry',[ './arrayRemoveDuplicates', './BoundingSphere', './Cartesian3', + './Check', './ComponentDatatype', './CornerType', './CorridorGeometryLibrary', './defaultValue', './defined', - './DeveloperError', './Ellipsoid', './Geometry', './GeometryAttribute', @@ -42709,12 +43440,12 @@ define('Core/CorridorOutlineGeometry',[ arrayRemoveDuplicates, BoundingSphere, Cartesian3, + Check, ComponentDatatype, CornerType, CorridorGeometryLibrary, defaultValue, defined, - DeveloperError, Ellipsoid, Geometry, GeometryAttribute, @@ -42729,6 +43460,13 @@ define('Core/CorridorOutlineGeometry',[ var cartesian2 = new Cartesian3(); var cartesian3 = new Cartesian3(); + function scaleToSurface(positions, ellipsoid) { + for (var i = 0; i < positions.length; i++) { + positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); + } + return positions; + } + function combine(computedPositions, cornerType) { var wallIndices = []; var positions = computedPositions.positions; @@ -43140,14 +43878,15 @@ define('Core/CorridorOutlineGeometry',[ var width = corridorOutlineGeometry._width; var extrudedHeight = corridorOutlineGeometry._extrudedHeight; var extrude = (height !== extrudedHeight); + var ellipsoid = corridorOutlineGeometry._ellipsoid; + positions = scaleToSurface(positions, ellipsoid); var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); if ((cleanPositions.length < 2) || (width <= 0)) { return; } - var ellipsoid = corridorOutlineGeometry._ellipsoid; var params = { ellipsoid : ellipsoid, positions : cleanPositions, @@ -43183,7 +43922,6 @@ define('Core/CorridorOutlineGeometry',[ return CorridorOutlineGeometry; }); -/*global define*/ define('Core/createGuid',[],function() { 'use strict'; @@ -43197,7 +43935,7 @@ define('Core/createGuid',[],function() { * * @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() { @@ -43212,7 +43950,205 @@ define('Core/createGuid',[],function() { return createGuid; }); -/*global define*/ +define('Core/CullingVolume',[ + './Cartesian3', + './Cartesian4', + './defaultValue', + './defined', + './DeveloperError', + './Intersect', + './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(1.0, 0.0, 0.0), 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(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) { + + 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 (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; +}); + define('Core/CylinderGeometryLibrary',[ './Math' ], function( @@ -43273,7 +44209,6 @@ define('Core/CylinderGeometryLibrary',[ return CylinderGeometryLibrary; }); -/*global define*/ define('Core/CylinderGeometry',[ './BoundingSphere', './Cartesian2', @@ -43664,16 +44599,15 @@ define('Core/CylinderGeometry',[ }); -/*global define*/ define('Core/CylinderOutlineGeometry',[ './BoundingSphere', './Cartesian2', './Cartesian3', + './Check', './ComponentDatatype', './CylinderGeometryLibrary', './defaultValue', './defined', - './DeveloperError', './Geometry', './GeometryAttribute', './GeometryAttributes', @@ -43683,11 +44617,11 @@ define('Core/CylinderOutlineGeometry',[ BoundingSphere, Cartesian2, Cartesian3, + Check, ComponentDatatype, CylinderGeometryLibrary, defaultValue, defined, - DeveloperError, Geometry, GeometryAttribute, GeometryAttributes, @@ -43847,7 +44781,8 @@ define('Core/CylinderOutlineGeometry',[ var indices = IndexDatatype.createTypedArray(numVertices, numIndices * 2); var index = 0; - for (var i = 0; i < slices - 1; i++) { + var i; + for (i = 0; i < slices - 1; i++) { indices[index++] = i; indices[index++] = i + 1; indices[index++] = i + slices; @@ -43889,15 +44824,14 @@ define('Core/CylinderOutlineGeometry',[ return CylinderOutlineGeometry; }); -/*global define*/ define('Core/decodeGoogleEarthEnterpriseData',[ - './Check', - './defined', - './RuntimeError' -], function( - Check, - defined, - RuntimeError) { + './Check', + './defined', + './RuntimeError' + ], function( + Check, + defined, + RuntimeError) { 'use strict'; var compressedMagic = 0x7468dead; @@ -43979,7 +44913,6 @@ define('Core/decodeGoogleEarthEnterpriseData',[ return decodeGoogleEarthEnterpriseData; }); -/*global define*/ define('Core/DefaultProxy',[],function() { 'use strict'; @@ -44004,15 +44937,118 @@ define('Core/DefaultProxy',[],function() { */ DefaultProxy.prototype.getURL = function(resource) { //acevedo - fix from Ish to make it to work with the emp3 urlproxy - //var prefix = this.proxy.indexOf('?') === -1 ? '?' : ''; - //return this.proxy + prefix + encodeURIComponent(resource); + //var prefix = this.proxy.indexOf('?') === -1 ? '?' : ''; + //return this.proxy + prefix + encodeURIComponent(resource); return this.proxy + '?' + 'url=' + encodeURIComponent(resource); }; return DefaultProxy; }); -/*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) { + + oneTimeWarning(identifier, message); + } + + return deprecationWarning; +}); + define('Core/DistanceDisplayCondition',[ './defaultValue', './defined', @@ -44134,7 +45170,6 @@ define('Core/DistanceDisplayCondition',[ return DistanceDisplayCondition; }); -/*global define*/ define('Core/DistanceDisplayConditionGeometryInstanceAttribute',[ './ComponentDatatype', './defaultValue', @@ -44291,6 +45326,111 @@ define('Core/DistanceDisplayConditionGeometryInstanceAttribute',[ return DistanceDisplayConditionGeometryInstanceAttribute; }); +define('Core/DoublyLinkedList',[ + '../Core/defined', + '../Core/defineProperties' + ], function( + defined, + defineProperties) { + 'use strict'; + + /** + * @private + */ + function DoublyLinkedList() { + this.head = undefined; + this.tail = undefined; + this._length = 0; + } + + defineProperties(DoublyLinkedList.prototype, { + length : { + get : function() { + return this._length; + } + } + }); + + function DoublyLinkedListNode(item, previous, next) { + this.item = item; + this.previous = previous; + this.next = next; + } + + DoublyLinkedList.prototype.add = function(item) { + var node = new DoublyLinkedListNode(item, this.tail, undefined); + + if (defined(this.tail)) { + this.tail.next = node; + this.tail = node; + } else { + // Insert into empty linked list + this.head = node; + this.tail = node; + } + + ++this._length; + + return node; + }; + + function remove(list, node) { + if (defined(node.previous) && defined(node.next)) { + node.previous.next = node.next; + node.next.previous = node.previous; + } else if (defined(node.previous)) { + // Remove last node + node.previous.next = undefined; + list.tail = node.previous; + } else if (defined(node.next)) { + // Remove first node + node.next.previous = undefined; + list.head = node.next; + } else { + // Remove last node in the linked list + list.head = undefined; + list.tail = undefined; + } + + node.next = undefined; + node.previous = undefined; + } + + DoublyLinkedList.prototype.remove = function(node) { + if (!defined(node)) { + return; + } + + remove(this, node); + + --this._length; + }; + + DoublyLinkedList.prototype.splice = function(node, nextNode) { + if (node === nextNode) { + return; + } + + // Remove nextNode, then insert after node + remove(this, nextNode); + + var oldNodeNext = node.next; + node.next = nextNode; + + // nextNode is the new tail + if (this.tail === node) { + this.tail = nextNode; + } else { + oldNodeNext.previous = nextNode; + } + + nextNode.next = oldNodeNext; + nextNode.previous = node; + }; + + return DoublyLinkedList; +}); + /** @license tween.js - https://github.com/sole/tween.js @@ -44332,7 +45472,6 @@ THE SOFTWARE. * @author Ben Delarre / http://delarre.net */ -/*global define*/ define('ThirdParty/Tween',[],function() { // Date.now shim for (ahem) Internet Explo(d|r)er @@ -45085,7 +46224,6 @@ define('ThirdParty/Tween',[],function() { return TWEEN; }); -/*global define*/ define('Core/EasingFunction',[ '../ThirdParty/Tween', './freezeObject' @@ -45351,7 +46489,6 @@ define('Core/EasingFunction',[ return freezeObject(EasingFunction); }); -/*global define*/ define('Core/EllipsoidGeometry',[ './BoundingSphere', './Cartesian2', @@ -45736,7 +46873,6 @@ define('Core/EllipsoidGeometry',[ return EllipsoidGeometry; }); -/*global define*/ define('Core/EllipsoidOutlineGeometry',[ './BoundingSphere', './Cartesian3', @@ -46017,7 +47153,6 @@ define('Core/EllipsoidOutlineGeometry',[ return EllipsoidOutlineGeometry; }); -/*global define*/ define('Core/EllipsoidTerrainProvider',[ '../ThirdParty/when', './defaultValue', @@ -46171,14 +47306,13 @@ define('Core/EllipsoidTerrainProvider',[ * @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. + * @param {Request} [request] The request object. Intended for internal use only. + * * @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) { + EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { var width = 16; var height = 16; return new HeightmapTerrainData({ @@ -46213,7 +47347,6 @@ define('Core/EllipsoidTerrainProvider',[ return EllipsoidTerrainProvider; }); -/*global define*/ define('Core/EventHelper',[ './defined', './DeveloperError' @@ -46239,7 +47372,7 @@ define('Core/EventHelper',[ * * // later... * helper.removeAll(); - * + * * @see Event */ function EventHelper() { @@ -46291,7 +47424,6 @@ define('Core/EventHelper',[ return EventHelper; }); -/*global define*/ define('Core/ExtrapolationType',[ './freezeObject' ], function( @@ -46301,7 +47433,7 @@ define('Core/ExtrapolationType',[ /** * Constants to determine how an interpolated value is extrapolated * when querying outside the bounds of available data. - * + * * @exports ExtrapolationType * * @see SampledProperty @@ -46335,4402 +47467,5178 @@ define('Core/ExtrapolationType',[ return freezeObject(ExtrapolationType); }); -/*global define*/ -define('Core/GeocoderService',[ - './DeveloperError' - ], function( - DeveloperError) { - 'use strict'; - - /** - * @typedef {Object} GeocoderResult - * @property {String} displayName The display name for a location - * @property {Rectangle|Cartesian3} destination The bounding box for a location - */ - - /** - * Provides geocoding through an external service. This type describes an interface and - * is not intended to be used. - * @alias GeocoderService - * @constructor - * - * @see BingMapsGeocoderService - */ - function GeocoderService() { - } - - /** - * @function - * - * @param {String} query The query to be sent to the geocoder service - * @returns {Promise} - */ - GeocoderService.prototype.geocode = DeveloperError.throwInstantiationError; - - return GeocoderService; -}); - -/*global define*/ -define('Core/GeometryInstanceAttribute',[ +define('Core/OrthographicOffCenterFrustum',[ + './Cartesian3', + './Cartesian4', + './CullingVolume', './defaultValue', './defined', - './DeveloperError' + './defineProperties', + './DeveloperError', + './Matrix4' ], function( + Cartesian3, + Cartesian4, + CullingVolume, defaultValue, defined, - DeveloperError) { + defineProperties, + DeveloperError, + Matrix4) { 'use strict'; /** - * Values and type information for per-instance geometry attributes. + * 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 GeometryInstanceAttribute + * @alias OrthographicOffCenterFrustum * @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. - * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.left] The left clipping plane distance. + * @param {Number} [options.right] The right clipping plane distance. + * @param {Number} [options.top] The top clipping plane distance. + * @param {Number} [options.bottom] The bottom clipping plane distance. + * @param {Number} [options.near=1.0] The near clipping plane distance. + * @param {Number} [options.far=500000000.0] The far clipping plane distance. * * @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 + * var maxRadii = ellipsoid.maximumRadius; + * + * var frustum = new Cesium.OrthographicOffCenterFrustum(); + * 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 GeometryInstanceAttribute(options) { + function OrthographicOffCenterFrustum(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - /** - * The datatype of each component in the attribute, e.g., individual elements in - * {@link GeometryInstanceAttribute#value}. - * - * @type ComponentDatatype - * + * The left clipping plane. + * @type {Number} * @default undefined */ - this.componentDatatype = options.componentDatatype; + this.left = options.left; + this._left = undefined; /** - * 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 - * + * The right clipping plane. + * @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; + this.right = options.right; + this._right = undefined; /** - * 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) - * ]; + * The top clipping plane. + * @type {Number} + * @default undefined */ - this.normalize = defaultValue(options.normalize, false); + this.top = options.top; + this._top = undefined; /** - * 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[]} - * + * The bottom clipping plane. + * @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; + this.bottom = options.bottom; + this._bottom = undefined; + + /** + * The distance of the near plane. + * @type {Number} + * @default 1.0 + */ + this.near = defaultValue(options.near, 1.0); + this._near = this.near; + + /** + * The distance of the far plane. + * @type {Number} + * @default 500000000.0; + */ + this.far = defaultValue(options.far, 500000000.0); + this._far = this.far; + + this._cullingVolume = new CullingVolume(); + this._orthographicMatrix = new Matrix4(); } - return GeometryInstanceAttribute; -}); + function update(frustum) { + + 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) { -/*global define*/ -define('Core/getBaseUri',[ - '../ThirdParty/Uri', - './defined', - './DeveloperError' - ], function( - Uri, - defined, - DeveloperError) { - 'use strict'; + + 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(OrthographicOffCenterFrustum.prototype, { + /** + * Gets the orthographic projection matrix computed from the view frustum. + * @memberof OrthographicOffCenterFrustum.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(); /** - * Given a URI, returns the base path of the URI. - * @exports getBaseUri + * Creates a culling volume for this frustum. * - * @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. + * @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 - * // 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); + * // Check if a bounding volume intersects the frustum. + * var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp); + * var intersect = cullingVolume.computeVisibility(boundingVolume); */ - function getBaseUri(uri, includeQuery) { + OrthographicOffCenterFrustum.prototype.computeCullingVolume = function(position, direction, up) { - var basePath = ''; - var i = uri.lastIndexOf('/'); - if (i !== -1) { - basePath = uri.substring(0, i + 1); + 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); + Cartesian3.normalize(right, right); + 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); - if (!includeQuery) { - return basePath; + // 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); - uri = new Uri(uri); - if (defined(uri.query)) { - basePath += '?' + uri.query; + // Bottom plane + Cartesian3.multiplyByScalar(up, b, point); + Cartesian3.add(nearCenter, point, point); + + plane = planes[2]; + if (!defined(plane)) { + plane = planes[2] = new Cartesian4(); } - if (defined(uri.fragment)){ - basePath += '#' + uri.fragment; + 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); - return basePath; - } + // 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); - return getBaseUri; -}); + // Far plane + Cartesian3.multiplyByScalar(direction, f, point); + Cartesian3.add(position, point, point); -/*global define*/ -define('Core/getExtensionFromUri',[ - '../ThirdParty/Uri', - './defined', - './DeveloperError' - ], function( - Uri, - defined, - DeveloperError) { - 'use strict'; + 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; + }; /** - * Given a URI, returns the extension of the URI. - * @exports getExtensionFromUri + * Returns the pixel's width and height in meters. * - * @param {String} uri The Uri. - * @returns {String} The extension of the Uri. + * @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 - * //extension will be "czml"; - * var extension = Cesium.getExtensionFromUri('/Gallery/simple.czml?value=true&example=false'); + * // Example 1 + * // Get the width and height of a pixel. + * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 0.0, new Cesium.Cartesian2()); */ - function getExtensionFromUri(uri) { + OrthographicOffCenterFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) { + update(this); + - 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); + 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 OrthographicOffCenterFrustum instance. + * + * @param {OrthographicOffCenterFrustum} [result] The object onto which to store the result. + * @returns {OrthographicOffCenterFrustum} The modified result parameter or a new OrthographicOffCenterFrustum instance if one was not provided. + */ + OrthographicOffCenterFrustum.prototype.clone = function(result) { + if (!defined(result)) { + result = new OrthographicOffCenterFrustum(); } - return path; - } - return getExtensionFromUri; + 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 OrthographicOffCenterFrustum componentwise and returns + * true if they are equal, false otherwise. + * + * @param {OrthographicOffCenterFrustum} [other] The right hand side OrthographicOffCenterFrustum. + * @returns {Boolean} true if they are equal, false otherwise. + */ + OrthographicOffCenterFrustum.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 OrthographicOffCenterFrustum; }); -/*global define*/ -define('Core/getFilenameFromUri',[ - '../ThirdParty/Uri', +define('Core/OrthographicFrustum',[ + './Check', + './defaultValue', './defined', - './DeveloperError' + './defineProperties', + './DeveloperError', + './OrthographicOffCenterFrustum' ], function( - Uri, + Check, + defaultValue, defined, - DeveloperError) { + defineProperties, + DeveloperError, + OrthographicOffCenterFrustum) { 'use strict'; /** - * Given a URI, returns the last segment of the URI, removing any path or query information. - * @exports getFilenameFromUri + * 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. * - * @param {String} uri The Uri. - * @returns {String} The last segment of the Uri. + * @alias OrthographicFrustum + * @constructor + * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.width] The width of the frustum in meters. + * @param {Number} [options.aspectRatio] The aspect ratio of the frustum's width to it's height. + * @param {Number} [options.near=1.0] The distance of the near plane. + * @param {Number} [options.far=500000000.0] The distance of the far plane. * * @example - * //fileName will be"simple.czml"; - * var fileName = Cesium.getFilenameFromUri('/Gallery/simple.czml?value=true&example=false'); + * var maxRadii = ellipsoid.maximumRadius; + * + * var frustum = new Cesium.OrthographicFrustum(); + * frustum.near = 0.01 * maxRadii; + * frustum.far = 50.0 * maxRadii; */ - function getFilenameFromUri(uri) { - - 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; - } + function OrthographicFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - return getFilenameFromUri; -}); + this._offCenterFrustum = new OrthographicOffCenterFrustum(); -/*global define*/ -define('Core/getImagePixels',[ - './defined' - ], function( - defined) { - 'use strict'; + /** + * The horizontal width of the frustum in meters. + * @type {Number} + * @default undefined + */ + this.width = options.width; + this._width = undefined; - var context2DsByWidthAndHeight = {}; + /** + * The aspect ratio of the frustum's width to it's height. + * @type {Number} + * @default undefined + */ + this.aspectRatio = options.aspectRatio; + this._aspectRatio = undefined; + + /** + * The distance of the near plane. + * @type {Number} + * @default 1.0 + */ + this.near = defaultValue(options.near, 1.0); + this._near = this.near; + + /** + * The distance of the far plane. + * @type {Number} + * @default 500000000.0; + */ + this.far = defaultValue(options.far, 500000000.0); + this._far = this.far; + } /** - * Extract a pixel array from a loaded image. Draws the image - * into a canvas so it can read the pixels back. + * The number of elements used to pack the object into an array. + * @type {Number} + */ + OrthographicFrustum.packedLength = 4; + + /** + * Stores the provided instance into the provided array. * - * @exports getImagePixels + * @param {OrthographicFrustum} 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. * - * @param {Image} image The image to extract pixels from. - * @param {Number} width The width of the image. If not defined, then image.width is assigned. - * @param {Number} height The height of the image. If not defined, then image.height is assigned. - * @returns {CanvasPixelArray} The pixels of the image. + * @returns {Number[]} The array that was packed into */ - function getImagePixels(image, width, height) { - if (!defined(width)) { - width = image.width; - } - if (!defined(height)) { - height = image.height; - } + OrthographicFrustum.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); - var context2DsByHeight = context2DsByWidthAndHeight[width]; - if (!defined(context2DsByHeight)) { - context2DsByHeight = {}; - context2DsByWidthAndHeight[width] = context2DsByHeight; - } + array[startingIndex++] = value.width; + array[startingIndex++] = value.aspectRatio; + array[startingIndex++] = value.near; + array[startingIndex] = value.far; - 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; - } + return array; + }; - context2d.drawImage(image, 0, 0, width, height); - return context2d.getImageData(0, 0, width, height).data; - } + /** + * 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 {OrthographicFrustum} [result] The object into which to store the result. + * @returns {OrthographicFrustum} The modified result parameter or a new OrthographicFrustum instance if one was not provided. + */ + OrthographicFrustum.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); - return getImagePixels; -}); + if (!defined(result)) { + result = new OrthographicFrustum(); + } -/*global define*/ -define('Core/getStringFromTypedArray',[ - './defaultValue', - './defined', - './DeveloperError' - ], function( - defaultValue, - defined, - DeveloperError) { - 'use strict'; - /*global TextDecoder*/ + result.width = array[startingIndex++]; + result.aspectRatio = array[startingIndex++]; + result.near = array[startingIndex++]; + result.far = array[startingIndex]; - /** - * @private - */ - function getStringFromTypedArray(uint8Array, byteOffset, byteLength) { + return result; + }; + + function update(frustum) { - byteOffset = defaultValue(byteOffset, 0); - byteLength = defaultValue(byteLength, uint8Array.byteLength - byteOffset); + var f = frustum._offCenterFrustum; - uint8Array = uint8Array.subarray(byteOffset, byteOffset + byteLength); + if (frustum.width !== frustum._width || frustum.aspectRatio !== frustum._aspectRatio || + frustum.near !== frustum._near || frustum.far !== frustum._far) { + + frustum._aspectRatio = frustum.aspectRatio; + frustum._width = frustum.width; + frustum._near = frustum.near; + frustum._far = frustum.far; - return getStringFromTypedArray.decode(uint8Array); + var ratio = 1.0 / frustum.aspectRatio; + f.right = frustum.width * 0.5; + f.left = -f.right; + f.top = ratio * f.right; + f.bottom = -f.top; + f.near = frustum.near; + f.far = frustum.far; + + } } - // Exposed functions for testing - getStringFromTypedArray.decodeWithTextDecoder = function(view) { - var decoder = new TextDecoder('utf-8'); - return decoder.decode(view); + 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._offCenterFrustum.projectionMatrix; + } + } + + }); + + /** + * 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) { + update(this); + return this._offCenterFrustum.computeCullingVolume(position, direction, up); }; - getStringFromTypedArray.decodeWithFromCharCode = function(view) { - var result = ''; - var length = view.length; + /** + * 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); + return this._offCenterFrustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, result); + }; - // 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]); + /** + * 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 OrthographicFrustum instance if one was not provided. + */ + OrthographicFrustum.prototype.clone = function(result) { + if (!defined(result)) { + result = new OrthographicFrustum(); } + + result.aspectRatio = this.aspectRatio; + result.width = this.width; + result.near = this.near; + result.far = this.far; + + // force update of clone to compute matrices + result._aspectRatio = undefined; + result._width = undefined; + result._near = undefined; + result._far = undefined; + + this._offCenterFrustum.clone(result._offCenterFrustum); + return result; }; - if (typeof TextDecoder !== 'undefined') { - getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithTextDecoder; - } else { - getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithFromCharCode; - } + /** + * 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) { + if (!defined(other)) { + return false; + } - return getStringFromTypedArray; + update(this); + update(other); + + return (this.width === other.width && + this.aspectRatio === other.aspectRatio && + this.near === other.near && + this.far === other.far && + this._offCenterFrustum.equals(other._offCenterFrustum)); + }; + + return OrthographicFrustum; }); -/*global define*/ -define('Core/getMagic',[ +define('Core/PerspectiveOffCenterFrustum',[ + './Cartesian3', + './Cartesian4', + './CullingVolume', './defaultValue', - './getStringFromTypedArray' + './defined', + './defineProperties', + './DeveloperError', + './Matrix4' ], function( + Cartesian3, + Cartesian4, + CullingVolume, defaultValue, - getStringFromTypedArray) { + defined, + defineProperties, + DeveloperError, + Matrix4) { 'use strict'; /** - * @private + * 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 + * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.left] The left clipping plane distance. + * @param {Number} [options.right] The right clipping plane distance. + * @param {Number} [options.top] The top clipping plane distance. + * @param {Number} [options.bottom] The bottom clipping plane distance. + * @param {Number} [options.near=1.0] The near clipping plane distance. + * @param {Number} [options.far=500000000.0] The far clipping plane distance. + * + * @example + * var frustum = new Cesium.PerspectiveOffCenterFrustum({ + * left : -1.0, + * right : 1.0, + * top : 1.0, + * bottom : -1.0, + * near : 1.0, + * far : 100.0 + * }); + * + * @see PerspectiveFrustum */ - function getMagic(uint8Array, byteOffset) { - byteOffset = defaultValue(byteOffset, 0); - return getStringFromTypedArray(uint8Array, byteOffset, Math.min(4, uint8Array.length)); - } + function PerspectiveOffCenterFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - return getMagic; -}); + /** + * Defines the left clipping plane. + * @type {Number} + * @default undefined + */ + this.left = options.left; + this._left = undefined; -/*! - * protobuf.js v6.7.0 (c) 2016, Daniel Wirtz - * Compiled Wed, 22 Mar 2017 17:30:26 UTC - * Licensed under the BSD-3-Clause License - * see: https://github.com/dcodeIO/protobuf.js for details - */ -(function(global,undefined){"use strict";(function prelude(modules, cache, entries) { + /** + * Defines the right clipping plane. + * @type {Number} + * @default undefined + */ + this.right = options.right; + this._right = undefined; - // This is the prelude used to bundle protobuf.js for the browser. Wraps up the CommonJS - // sources through a conflict-free require shim and is again wrapped within an iife that - // provides a unified `global` and a minification-friendly `undefined` var plus a global - // "use strict" directive so that minification can remove the directives of each module. + /** + * Defines the top clipping plane. + * @type {Number} + * @default undefined + */ + this.top = options.top; + this._top = undefined; - function $require(name) { - var $module = cache[name]; - if (!$module) - modules[name][0].call($module = cache[name] = { exports: {} }, $require, $module, $module.exports); - return $module.exports; - } + /** + * Defines the bottom clipping plane. + * @type {Number} + * @default undefined + */ + this.bottom = options.bottom; + this._bottom = undefined; - // Expose globally - var protobuf = global.protobuf = $require(entries[0]); + /** + * The distance of the near plane. + * @type {Number} + * @default 1.0 + */ + this.near = defaultValue(options.near, 1.0); + this._near = this.near; - // Be nice to AMD - if (typeof define === "function" && define.amd) - define('ThirdParty/protobuf-minimal',[], function() { - protobuf.configure(); - return protobuf; - }); + /** + * The distance of the far plane. + * @type {Number} + * @default 500000000.0 + */ + this.far = defaultValue(options.far, 500000000.0); + this._far = this.far; - // Be nice to CommonJS - if (typeof module === "object" && module && module.exports) - module.exports = protobuf; + this._cullingVolume = new CullingVolume(); + this._perspectiveMatrix = new Matrix4(); + this._infinitePerspective = new Matrix4(); + } -})/* end of prelude */({1:[function(require,module,exports){ -"use strict"; -module.exports = asPromise; + function update(frustum) { + + var t = frustum.top; + var b = frustum.bottom; + var r = frustum.right; + var l = frustum.left; + var n = frustum.near; + var f = frustum.far; -/** - * Returns a promise from a node-style callback function. - * @memberof util - * @param {function(?Error, ...*)} fn Function to call - * @param {*} ctx Function context - * @param {...*} params Function arguments - * @returns {Promise<*>} Promisified function - */ -function asPromise(fn, ctx/*, varargs */) { - var params = []; - for (var i = 2; i < arguments.length;) - params.push(arguments[i++]); - var pending = true; - return new Promise(function asPromiseExecutor(resolve, reject) { - params.push(function asPromiseCallback(err/*, varargs */) { - if (pending) { - pending = false; - if (err) - reject(err); - else { - var args = []; - for (var i = 1; i < arguments.length;) - args.push(arguments[i++]); - resolve.apply(null, args); - } + if (t !== frustum._top || b !== frustum._bottom || + l !== frustum._left || r !== frustum._right || + n !== frustum._near || f !== frustum._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; } - }); - try { - fn.apply(ctx || this, params); // eslint-disable-line no-invalid-this - } catch (err) { - if (pending) { - pending = false; - reject(err); + }, + + /** + * 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; } } }); -} -},{}],2:[function(require,module,exports){ -"use strict"; - -/** - * A minimal base64 implementation for number arrays. - * @memberof util - * @namespace - */ -var base64 = exports; + 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) { + + var planes = this._cullingVolume.planes; -/** - * Calculates the byte length of a base64 encoded string. - * @param {string} string Base64 encoded string - * @returns {number} Byte length - */ -base64.length = function length(string) { - var p = string.length; - if (!p) - return 0; - var n = 0; - while (--p % 4 > 1 && string.charAt(p) === "=") - ++n; - return Math.ceil(string.length * 3) / 4 - n; -}; + var t = this.top; + var b = this.bottom; + var r = this.right; + var l = this.left; + var n = this.near; + var f = this.far; -// Base64 encoding table -var b64 = new Array(64); + var right = Cartesian3.cross(direction, up, getPlanesRight); -// Base64 decoding table -var s64 = new Array(123); + var nearCenter = getPlanesNearCenter; + Cartesian3.multiplyByScalar(direction, n, nearCenter); + Cartesian3.add(position, nearCenter, nearCenter); -// 65..90, 97..122, 48..57, 43, 47 -for (var i = 0; i < 64;) - s64[b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; + var farCenter = getPlanesFarCenter; + Cartesian3.multiplyByScalar(direction, f, farCenter); + Cartesian3.add(position, farCenter, farCenter); -/** - * Encodes a buffer to a base64 encoded string. - * @param {Uint8Array} buffer Source buffer - * @param {number} start Source start - * @param {number} end Source end - * @returns {string} Base64 encoded string - */ -base64.encode = function encode(buffer, start, end) { - var string = []; // alt: new Array(Math.ceil((end - start) / 3) * 4); - var i = 0, // output index - j = 0, // goto index - t; // temporary - while (start < end) { - var b = buffer[start++]; - switch (j) { - case 0: - string[i++] = b64[b >> 2]; - t = (b & 3) << 4; - j = 1; - break; - case 1: - string[i++] = b64[t | b >> 4]; - t = (b & 15) << 2; - j = 2; - break; - case 2: - string[i++] = b64[t | b >> 6]; - string[i++] = b64[b & 63]; - j = 0; - break; - } - } - if (j) { - string[i++] = b64[t]; - string[i ] = 61; - if (j === 1) - string[i + 1] = 61; - } - return String.fromCharCode.apply(String, string); -}; + var normal = getPlanesNormal; -var invalidEncoding = "invalid encoding"; + //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); + Cartesian3.normalize(normal, normal); -/** - * Decodes a base64 encoded string to a buffer. - * @param {string} string Source string - * @param {Uint8Array} buffer Destination buffer - * @param {number} offset Destination offset - * @returns {number} Number of bytes written - * @throws {Error} If encoding is invalid - */ -base64.decode = function decode(string, buffer, offset) { - var start = offset; - var j = 0, // goto index - t; // temporary - for (var i = 0; i < string.length;) { - var c = string.charCodeAt(i++); - if (c === 61 && j > 1) - break; - if ((c = s64[c]) === undefined) - throw Error(invalidEncoding); - switch (j) { - case 0: - t = c; - j = 1; - break; - case 1: - buffer[offset++] = t << 2 | (c & 48) >> 4; - t = c; - j = 2; - break; - case 2: - buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2; - t = c; - j = 3; - break; - case 3: - buffer[offset++] = (t & 3) << 6 | c; - j = 0; - break; + var plane = planes[0]; + if (!defined(plane)) { + plane = planes[0] = new Cartesian4(); } - } - if (j === 1) - throw Error(invalidEncoding); - return offset - start; -}; - -/** - * Tests if the specified string appears to be base64 encoded. - * @param {string} string String to test - * @returns {boolean} `true` if probably base64 encoded, otherwise false - */ -base64.test = function test(string) { - return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(string); -}; - -},{}],3:[function(require,module,exports){ -"use strict"; -module.exports = EventEmitter; - -/** - * Constructs a new event emitter instance. - * @classdesc A minimal event emitter. - * @memberof util - * @constructor - */ -function EventEmitter() { - - /** - * Registered listeners. - * @type {Object.} - * @private - */ - this._listeners = {}; -} + plane.x = normal.x; + plane.y = normal.y; + plane.z = normal.z; + plane.w = -Cartesian3.dot(normal, position); -/** - * Registers an event listener. - * @param {string} evt Event name - * @param {function} fn Listener - * @param {*} [ctx] Listener context - * @returns {util.EventEmitter} `this` - */ -EventEmitter.prototype.on = function on(evt, fn, ctx) { - (this._listeners[evt] || (this._listeners[evt] = [])).push({ - fn : fn, - ctx : ctx || this - }); - return this; -}; + //Right plane computation + Cartesian3.multiplyByScalar(right, r, normal); + Cartesian3.add(nearCenter, normal, normal); + Cartesian3.subtract(normal, position, normal); + Cartesian3.cross(up, normal, normal); + Cartesian3.normalize(normal, normal); -/** - * Removes an event listener or any matching listeners if arguments are omitted. - * @param {string} [evt] Event name. Removes all listeners if omitted. - * @param {function} [fn] Listener to remove. Removes all listeners of `evt` if omitted. - * @returns {util.EventEmitter} `this` - */ -EventEmitter.prototype.off = function off(evt, fn) { - if (evt === undefined) - this._listeners = {}; - else { - if (fn === undefined) - this._listeners[evt] = []; - else { - var listeners = this._listeners[evt]; - for (var i = 0; i < listeners.length;) - if (listeners[i].fn === fn) - listeners.splice(i, 1); - else - ++i; + plane = planes[1]; + if (!defined(plane)) { + plane = planes[1] = new Cartesian4(); } - } - return this; -}; + plane.x = normal.x; + plane.y = normal.y; + plane.z = normal.z; + plane.w = -Cartesian3.dot(normal, position); -/** - * Emits an event by calling its listeners with the specified arguments. - * @param {string} evt Event name - * @param {...*} args Arguments - * @returns {util.EventEmitter} `this` - */ -EventEmitter.prototype.emit = function emit(evt) { - var listeners = this._listeners[evt]; - if (listeners) { - var args = [], - i = 1; - for (; i < arguments.length;) - args.push(arguments[i++]); - for (i = 0; i < listeners.length;) - listeners[i].fn.apply(listeners[i++].ctx, args); - } - return this; -}; + //Bottom plane computation + Cartesian3.multiplyByScalar(up, b, normal); + Cartesian3.add(nearCenter, normal, normal); + Cartesian3.subtract(normal, position, normal); + Cartesian3.cross(right, normal, normal); + Cartesian3.normalize(normal, normal); -},{}],4:[function(require,module,exports){ -"use strict"; -module.exports = inquire; + 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); -/** - * Requires a module only if available. - * @memberof util - * @param {string} moduleName Module to require - * @returns {?Object} Required module if available and not empty, otherwise `null` - */ -function inquire(moduleName) { - try { - var mod = eval("quire".replace(/^/,"re"))(moduleName); // eslint-disable-line no-eval - if (mod && (mod.length || Object.keys(mod).length)) - return mod; - } catch (e) {} // eslint-disable-line no-empty - return null; -} + //Top plane computation + Cartesian3.multiplyByScalar(up, t, normal); + Cartesian3.add(nearCenter, normal, normal); + Cartesian3.subtract(normal, position, normal); + Cartesian3.cross(normal, right, normal); + Cartesian3.normalize(normal, normal); -},{}],5:[function(require,module,exports){ -"use strict"; -module.exports = pool; + 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); -/** - * An allocator as used by {@link util.pool}. - * @typedef PoolAllocator - * @type {function} - * @param {number} size Buffer size - * @returns {Uint8Array} Buffer - */ + //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); -/** - * A slicer as used by {@link util.pool}. - * @typedef PoolSlicer - * @type {function} - * @param {number} start Start offset - * @param {number} end End offset - * @returns {Uint8Array} Buffer slice - * @this {Uint8Array} - */ + //Far plane computation + Cartesian3.negate(direction, normal); -/** - * A general purpose buffer pool. - * @memberof util - * @function - * @param {PoolAllocator} alloc Allocator - * @param {PoolSlicer} slice Slicer - * @param {number} [size=8192] Slab size - * @returns {PoolAllocator} Pooled allocator - */ -function pool(alloc, slice, size) { - var SIZE = size || 8192; - var MAX = SIZE >>> 1; - var slab = null; - var offset = SIZE; - return function pool_alloc(size) { - if (size < 1 || size > MAX) - return alloc(size); - if (offset + size > SIZE) { - slab = alloc(SIZE); - offset = 0; + plane = planes[5]; + if (!defined(plane)) { + plane = planes[5] = new Cartesian4(); } - var buf = slice.call(slab, offset, offset += size); - if (offset & 7) // align to 32 bit - offset = (offset | 7) + 1; - return buf; + plane.x = normal.x; + plane.y = normal.y; + plane.z = normal.z; + plane.w = -Cartesian3.dot(normal, farCenter); + + return this._cullingVolume; }; -} -},{}],6:[function(require,module,exports){ -"use strict"; + /** + * 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); -/** - * A minimal UTF8 implementation for number arrays. - * @memberof util - * @namespace - */ -var utf8 = exports; + + 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; -/** - * Calculates the UTF8 byte length of a string. - * @param {string} string String - * @returns {number} Byte length - */ -utf8.length = function utf8_length(string) { - var len = 0, - c = 0; - for (var i = 0; i < string.length; ++i) { - c = string.charCodeAt(i); - if (c < 128) - len += 1; - else if (c < 2048) - len += 2; - else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { - ++i; - len += 4; - } else - len += 3; - } - return len; -}; + result.x = pixelWidth; + result.y = pixelHeight; + return result; + }; -/** - * Reads UTF8 bytes as a string. - * @param {Uint8Array} buffer Source buffer - * @param {number} start Source start - * @param {number} end Source end - * @returns {string} String read - */ -utf8.read = function utf8_read(buffer, start, end) { - var len = end - start; - if (len < 1) - return ""; - var parts = null, - chunk = [], - i = 0, // char offset - t; // temporary - while (start < end) { - t = buffer[start++]; - if (t < 128) - chunk[i++] = t; - else if (t > 191 && t < 224) - chunk[i++] = (t & 31) << 6 | buffer[start++] & 63; - else if (t > 239 && t < 365) { - t = ((t & 7) << 18 | (buffer[start++] & 63) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63) - 0x10000; - chunk[i++] = 0xD800 + (t >> 10); - chunk[i++] = 0xDC00 + (t & 1023); - } else - chunk[i++] = (t & 15) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63; - if (i > 8191) { - (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); - i = 0; + /** + * 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(); } - } - if (parts) { - if (i) - parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); - return parts.join(""); - } - return String.fromCharCode.apply(String, chunk.slice(0, i)); -}; -/** - * Writes a string as UTF8 bytes. - * @param {string} string Source string - * @param {Uint8Array} buffer Destination buffer - * @param {number} offset Destination offset - * @returns {number} Bytes written - */ -utf8.write = function utf8_write(string, buffer, offset) { - var start = offset, - c1, // character 1 - c2; // character 2 - for (var i = 0; i < string.length; ++i) { - c1 = string.charCodeAt(i); - if (c1 < 128) { - buffer[offset++] = c1; - } else if (c1 < 2048) { - buffer[offset++] = c1 >> 6 | 192; - buffer[offset++] = c1 & 63 | 128; - } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) { - c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); - ++i; - buffer[offset++] = c1 >> 18 | 240; - buffer[offset++] = c1 >> 12 & 63 | 128; - buffer[offset++] = c1 >> 6 & 63 | 128; - buffer[offset++] = c1 & 63 | 128; - } else { - buffer[offset++] = c1 >> 12 | 224; - buffer[offset++] = c1 >> 6 & 63 | 128; - buffer[offset++] = c1 & 63 | 128; - } - } - return offset - start; -}; + result.right = this.right; + result.left = this.left; + result.top = this.top; + result.bottom = this.bottom; + result.near = this.near; + result.far = this.far; -},{}],7:[function(require,module,exports){ -"use strict"; -var protobuf = exports; + // 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; -/** - * Build type, one of `"full"`, `"light"` or `"minimal"`. - * @name build - * @type {string} - * @const - */ -protobuf.build = "minimal"; + return result; + }; -/** - * Named roots. - * This is where pbjs stores generated structures (the option `-r, --root` specifies a name). - * Can also be used manually to make roots available accross modules. - * @name roots - * @type {Object.} - * @example - * // pbjs -r myroot -o compiled.js ... - * - * // in another module: - * require("./compiled.js"); - * - * // in any subsequent module: - * var root = protobuf.roots["myroot"]; - */ -protobuf.roots = {}; + /** + * 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); + }; -// Serialization -protobuf.Writer = require(14); -protobuf.BufferWriter = require(15); -protobuf.Reader = require(8); -protobuf.BufferReader = require(9); + return PerspectiveOffCenterFrustum; +}); -// Utility -protobuf.util = require(13); -protobuf.rpc = require(10); -protobuf.configure = configure; +define('Core/PerspectiveFrustum',[ + './Check', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './PerspectiveOffCenterFrustum' + ], function( + Check, + defaultValue, + defined, + defineProperties, + DeveloperError, + PerspectiveOffCenterFrustum) { + 'use strict'; -/* istanbul ignore next */ -/** - * Reconfigures the library according to the environment. - * @returns {undefined} - */ -function configure() { - protobuf.Reader._configure(protobuf.BufferReader); - protobuf.util._configure(); -} + /** + * 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 + * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.fov] The angle of the field of view (FOV), in radians. + * @param {Number} [options.aspectRatio] The aspect ratio of the frustum's width to it's height. + * @param {Number} [options.near=1.0] The distance of the near plane. + * @param {Number} [options.far=500000000.0] The distance of the far plane. + * @param {Number} [options.xOffset=0.0] The offset in the x direction. + * @param {Number} [options.yOffset=0.0] The offset in the y direction. + * + * @example + * var frustum = new Cesium.PerspectiveFrustum({ + * fov : Cesium.Math.PI_OVER_THREE, + * aspectRatio : canvas.clientWidth / canvas.clientHeight + * near : 1.0, + * far : 1000.0 + * }); + * + * @see PerspectiveOffCenterFrustum + */ + function PerspectiveFrustum(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); -// Configure serialization -protobuf.Writer._configure(protobuf.BufferWriter); -configure(); + this._offCenterFrustum = new PerspectiveOffCenterFrustum(); -},{"10":10,"13":13,"14":14,"15":15,"8":8,"9":9}],8:[function(require,module,exports){ -"use strict"; -module.exports = Reader; + /** + * 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 = options.fov; + this._fov = undefined; + this._fovy = undefined; -var util = require(13); + this._sseDenominator = undefined; -var BufferReader; // cyclic + /** + * The aspect ratio of the frustum's width to it's height. + * @type {Number} + * @default undefined + */ + this.aspectRatio = options.aspectRatio; + this._aspectRatio = undefined; -var LongBits = util.LongBits, - utf8 = util.utf8; + /** + * The distance of the near plane. + * @type {Number} + * @default 1.0 + */ + this.near = defaultValue(options.near, 1.0); + this._near = this.near; -/* istanbul ignore next */ -function indexOutOfRange(reader, writeLength) { - return RangeError("index out of range: " + reader.pos + " + " + (writeLength || 1) + " > " + reader.len); -} + /** + * The distance of the far plane. + * @type {Number} + * @default 500000000.0 + */ + this.far = defaultValue(options.far, 500000000.0); + this._far = this.far; -/** - * Constructs a new reader instance using the specified buffer. - * @classdesc Wire format reader using `Uint8Array` if available, otherwise `Array`. - * @constructor - * @param {Uint8Array} buffer Buffer to read from - */ -function Reader(buffer) { + /** + * Offsets the frustum in the x direction. + * @type {Number} + * @default 0.0 + */ + this.xOffset = defaultValue(options.xOffset, 0.0); + this._xOffset = this.xOffset; - /** - * Read buffer. - * @type {Uint8Array} - */ - this.buf = buffer; + /** + * Offsets the frustum in the y direction. + * @type {Number} + * @default 0.0 + */ + this.yOffset = defaultValue(options.yOffset, 0.0); + this._yOffset = this.yOffset; + } /** - * Read buffer position. - * @type {number} + * The number of elements used to pack the object into an array. + * @type {Number} */ - this.pos = 0; + PerspectiveFrustum.packedLength = 6; /** - * Read buffer length. - * @type {number} + * Stores the provided instance into the provided array. + * + * @param {PerspectiveFrustum} 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 */ - this.len = buffer.length; -} + PerspectiveFrustum.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); -var create_array = typeof Uint8Array !== "undefined" - ? function create_typed_array(buffer) { - if (buffer instanceof Uint8Array || Array.isArray(buffer)) - return new Reader(buffer); - throw Error("illegal buffer"); - } - /* istanbul ignore next */ - : function create_array(buffer) { - if (Array.isArray(buffer)) - return new Reader(buffer); - throw Error("illegal buffer"); + array[startingIndex++] = value.fov; + array[startingIndex++] = value.aspectRatio; + array[startingIndex++] = value.near; + array[startingIndex++] = value.far; + array[startingIndex++] = value.xOffset; + array[startingIndex] = value.yOffset; + + return array; }; -/** - * Creates a new reader using the specified buffer. - * @function - * @param {Uint8Array|Buffer} buffer Buffer to read from - * @returns {Reader|BufferReader} A {@link BufferReader} if `buffer` is a Buffer, otherwise a {@link Reader} - * @throws {Error} If `buffer` is not a valid buffer - */ -Reader.create = util.Buffer - ? function create_buffer_setup(buffer) { - return (Reader.create = function create_buffer(buffer) { - return util.Buffer.isBuffer(buffer) - ? new BufferReader(buffer) - /* istanbul ignore next */ - : create_array(buffer); - })(buffer); - } - /* istanbul ignore next */ - : create_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 {PerspectiveFrustum} [result] The object into which to store the result. + * @returns {PerspectiveFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided. + */ + PerspectiveFrustum.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); -Reader.prototype._slice = util.Array.prototype.subarray || /* istanbul ignore next */ util.Array.prototype.slice; + if (!defined(result)) { + result = new PerspectiveFrustum(); + } -/** - * Reads a varint as an unsigned 32 bit value. - * @function - * @returns {number} Value read - */ -Reader.prototype.uint32 = (function read_uint32_setup() { - var value = 4294967295; // optimizer type-hint, tends to deopt otherwise (?!) - return function read_uint32() { - value = ( this.buf[this.pos] & 127 ) >>> 0; if (this.buf[this.pos++] < 128) return value; - value = (value | (this.buf[this.pos] & 127) << 7) >>> 0; if (this.buf[this.pos++] < 128) return value; - value = (value | (this.buf[this.pos] & 127) << 14) >>> 0; if (this.buf[this.pos++] < 128) return value; - value = (value | (this.buf[this.pos] & 127) << 21) >>> 0; if (this.buf[this.pos++] < 128) return value; - value = (value | (this.buf[this.pos] & 15) << 28) >>> 0; if (this.buf[this.pos++] < 128) return value; + result.fov = array[startingIndex++]; + result.aspectRatio = array[startingIndex++]; + result.near = array[startingIndex++]; + result.far = array[startingIndex++]; + result.xOffset = array[startingIndex++]; + result.yOffset = array[startingIndex]; - /* istanbul ignore next */ - if ((this.pos += 5) > this.len) { - this.pos = this.len; - throw indexOutOfRange(this, 10); - } - return value; + return result; }; -})(); -/** - * Reads a varint as a signed 32 bit value. - * @returns {number} Value read - */ -Reader.prototype.int32 = function read_int32() { - return this.uint32() | 0; -}; + function update(frustum) { + + var f = frustum._offCenterFrustum; -/** - * Reads a zig-zag encoded varint as a signed 32 bit value. - * @returns {number} Value read - */ -Reader.prototype.sint32 = function read_sint32() { - var value = this.uint32(); - return value >>> 1 ^ -(value & 1) | 0; -}; + 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) { + + 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; -/* eslint-disable no-invalid-this */ + 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; -function readLongVarint() { - // tends to deopt with local vars for octet etc. - var bits = new LongBits(0, 0); - var i = 0; - if (this.len - this.pos > 4) { // fast route (lo) - for (; i < 4; ++i) { - // 1st..4th - bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0; - if (this.buf[this.pos++] < 128) - return bits; - } - // 5th - bits.lo = (bits.lo | (this.buf[this.pos] & 127) << 28) >>> 0; - bits.hi = (bits.hi | (this.buf[this.pos] & 127) >> 4) >>> 0; - if (this.buf[this.pos++] < 128) - return bits; - i = 0; - } else { - for (; i < 3; ++i) { - /* istanbul ignore next */ - if (this.pos >= this.len) - throw indexOutOfRange(this); - // 1st..3th - bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0; - if (this.buf[this.pos++] < 128) - return bits; - } - // 4th - bits.lo = (bits.lo | (this.buf[this.pos++] & 127) << i * 7) >>> 0; - return bits; - } - if (this.len - this.pos > 4) { // fast route (hi) - for (; i < 5; ++i) { - // 6th..10th - bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0; - if (this.buf[this.pos++] < 128) - return bits; - } - } else { - for (; i < 5; ++i) { - /* istanbul ignore next */ - if (this.pos >= this.len) - throw indexOutOfRange(this); - // 6th..10th - bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0; - if (this.buf[this.pos++] < 128) - return bits; + f.right += frustum.xOffset; + f.left += frustum.xOffset; + f.top += frustum.yOffset; + f.bottom += frustum.yOffset; } } - /* istanbul ignore next */ - throw Error("invalid varint encoding"); -} -/* eslint-enable no-invalid-this */ + 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; + } + }, -/** - * Reads a varint as a signed 64 bit value. - * @name Reader#int64 - * @function - * @returns {Long|number} Value read - */ + /** + * 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; + } + }, -/** - * Reads a varint as an unsigned 64 bit value. - * @name Reader#uint64 - * @function - * @returns {Long|number} Value read - */ + /** + * 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; + } + }, -/** - * Reads a zig-zag encoded varint as a signed 64 bit value. - * @name Reader#sint64 - * @function - * @returns {Long|number} Value read - */ + /** + * @readonly + * @private + */ + sseDenominator : { + get : function () { + update(this); + return this._sseDenominator; + } + } + }); -/** - * Reads a varint as a boolean. - * @returns {boolean} Value read - */ -Reader.prototype.bool = function read_bool() { - return this.uint32() !== 0; -}; + /** + * 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); + }; -function readFixed32(buf, end) { - return (buf[end - 4] - | buf[end - 3] << 8 - | buf[end - 2] << 16 - | buf[end - 1] << 24) >>> 0; -} + /** + * 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); + }; -/** - * Reads fixed 32 bits as an unsigned 32 bit integer. - * @returns {number} Value read - */ -Reader.prototype.fixed32 = function read_fixed32() { + /** + * 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(); + } - /* istanbul ignore next */ - if (this.pos + 4 > this.len) - throw indexOutOfRange(this, 4); + result.aspectRatio = this.aspectRatio; + result.fov = this.fov; + result.near = this.near; + result.far = this.far; - return readFixed32(this.buf, this.pos += 4); -}; + // force update of clone to compute matrices + result._aspectRatio = undefined; + result._fov = undefined; + result._near = undefined; + result._far = undefined; -/** - * Reads fixed 32 bits as a signed 32 bit integer. - * @returns {number} Value read - */ -Reader.prototype.sfixed32 = function read_sfixed32() { + this._offCenterFrustum.clone(result._offCenterFrustum); - /* istanbul ignore next */ - if (this.pos + 4 > this.len) - throw indexOutOfRange(this, 4); + return result; + }; - return readFixed32(this.buf, this.pos += 4) | 0; -}; + /** + * 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; + } -/* eslint-disable no-invalid-this */ + update(this); + update(other); -function readFixed64(/* this: Reader */) { + return (this.fov === other.fov && + this.aspectRatio === other.aspectRatio && + this.near === other.near && + this.far === other.far && + this._offCenterFrustum.equals(other._offCenterFrustum)); + }; - /* istanbul ignore next */ - if (this.pos + 8 > this.len) - throw indexOutOfRange(this, 8); + return PerspectiveFrustum; +}); - return new LongBits(readFixed32(this.buf, this.pos += 4), readFixed32(this.buf, this.pos += 4)); -} +define('Core/FrustumGeometry',[ + './BoundingSphere', + './Cartesian3', + './Cartesian4', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './Matrix3', + './Matrix4', + './OrthographicFrustum', + './PerspectiveFrustum', + './PrimitiveType', + './Quaternion', + './VertexFormat' + ], function( + BoundingSphere, + Cartesian3, + Cartesian4, + Check, + ComponentDatatype, + defaultValue, + defined, + Geometry, + GeometryAttribute, + GeometryAttributes, + Matrix3, + Matrix4, + OrthographicFrustum, + PerspectiveFrustum, + PrimitiveType, + Quaternion, + VertexFormat) { + 'use strict'; -/* eslint-enable no-invalid-this */ + var PERSPECTIVE = 0; + var ORTHOGRAPHIC = 1; -/** - * Reads fixed 64 bits. - * @name Reader#fixed64 - * @function - * @returns {Long|number} Value read - */ + /** + * Describes a frustum at the given the origin and orientation. + * + * @alias FrustumGeometry + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {PerspectiveFrustum|OrthographicFrustum} options.frustum The frustum. + * @param {Cartesian3} options.origin The origin of the frustum. + * @param {Quaternion} options.orientation The orientation of the frustum. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + */ + function FrustumGeometry(options) { + + var frustum = options.frustum; + var orientation = options.orientation; + var origin = options.origin; + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); -/** - * Reads zig-zag encoded fixed 64 bits. - * @name Reader#sfixed64 - * @function - * @returns {Long|number} Value read - */ + // This is private because it is used by DebugCameraPrimitive to draw a multi-frustum by + // creating multiple FrustumGeometrys. This way the near plane of one frustum doesn't overlap + // the far plane of another. + var drawNearPlane = defaultValue(options._drawNearPlane, true); -var readFloat = typeof Float32Array !== "undefined" - ? (function() { - var f32 = new Float32Array(1), - f8b = new Uint8Array(f32.buffer); - f32[0] = -0; - return f8b[3] // already le? - ? function readFloat_f32(buf, pos) { - f8b[0] = buf[pos ]; - f8b[1] = buf[pos + 1]; - f8b[2] = buf[pos + 2]; - f8b[3] = buf[pos + 3]; - return f32[0]; - } - /* istanbul ignore next */ - : function readFloat_f32_le(buf, pos) { - f8b[0] = buf[pos + 3]; - f8b[1] = buf[pos + 2]; - f8b[2] = buf[pos + 1]; - f8b[3] = buf[pos ]; - return f32[0]; - }; - })() - /* istanbul ignore next */ - : function readFloat_ieee754(buf, pos) { - var uint = readFixed32(buf, pos + 4), - sign = (uint >> 31) * 2 + 1, - exponent = uint >>> 23 & 255, - mantissa = uint & 8388607; - return exponent === 255 - ? mantissa - ? NaN - : sign * Infinity - : exponent === 0 // denormal - ? sign * 1.401298464324817e-45 * mantissa - : sign * Math.pow(2, exponent - 150) * (mantissa + 8388608); - }; + var frustumType; + var frustumPackedLength; + if (frustum instanceof PerspectiveFrustum) { + frustumType = PERSPECTIVE; + frustumPackedLength = PerspectiveFrustum.packedLength; + } else if (frustum instanceof OrthographicFrustum) { + frustumType = ORTHOGRAPHIC; + frustumPackedLength = OrthographicFrustum.packedLength; + } + + this._frustumType = frustumType; + this._frustum = frustum.clone(); + this._origin = Cartesian3.clone(origin); + this._orientation = Quaternion.clone(orientation); + this._drawNearPlane = drawNearPlane; + this._vertexFormat = vertexFormat; + this._workerName = 'createFrustumGeometry'; -/** - * Reads a float (32 bit) as a number. - * @function - * @returns {number} Value read - */ -Reader.prototype.float = function read_float() { + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + this.packedLength = 2 + frustumPackedLength + Cartesian3.packedLength + Quaternion.packedLength + VertexFormat.packedLength; + } - /* istanbul ignore next */ - if (this.pos + 4 > this.len) - throw indexOutOfRange(this, 4); + /** + * Stores the provided instance into the provided array. + * + * @param {FrustumGeometry} 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 + */ + FrustumGeometry.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); - var value = readFloat(this.buf, this.pos); - this.pos += 4; - return value; -}; + var frustumType = value._frustumType; + var frustum = value._frustum; -var readDouble = typeof Float64Array !== "undefined" - ? (function() { - var f64 = new Float64Array(1), - f8b = new Uint8Array(f64.buffer); - f64[0] = -0; - return f8b[7] // already le? - ? function readDouble_f64(buf, pos) { - f8b[0] = buf[pos ]; - f8b[1] = buf[pos + 1]; - f8b[2] = buf[pos + 2]; - f8b[3] = buf[pos + 3]; - f8b[4] = buf[pos + 4]; - f8b[5] = buf[pos + 5]; - f8b[6] = buf[pos + 6]; - f8b[7] = buf[pos + 7]; - return f64[0]; - } - /* istanbul ignore next */ - : function readDouble_f64_le(buf, pos) { - f8b[0] = buf[pos + 7]; - f8b[1] = buf[pos + 6]; - f8b[2] = buf[pos + 5]; - f8b[3] = buf[pos + 4]; - f8b[4] = buf[pos + 3]; - f8b[5] = buf[pos + 2]; - f8b[6] = buf[pos + 1]; - f8b[7] = buf[pos ]; - return f64[0]; - }; - })() - /* istanbul ignore next */ - : function readDouble_ieee754(buf, pos) { - var lo = readFixed32(buf, pos + 4), - hi = readFixed32(buf, pos + 8); - var sign = (hi >> 31) * 2 + 1, - exponent = hi >>> 20 & 2047, - mantissa = 4294967296 * (hi & 1048575) + lo; - return exponent === 2047 - ? mantissa - ? NaN - : sign * Infinity - : exponent === 0 // denormal - ? sign * 5e-324 * mantissa - : sign * Math.pow(2, exponent - 1075) * (mantissa + 4503599627370496); - }; + array[startingIndex++] = frustumType; -/** - * Reads a double (64 bit float) as a number. - * @function - * @returns {number} Value read - */ -Reader.prototype.double = function read_double() { + if (frustumType === PERSPECTIVE) { + PerspectiveFrustum.pack(frustum, array, startingIndex); + startingIndex += PerspectiveFrustum.packedLength; + } else { + OrthographicFrustum.pack(frustum, array, startingIndex); + startingIndex += OrthographicFrustum.packedLength; + } - /* istanbul ignore next */ - if (this.pos + 8 > this.len) - throw indexOutOfRange(this, 4); + Cartesian3.pack(value._origin, array, startingIndex); + startingIndex += Cartesian3.packedLength; + Quaternion.pack(value._orientation, array, startingIndex); + startingIndex += Quaternion.packedLength; + VertexFormat.pack(value._vertexFormat, array, startingIndex); + startingIndex += VertexFormat.packedLength; + array[startingIndex] = value._drawNearPlane ? 1.0 : 0.0; - var value = readDouble(this.buf, this.pos); - this.pos += 8; - return value; -}; + return array; + }; -/** - * Reads a sequence of bytes preceeded by its length as a varint. - * @returns {Uint8Array} Value read - */ -Reader.prototype.bytes = function read_bytes() { - var length = this.uint32(), - start = this.pos, - end = this.pos + length; + var scratchPackPerspective = new PerspectiveFrustum(); + var scratchPackOrthographic = new OrthographicFrustum(); + var scratchPackQuaternion = new Quaternion(); + var scratchPackorigin = new Cartesian3(); + var scratchVertexFormat = new VertexFormat(); - /* istanbul ignore next */ - if (end > this.len) - throw indexOutOfRange(this, length); + /** + * 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 {FrustumGeometry} [result] The object into which to store the result. + */ + FrustumGeometry.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); - this.pos += length; - return start === end // fix for IE 10/Win8 and others' subarray returning array of size 1 - ? new this.buf.constructor(0) - : this._slice.call(this.buf, start, end); -}; + var frustumType = array[startingIndex++]; -/** - * Reads a string preceeded by its byte length as a varint. - * @returns {string} Value read - */ -Reader.prototype.string = function read_string() { - var bytes = this.bytes(); - return utf8.read(bytes, 0, bytes.length); -}; + var frustum; + if (frustumType === PERSPECTIVE) { + frustum = PerspectiveFrustum.unpack(array, startingIndex, scratchPackPerspective); + startingIndex += PerspectiveFrustum.packedLength; + } else { + frustum = OrthographicFrustum.unpack(array, startingIndex, scratchPackOrthographic); + startingIndex += OrthographicFrustum.packedLength; + } -/** - * Skips the specified number of bytes if specified, otherwise skips a varint. - * @param {number} [length] Length if known, otherwise a varint is assumed - * @returns {Reader} `this` - */ -Reader.prototype.skip = function skip(length) { - if (typeof length === "number") { - /* istanbul ignore next */ - if (this.pos + length > this.len) - throw indexOutOfRange(this, length); - this.pos += length; - } else { - /* istanbul ignore next */ - do { - if (this.pos >= this.len) - throw indexOutOfRange(this); - } while (this.buf[this.pos++] & 128); - } - return this; -}; + var origin = Cartesian3.unpack(array, startingIndex, scratchPackorigin); + startingIndex += Cartesian3.packedLength; + var orientation = Quaternion.unpack(array, startingIndex, scratchPackQuaternion); + startingIndex += Quaternion.packedLength; + var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); + startingIndex += VertexFormat.packedLength; + var drawNearPlane = array[startingIndex] === 1.0; -/** - * Skips the next element of the specified wire type. - * @param {number} wireType Wire type received - * @returns {Reader} `this` - */ -Reader.prototype.skipType = function(wireType) { - switch (wireType) { - case 0: - this.skip(); - break; - case 1: - this.skip(8); - break; - case 2: - this.skip(this.uint32()); - break; - case 3: - do { // eslint-disable-line no-constant-condition - if ((wireType = this.uint32() & 7) === 4) - break; - this.skipType(wireType); - } while (true); - break; - case 5: - this.skip(4); - break; + if (!defined(result)) { + return new FrustumGeometry({ + frustum : frustum, + origin : origin, + orientation : orientation, + vertexFormat : vertexFormat, + _drawNearPlane : drawNearPlane + }); + } - /* istanbul ignore next */ - default: - throw Error("invalid wire type " + wireType + " at offset " + this.pos); - } - return this; -}; + var frustumResult = frustumType === result._frustumType ? result._frustum : undefined; + result._frustum = frustum.clone(frustumResult); -Reader._configure = function(BufferReader_) { - BufferReader = BufferReader_; + result._frustumType = frustumType; + result._origin = Cartesian3.clone(origin, result._origin); + result._orientation = Quaternion.clone(orientation, result._orientation); + result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat); + result._drawNearPlane = drawNearPlane; - var fn = util.Long ? "toLong" : /* istanbul ignore next */ "toNumber"; - util.merge(Reader.prototype, { + return result; + }; - int64: function read_int64() { - return readLongVarint.call(this)[fn](false); - }, + function getAttributes(offset, normals, tangents, bitangents, st, normal, tangent, bitangent) { + var stOffset = offset / 3 * 2; - uint64: function read_uint64() { - return readLongVarint.call(this)[fn](true); - }, + for (var i = 0; i < 4; ++i) { + if (defined(normals)) { + normals[offset] = normal.x; + normals[offset + 1] = normal.y; + normals[offset + 2] = normal.z; + } + if (defined(tangents)) { + tangents[offset] = tangent.x; + tangents[offset + 1] = tangent.y; + tangents[offset + 2] = tangent.z; + } + if (defined(bitangents)) { + bitangents[offset] = bitangent.x; + bitangents[offset + 1] = bitangent.y; + bitangents[offset + 2] = bitangent.z; + } + offset += 3; + } - sint64: function read_sint64() { - return readLongVarint.call(this).zzDecode()[fn](false); - }, + st[stOffset] = 0.0; + st[stOffset + 1] = 0.0; + st[stOffset + 2] = 1.0; + st[stOffset + 3] = 0.0; + st[stOffset + 4] = 1.0; + st[stOffset + 5] = 1.0; + st[stOffset + 6] = 0.0; + st[stOffset + 7] = 1.0; + } - fixed64: function read_fixed64() { - return readFixed64.call(this)[fn](true); - }, + var scratchRotationMatrix = new Matrix3(); + var scratchViewMatrix = new Matrix4(); + var scratchInverseMatrix = new Matrix4(); - sfixed64: function read_sfixed64() { - return readFixed64.call(this)[fn](false); - } + var scratchXDirection = new Cartesian3(); + var scratchYDirection = new Cartesian3(); + var scratchZDirection = new Cartesian3(); + var scratchNegativeX = new Cartesian3(); + var scratchNegativeY = new Cartesian3(); + var scratchNegativeZ = new Cartesian3(); - }); -}; + var frustumSplits = new Array(3); -},{"13":13}],9:[function(require,module,exports){ -"use strict"; -module.exports = BufferReader; + var frustumCornersNDC = new Array(4); + 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); -// extends Reader -var Reader = require(8); -(BufferReader.prototype = Object.create(Reader.prototype)).constructor = BufferReader; + var scratchFrustumCorners = new Array(4); + for (var i = 0; i < 4; ++i) { + scratchFrustumCorners[i] = new Cartesian4(); + } -var util = require(13); + FrustumGeometry._computeNearFarPlanes = function(origin, orientation, frustumType, frustum, positions, xDirection, yDirection, zDirection) { + var rotationMatrix = Matrix3.fromQuaternion(orientation, scratchRotationMatrix); + var x = defaultValue(xDirection, scratchXDirection); + var y = defaultValue(yDirection, scratchYDirection); + var z = defaultValue(zDirection, scratchZDirection); -/** - * Constructs a new buffer reader instance. - * @classdesc Wire format reader using node buffers. - * @extends Reader - * @constructor - * @param {Buffer} buffer Buffer to read from - */ -function BufferReader(buffer) { - Reader.call(this, buffer); + x = Matrix3.getColumn(rotationMatrix, 0, x); + y = Matrix3.getColumn(rotationMatrix, 1, y); + z = Matrix3.getColumn(rotationMatrix, 2, z); - /** - * Read buffer. - * @name BufferReader#buf - * @type {Buffer} - */ -} + Cartesian3.normalize(x, x); + Cartesian3.normalize(y, y); + Cartesian3.normalize(z, z); -/* istanbul ignore else */ -if (util.Buffer) - BufferReader.prototype._slice = util.Buffer.prototype.slice; + Cartesian3.negate(x, x); -/** - * @override - */ -BufferReader.prototype.string = function read_string_buffer() { - var len = this.uint32(); // modifies pos - return this.buf.utf8Slice(this.pos, this.pos = Math.min(this.pos + len, this.len)); -}; + var view = Matrix4.computeView(origin, z, y, x, scratchViewMatrix); -/** - * Reads a sequence of bytes preceeded by its length as a varint. - * @name BufferReader#bytes - * @function - * @returns {Buffer} Value read - */ + var inverseView; + var inverseViewProjection; + if (frustumType === PERSPECTIVE) { + var projection = frustum.projectionMatrix; + var viewProjection = Matrix4.multiply(projection, view, scratchInverseMatrix); + inverseViewProjection = Matrix4.inverse(viewProjection, scratchInverseMatrix); + } else { + inverseView = Matrix4.inverseTransformation(view, scratchInverseMatrix); + } -},{"13":13,"8":8}],10:[function(require,module,exports){ -"use strict"; + if (defined(inverseViewProjection)) { + frustumSplits[0] = frustum.near; + frustumSplits[1] = frustum.far; + } else { + frustumSplits[0] = 0.0; + frustumSplits[1] = frustum.near; + frustumSplits[2] = frustum.far; + } -/** - * Streaming RPC helpers. - * @namespace - */ -var rpc = exports; + for (var i = 0; i < 2; ++i) { + for (var j = 0; j < 4; ++j) { + var corner = Cartesian4.clone(frustumCornersNDC[j], scratchFrustumCorners[j]); -/** - * RPC implementation passed to {@link Service#create} performing a service request on network level, i.e. by utilizing http requests or websockets. - * @typedef RPCImpl - * @type {function} - * @param {Method|rpc.ServiceMethod} method Reflected or static method being called - * @param {Uint8Array} requestData Request data - * @param {RPCImplCallback} callback Callback function - * @returns {undefined} - * @example - * function rpcImpl(method, requestData, callback) { - * if (protobuf.util.lcFirst(method.name) !== "myMethod") // compatible with static code - * throw Error("no such method"); - * asynchronouslyObtainAResponse(requestData, function(err, responseData) { - * callback(err, responseData); - * }); - * } - */ + if (!defined(inverseViewProjection)) { + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } -/** - * Node-style callback as used by {@link RPCImpl}. - * @typedef RPCImplCallback - * @type {function} - * @param {?Error} error Error, if any, otherwise `null` - * @param {?Uint8Array} [response] Response data or `null` to signal end of stream, if there hasn't been an error - * @returns {undefined} - */ + var near = frustumSplits[i]; + var far = frustumSplits[i + 1]; -rpc.Service = require(11); + corner.x = (corner.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; + corner.y = (corner.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; + corner.z = (corner.z * (near - far) - near - far) * 0.5; + corner.w = 1.0; -},{"11":11}],11:[function(require,module,exports){ -"use strict"; -module.exports = Service; + Matrix4.multiplyByVector(inverseView, corner, corner); + } else { + corner = Matrix4.multiplyByVector(inverseViewProjection, corner, corner); -var util = require(13); + // Reverse perspective divide + var w = 1.0 / corner.w; + Cartesian3.multiplyByScalar(corner, w, corner); -// Extends EventEmitter -(Service.prototype = Object.create(util.EventEmitter.prototype)).constructor = Service; + Cartesian3.subtract(corner, origin, corner); + Cartesian3.normalize(corner, corner); -/** - * A service method callback as used by {@link rpc.ServiceMethod|ServiceMethod}. - * - * Differs from {@link RPCImplCallback} in that it is an actual callback of a service method which may not return `response = null`. - * @typedef rpc.ServiceMethodCallback - * @type {function} - * @param {?Error} error Error, if any - * @param {?Message} [response] Response message - * @returns {undefined} - */ + var fac = Cartesian3.dot(z, corner); + Cartesian3.multiplyByScalar(corner, frustumSplits[i] / fac, corner); + Cartesian3.add(corner, origin, corner); + } -/** - * A service method part of a {@link rpc.ServiceMethodMixin|ServiceMethodMixin} and thus {@link rpc.Service} as created by {@link Service.create}. - * @typedef rpc.ServiceMethod - * @type {function} - * @param {Message|Object.} request Request message or plain object - * @param {rpc.ServiceMethodCallback} [callback] Node-style callback called with the error, if any, and the response message - * @returns {Promise} Promise if `callback` has been omitted, otherwise `undefined` - */ + positions[12 * i + j * 3] = corner.x; + positions[12 * i + j * 3 + 1] = corner.y; + positions[12 * i + j * 3 + 2] = corner.z; + } + } + }; -/** - * A service method mixin. - * - * When using TypeScript, mixed in service methods are only supported directly with a type definition of a static module (used with reflection). Otherwise, explicit casting is required. - * @typedef rpc.ServiceMethodMixin - * @type {Object.} - * @example - * // Explicit casting with TypeScript - * (myRpcService["myMethod"] as protobuf.rpc.ServiceMethod)(...) - */ + /** + * Computes the geometric representation of a frustum, including its vertices, indices, and a bounding sphere. + * + * @param {FrustumGeometry} frustumGeometry A description of the frustum. + * @returns {Geometry|undefined} The computed vertices and indices. + */ + FrustumGeometry.createGeometry = function(frustumGeometry) { + var frustumType = frustumGeometry._frustumType; + var frustum = frustumGeometry._frustum; + var origin = frustumGeometry._origin; + var orientation = frustumGeometry._orientation; + var drawNearPlane = frustumGeometry._drawNearPlane; + var vertexFormat = frustumGeometry._vertexFormat; + + var numberOfPlanes = drawNearPlane ? 6 : 5; + var positions = new Float64Array(3 * 4 * 6); + FrustumGeometry._computeNearFarPlanes(origin, orientation, frustumType, frustum, positions); + + // -x plane + var offset = 3 * 4 * 2; + positions[offset] = positions[3 * 4]; + positions[offset + 1] = positions[3 * 4 + 1]; + positions[offset + 2] = positions[3 * 4 + 2]; + positions[offset + 3] = positions[0]; + positions[offset + 4] = positions[1]; + positions[offset + 5] = positions[2]; + positions[offset + 6] = positions[3 * 3]; + positions[offset + 7] = positions[3 * 3 + 1]; + positions[offset + 8] = positions[3 * 3 + 2]; + positions[offset + 9] = positions[3 * 7]; + positions[offset + 10] = positions[3 * 7 + 1]; + positions[offset + 11] = positions[3 * 7 + 2]; + + // -y plane + offset += 3 * 4; + positions[offset] = positions[3 * 5]; + positions[offset + 1] = positions[3 * 5 + 1]; + positions[offset + 2] = positions[3 * 5 + 2]; + positions[offset + 3] = positions[3]; + positions[offset + 4] = positions[3 + 1]; + positions[offset + 5] = positions[3 + 2]; + positions[offset + 6] = positions[0]; + positions[offset + 7] = positions[1]; + positions[offset + 8] = positions[2]; + positions[offset + 9] = positions[3 * 4]; + positions[offset + 10] = positions[3 * 4 + 1]; + positions[offset + 11] = positions[3 * 4 + 2]; + + // +x plane + offset += 3 * 4; + positions[offset] = positions[3]; + positions[offset + 1] = positions[3 + 1]; + positions[offset + 2] = positions[3 + 2]; + positions[offset + 3] = positions[3 * 5]; + positions[offset + 4] = positions[3 * 5 + 1]; + positions[offset + 5] = positions[3 * 5 + 2]; + positions[offset + 6] = positions[3 * 6]; + positions[offset + 7] = positions[3 * 6 + 1]; + positions[offset + 8] = positions[3 * 6 + 2]; + positions[offset + 9] = positions[3 * 2]; + positions[offset + 10] = positions[3 * 2 + 1]; + positions[offset + 11] = positions[3 * 2 + 2]; + + // +y plane + offset += 3 * 4; + positions[offset] = positions[3 * 2]; + positions[offset + 1] = positions[3 * 2 + 1]; + positions[offset + 2] = positions[3 * 2 + 2]; + positions[offset + 3] = positions[3 * 6]; + positions[offset + 4] = positions[3 * 6 + 1]; + positions[offset + 5] = positions[3 * 6 + 2]; + positions[offset + 6] = positions[3 * 7]; + positions[offset + 7] = positions[3 * 7 + 1]; + positions[offset + 8] = positions[3 * 7 + 2]; + positions[offset + 9] = positions[3 * 3]; + positions[offset + 10] = positions[3 * 3 + 1]; + positions[offset + 11] = positions[3 * 3 + 2]; + + if (!drawNearPlane) { + positions = positions.subarray(3 * 4); + } -/** - * Constructs a new RPC service instance. - * @classdesc An RPC service as returned by {@link Service#create}. - * @exports rpc.Service - * @extends util.EventEmitter - * @augments rpc.ServiceMethodMixin - * @constructor - * @param {RPCImpl} rpcImpl RPC implementation - * @param {boolean} [requestDelimited=false] Whether requests are length-delimited - * @param {boolean} [responseDelimited=false] Whether responses are length-delimited - */ -function Service(rpcImpl, requestDelimited, responseDelimited) { + var attributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }) + }); - if (typeof rpcImpl !== "function") - throw TypeError("rpcImpl must be a function"); + if (defined(vertexFormat.normal) || defined(vertexFormat.tangent) || defined(vertexFormat.bitangent) || defined(vertexFormat.st)) { + var normals = defined(vertexFormat.normal) ? new Float32Array(3 * 4 * numberOfPlanes) : undefined; + var tangents = defined(vertexFormat.tangent) ? new Float32Array(3 * 4 * numberOfPlanes) : undefined; + var bitangents = defined(vertexFormat.bitangent) ? new Float32Array(3 * 4 * numberOfPlanes) : undefined; + var st = defined(vertexFormat.st) ? new Float32Array(2 * 4 * numberOfPlanes) : undefined; - util.EventEmitter.call(this); + var x = scratchXDirection; + var y = scratchYDirection; + var z = scratchZDirection; - /** - * RPC implementation. Becomes `null` once the service is ended. - * @type {?RPCImpl} - */ - this.rpcImpl = rpcImpl; + var negativeX = Cartesian3.negate(x, scratchNegativeX); + var negativeY = Cartesian3.negate(y, scratchNegativeY); + var negativeZ = Cartesian3.negate(z, scratchNegativeZ); - /** - * Whether requests are length-delimited. - * @type {boolean} - */ - this.requestDelimited = Boolean(requestDelimited); + offset = 0; + if (drawNearPlane) { + getAttributes(offset, normals, tangents, bitangents, st, negativeZ, x, y); // near + offset += 3 * 4; + } + getAttributes(offset, normals, tangents, bitangents, st, z, negativeX, y); // far + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, negativeX, negativeZ, y); // -x + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, negativeY, negativeZ, negativeX); // -y + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, x, z, y); // +x + offset += 3 * 4; + getAttributes(offset, normals, tangents, bitangents, st, y, z, negativeX); // +y + + if (defined(normals)) { + attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); + } + if (defined(tangents)) { + attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + if (defined(bitangents)) { + attributes.bitangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : bitangents + }); + } + if (defined(st)) { + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : st + }); + } + } - /** - * Whether responses are length-delimited. - * @type {boolean} - */ - this.responseDelimited = Boolean(responseDelimited); -} + var indices = new Uint16Array(6 * numberOfPlanes); + for (var i = 0; i < numberOfPlanes; ++i) { + var indexOffset = i * 6; + var index = i * 4; -/** - * Calls a service method through {@link rpc.Service#rpcImpl|rpcImpl}. - * @param {Method|rpc.ServiceMethod} method Reflected or static method - * @param {function} requestCtor Request constructor - * @param {function} responseCtor Response constructor - * @param {Message|Object.} request Request message or plain object - * @param {rpc.ServiceMethodCallback} callback Service callback - * @returns {undefined} - */ -Service.prototype.rpcCall = function rpcCall(method, requestCtor, responseCtor, request, callback) { + indices[indexOffset] = index; + indices[indexOffset + 1] = index + 1; + indices[indexOffset + 2] = index + 2; + indices[indexOffset + 3] = index; + indices[indexOffset + 4] = index + 2; + indices[indexOffset + 5] = index + 3; + } - if (!request) - throw TypeError("request must be specified"); + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : BoundingSphere.fromVertices(positions) + }); + }; - var self = this; - if (!callback) - return util.asPromise(rpcCall, self, method, requestCtor, responseCtor, request); + return FrustumGeometry; +}); - if (!self.rpcImpl) { - setTimeout(function() { callback(Error("already ended")); }, 0); - return undefined; - } +define('Core/FrustumOutlineGeometry',[ + './BoundingSphere', + './Cartesian3', + './Cartesian4', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './FrustumGeometry', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './Matrix3', + './Matrix4', + './OrthographicFrustum', + './PerspectiveFrustum', + './PrimitiveType', + './Quaternion' + ], function( + BoundingSphere, + Cartesian3, + Cartesian4, + Check, + ComponentDatatype, + defaultValue, + defined, + FrustumGeometry, + Geometry, + GeometryAttribute, + GeometryAttributes, + Matrix3, + Matrix4, + OrthographicFrustum, + PerspectiveFrustum, + PrimitiveType, + Quaternion) { + 'use strict'; - try { - return self.rpcImpl( - method, - requestCtor[self.requestDelimited ? "encodeDelimited" : "encode"](request).finish(), - function rpcCallback(err, response) { + var PERSPECTIVE = 0; + var ORTHOGRAPHIC = 1; - if (err) { - self.emit("error", err, method); - return callback(err); - } + /** + * A description of the outline of a frustum with the given the origin and orientation. + * + * @alias FrustumOutlineGeometry + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {PerspectiveFrustum|OrthographicFrustum} options.frustum The frustum. + * @param {Cartesian3} options.origin The origin of the frustum. + * @param {Quaternion} options.orientation The orientation of the frustum. + */ + function FrustumOutlineGeometry(options) { + + var frustum = options.frustum; + var orientation = options.orientation; + var origin = options.origin; - if (response === null) { - self.end(/* endedByRPC */ true); - return undefined; - } + // This is private because it is used by DebugCameraPrimitive to draw a multi-frustum by + // creating multiple FrustumOutlineGeometrys. This way the near plane of one frustum doesn't overlap + // the far plane of another. + var drawNearPlane = defaultValue(options._drawNearPlane, true); - if (!(response instanceof responseCtor)) { - try { - response = responseCtor[self.responseDelimited ? "decodeDelimited" : "decode"](response); - } catch (err) { - self.emit("error", err, method); - return callback(err); - } - } + var frustumType; + var frustumPackedLength; + if (frustum instanceof PerspectiveFrustum) { + frustumType = PERSPECTIVE; + frustumPackedLength = PerspectiveFrustum.packedLength; + } else if (frustum instanceof OrthographicFrustum) { + frustumType = ORTHOGRAPHIC; + frustumPackedLength = OrthographicFrustum.packedLength; + } - self.emit("data", response, method); - return callback(null, response); - } - ); - } catch (err) { - self.emit("error", err, method); - setTimeout(function() { callback(err); }, 0); - return undefined; - } -}; + this._frustumType = frustumType; + this._frustum = frustum.clone(); + this._origin = Cartesian3.clone(origin); + this._orientation = Quaternion.clone(orientation); + this._drawNearPlane = drawNearPlane; + this._workerName = 'createFrustumOutlineGeometry'; -/** - * Ends this service and emits the `end` event. - * @param {boolean} [endedByRPC=false] Whether the service has been ended by the RPC implementation. - * @returns {rpc.Service} `this` - */ -Service.prototype.end = function end(endedByRPC) { - if (this.rpcImpl) { - if (!endedByRPC) // signal end to rpcImpl - this.rpcImpl(null, null, null); - this.rpcImpl = null; - this.emit("end").off(); + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + this.packedLength = 2 + frustumPackedLength + Cartesian3.packedLength + Quaternion.packedLength; } - return this; -}; -},{"13":13}],12:[function(require,module,exports){ -"use strict"; -module.exports = LongBits; + /** + * Stores the provided instance into the provided array. + * + * @param {FrustumOutlineGeometry} 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 + */ + FrustumOutlineGeometry.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); -var util = require(13); + var frustumType = value._frustumType; + var frustum = value._frustum; -/** - * Any compatible Long instance. - * - * This is a minimal stand-alone definition of a Long instance. The actual type is that exported by long.js. - * @typedef Long - * @type {Object} - * @property {number} low Low bits - * @property {number} high High bits - * @property {boolean} unsigned Whether unsigned or not - */ + array[startingIndex++] = frustumType; -/** - * Constructs new long bits. - * @classdesc Helper class for working with the low and high bits of a 64 bit value. - * @memberof util - * @constructor - * @param {number} lo Low 32 bits, unsigned - * @param {number} hi High 32 bits, unsigned - */ -function LongBits(lo, hi) { + if (frustumType === PERSPECTIVE) { + PerspectiveFrustum.pack(frustum, array, startingIndex); + startingIndex += PerspectiveFrustum.packedLength; + } else { + OrthographicFrustum.pack(frustum, array, startingIndex); + startingIndex += OrthographicFrustum.packedLength; + } - // note that the casts below are theoretically unnecessary as of today, but older statically - // generated converter code might still call the ctor with signed 32bits. kept for compat. + Cartesian3.pack(value._origin, array, startingIndex); + startingIndex += Cartesian3.packedLength; + Quaternion.pack(value._orientation, array, startingIndex); + startingIndex += Quaternion.packedLength; + array[startingIndex] = value._drawNearPlane ? 1.0 : 0.0; - /** - * Low bits. - * @type {number} - */ - this.lo = lo >>> 0; + return array; + }; + + var scratchPackPerspective = new PerspectiveFrustum(); + var scratchPackOrthographic = new OrthographicFrustum(); + var scratchPackQuaternion = new Quaternion(); + var scratchPackorigin = new Cartesian3(); /** - * High bits. - * @type {number} + * 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 {FrustumOutlineGeometry} [result] The object into which to store the result. */ - this.hi = hi >>> 0; -} + FrustumOutlineGeometry.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); -/** - * Zero bits. - * @memberof util.LongBits - * @type {util.LongBits} - */ -var zero = LongBits.zero = new LongBits(0, 0); + var frustumType = array[startingIndex++]; -zero.toNumber = function() { return 0; }; -zero.zzEncode = zero.zzDecode = function() { return this; }; -zero.length = function() { return 1; }; + var frustum; + if (frustumType === PERSPECTIVE) { + frustum = PerspectiveFrustum.unpack(array, startingIndex, scratchPackPerspective); + startingIndex += PerspectiveFrustum.packedLength; + } else { + frustum = OrthographicFrustum.unpack(array, startingIndex, scratchPackOrthographic); + startingIndex += OrthographicFrustum.packedLength; + } -/** - * Zero hash. - * @memberof util.LongBits - * @type {string} - */ -var zeroHash = LongBits.zeroHash = "\0\0\0\0\0\0\0\0"; + var origin = Cartesian3.unpack(array, startingIndex, scratchPackorigin); + startingIndex += Cartesian3.packedLength; + var orientation = Quaternion.unpack(array, startingIndex, scratchPackQuaternion); + startingIndex += Quaternion.packedLength; + var drawNearPlane = array[startingIndex] === 1.0; -/** - * Constructs new long bits from the specified number. - * @param {number} value Value - * @returns {util.LongBits} Instance - */ -LongBits.fromNumber = function fromNumber(value) { - if (value === 0) - return zero; - var sign = value < 0; - if (sign) - value = -value; - var lo = value >>> 0, - hi = (value - lo) / 4294967296 >>> 0; - if (sign) { - hi = ~hi >>> 0; - lo = ~lo >>> 0; - if (++lo > 4294967295) { - lo = 0; - if (++hi > 4294967295) - hi = 0; + if (!defined(result)) { + return new FrustumOutlineGeometry({ + frustum : frustum, + origin : origin, + orientation : orientation, + _drawNearPlane : drawNearPlane + }); } - } - return new LongBits(lo, hi); -}; -/** - * Constructs new long bits from a number, long or string. - * @param {Long|number|string} value Value - * @returns {util.LongBits} Instance - */ -LongBits.from = function from(value) { - if (typeof value === "number") - return LongBits.fromNumber(value); - if (util.isString(value)) { - /* istanbul ignore else */ - if (util.Long) - value = util.Long.fromString(value); - else - return LongBits.fromNumber(parseInt(value, 10)); - } - return value.low || value.high ? new LongBits(value.low >>> 0, value.high >>> 0) : zero; -}; + var frustumResult = frustumType === result._frustumType ? result._frustum : undefined; + result._frustum = frustum.clone(frustumResult); -/** - * Converts this long bits to a possibly unsafe JavaScript number. - * @param {boolean} [unsigned=false] Whether unsigned or not - * @returns {number} Possibly unsafe number - */ -LongBits.prototype.toNumber = function toNumber(unsigned) { - if (!unsigned && this.hi >>> 31) { - var lo = ~this.lo + 1 >>> 0, - hi = ~this.hi >>> 0; - if (!lo) - hi = hi + 1 >>> 0; - return -(lo + hi * 4294967296); - } - return this.lo + this.hi * 4294967296; -}; + result._frustumType = frustumType; + result._origin = Cartesian3.clone(origin, result._origin); + result._orientation = Quaternion.clone(orientation, result._orientation); + result._drawNearPlane = drawNearPlane; -/** - * Converts this long bits to a long. - * @param {boolean} [unsigned=false] Whether unsigned or not - * @returns {Long} Long - */ -LongBits.prototype.toLong = function toLong(unsigned) { - return util.Long - ? new util.Long(this.lo | 0, this.hi | 0, Boolean(unsigned)) - /* istanbul ignore next */ - : { low: this.lo | 0, high: this.hi | 0, unsigned: Boolean(unsigned) }; -}; + return result; + }; -var charCodeAt = String.prototype.charCodeAt; + /** + * Computes the geometric representation of a frustum outline, including its vertices, indices, and a bounding sphere. + * + * @param {FrustumOutlineGeometry} frustumGeometry A description of the frustum. + * @returns {Geometry|undefined} The computed vertices and indices. + */ + FrustumOutlineGeometry.createGeometry = function(frustumGeometry) { + var frustumType = frustumGeometry._frustumType; + var frustum = frustumGeometry._frustum; + var origin = frustumGeometry._origin; + var orientation = frustumGeometry._orientation; + var drawNearPlane = frustumGeometry._drawNearPlane; -/** - * Constructs new long bits from the specified 8 characters long hash. - * @param {string} hash Hash - * @returns {util.LongBits} Bits - */ -LongBits.fromHash = function fromHash(hash) { - if (hash === zeroHash) - return zero; - return new LongBits( - ( charCodeAt.call(hash, 0) - | charCodeAt.call(hash, 1) << 8 - | charCodeAt.call(hash, 2) << 16 - | charCodeAt.call(hash, 3) << 24) >>> 0 - , - ( charCodeAt.call(hash, 4) - | charCodeAt.call(hash, 5) << 8 - | charCodeAt.call(hash, 6) << 16 - | charCodeAt.call(hash, 7) << 24) >>> 0 - ); -}; + var positions = new Float64Array(3 * 4 * 2); + FrustumGeometry._computeNearFarPlanes(origin, orientation, frustumType, frustum, positions); -/** - * Converts this long bits to a 8 characters long hash. - * @returns {string} Hash - */ -LongBits.prototype.toHash = function toHash() { - return String.fromCharCode( - this.lo & 255, - this.lo >>> 8 & 255, - this.lo >>> 16 & 255, - this.lo >>> 24 , - this.hi & 255, - this.hi >>> 8 & 255, - this.hi >>> 16 & 255, - this.hi >>> 24 - ); -}; + var attributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }) + }); -/** - * Zig-zag encodes this long bits. - * @returns {util.LongBits} `this` - */ -LongBits.prototype.zzEncode = function zzEncode() { - var mask = this.hi >> 31; - this.hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0; - this.lo = ( this.lo << 1 ^ mask) >>> 0; - return this; -}; + var offset; + var index; -/** - * Zig-zag decodes this long bits. - * @returns {util.LongBits} `this` - */ -LongBits.prototype.zzDecode = function zzDecode() { - var mask = -(this.lo & 1); - this.lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0; - this.hi = ( this.hi >>> 1 ^ mask) >>> 0; - return this; -}; + var numberOfPlanes = drawNearPlane ? 2 : 1; + var indices = new Uint16Array(8 * (numberOfPlanes + 1)); -/** - * Calculates the length of this longbits when encoded as a varint. - * @returns {number} Length - */ -LongBits.prototype.length = function length() { - var part0 = this.lo, - part1 = (this.lo >>> 28 | this.hi << 4) >>> 0, - part2 = this.hi >>> 24; - return part2 === 0 - ? part1 === 0 - ? part0 < 16384 - ? part0 < 128 ? 1 : 2 - : part0 < 2097152 ? 3 : 4 - : part1 < 16384 - ? part1 < 128 ? 5 : 6 - : part1 < 2097152 ? 7 : 8 - : part2 < 128 ? 9 : 10; -}; + // Build the near/far planes + var i = drawNearPlane ? 0 : 1; + for (; i < 2; ++i) { + offset = drawNearPlane ? i * 8 : 0; + index = i * 4; -},{"13":13}],13:[function(require,module,exports){ -"use strict"; -var util = exports; + indices[offset] = index; + indices[offset + 1] = index + 1; + indices[offset + 2] = index + 1; + indices[offset + 3] = index + 2; + indices[offset + 4] = index + 2; + indices[offset + 5] = index + 3; + indices[offset + 6] = index + 3; + indices[offset + 7] = index; + } -// used to return a Promise where callback is omitted -util.asPromise = require(1); + // Build the sides of the frustums + for (i = 0; i < 2; ++i) { + offset = (numberOfPlanes + i) * 8; + index = i * 4; -// converts to / from base64 encoded strings -util.base64 = require(2); + indices[offset] = index; + indices[offset + 1] = index + 4; + indices[offset + 2] = index + 1; + indices[offset + 3] = index + 5; + indices[offset + 4] = index + 2; + indices[offset + 5] = index + 6; + indices[offset + 6] = index + 3; + indices[offset + 7] = index + 7; + } -// base class of rpc.Service -util.EventEmitter = require(3); + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.LINES, + boundingSphere : BoundingSphere.fromVertices(positions) + }); + }; -// requires modules optionally and hides the call from bundlers -util.inquire = require(4); + return FrustumOutlineGeometry; +}); -// converts to / from utf8 encoded strings -util.utf8 = require(6); +define('Core/GeocoderService',[ + './DeveloperError' + ], function( + DeveloperError) { + 'use strict'; -// provides a node-like buffer pool in the browser -util.pool = require(5); + /** + * @typedef {Object} GeocoderResult + * @property {String} displayName The display name for a location + * @property {Rectangle|Cartesian3} destination The bounding box for a location + */ -// utility to work with the low and high bits of a 64 bit value -util.LongBits = require(12); + /** + * Provides geocoding through an external service. This type describes an interface and + * is not intended to be used. + * @alias GeocoderService + * @constructor + * + * @see BingMapsGeocoderService + */ + function GeocoderService() { + } -/** - * An immuable empty array. - * @memberof util - * @type {Array.<*>} - */ -util.emptyArray = Object.freeze ? Object.freeze([]) : /* istanbul ignore next */ []; // used on prototypes + /** + * @function + * + * @param {String} query The query to be sent to the geocoder service + * @returns {Promise} + */ + GeocoderService.prototype.geocode = DeveloperError.throwInstantiationError; -/** - * An immutable empty object. - * @type {Object} - */ -util.emptyObject = Object.freeze ? Object.freeze({}) : /* istanbul ignore next */ {}; // used on prototypes + return GeocoderService; +}); -/** - * Whether running within node or not. - * @memberof util - * @type {boolean} - */ -util.isNode = Boolean(global.process && global.process.versions && global.process.versions.node); +define('Core/GeometryInstanceAttribute',[ + './defaultValue', + './defined', + './DeveloperError' + ], function( + defaultValue, + defined, + DeveloperError) { + 'use strict'; -/** - * Tests if the specified value is an integer. - * @function - * @param {*} value Value to test - * @returns {boolean} `true` if the value is an integer - */ -util.isInteger = Number.isInteger || /* istanbul ignore next */ function isInteger(value) { - return typeof value === "number" && isFinite(value) && Math.floor(value) === value; -}; + /** + * 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); -/** - * Tests if the specified value is a string. - * @param {*} value Value to test - * @returns {boolean} `true` if the value is a string - */ -util.isString = function isString(value) { - return typeof value === "string" || value instanceof String; -}; + + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link GeometryInstanceAttribute#value}. + * + * @type ComponentDatatype + * + * @default undefined + */ + this.componentDatatype = options.componentDatatype; -/** - * Tests if the specified value is a non-null object. - * @param {*} value Value to test - * @returns {boolean} `true` if the value is a non-null object - */ -util.isObject = function isObject(value) { - return value && typeof value === "object"; -}; + /** + * 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; -/** - * Node's Buffer class if available. - * @type {?function(new: Buffer)} - */ -util.Buffer = (function() { - try { - var Buffer = util.inquire("buffer").Buffer; - // refuse to use non-node buffers if not explicitly assigned (perf reasons): - return Buffer.prototype.utf8Write ? Buffer : /* istanbul ignore next */ null; - } catch (e) { - /* istanbul ignore next */ - return null; - } -})(); + /** + * 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); -/** - * Internal alias of or polyfull for Buffer.from. - * @type {?function} - * @param {string|number[]} value Value - * @param {string} [encoding] Encoding if value is a string - * @returns {Uint8Array} - * @private - */ -util._Buffer_from = null; + /** + * 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; + } -/** - * Internal alias of or polyfill for Buffer.allocUnsafe. - * @type {?function} - * @param {number} size Buffer size - * @returns {Uint8Array} - * @private - */ -util._Buffer_allocUnsafe = null; + return GeometryInstanceAttribute; +}); -/** - * Creates a new buffer of whatever type supported by the environment. - * @param {number|number[]} [sizeOrArray=0] Buffer size or number array - * @returns {Uint8Array|Buffer} Buffer - */ -util.newBuffer = function newBuffer(sizeOrArray) { - /* istanbul ignore next */ - return typeof sizeOrArray === "number" - ? util.Buffer - ? util._Buffer_allocUnsafe(sizeOrArray) - : new util.Array(sizeOrArray) - : util.Buffer - ? util._Buffer_from(sizeOrArray) - : typeof Uint8Array === "undefined" - ? sizeOrArray - : new Uint8Array(sizeOrArray); -}; +define('Core/getBaseUri',[ + '../ThirdParty/Uri', + './defined', + './DeveloperError' + ], function( + Uri, + defined, + DeveloperError) { + 'use strict'; -/** - * Array implementation used in the browser. `Uint8Array` if supported, otherwise `Array`. - * @type {?function(new: Uint8Array, *)} - */ -util.Array = typeof Uint8Array !== "undefined" ? Uint8Array /* istanbul ignore next */ : Array; + /** + * 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) { + + var basePath = ''; + var i = uri.lastIndexOf('/'); + if (i !== -1) { + basePath = uri.substring(0, i + 1); + } -/** - * Long.js's Long class if available. - * @type {?function(new: Long)} - */ -util.Long = /* istanbul ignore next */ global.dcodeIO && /* istanbul ignore next */ global.dcodeIO.Long || util.inquire("long"); + if (!includeQuery) { + return basePath; + } -/** - * Regular expression used to verify 2 bit (`bool`) map keys. - * @type {RegExp} - */ -util.key2Re = /^true|false|0|1$/; + uri = new Uri(uri); + if (defined(uri.query)) { + basePath += '?' + uri.query; + } + if (defined(uri.fragment)){ + basePath += '#' + uri.fragment; + } -/** - * Regular expression used to verify 32 bit (`int32` etc.) map keys. - * @type {RegExp} - */ -util.key32Re = /^-?(?:0|[1-9][0-9]*)$/; + return basePath; + } -/** - * Regular expression used to verify 64 bit (`int64` etc.) map keys. - * @type {RegExp} - */ -util.key64Re = /^(?:[\\x00-\\xff]{8}|-?(?:0|[1-9][0-9]*))$/; + return getBaseUri; +}); -/** - * Converts a number or long to an 8 characters long hash string. - * @param {Long|number} value Value to convert - * @returns {string} Hash - */ -util.longToHash = function longToHash(value) { - return value - ? util.LongBits.from(value).toHash() - : util.LongBits.zeroHash; -}; +define('Core/getExtensionFromUri',[ + '../ThirdParty/Uri', + './defined', + './DeveloperError' + ], function( + Uri, + defined, + DeveloperError) { + 'use strict'; -/** - * Converts an 8 characters long hash string to a long or number. - * @param {string} hash Hash - * @param {boolean} [unsigned=false] Whether unsigned or not - * @returns {Long|number} Original value - */ -util.longFromHash = function longFromHash(hash, unsigned) { - var bits = util.LongBits.fromHash(hash); - if (util.Long) - return util.Long.fromBits(bits.lo, bits.hi, unsigned); - return bits.toNumber(Boolean(unsigned)); -}; + /** + * 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) { + + 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; + } -/** - * Merges the properties of the source object into the destination object. - * @memberof util - * @param {Object.} dst Destination object - * @param {Object.} src Source object - * @param {boolean} [ifNotSet=false] Merges only if the key is not already set - * @returns {Object.} Destination object - */ -function merge(dst, src, ifNotSet) { // used by converters - for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) - if (dst[keys[i]] === undefined || !ifNotSet) - dst[keys[i]] = src[keys[i]]; - return dst; -} + return getExtensionFromUri; +}); -util.merge = merge; +define('Core/getFilenameFromUri',[ + '../ThirdParty/Uri', + './defined', + './DeveloperError' + ], function( + Uri, + defined, + DeveloperError) { + 'use strict'; -/** - * Converts the first character of a string to lower case. - * @param {string} str String to convert - * @returns {string} Converted string - */ -util.lcFirst = function lcFirst(str) { - return str.charAt(0).toLowerCase() + str.substring(1); -}; + /** + * 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) { + + 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; + } -/** - * Creates a custom error constructor. - * @memberof util - * @param {string} name Error name - * @returns {function} Custom error constructor - */ -function newError(name) { + return getFilenameFromUri; +}); - function CustomError(message, properties) { +define('Core/getImagePixels',[ + './defined' + ], function( + defined) { + 'use strict'; - if (!(this instanceof CustomError)) - return new CustomError(message, properties); + var context2DsByWidthAndHeight = {}; - // Error.call(this, message); - // ^ just returns a new error instance because the ctor can be called as a function + /** + * 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. + * @param {Number} width The width of the image. If not defined, then image.width is assigned. + * @param {Number} height The height of the image. If not defined, then image.height is assigned. + * @returns {CanvasPixelArray} The pixels of the image. + */ + function getImagePixels(image, width, height) { + if (!defined(width)) { + width = image.width; + } + if (!defined(height)) { + height = image.height; + } - Object.defineProperty(this, "message", { get: function() { return message; } }); + var context2DsByHeight = context2DsByWidthAndHeight[width]; + if (!defined(context2DsByHeight)) { + context2DsByHeight = {}; + context2DsByWidthAndHeight[width] = context2DsByHeight; + } - /* istanbul ignore next */ - if (Error.captureStackTrace) // node - Error.captureStackTrace(this, CustomError); - else - Object.defineProperty(this, "stack", { value: (new Error()).stack || "" }); + 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; + } - if (properties) - merge(this, properties); + context2d.drawImage(image, 0, 0, width, height); + return context2d.getImageData(0, 0, width, height).data; } - (CustomError.prototype = Object.create(Error.prototype)).constructor = CustomError; - - Object.defineProperty(CustomError.prototype, "name", { get: function() { return name; } }); - - CustomError.prototype.toString = function toString() { - return this.name + ": " + this.message; - }; - - return CustomError; -} + return getImagePixels; +}); -util.newError = newError; +define('Core/getStringFromTypedArray',[ + './defaultValue', + './defined', + './DeveloperError', + './RuntimeError' + ], function( + defaultValue, + defined, + DeveloperError, + RuntimeError) { + 'use strict'; -/** - * Constructs a new protocol error. - * @classdesc Error subclass indicating a protocol specifc error. - * @memberof util - * @extends Error - * @constructor - * @param {string} message Error message - * @param {Object.=} properties Additional properties - * @example - * try { - * MyMessage.decode(someBuffer); // throws if required fields are missing - * } catch (e) { - * if (e instanceof ProtocolError && e.instance) - * console.log("decoded so far: " + JSON.stringify(e.instance)); - * } - */ -util.ProtocolError = newError("ProtocolError"); + /** + * @private + */ + function getStringFromTypedArray(uint8Array, byteOffset, byteLength) { + + byteOffset = defaultValue(byteOffset, 0); + byteLength = defaultValue(byteLength, uint8Array.byteLength - byteOffset); -/** - * So far decoded message instance. - * @name util.ProtocolError#instance - * @type {Message} - */ + uint8Array = uint8Array.subarray(byteOffset, byteOffset + byteLength); -/** - * Builds a getter for a oneof's present field name. - * @param {string[]} fieldNames Field names - * @returns {function():string|undefined} Unbound getter - */ -util.oneOfGetter = function getOneOf(fieldNames) { - var fieldMap = {}; - for (var i = 0; i < fieldNames.length; ++i) - fieldMap[fieldNames[i]] = 1; + return getStringFromTypedArray.decode(uint8Array); + } - /** - * @returns {string|undefined} Set field name, if any - * @this Object - * @ignore - */ - return function() { // eslint-disable-line consistent-return - for (var keys = Object.keys(this), i = keys.length - 1; i > -1; --i) - if (fieldMap[keys[i]] === 1 && this[keys[i]] !== undefined && this[keys[i]] !== null) - return keys[i]; + // Exposed functions for testing + getStringFromTypedArray.decodeWithTextDecoder = function(view) { + var decoder = new TextDecoder('utf-8'); + return decoder.decode(view); }; -}; -/** - * Builds a setter for a oneof's present field name. - * @param {string[]} fieldNames Field names - * @returns {function(?string):undefined} Unbound setter - */ -util.oneOfSetter = function setOneOf(fieldNames) { - /** - * @param {string} name Field name - * @returns {undefined} - * @this Object - * @ignore - */ - return function(name) { - for (var i = 0; i < fieldNames.length; ++i) - if (fieldNames[i] !== name) - delete this[fieldNames[i]]; - }; -}; + getStringFromTypedArray.decodeWithFromCharCode = function(view) { + var result = ''; + var codePoints = utf8Handler(view); + var length = codePoints.length; + for (var i = 0; i < length; ++i) { + var cp = codePoints[i]; + if (cp <= 0xFFFF) { + result += String.fromCharCode(cp); + } else { + cp -= 0x10000; + result += String.fromCharCode((cp >> 10) + 0xD800, + (cp & 0x3FF) + 0xDC00); + } -/** - * Lazily resolves fully qualified type names against the specified root. - * @param {Root} root Root instanceof - * @param {Object.} lazyTypes Type names - * @returns {undefined} - */ -util.lazyResolve = function lazyResolve(root, lazyTypes) { - for (var i = 0; i < lazyTypes.length; ++i) { - for (var keys = Object.keys(lazyTypes[i]), j = 0; j < keys.length; ++j) { - var path = lazyTypes[i][keys[j]].split("."), - ptr = root; - while (path.length) - ptr = ptr[path.shift()]; - lazyTypes[i][keys[j]] = ptr; } - } -}; - -/** - * Default conversion options used for {@link Message#toJSON} implementations. Longs, enums and bytes are converted to strings by default. - * @type {ConversionOptions} - */ -util.toJSONOptions = { - longs: String, - enums: String, - bytes: String -}; + return result; + }; -util._configure = function() { - var Buffer = util.Buffer; - /* istanbul ignore if */ - if (!Buffer) { - util._Buffer_from = util._Buffer_allocUnsafe = null; - return; + function inRange(a, min, max) { + return min <= a && a <= max; } - // because node 4.x buffers are incompatible & immutable - // see: https://github.com/dcodeIO/protobuf.js/pull/665 - util._Buffer_from = Buffer.from !== Uint8Array.from && Buffer.from || - /* istanbul ignore next */ - function Buffer_from(value, encoding) { - return new Buffer(value, encoding); - }; - util._Buffer_allocUnsafe = Buffer.allocUnsafe || - /* istanbul ignore next */ - function Buffer_allocUnsafe(size) { - return new Buffer(size); - }; -}; - -},{"1":1,"12":12,"2":2,"3":3,"4":4,"5":5,"6":6}],14:[function(require,module,exports){ -"use strict"; -module.exports = Writer; -var util = require(13); + // This code is inspired by public domain code found here: https://github.com/inexorabletash/text-encoding + function utf8Handler(utfBytes) { + var codePoint = 0; + var bytesSeen = 0; + var bytesNeeded = 0; + var lowerBoundary = 0x80; + var upperBoundary = 0xBF; -var BufferWriter; // cyclic + var codePoints = []; + var length = utfBytes.length; + for (var i = 0; i < length; ++i) { + var byte = utfBytes[i]; + + // If bytesNeeded = 0, then we are starting a new character + if (bytesNeeded === 0) { + // 1 Byte Ascii character + if (inRange(byte, 0x00, 0x7F)) { + // Return a code point whose value is byte. + codePoints.push(byte); + continue; + } -var LongBits = util.LongBits, - base64 = util.base64, - utf8 = util.utf8; + // 2 Byte character + if (inRange(byte, 0xC2, 0xDF)) { + bytesNeeded = 1; + codePoint = byte & 0x1F; + continue; + } -/** - * Constructs a new writer operation instance. - * @classdesc Scheduled writer operation. - * @constructor - * @param {function(*, Uint8Array, number)} fn Function to call - * @param {number} len Value byte length - * @param {*} val Value to write - * @ignore - */ -function Op(fn, len, val) { + // 3 Byte character + if (inRange(byte, 0xE0, 0xEF)) { + // If byte is 0xE0, set utf-8 lower boundary to 0xA0. + if (byte === 0xE0) { + lowerBoundary = 0xA0; + } + // If byte is 0xED, set utf-8 upper boundary to 0x9F. + if (byte === 0xED) { + upperBoundary = 0x9F; + } - /** - * Function to call. - * @type {function(Uint8Array, number, *)} - */ - this.fn = fn; + bytesNeeded = 2; + codePoint = byte & 0xF; + continue; + } - /** - * Value byte length. - * @type {number} - */ - this.len = len; + // 4 Byte character + if (inRange(byte, 0xF0, 0xF4)) { + // If byte is 0xF0, set utf-8 lower boundary to 0x90. + if (byte === 0xF0) { + lowerBoundary = 0x90; + } + // If byte is 0xF4, set utf-8 upper boundary to 0x8F. + if (byte === 0xF4) { + upperBoundary = 0x8F; + } - /** - * Next operation. - * @type {Writer.Op|undefined} - */ - this.next = undefined; + bytesNeeded = 3; + codePoint = byte & 0x7; + continue; + } - /** - * Value to write. - * @type {*} - */ - this.val = val; // type varies -} + throw new RuntimeError('String decoding failed.'); + } -/* istanbul ignore next */ -function noop() {} // eslint-disable-line no-empty-function + // Out of range, so ignore the first part(s) of the character and continue with this byte on its own + if (!inRange(byte, lowerBoundary, upperBoundary)) { + codePoint = bytesNeeded = bytesSeen = 0; + lowerBoundary = 0x80; + upperBoundary = 0xBF; + --i; + continue; + } -/** - * Constructs a new writer state instance. - * @classdesc Copied writer state. - * @memberof Writer - * @constructor - * @param {Writer} writer Writer to copy state from - * @private - * @ignore - */ -function State(writer) { + // Set appropriate boundaries, since we've now checked byte 2 of a potential longer character + lowerBoundary = 0x80; + upperBoundary = 0xBF; - /** - * Current head. - * @type {Writer.Op} - */ - this.head = writer.head; + // Add byte to code point + codePoint = (codePoint << 6) | (byte & 0x3F); - /** - * Current tail. - * @type {Writer.Op} - */ - this.tail = writer.tail; + // We have the correct number of bytes, so push and reset for next character + ++bytesSeen; + if (bytesSeen === bytesNeeded) { + codePoints.push(codePoint); + codePoint = bytesNeeded = bytesSeen = 0; + } + } - /** - * Current buffer length. - * @type {number} - */ - this.len = writer.len; + return codePoints; + } - /** - * Next state. - * @type {?State} - */ - this.next = writer.states; -} + if (typeof TextDecoder !== 'undefined') { + getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithTextDecoder; + } else { + getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithFromCharCode; + } -/** - * Constructs a new writer instance. - * @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`. - * @constructor - */ -function Writer() { + return getStringFromTypedArray; +}); - /** - * Current length. - * @type {number} - */ - this.len = 0; +define('Core/getMagic',[ + './defaultValue', + './getStringFromTypedArray' + ], function( + defaultValue, + getStringFromTypedArray) { + 'use strict'; /** - * Operations head. - * @type {Object} + * @private */ - this.head = new Op(noop, 0, 0); + function getMagic(uint8Array, byteOffset) { + byteOffset = defaultValue(byteOffset, 0); + return getStringFromTypedArray(uint8Array, byteOffset, Math.min(4, uint8Array.length)); + } - /** - * Operations tail - * @type {Object} - */ - this.tail = this.head; + return getMagic; +}); - /** - * Linked forked states. - * @type {?Object} - */ - this.states = null; +/*! + * protobuf.js v6.7.0 (c) 2016, Daniel Wirtz + * Compiled Wed, 22 Mar 2017 17:30:26 UTC + * Licensed under the BSD-3-Clause License + * see: https://github.com/dcodeIO/protobuf.js for details + */ +(function(global,undefined){"use strict";(function prelude(modules, cache, entries) { - // When a value is written, the writer calculates its byte length and puts it into a linked - // list of operations to perform when finish() is called. This both allows us to allocate - // buffers of the exact required size and reduces the amount of work we have to do compared - // to first calculating over objects and then encoding over objects. In our case, the encoding - // part is just a linked list walk calling operations with already prepared values. -} + // This is the prelude used to bundle protobuf.js for the browser. Wraps up the CommonJS + // sources through a conflict-free require shim and is again wrapped within an iife that + // provides a unified `global` and a minification-friendly `undefined` var plus a global + // "use strict" directive so that minification can remove the directives of each module. -/** - * Creates a new writer. - * @function - * @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer} - */ -Writer.create = util.Buffer - ? function create_buffer_setup() { - return (Writer.create = function create_buffer() { - return new BufferWriter(); - })(); + function $require(name) { + var $module = cache[name]; + if (!$module) + modules[name][0].call($module = cache[name] = { exports: {} }, $require, $module, $module.exports); + return $module.exports; } - /* istanbul ignore next */ - : function create_array() { - return new Writer(); - }; - -/** - * Allocates a buffer of the specified size. - * @param {number} size Buffer size - * @returns {Uint8Array} Buffer - */ -Writer.alloc = function alloc(size) { - return new util.Array(size); -}; -// Use Uint8Array buffer pool in the browser, just like node does with buffers -/* istanbul ignore else */ -if (util.Array !== Array) - Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray); + // Expose globally + var protobuf = global.protobuf = $require(entries[0]); -/** - * Pushes a new operation to the queue. - * @param {function(Uint8Array, number, *)} fn Function to call - * @param {number} len Value byte length - * @param {number} val Value to write - * @returns {Writer} `this` - */ -Writer.prototype.push = function push(fn, len, val) { - this.tail = this.tail.next = new Op(fn, len, val); - this.len += len; - return this; -}; + // Be nice to AMD + if (typeof define === "function" && define.amd) + define('ThirdParty/protobuf-minimal',[], function() { + protobuf.configure(); + return protobuf; + }); -function writeByte(val, buf, pos) { - buf[pos] = val & 255; -} + // Be nice to CommonJS + if (typeof module === "object" && module && module.exports) + module.exports = protobuf; -function writeVarint32(val, buf, pos) { - while (val > 127) { - buf[pos++] = val & 127 | 128; - val >>>= 7; - } - buf[pos] = val; -} +})/* end of prelude */({1:[function(require,module,exports){ +"use strict"; +module.exports = asPromise; /** - * Constructs a new varint writer operation instance. - * @classdesc Scheduled varint writer operation. - * @extends Op - * @constructor - * @param {number} len Value byte length - * @param {number} val Value to write - * @ignore + * Returns a promise from a node-style callback function. + * @memberof util + * @param {function(?Error, ...*)} fn Function to call + * @param {*} ctx Function context + * @param {...*} params Function arguments + * @returns {Promise<*>} Promisified function */ -function VarintOp(len, val) { - this.len = len; - this.next = undefined; - this.val = val; +function asPromise(fn, ctx/*, varargs */) { + var params = []; + for (var i = 2; i < arguments.length;) + params.push(arguments[i++]); + var pending = true; + return new Promise(function asPromiseExecutor(resolve, reject) { + params.push(function asPromiseCallback(err/*, varargs */) { + if (pending) { + pending = false; + if (err) + reject(err); + else { + var args = []; + for (var i = 1; i < arguments.length;) + args.push(arguments[i++]); + resolve.apply(null, args); + } + } + }); + try { + fn.apply(ctx || this, params); // eslint-disable-line no-invalid-this + } catch (err) { + if (pending) { + pending = false; + reject(err); + } + } + }); } -VarintOp.prototype = Object.create(Op.prototype); -VarintOp.prototype.fn = writeVarint32; +},{}],2:[function(require,module,exports){ +"use strict"; /** - * Writes an unsigned 32 bit value as a varint. - * @param {number} value Value to write - * @returns {Writer} `this` + * A minimal base64 implementation for number arrays. + * @memberof util + * @namespace */ -Writer.prototype.uint32 = function write_uint32(value) { - // here, the call to this.push has been inlined and a varint specific Op subclass is used. - // uint32 is by far the most frequently used operation and benefits significantly from this. - this.len += (this.tail = this.tail.next = new VarintOp( - (value = value >>> 0) - < 128 ? 1 - : value < 16384 ? 2 - : value < 2097152 ? 3 - : value < 268435456 ? 4 - : 5, - value)).len; - return this; -}; +var base64 = exports; /** - * Writes a signed 32 bit value as a varint. - * @function - * @param {number} value Value to write - * @returns {Writer} `this` + * Calculates the byte length of a base64 encoded string. + * @param {string} string Base64 encoded string + * @returns {number} Byte length */ -Writer.prototype.int32 = function write_int32(value) { - return value < 0 - ? this.push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec - : this.uint32(value); +base64.length = function length(string) { + var p = string.length; + if (!p) + return 0; + var n = 0; + while (--p % 4 > 1 && string.charAt(p) === "=") + ++n; + return Math.ceil(string.length * 3) / 4 - n; }; -/** - * Writes a 32 bit value as a varint, zig-zag encoded. - * @param {number} value Value to write - * @returns {Writer} `this` - */ -Writer.prototype.sint32 = function write_sint32(value) { - return this.uint32((value << 1 ^ value >> 31) >>> 0); -}; +// Base64 encoding table +var b64 = new Array(64); -function writeVarint64(val, buf, pos) { - while (val.hi) { - buf[pos++] = val.lo & 127 | 128; - val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; - val.hi >>>= 7; - } - while (val.lo > 127) { - buf[pos++] = val.lo & 127 | 128; - val.lo = val.lo >>> 7; - } - buf[pos++] = val.lo; -} +// Base64 decoding table +var s64 = new Array(123); + +// 65..90, 97..122, 48..57, 43, 47 +for (var i = 0; i < 64;) + s64[b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++; /** - * Writes an unsigned 64 bit value as a varint. - * @param {Long|number|string} value Value to write - * @returns {Writer} `this` - * @throws {TypeError} If `value` is a string and no long library is present. + * Encodes a buffer to a base64 encoded string. + * @param {Uint8Array} buffer Source buffer + * @param {number} start Source start + * @param {number} end Source end + * @returns {string} Base64 encoded string */ -Writer.prototype.uint64 = function write_uint64(value) { - var bits = LongBits.from(value); - return this.push(writeVarint64, bits.length(), bits); +base64.encode = function encode(buffer, start, end) { + var string = []; // alt: new Array(Math.ceil((end - start) / 3) * 4); + var i = 0, // output index + j = 0, // goto index + t; // temporary + while (start < end) { + var b = buffer[start++]; + switch (j) { + case 0: + string[i++] = b64[b >> 2]; + t = (b & 3) << 4; + j = 1; + break; + case 1: + string[i++] = b64[t | b >> 4]; + t = (b & 15) << 2; + j = 2; + break; + case 2: + string[i++] = b64[t | b >> 6]; + string[i++] = b64[b & 63]; + j = 0; + break; + } + } + if (j) { + string[i++] = b64[t]; + string[i ] = 61; + if (j === 1) + string[i + 1] = 61; + } + return String.fromCharCode.apply(String, string); }; -/** - * Writes a signed 64 bit value as a varint. - * @function - * @param {Long|number|string} value Value to write - * @returns {Writer} `this` - * @throws {TypeError} If `value` is a string and no long library is present. - */ -Writer.prototype.int64 = Writer.prototype.uint64; +var invalidEncoding = "invalid encoding"; /** - * Writes a signed 64 bit value as a varint, zig-zag encoded. - * @param {Long|number|string} value Value to write - * @returns {Writer} `this` - * @throws {TypeError} If `value` is a string and no long library is present. - */ -Writer.prototype.sint64 = function write_sint64(value) { - var bits = LongBits.from(value).zzEncode(); - return this.push(writeVarint64, bits.length(), bits); + * Decodes a base64 encoded string to a buffer. + * @param {string} string Source string + * @param {Uint8Array} buffer Destination buffer + * @param {number} offset Destination offset + * @returns {number} Number of bytes written + * @throws {Error} If encoding is invalid + */ +base64.decode = function decode(string, buffer, offset) { + var start = offset; + var j = 0, // goto index + t; // temporary + for (var i = 0; i < string.length;) { + var c = string.charCodeAt(i++); + if (c === 61 && j > 1) + break; + if ((c = s64[c]) === undefined) + throw Error(invalidEncoding); + switch (j) { + case 0: + t = c; + j = 1; + break; + case 1: + buffer[offset++] = t << 2 | (c & 48) >> 4; + t = c; + j = 2; + break; + case 2: + buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2; + t = c; + j = 3; + break; + case 3: + buffer[offset++] = (t & 3) << 6 | c; + j = 0; + break; + } + } + if (j === 1) + throw Error(invalidEncoding); + return offset - start; }; /** - * Writes a boolish value as a varint. - * @param {boolean} value Value to write - * @returns {Writer} `this` + * Tests if the specified string appears to be base64 encoded. + * @param {string} string String to test + * @returns {boolean} `true` if probably base64 encoded, otherwise false */ -Writer.prototype.bool = function write_bool(value) { - return this.push(writeByte, 1, value ? 1 : 0); +base64.test = function test(string) { + return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(string); }; -function writeFixed32(val, buf, pos) { - buf[pos++] = val & 255; - buf[pos++] = val >>> 8 & 255; - buf[pos++] = val >>> 16 & 255; - buf[pos ] = val >>> 24; +},{}],3:[function(require,module,exports){ +"use strict"; +module.exports = EventEmitter; + +/** + * Constructs a new event emitter instance. + * @classdesc A minimal event emitter. + * @memberof util + * @constructor + */ +function EventEmitter() { + + /** + * Registered listeners. + * @type {Object.} + * @private + */ + this._listeners = {}; } /** - * Writes an unsigned 32 bit value as fixed 32 bits. - * @param {number} value Value to write - * @returns {Writer} `this` + * Registers an event listener. + * @param {string} evt Event name + * @param {function} fn Listener + * @param {*} [ctx] Listener context + * @returns {util.EventEmitter} `this` */ -Writer.prototype.fixed32 = function write_fixed32(value) { - return this.push(writeFixed32, 4, value >>> 0); +EventEmitter.prototype.on = function on(evt, fn, ctx) { + (this._listeners[evt] || (this._listeners[evt] = [])).push({ + fn : fn, + ctx : ctx || this + }); + return this; }; /** - * Writes a signed 32 bit value as fixed 32 bits. - * @function - * @param {number} value Value to write - * @returns {Writer} `this` + * Removes an event listener or any matching listeners if arguments are omitted. + * @param {string} [evt] Event name. Removes all listeners if omitted. + * @param {function} [fn] Listener to remove. Removes all listeners of `evt` if omitted. + * @returns {util.EventEmitter} `this` */ -Writer.prototype.sfixed32 = Writer.prototype.fixed32; +EventEmitter.prototype.off = function off(evt, fn) { + if (evt === undefined) + this._listeners = {}; + else { + if (fn === undefined) + this._listeners[evt] = []; + else { + var listeners = this._listeners[evt]; + for (var i = 0; i < listeners.length;) + if (listeners[i].fn === fn) + listeners.splice(i, 1); + else + ++i; + } + } + return this; +}; /** - * Writes an unsigned 64 bit value as fixed 64 bits. - * @param {Long|number|string} value Value to write - * @returns {Writer} `this` - * @throws {TypeError} If `value` is a string and no long library is present. + * Emits an event by calling its listeners with the specified arguments. + * @param {string} evt Event name + * @param {...*} args Arguments + * @returns {util.EventEmitter} `this` */ -Writer.prototype.fixed64 = function write_fixed64(value) { - var bits = LongBits.from(value); - return this.push(writeFixed32, 4, bits.lo).push(writeFixed32, 4, bits.hi); +EventEmitter.prototype.emit = function emit(evt) { + var listeners = this._listeners[evt]; + if (listeners) { + var args = [], + i = 1; + for (; i < arguments.length;) + args.push(arguments[i++]); + for (i = 0; i < listeners.length;) + listeners[i].fn.apply(listeners[i++].ctx, args); + } + return this; }; +},{}],4:[function(require,module,exports){ +"use strict"; +module.exports = inquire; + /** - * Writes a signed 64 bit value as fixed 64 bits. - * @function - * @param {Long|number|string} value Value to write - * @returns {Writer} `this` - * @throws {TypeError} If `value` is a string and no long library is present. + * Requires a module only if available. + * @memberof util + * @param {string} moduleName Module to require + * @returns {?Object} Required module if available and not empty, otherwise `null` */ -Writer.prototype.sfixed64 = Writer.prototype.fixed64; +function inquire(moduleName) { + try { + var mod = eval("quire".replace(/^/,"re"))(moduleName); // eslint-disable-line no-eval + if (mod && (mod.length || Object.keys(mod).length)) + return mod; + } catch (e) {} // eslint-disable-line no-empty + return null; +} -var writeFloat = typeof Float32Array !== "undefined" - ? (function() { - var f32 = new Float32Array(1), - f8b = new Uint8Array(f32.buffer); - f32[0] = -0; - return f8b[3] // already le? - ? function writeFloat_f32(val, buf, pos) { - f32[0] = val; - buf[pos++] = f8b[0]; - buf[pos++] = f8b[1]; - buf[pos++] = f8b[2]; - buf[pos ] = f8b[3]; - } - /* istanbul ignore next */ - : function writeFloat_f32_le(val, buf, pos) { - f32[0] = val; - buf[pos++] = f8b[3]; - buf[pos++] = f8b[2]; - buf[pos++] = f8b[1]; - buf[pos ] = f8b[0]; - }; - })() - /* istanbul ignore next */ - : function writeFloat_ieee754(value, buf, pos) { - var sign = value < 0 ? 1 : 0; - if (sign) - value = -value; - if (value === 0) - writeFixed32(1 / value > 0 ? /* positive */ 0 : /* negative 0 */ 2147483648, buf, pos); - else if (isNaN(value)) - writeFixed32(2147483647, buf, pos); - else if (value > 3.4028234663852886e+38) // +-Infinity - writeFixed32((sign << 31 | 2139095040) >>> 0, buf, pos); - else if (value < 1.1754943508222875e-38) // denormal - writeFixed32((sign << 31 | Math.round(value / 1.401298464324817e-45)) >>> 0, buf, pos); - else { - var exponent = Math.floor(Math.log(value) / Math.LN2), - mantissa = Math.round(value * Math.pow(2, -exponent) * 8388608) & 8388607; - writeFixed32((sign << 31 | exponent + 127 << 23 | mantissa) >>> 0, buf, pos); - } - }; +},{}],5:[function(require,module,exports){ +"use strict"; +module.exports = pool; /** - * Writes a float (32 bit). - * @function - * @param {number} value Value to write - * @returns {Writer} `this` + * An allocator as used by {@link util.pool}. + * @typedef PoolAllocator + * @type {function} + * @param {number} size Buffer size + * @returns {Uint8Array} Buffer */ -Writer.prototype.float = function write_float(value) { - return this.push(writeFloat, 4, value); -}; -var writeDouble = typeof Float64Array !== "undefined" - ? (function() { - var f64 = new Float64Array(1), - f8b = new Uint8Array(f64.buffer); - f64[0] = -0; - return f8b[7] // already le? - ? function writeDouble_f64(val, buf, pos) { - f64[0] = val; - buf[pos++] = f8b[0]; - buf[pos++] = f8b[1]; - buf[pos++] = f8b[2]; - buf[pos++] = f8b[3]; - buf[pos++] = f8b[4]; - buf[pos++] = f8b[5]; - buf[pos++] = f8b[6]; - buf[pos ] = f8b[7]; - } - /* istanbul ignore next */ - : function writeDouble_f64_le(val, buf, pos) { - f64[0] = val; - buf[pos++] = f8b[7]; - buf[pos++] = f8b[6]; - buf[pos++] = f8b[5]; - buf[pos++] = f8b[4]; - buf[pos++] = f8b[3]; - buf[pos++] = f8b[2]; - buf[pos++] = f8b[1]; - buf[pos ] = f8b[0]; - }; - })() - /* istanbul ignore next */ - : function writeDouble_ieee754(value, buf, pos) { - var sign = value < 0 ? 1 : 0; - if (sign) - value = -value; - if (value === 0) { - writeFixed32(0, buf, pos); - writeFixed32(1 / value > 0 ? /* positive */ 0 : /* negative 0 */ 2147483648, buf, pos + 4); - } else if (isNaN(value)) { - writeFixed32(4294967295, buf, pos); - writeFixed32(2147483647, buf, pos + 4); - } else if (value > 1.7976931348623157e+308) { // +-Infinity - writeFixed32(0, buf, pos); - writeFixed32((sign << 31 | 2146435072) >>> 0, buf, pos + 4); - } else { - var mantissa; - if (value < 2.2250738585072014e-308) { // denormal - mantissa = value / 5e-324; - writeFixed32(mantissa >>> 0, buf, pos); - writeFixed32((sign << 31 | mantissa / 4294967296) >>> 0, buf, pos + 4); - } else { - var exponent = Math.floor(Math.log(value) / Math.LN2); - if (exponent === 1024) - exponent = 1023; - mantissa = value * Math.pow(2, -exponent); - writeFixed32(mantissa * 4503599627370496 >>> 0, buf, pos); - writeFixed32((sign << 31 | exponent + 1023 << 20 | mantissa * 1048576 & 1048575) >>> 0, buf, pos + 4); - } - } - }; +/** + * A slicer as used by {@link util.pool}. + * @typedef PoolSlicer + * @type {function} + * @param {number} start Start offset + * @param {number} end End offset + * @returns {Uint8Array} Buffer slice + * @this {Uint8Array} + */ /** - * Writes a double (64 bit float). + * A general purpose buffer pool. + * @memberof util * @function - * @param {number} value Value to write - * @returns {Writer} `this` + * @param {PoolAllocator} alloc Allocator + * @param {PoolSlicer} slice Slicer + * @param {number} [size=8192] Slab size + * @returns {PoolAllocator} Pooled allocator */ -Writer.prototype.double = function write_double(value) { - return this.push(writeDouble, 8, value); -}; - -var writeBytes = util.Array.prototype.set - ? function writeBytes_set(val, buf, pos) { - buf.set(val, pos); // also works for plain array values - } - /* istanbul ignore next */ - : function writeBytes_for(val, buf, pos) { - for (var i = 0; i < val.length; ++i) - buf[pos + i] = val[i]; +function pool(alloc, slice, size) { + var SIZE = size || 8192; + var MAX = SIZE >>> 1; + var slab = null; + var offset = SIZE; + return function pool_alloc(size) { + if (size < 1 || size > MAX) + return alloc(size); + if (offset + size > SIZE) { + slab = alloc(SIZE); + offset = 0; + } + var buf = slice.call(slab, offset, offset += size); + if (offset & 7) // align to 32 bit + offset = (offset | 7) + 1; + return buf; }; +} + +},{}],6:[function(require,module,exports){ +"use strict"; /** - * Writes a sequence of bytes. - * @param {Uint8Array|string} value Buffer or base64 encoded string to write - * @returns {Writer} `this` + * A minimal UTF8 implementation for number arrays. + * @memberof util + * @namespace */ -Writer.prototype.bytes = function write_bytes(value) { - var len = value.length >>> 0; - if (!len) - return this.push(writeByte, 1, 0); - if (util.isString(value)) { - var buf = Writer.alloc(len = base64.length(value)); - base64.decode(value, buf, 0); - value = buf; - } - return this.uint32(len).push(writeBytes, len, value); -}; +var utf8 = exports; /** - * Writes a string. - * @param {string} value Value to write - * @returns {Writer} `this` + * Calculates the UTF8 byte length of a string. + * @param {string} string String + * @returns {number} Byte length */ -Writer.prototype.string = function write_string(value) { - var len = utf8.length(value); - return len - ? this.uint32(len).push(utf8.write, len, value) - : this.push(writeByte, 1, 0); +utf8.length = function utf8_length(string) { + var len = 0, + c = 0; + for (var i = 0; i < string.length; ++i) { + c = string.charCodeAt(i); + if (c < 128) + len += 1; + else if (c < 2048) + len += 2; + else if ((c & 0xFC00) === 0xD800 && (string.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { + ++i; + len += 4; + } else + len += 3; + } + return len; }; /** - * Forks this writer's state by pushing it to a stack. - * Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state. - * @returns {Writer} `this` + * Reads UTF8 bytes as a string. + * @param {Uint8Array} buffer Source buffer + * @param {number} start Source start + * @param {number} end Source end + * @returns {string} String read */ -Writer.prototype.fork = function fork() { - this.states = new State(this); - this.head = this.tail = new Op(noop, 0, 0); - this.len = 0; - return this; +utf8.read = function utf8_read(buffer, start, end) { + var len = end - start; + if (len < 1) + return ""; + var parts = null, + chunk = [], + i = 0, // char offset + t; // temporary + while (start < end) { + t = buffer[start++]; + if (t < 128) + chunk[i++] = t; + else if (t > 191 && t < 224) + chunk[i++] = (t & 31) << 6 | buffer[start++] & 63; + else if (t > 239 && t < 365) { + t = ((t & 7) << 18 | (buffer[start++] & 63) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63) - 0x10000; + chunk[i++] = 0xD800 + (t >> 10); + chunk[i++] = 0xDC00 + (t & 1023); + } else + chunk[i++] = (t & 15) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63; + if (i > 8191) { + (parts || (parts = [])).push(String.fromCharCode.apply(String, chunk)); + i = 0; + } + } + if (parts) { + if (i) + parts.push(String.fromCharCode.apply(String, chunk.slice(0, i))); + return parts.join(""); + } + return String.fromCharCode.apply(String, chunk.slice(0, i)); }; /** - * Resets this instance to the last state. - * @returns {Writer} `this` + * Writes a string as UTF8 bytes. + * @param {string} string Source string + * @param {Uint8Array} buffer Destination buffer + * @param {number} offset Destination offset + * @returns {number} Bytes written */ -Writer.prototype.reset = function reset() { - if (this.states) { - this.head = this.states.head; - this.tail = this.states.tail; - this.len = this.states.len; - this.states = this.states.next; - } else { - this.head = this.tail = new Op(noop, 0, 0); - this.len = 0; +utf8.write = function utf8_write(string, buffer, offset) { + var start = offset, + c1, // character 1 + c2; // character 2 + for (var i = 0; i < string.length; ++i) { + c1 = string.charCodeAt(i); + if (c1 < 128) { + buffer[offset++] = c1; + } else if (c1 < 2048) { + buffer[offset++] = c1 >> 6 | 192; + buffer[offset++] = c1 & 63 | 128; + } else if ((c1 & 0xFC00) === 0xD800 && ((c2 = string.charCodeAt(i + 1)) & 0xFC00) === 0xDC00) { + c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF); + ++i; + buffer[offset++] = c1 >> 18 | 240; + buffer[offset++] = c1 >> 12 & 63 | 128; + buffer[offset++] = c1 >> 6 & 63 | 128; + buffer[offset++] = c1 & 63 | 128; + } else { + buffer[offset++] = c1 >> 12 | 224; + buffer[offset++] = c1 >> 6 & 63 | 128; + buffer[offset++] = c1 & 63 | 128; + } } - return this; + return offset - start; }; +},{}],7:[function(require,module,exports){ +"use strict"; +var protobuf = exports; + /** - * Resets to the last state and appends the fork state's current write length as a varint followed by its operations. - * @returns {Writer} `this` + * Build type, one of `"full"`, `"light"` or `"minimal"`. + * @name build + * @type {string} + * @const */ -Writer.prototype.ldelim = function ldelim() { - var head = this.head, - tail = this.tail, - len = this.len; - this.reset().uint32(len); - if (len) { - this.tail.next = head.next; // skip noop - this.tail = tail; - this.len += len; - } - return this; -}; +protobuf.build = "minimal"; /** - * Finishes the write operation. - * @returns {Uint8Array} Finished buffer + * Named roots. + * This is where pbjs stores generated structures (the option `-r, --root` specifies a name). + * Can also be used manually to make roots available accross modules. + * @name roots + * @type {Object.} + * @example + * // pbjs -r myroot -o compiled.js ... + * + * // in another module: + * require("./compiled.js"); + * + * // in any subsequent module: + * var root = protobuf.roots["myroot"]; */ -Writer.prototype.finish = function finish() { - var head = this.head.next, // skip noop - buf = this.constructor.alloc(this.len), - pos = 0; - while (head) { - head.fn(head.val, buf, pos); - pos += head.len; - head = head.next; - } - // this.head = this.tail = null; - return buf; -}; +protobuf.roots = {}; -Writer._configure = function(BufferWriter_) { - BufferWriter = BufferWriter_; -}; +// Serialization +protobuf.Writer = require(14); +protobuf.BufferWriter = require(15); +protobuf.Reader = require(8); +protobuf.BufferReader = require(9); -},{"13":13}],15:[function(require,module,exports){ +// Utility +protobuf.util = require(13); +protobuf.rpc = require(10); +protobuf.configure = configure; + +/* istanbul ignore next */ +/** + * Reconfigures the library according to the environment. + * @returns {undefined} + */ +function configure() { + protobuf.Reader._configure(protobuf.BufferReader); + protobuf.util._configure(); +} + +// Configure serialization +protobuf.Writer._configure(protobuf.BufferWriter); +configure(); + +},{"10":10,"13":13,"14":14,"15":15,"8":8,"9":9}],8:[function(require,module,exports){ "use strict"; -module.exports = BufferWriter; +module.exports = Reader; -// extends Writer -var Writer = require(14); -(BufferWriter.prototype = Object.create(Writer.prototype)).constructor = BufferWriter; +var util = require(13); -var util = require(13); +var BufferReader; // cyclic -var Buffer = util.Buffer; +var LongBits = util.LongBits, + utf8 = util.utf8; + +/* istanbul ignore next */ +function indexOutOfRange(reader, writeLength) { + return RangeError("index out of range: " + reader.pos + " + " + (writeLength || 1) + " > " + reader.len); +} /** - * Constructs a new buffer writer instance. - * @classdesc Wire format writer using node buffers. - * @extends Writer + * Constructs a new reader instance using the specified buffer. + * @classdesc Wire format reader using `Uint8Array` if available, otherwise `Array`. * @constructor + * @param {Uint8Array} buffer Buffer to read from */ -function BufferWriter() { - Writer.call(this); +function Reader(buffer) { + + /** + * Read buffer. + * @type {Uint8Array} + */ + this.buf = buffer; + + /** + * Read buffer position. + * @type {number} + */ + this.pos = 0; + + /** + * Read buffer length. + * @type {number} + */ + this.len = buffer.length; } +var create_array = typeof Uint8Array !== "undefined" + ? function create_typed_array(buffer) { + if (buffer instanceof Uint8Array || Array.isArray(buffer)) + return new Reader(buffer); + throw Error("illegal buffer"); + } + /* istanbul ignore next */ + : function create_array(buffer) { + if (Array.isArray(buffer)) + return new Reader(buffer); + throw Error("illegal buffer"); + }; + /** - * Allocates a buffer of the specified size. - * @param {number} size Buffer size - * @returns {Buffer} Buffer + * Creates a new reader using the specified buffer. + * @function + * @param {Uint8Array|Buffer} buffer Buffer to read from + * @returns {Reader|BufferReader} A {@link BufferReader} if `buffer` is a Buffer, otherwise a {@link Reader} + * @throws {Error} If `buffer` is not a valid buffer */ -BufferWriter.alloc = function alloc_buffer(size) { - return (BufferWriter.alloc = util._Buffer_allocUnsafe)(size); -}; - -var writeBytesBuffer = Buffer && Buffer.prototype instanceof Uint8Array && Buffer.prototype.set.name === "set" - ? function writeBytesBuffer_set(val, buf, pos) { - buf.set(val, pos); // faster than copy (requires node >= 4 where Buffers extend Uint8Array and set is properly inherited) - // also works for plain array values +Reader.create = util.Buffer + ? function create_buffer_setup(buffer) { + return (Reader.create = function create_buffer(buffer) { + return util.Buffer.isBuffer(buffer) + ? new BufferReader(buffer) + /* istanbul ignore next */ + : create_array(buffer); + })(buffer); } /* istanbul ignore next */ - : function writeBytesBuffer_copy(val, buf, pos) { - if (val.copy) // Buffer values - val.copy(buf, pos, 0, val.length); - else for (var i = 0; i < val.length;) // plain array values - buf[pos++] = val[i++]; + : create_array; + +Reader.prototype._slice = util.Array.prototype.subarray || /* istanbul ignore next */ util.Array.prototype.slice; + +/** + * Reads a varint as an unsigned 32 bit value. + * @function + * @returns {number} Value read + */ +Reader.prototype.uint32 = (function read_uint32_setup() { + var value = 4294967295; // optimizer type-hint, tends to deopt otherwise (?!) + return function read_uint32() { + value = ( this.buf[this.pos] & 127 ) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 127) << 7) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 127) << 14) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 127) << 21) >>> 0; if (this.buf[this.pos++] < 128) return value; + value = (value | (this.buf[this.pos] & 15) << 28) >>> 0; if (this.buf[this.pos++] < 128) return value; + + /* istanbul ignore next */ + if ((this.pos += 5) > this.len) { + this.pos = this.len; + throw indexOutOfRange(this, 10); + } + return value; }; +})(); /** - * @override + * Reads a varint as a signed 32 bit value. + * @returns {number} Value read */ -BufferWriter.prototype.bytes = function write_bytes_buffer(value) { - if (util.isString(value)) - value = util._Buffer_from(value, "base64"); - var len = value.length >>> 0; - this.uint32(len); - if (len) - this.push(writeBytesBuffer, len, value); - return this; +Reader.prototype.int32 = function read_int32() { + return this.uint32() | 0; }; -function writeStringBuffer(val, buf, pos) { - if (val.length < 40) // plain js is faster for short strings (probably due to redundant assertions) - util.utf8.write(val, buf, pos); - else - buf.utf8Write(val, pos); -} - /** - * @override + * Reads a zig-zag encoded varint as a signed 32 bit value. + * @returns {number} Value read */ -BufferWriter.prototype.string = function write_string_buffer(value) { - var len = Buffer.byteLength(value); - this.uint32(len); - if (len) - this.push(writeStringBuffer, len, value); - return this; +Reader.prototype.sint32 = function read_sint32() { + var value = this.uint32(); + return value >>> 1 ^ -(value & 1) | 0; }; +/* eslint-disable no-invalid-this */ + +function readLongVarint() { + // tends to deopt with local vars for octet etc. + var bits = new LongBits(0, 0); + var i = 0; + if (this.len - this.pos > 4) { // fast route (lo) + for (; i < 4; ++i) { + // 1st..4th + bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + // 5th + bits.lo = (bits.lo | (this.buf[this.pos] & 127) << 28) >>> 0; + bits.hi = (bits.hi | (this.buf[this.pos] & 127) >> 4) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + i = 0; + } else { + for (; i < 3; ++i) { + /* istanbul ignore next */ + if (this.pos >= this.len) + throw indexOutOfRange(this); + // 1st..3th + bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + // 4th + bits.lo = (bits.lo | (this.buf[this.pos++] & 127) << i * 7) >>> 0; + return bits; + } + if (this.len - this.pos > 4) { // fast route (hi) + for (; i < 5; ++i) { + // 6th..10th + bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + } else { + for (; i < 5; ++i) { + /* istanbul ignore next */ + if (this.pos >= this.len) + throw indexOutOfRange(this); + // 6th..10th + bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0; + if (this.buf[this.pos++] < 128) + return bits; + } + } + /* istanbul ignore next */ + throw Error("invalid varint encoding"); +} + +/* eslint-enable no-invalid-this */ /** - * Finishes the write operation. - * @name BufferWriter#finish + * Reads a varint as a signed 64 bit value. + * @name Reader#int64 * @function - * @returns {Buffer} Finished buffer + * @returns {Long|number} Value read */ -},{"13":13,"14":14}]},{},[7]) +/** + * Reads a varint as an unsigned 64 bit value. + * @name Reader#uint64 + * @function + * @returns {Long|number} Value read + */ -})(typeof window==="object"&&window||typeof self==="object"&&self||this); -//# sourceMappingURL=protobuf.js.map -; -/*global define*/ -define('ThirdParty/google-earth-dbroot-parser',[ - './protobuf-minimal' -], function( - $protobuf) { - /* jshint curly: false, sub: true, newcap: false, shadow: true, unused: false*/ - 'use strict'; +/** + * Reads a zig-zag encoded varint as a signed 64 bit value. + * @name Reader#sint64 + * @function + * @returns {Long|number} Value read + */ - // - // Creates a parser for a dbroot protocol buffer - // Below code is generated using protobufjs with the following command - // - // ./pbjs --no-encode --no-create --no-comments --no-delimited -w amd -t static dbroot_v2.proto - // - // .proto file can be found here: https://github.com/google/earthenterprise/blob/master/earth_enterprise/src/keyhole/proto/dbroot/dbroot_v2.proto +/** + * Reads a varint as a boolean. + * @returns {boolean} Value read + */ +Reader.prototype.bool = function read_bool() { + return this.uint32() !== 0; +}; - var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; +function readFixed32(buf, end) { + return (buf[end - 4] + | buf[end - 3] << 8 + | buf[end - 2] << 16 + | buf[end - 1] << 24) >>> 0; +} - var $lazyTypes = []; +/** + * Reads fixed 32 bits as an unsigned 32 bit integer. + * @returns {number} Value read + */ +Reader.prototype.fixed32 = function read_fixed32() { - var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + /* istanbul ignore next */ + if (this.pos + 4 > this.len) + throw indexOutOfRange(this, 4); - $root.keyhole = (function() { + return readFixed32(this.buf, this.pos += 4); +}; - var keyhole = {}; +/** + * Reads fixed 32 bits as a signed 32 bit integer. + * @returns {number} Value read + */ +Reader.prototype.sfixed32 = function read_sfixed32() { - keyhole.dbroot = (function() { + /* istanbul ignore next */ + if (this.pos + 4 > this.len) + throw indexOutOfRange(this, 4); - var dbroot = {}; + return readFixed32(this.buf, this.pos += 4) | 0; +}; - dbroot.StringEntryProto = (function() { +/* eslint-disable no-invalid-this */ - function StringEntryProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +function readFixed64(/* this: Reader */) { - StringEntryProto.prototype.stringId = 0; - StringEntryProto.prototype.stringValue = ""; + /* istanbul ignore next */ + if (this.pos + 8 > this.len) + throw indexOutOfRange(this, 8); - StringEntryProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.StringEntryProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.stringId = reader.fixed32(); - break; - case 2: - message.stringValue = reader.string(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + return new LongBits(readFixed32(this.buf, this.pos += 4), readFixed32(this.buf, this.pos += 4)); +} - StringEntryProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (!$util.isInteger(message.stringId)) - return "stringId: integer expected"; - if (!$util.isString(message.stringValue)) - return "stringValue: string expected"; - return null; - }; +/* eslint-enable no-invalid-this */ - StringEntryProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.StringEntryProto) - return object; - var message = new $root.keyhole.dbroot.StringEntryProto(); - if (object.stringId !== undefined && object.stringId !== null) - message.stringId = object.stringId >>> 0; - if (object.stringValue !== undefined && object.stringValue !== null) - message.stringValue = String(object.stringValue); - return message; - }; +/** + * Reads fixed 64 bits. + * @name Reader#fixed64 + * @function + * @returns {Long|number} Value read + */ - StringEntryProto.from = StringEntryProto.fromObject; +/** + * Reads zig-zag encoded fixed 64 bits. + * @name Reader#sfixed64 + * @function + * @returns {Long|number} Value read + */ - StringEntryProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.stringId = 0; - object.stringValue = ""; - } - if (message.stringId !== undefined && message.stringId !== null && message.hasOwnProperty("stringId")) - object.stringId = message.stringId; - if (message.stringValue !== undefined && message.stringValue !== null && message.hasOwnProperty("stringValue")) - object.stringValue = message.stringValue; - return object; - }; +var readFloat = typeof Float32Array !== "undefined" + ? (function() { + var f32 = new Float32Array(1), + f8b = new Uint8Array(f32.buffer); + f32[0] = -0; + return f8b[3] // already le? + ? function readFloat_f32(buf, pos) { + f8b[0] = buf[pos ]; + f8b[1] = buf[pos + 1]; + f8b[2] = buf[pos + 2]; + f8b[3] = buf[pos + 3]; + return f32[0]; + } + /* istanbul ignore next */ + : function readFloat_f32_le(buf, pos) { + f8b[0] = buf[pos + 3]; + f8b[1] = buf[pos + 2]; + f8b[2] = buf[pos + 1]; + f8b[3] = buf[pos ]; + return f32[0]; + }; + })() + /* istanbul ignore next */ + : function readFloat_ieee754(buf, pos) { + var uint = readFixed32(buf, pos + 4), + sign = (uint >> 31) * 2 + 1, + exponent = uint >>> 23 & 255, + mantissa = uint & 8388607; + return exponent === 255 + ? mantissa + ? NaN + : sign * Infinity + : exponent === 0 // denormal + ? sign * 1.401298464324817e-45 * mantissa + : sign * Math.pow(2, exponent - 150) * (mantissa + 8388608); + }; - StringEntryProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +/** + * Reads a float (32 bit) as a number. + * @function + * @returns {number} Value read + */ +Reader.prototype.float = function read_float() { - StringEntryProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + /* istanbul ignore next */ + if (this.pos + 4 > this.len) + throw indexOutOfRange(this, 4); - return StringEntryProto; - })(); + var value = readFloat(this.buf, this.pos); + this.pos += 4; + return value; +}; - dbroot.StringIdOrValueProto = (function() { +var readDouble = typeof Float64Array !== "undefined" + ? (function() { + var f64 = new Float64Array(1), + f8b = new Uint8Array(f64.buffer); + f64[0] = -0; + return f8b[7] // already le? + ? function readDouble_f64(buf, pos) { + f8b[0] = buf[pos ]; + f8b[1] = buf[pos + 1]; + f8b[2] = buf[pos + 2]; + f8b[3] = buf[pos + 3]; + f8b[4] = buf[pos + 4]; + f8b[5] = buf[pos + 5]; + f8b[6] = buf[pos + 6]; + f8b[7] = buf[pos + 7]; + return f64[0]; + } + /* istanbul ignore next */ + : function readDouble_f64_le(buf, pos) { + f8b[0] = buf[pos + 7]; + f8b[1] = buf[pos + 6]; + f8b[2] = buf[pos + 5]; + f8b[3] = buf[pos + 4]; + f8b[4] = buf[pos + 3]; + f8b[5] = buf[pos + 2]; + f8b[6] = buf[pos + 1]; + f8b[7] = buf[pos ]; + return f64[0]; + }; + })() + /* istanbul ignore next */ + : function readDouble_ieee754(buf, pos) { + var lo = readFixed32(buf, pos + 4), + hi = readFixed32(buf, pos + 8); + var sign = (hi >> 31) * 2 + 1, + exponent = hi >>> 20 & 2047, + mantissa = 4294967296 * (hi & 1048575) + lo; + return exponent === 2047 + ? mantissa + ? NaN + : sign * Infinity + : exponent === 0 // denormal + ? sign * 5e-324 * mantissa + : sign * Math.pow(2, exponent - 1075) * (mantissa + 4503599627370496); + }; - function StringIdOrValueProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +/** + * Reads a double (64 bit float) as a number. + * @function + * @returns {number} Value read + */ +Reader.prototype.double = function read_double() { - StringIdOrValueProto.prototype.stringId = 0; - StringIdOrValueProto.prototype.value = ""; + /* istanbul ignore next */ + if (this.pos + 8 > this.len) + throw indexOutOfRange(this, 4); - StringIdOrValueProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.StringIdOrValueProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.stringId = reader.fixed32(); - break; - case 2: - message.value = reader.string(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + var value = readDouble(this.buf, this.pos); + this.pos += 8; + return value; +}; - StringIdOrValueProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.stringId !== undefined) - if (!$util.isInteger(message.stringId)) - return "stringId: integer expected"; - if (message.value !== undefined) - if (!$util.isString(message.value)) - return "value: string expected"; - return null; - }; +/** + * Reads a sequence of bytes preceeded by its length as a varint. + * @returns {Uint8Array} Value read + */ +Reader.prototype.bytes = function read_bytes() { + var length = this.uint32(), + start = this.pos, + end = this.pos + length; - StringIdOrValueProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.StringIdOrValueProto) - return object; - var message = new $root.keyhole.dbroot.StringIdOrValueProto(); - if (object.stringId !== undefined && object.stringId !== null) - message.stringId = object.stringId >>> 0; - if (object.value !== undefined && object.value !== null) - message.value = String(object.value); - return message; - }; + /* istanbul ignore next */ + if (end > this.len) + throw indexOutOfRange(this, length); - StringIdOrValueProto.from = StringIdOrValueProto.fromObject; + this.pos += length; + return start === end // fix for IE 10/Win8 and others' subarray returning array of size 1 + ? new this.buf.constructor(0) + : this._slice.call(this.buf, start, end); +}; - StringIdOrValueProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.stringId = 0; - object.value = ""; - } - if (message.stringId !== undefined && message.stringId !== null && message.hasOwnProperty("stringId")) - object.stringId = message.stringId; - if (message.value !== undefined && message.value !== null && message.hasOwnProperty("value")) - object.value = message.value; - return object; - }; +/** + * Reads a string preceeded by its byte length as a varint. + * @returns {string} Value read + */ +Reader.prototype.string = function read_string() { + var bytes = this.bytes(); + return utf8.read(bytes, 0, bytes.length); +}; - StringIdOrValueProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +/** + * Skips the specified number of bytes if specified, otherwise skips a varint. + * @param {number} [length] Length if known, otherwise a varint is assumed + * @returns {Reader} `this` + */ +Reader.prototype.skip = function skip(length) { + if (typeof length === "number") { + /* istanbul ignore next */ + if (this.pos + length > this.len) + throw indexOutOfRange(this, length); + this.pos += length; + } else { + /* istanbul ignore next */ + do { + if (this.pos >= this.len) + throw indexOutOfRange(this); + } while (this.buf[this.pos++] & 128); + } + return this; +}; - StringIdOrValueProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +/** + * Skips the next element of the specified wire type. + * @param {number} wireType Wire type received + * @returns {Reader} `this` + */ +Reader.prototype.skipType = function(wireType) { + switch (wireType) { + case 0: + this.skip(); + break; + case 1: + this.skip(8); + break; + case 2: + this.skip(this.uint32()); + break; + case 3: + do { // eslint-disable-line no-constant-condition + if ((wireType = this.uint32() & 7) === 4) + break; + this.skipType(wireType); + } while (true); + break; + case 5: + this.skip(4); + break; - return StringIdOrValueProto; - })(); + /* istanbul ignore next */ + default: + throw Error("invalid wire type " + wireType + " at offset " + this.pos); + } + return this; +}; - dbroot.PlanetModelProto = (function() { +Reader._configure = function(BufferReader_) { + BufferReader = BufferReader_; - function PlanetModelProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + var fn = util.Long ? "toLong" : /* istanbul ignore next */ "toNumber"; + util.merge(Reader.prototype, { - PlanetModelProto.prototype.radius = 6378.137; - PlanetModelProto.prototype.flattening = 0.00335281066474748; - PlanetModelProto.prototype.elevationBias = 0; - PlanetModelProto.prototype.negativeAltitudeExponentBias = 0; - PlanetModelProto.prototype.compressedNegativeAltitudeThreshold = 0; + int64: function read_int64() { + return readLongVarint.call(this)[fn](false); + }, - PlanetModelProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.PlanetModelProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.radius = reader.double(); - break; - case 2: - message.flattening = reader.double(); - break; - case 4: - message.elevationBias = reader.double(); - break; - case 5: - message.negativeAltitudeExponentBias = reader.int32(); - break; - case 6: - message.compressedNegativeAltitudeThreshold = reader.double(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + uint64: function read_uint64() { + return readLongVarint.call(this)[fn](true); + }, - PlanetModelProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.radius !== undefined) - if (typeof message.radius !== "number") - return "radius: number expected"; - if (message.flattening !== undefined) - if (typeof message.flattening !== "number") - return "flattening: number expected"; - if (message.elevationBias !== undefined) - if (typeof message.elevationBias !== "number") - return "elevationBias: number expected"; - if (message.negativeAltitudeExponentBias !== undefined) - if (!$util.isInteger(message.negativeAltitudeExponentBias)) - return "negativeAltitudeExponentBias: integer expected"; - if (message.compressedNegativeAltitudeThreshold !== undefined) - if (typeof message.compressedNegativeAltitudeThreshold !== "number") - return "compressedNegativeAltitudeThreshold: number expected"; - return null; - }; + sint64: function read_sint64() { + return readLongVarint.call(this).zzDecode()[fn](false); + }, - PlanetModelProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.PlanetModelProto) - return object; - var message = new $root.keyhole.dbroot.PlanetModelProto(); - if (object.radius !== undefined && object.radius !== null) - message.radius = Number(object.radius); - if (object.flattening !== undefined && object.flattening !== null) - message.flattening = Number(object.flattening); - if (object.elevationBias !== undefined && object.elevationBias !== null) - message.elevationBias = Number(object.elevationBias); - if (object.negativeAltitudeExponentBias !== undefined && object.negativeAltitudeExponentBias !== null) - message.negativeAltitudeExponentBias = object.negativeAltitudeExponentBias | 0; - if (object.compressedNegativeAltitudeThreshold !== undefined && object.compressedNegativeAltitudeThreshold !== null) - message.compressedNegativeAltitudeThreshold = Number(object.compressedNegativeAltitudeThreshold); - return message; - }; + fixed64: function read_fixed64() { + return readFixed64.call(this)[fn](true); + }, - PlanetModelProto.from = PlanetModelProto.fromObject; + sfixed64: function read_sfixed64() { + return readFixed64.call(this)[fn](false); + } - PlanetModelProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.radius = 6378.137; - object.flattening = 0.00335281066474748; - object.elevationBias = 0; - object.negativeAltitudeExponentBias = 0; - object.compressedNegativeAltitudeThreshold = 0; - } - if (message.radius !== undefined && message.radius !== null && message.hasOwnProperty("radius")) - object.radius = message.radius; - if (message.flattening !== undefined && message.flattening !== null && message.hasOwnProperty("flattening")) - object.flattening = message.flattening; - if (message.elevationBias !== undefined && message.elevationBias !== null && message.hasOwnProperty("elevationBias")) - object.elevationBias = message.elevationBias; - if (message.negativeAltitudeExponentBias !== undefined && message.negativeAltitudeExponentBias !== null && message.hasOwnProperty("negativeAltitudeExponentBias")) - object.negativeAltitudeExponentBias = message.negativeAltitudeExponentBias; - if (message.compressedNegativeAltitudeThreshold !== undefined && message.compressedNegativeAltitudeThreshold !== null && message.hasOwnProperty("compressedNegativeAltitudeThreshold")) - object.compressedNegativeAltitudeThreshold = message.compressedNegativeAltitudeThreshold; - return object; - }; + }); +}; - PlanetModelProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +},{"13":13}],9:[function(require,module,exports){ +"use strict"; +module.exports = BufferReader; - PlanetModelProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +// extends Reader +var Reader = require(8); +(BufferReader.prototype = Object.create(Reader.prototype)).constructor = BufferReader; - return PlanetModelProto; - })(); +var util = require(13); - dbroot.ProviderInfoProto = (function() { +/** + * Constructs a new buffer reader instance. + * @classdesc Wire format reader using node buffers. + * @extends Reader + * @constructor + * @param {Buffer} buffer Buffer to read from + */ +function BufferReader(buffer) { + Reader.call(this, buffer); - function ProviderInfoProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + /** + * Read buffer. + * @name BufferReader#buf + * @type {Buffer} + */ +} - ProviderInfoProto.prototype.providerId = 0; - ProviderInfoProto.prototype.copyrightString = null; - ProviderInfoProto.prototype.verticalPixelOffset = -1; +/* istanbul ignore else */ +if (util.Buffer) + BufferReader.prototype._slice = util.Buffer.prototype.slice; - var $types = { - 1 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); +/** + * @override + */ +BufferReader.prototype.string = function read_string_buffer() { + var len = this.uint32(); // modifies pos + return this.buf.utf8Slice(this.pos, this.pos = Math.min(this.pos + len, this.len)); +}; - ProviderInfoProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ProviderInfoProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.providerId = reader.int32(); - break; - case 2: - message.copyrightString = $types[1].decode(reader, reader.uint32()); - break; - case 3: - message.verticalPixelOffset = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * Reads a sequence of bytes preceeded by its length as a varint. + * @name BufferReader#bytes + * @function + * @returns {Buffer} Value read + */ - ProviderInfoProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (!$util.isInteger(message.providerId)) - return "providerId: integer expected"; - if (message.copyrightString !== undefined && message.copyrightString !== null) { - var error = $types[1].verify(message.copyrightString); - if (error) - return "copyrightString." + error; - } - if (message.verticalPixelOffset !== undefined) - if (!$util.isInteger(message.verticalPixelOffset)) - return "verticalPixelOffset: integer expected"; - return null; - }; +},{"13":13,"8":8}],10:[function(require,module,exports){ +"use strict"; - ProviderInfoProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ProviderInfoProto) - return object; - var message = new $root.keyhole.dbroot.ProviderInfoProto(); - if (object.providerId !== undefined && object.providerId !== null) - message.providerId = object.providerId | 0; - if (object.copyrightString !== undefined && object.copyrightString !== null) { - if (typeof object.copyrightString !== "object") - throw TypeError(".keyhole.dbroot.ProviderInfoProto.copyrightString: object expected"); - message.copyrightString = $types[1].fromObject(object.copyrightString); - } - if (object.verticalPixelOffset !== undefined && object.verticalPixelOffset !== null) - message.verticalPixelOffset = object.verticalPixelOffset | 0; - return message; - }; +/** + * Streaming RPC helpers. + * @namespace + */ +var rpc = exports; - ProviderInfoProto.from = ProviderInfoProto.fromObject; +/** + * RPC implementation passed to {@link Service#create} performing a service request on network level, i.e. by utilizing http requests or websockets. + * @typedef RPCImpl + * @type {function} + * @param {Method|rpc.ServiceMethod} method Reflected or static method being called + * @param {Uint8Array} requestData Request data + * @param {RPCImplCallback} callback Callback function + * @returns {undefined} + * @example + * function rpcImpl(method, requestData, callback) { + * if (protobuf.util.lcFirst(method.name) !== "myMethod") // compatible with static code + * throw Error("no such method"); + * asynchronouslyObtainAResponse(requestData, function(err, responseData) { + * callback(err, responseData); + * }); + * } + */ - ProviderInfoProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.providerId = 0; - object.copyrightString = null; - object.verticalPixelOffset = -1; - } - if (message.providerId !== undefined && message.providerId !== null && message.hasOwnProperty("providerId")) - object.providerId = message.providerId; - if (message.copyrightString !== undefined && message.copyrightString !== null && message.hasOwnProperty("copyrightString")) - object.copyrightString = $types[1].toObject(message.copyrightString, options); - if (message.verticalPixelOffset !== undefined && message.verticalPixelOffset !== null && message.hasOwnProperty("verticalPixelOffset")) - object.verticalPixelOffset = message.verticalPixelOffset; - return object; - }; +/** + * Node-style callback as used by {@link RPCImpl}. + * @typedef RPCImplCallback + * @type {function} + * @param {?Error} error Error, if any, otherwise `null` + * @param {?Uint8Array} [response] Response data or `null` to signal end of stream, if there hasn't been an error + * @returns {undefined} + */ - ProviderInfoProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +rpc.Service = require(11); - ProviderInfoProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +},{"11":11}],11:[function(require,module,exports){ +"use strict"; +module.exports = Service; - return ProviderInfoProto; - })(); +var util = require(13); - dbroot.PopUpProto = (function() { +// Extends EventEmitter +(Service.prototype = Object.create(util.EventEmitter.prototype)).constructor = Service; - function PopUpProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +/** + * A service method callback as used by {@link rpc.ServiceMethod|ServiceMethod}. + * + * Differs from {@link RPCImplCallback} in that it is an actual callback of a service method which may not return `response = null`. + * @typedef rpc.ServiceMethodCallback + * @type {function} + * @param {?Error} error Error, if any + * @param {?Message} [response] Response message + * @returns {undefined} + */ - PopUpProto.prototype.isBalloonStyle = false; - PopUpProto.prototype.text = null; - PopUpProto.prototype.backgroundColorAbgr = 4294967295; - PopUpProto.prototype.textColorAbgr = 4278190080; +/** + * A service method part of a {@link rpc.ServiceMethodMixin|ServiceMethodMixin} and thus {@link rpc.Service} as created by {@link Service.create}. + * @typedef rpc.ServiceMethod + * @type {function} + * @param {Message|Object.} request Request message or plain object + * @param {rpc.ServiceMethodCallback} [callback] Node-style callback called with the error, if any, and the response message + * @returns {Promise} Promise if `callback` has been omitted, otherwise `undefined` + */ - var $types = { - 1 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); +/** + * A service method mixin. + * + * When using TypeScript, mixed in service methods are only supported directly with a type definition of a static module (used with reflection). Otherwise, explicit casting is required. + * @typedef rpc.ServiceMethodMixin + * @type {Object.} + * @example + * // Explicit casting with TypeScript + * (myRpcService["myMethod"] as protobuf.rpc.ServiceMethod)(...) + */ - PopUpProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.PopUpProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.isBalloonStyle = reader.bool(); - break; - case 2: - message.text = $types[1].decode(reader, reader.uint32()); - break; - case 3: - message.backgroundColorAbgr = reader.fixed32(); - break; - case 4: - message.textColorAbgr = reader.fixed32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * Constructs a new RPC service instance. + * @classdesc An RPC service as returned by {@link Service#create}. + * @exports rpc.Service + * @extends util.EventEmitter + * @augments rpc.ServiceMethodMixin + * @constructor + * @param {RPCImpl} rpcImpl RPC implementation + * @param {boolean} [requestDelimited=false] Whether requests are length-delimited + * @param {boolean} [responseDelimited=false] Whether responses are length-delimited + */ +function Service(rpcImpl, requestDelimited, responseDelimited) { - PopUpProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.isBalloonStyle !== undefined) - if (typeof message.isBalloonStyle !== "boolean") - return "isBalloonStyle: boolean expected"; - if (message.text !== undefined && message.text !== null) { - var error = $types[1].verify(message.text); - if (error) - return "text." + error; - } - if (message.backgroundColorAbgr !== undefined) - if (!$util.isInteger(message.backgroundColorAbgr)) - return "backgroundColorAbgr: integer expected"; - if (message.textColorAbgr !== undefined) - if (!$util.isInteger(message.textColorAbgr)) - return "textColorAbgr: integer expected"; - return null; - }; + if (typeof rpcImpl !== "function") + throw TypeError("rpcImpl must be a function"); - PopUpProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.PopUpProto) - return object; - var message = new $root.keyhole.dbroot.PopUpProto(); - if (object.isBalloonStyle !== undefined && object.isBalloonStyle !== null) - message.isBalloonStyle = Boolean(object.isBalloonStyle); - if (object.text !== undefined && object.text !== null) { - if (typeof object.text !== "object") - throw TypeError(".keyhole.dbroot.PopUpProto.text: object expected"); - message.text = $types[1].fromObject(object.text); - } - if (object.backgroundColorAbgr !== undefined && object.backgroundColorAbgr !== null) - message.backgroundColorAbgr = object.backgroundColorAbgr >>> 0; - if (object.textColorAbgr !== undefined && object.textColorAbgr !== null) - message.textColorAbgr = object.textColorAbgr >>> 0; - return message; - }; + util.EventEmitter.call(this); - PopUpProto.from = PopUpProto.fromObject; + /** + * RPC implementation. Becomes `null` once the service is ended. + * @type {?RPCImpl} + */ + this.rpcImpl = rpcImpl; - PopUpProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.isBalloonStyle = false; - object.text = null; - object.backgroundColorAbgr = 4294967295; - object.textColorAbgr = 4278190080; - } - if (message.isBalloonStyle !== undefined && message.isBalloonStyle !== null && message.hasOwnProperty("isBalloonStyle")) - object.isBalloonStyle = message.isBalloonStyle; - if (message.text !== undefined && message.text !== null && message.hasOwnProperty("text")) - object.text = $types[1].toObject(message.text, options); - if (message.backgroundColorAbgr !== undefined && message.backgroundColorAbgr !== null && message.hasOwnProperty("backgroundColorAbgr")) - object.backgroundColorAbgr = message.backgroundColorAbgr; - if (message.textColorAbgr !== undefined && message.textColorAbgr !== null && message.hasOwnProperty("textColorAbgr")) - object.textColorAbgr = message.textColorAbgr; - return object; - }; + /** + * Whether requests are length-delimited. + * @type {boolean} + */ + this.requestDelimited = Boolean(requestDelimited); - PopUpProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + /** + * Whether responses are length-delimited. + * @type {boolean} + */ + this.responseDelimited = Boolean(responseDelimited); +} - PopUpProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +/** + * Calls a service method through {@link rpc.Service#rpcImpl|rpcImpl}. + * @param {Method|rpc.ServiceMethod} method Reflected or static method + * @param {function} requestCtor Request constructor + * @param {function} responseCtor Response constructor + * @param {Message|Object.} request Request message or plain object + * @param {rpc.ServiceMethodCallback} callback Service callback + * @returns {undefined} + */ +Service.prototype.rpcCall = function rpcCall(method, requestCtor, responseCtor, request, callback) { - return PopUpProto; - })(); + if (!request) + throw TypeError("request must be specified"); - dbroot.StyleAttributeProto = (function() { + var self = this; + if (!callback) + return util.asPromise(rpcCall, self, method, requestCtor, responseCtor, request); - function StyleAttributeProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + if (!self.rpcImpl) { + setTimeout(function() { callback(Error("already ended")); }, 0); + return undefined; + } - StyleAttributeProto.prototype.styleId = ""; - StyleAttributeProto.prototype.providerId = 0; - StyleAttributeProto.prototype.polyColorAbgr = 4294967295; - StyleAttributeProto.prototype.lineColorAbgr = 4294967295; - StyleAttributeProto.prototype.lineWidth = 1; - StyleAttributeProto.prototype.labelColorAbgr = 4294967295; - StyleAttributeProto.prototype.labelScale = 1; - StyleAttributeProto.prototype.placemarkIconColorAbgr = 4294967295; - StyleAttributeProto.prototype.placemarkIconScale = 1; - StyleAttributeProto.prototype.placemarkIconPath = null; - StyleAttributeProto.prototype.placemarkIconX = 0; - StyleAttributeProto.prototype.placemarkIconY = 0; - StyleAttributeProto.prototype.placemarkIconWidth = 32; - StyleAttributeProto.prototype.placemarkIconHeight = 32; - StyleAttributeProto.prototype.popUp = null; - StyleAttributeProto.prototype.drawFlag = $util.emptyArray; + try { + return self.rpcImpl( + method, + requestCtor[self.requestDelimited ? "encodeDelimited" : "encode"](request).finish(), + function rpcCallback(err, response) { - var $types = { - 9 : "keyhole.dbroot.StringIdOrValueProto", - 14 : "keyhole.dbroot.PopUpProto", - 15 : "keyhole.dbroot.DrawFlagProto" - }; - $lazyTypes.push($types); + if (err) { + self.emit("error", err, method); + return callback(err); + } - StyleAttributeProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.StyleAttributeProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.styleId = reader.string(); - break; - case 3: - message.providerId = reader.int32(); - break; - case 4: - message.polyColorAbgr = reader.fixed32(); - break; - case 5: - message.lineColorAbgr = reader.fixed32(); - break; - case 6: - message.lineWidth = reader.float(); - break; - case 7: - message.labelColorAbgr = reader.fixed32(); - break; - case 8: - message.labelScale = reader.float(); - break; - case 9: - message.placemarkIconColorAbgr = reader.fixed32(); - break; - case 10: - message.placemarkIconScale = reader.float(); - break; - case 11: - message.placemarkIconPath = $types[9].decode(reader, reader.uint32()); - break; - case 12: - message.placemarkIconX = reader.int32(); - break; - case 13: - message.placemarkIconY = reader.int32(); - break; - case 14: - message.placemarkIconWidth = reader.int32(); - break; - case 15: - message.placemarkIconHeight = reader.int32(); - break; - case 16: - message.popUp = $types[14].decode(reader, reader.uint32()); - break; - case 17: - if (!(message.drawFlag && message.drawFlag.length)) - message.drawFlag = []; - message.drawFlag.push($types[15].decode(reader, reader.uint32())); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + if (response === null) { + self.end(/* endedByRPC */ true); + return undefined; + } - StyleAttributeProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (!$util.isString(message.styleId)) - return "styleId: string expected"; - if (message.providerId !== undefined) - if (!$util.isInteger(message.providerId)) - return "providerId: integer expected"; - if (message.polyColorAbgr !== undefined) - if (!$util.isInteger(message.polyColorAbgr)) - return "polyColorAbgr: integer expected"; - if (message.lineColorAbgr !== undefined) - if (!$util.isInteger(message.lineColorAbgr)) - return "lineColorAbgr: integer expected"; - if (message.lineWidth !== undefined) - if (typeof message.lineWidth !== "number") - return "lineWidth: number expected"; - if (message.labelColorAbgr !== undefined) - if (!$util.isInteger(message.labelColorAbgr)) - return "labelColorAbgr: integer expected"; - if (message.labelScale !== undefined) - if (typeof message.labelScale !== "number") - return "labelScale: number expected"; - if (message.placemarkIconColorAbgr !== undefined) - if (!$util.isInteger(message.placemarkIconColorAbgr)) - return "placemarkIconColorAbgr: integer expected"; - if (message.placemarkIconScale !== undefined) - if (typeof message.placemarkIconScale !== "number") - return "placemarkIconScale: number expected"; - if (message.placemarkIconPath !== undefined && message.placemarkIconPath !== null) { - var error = $types[9].verify(message.placemarkIconPath); - if (error) - return "placemarkIconPath." + error; - } - if (message.placemarkIconX !== undefined) - if (!$util.isInteger(message.placemarkIconX)) - return "placemarkIconX: integer expected"; - if (message.placemarkIconY !== undefined) - if (!$util.isInteger(message.placemarkIconY)) - return "placemarkIconY: integer expected"; - if (message.placemarkIconWidth !== undefined) - if (!$util.isInteger(message.placemarkIconWidth)) - return "placemarkIconWidth: integer expected"; - if (message.placemarkIconHeight !== undefined) - if (!$util.isInteger(message.placemarkIconHeight)) - return "placemarkIconHeight: integer expected"; - if (message.popUp !== undefined && message.popUp !== null) { - var error = $types[14].verify(message.popUp); - if (error) - return "popUp." + error; - } - if (message.drawFlag !== undefined) { - if (!Array.isArray(message.drawFlag)) - return "drawFlag: array expected"; - for (var i = 0; i < message.drawFlag.length; ++i) { - var error = $types[15].verify(message.drawFlag[i]); - if (error) - return "drawFlag." + error; - } + if (!(response instanceof responseCtor)) { + try { + response = responseCtor[self.responseDelimited ? "decodeDelimited" : "decode"](response); + } catch (err) { + self.emit("error", err, method); + return callback(err); } - return null; - }; + } - StyleAttributeProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.StyleAttributeProto) - return object; - var message = new $root.keyhole.dbroot.StyleAttributeProto(); - if (object.styleId !== undefined && object.styleId !== null) - message.styleId = String(object.styleId); - if (object.providerId !== undefined && object.providerId !== null) - message.providerId = object.providerId | 0; - if (object.polyColorAbgr !== undefined && object.polyColorAbgr !== null) - message.polyColorAbgr = object.polyColorAbgr >>> 0; - if (object.lineColorAbgr !== undefined && object.lineColorAbgr !== null) - message.lineColorAbgr = object.lineColorAbgr >>> 0; - if (object.lineWidth !== undefined && object.lineWidth !== null) - message.lineWidth = Number(object.lineWidth); - if (object.labelColorAbgr !== undefined && object.labelColorAbgr !== null) - message.labelColorAbgr = object.labelColorAbgr >>> 0; - if (object.labelScale !== undefined && object.labelScale !== null) - message.labelScale = Number(object.labelScale); - if (object.placemarkIconColorAbgr !== undefined && object.placemarkIconColorAbgr !== null) - message.placemarkIconColorAbgr = object.placemarkIconColorAbgr >>> 0; - if (object.placemarkIconScale !== undefined && object.placemarkIconScale !== null) - message.placemarkIconScale = Number(object.placemarkIconScale); - if (object.placemarkIconPath !== undefined && object.placemarkIconPath !== null) { - if (typeof object.placemarkIconPath !== "object") - throw TypeError(".keyhole.dbroot.StyleAttributeProto.placemarkIconPath: object expected"); - message.placemarkIconPath = $types[9].fromObject(object.placemarkIconPath); - } - if (object.placemarkIconX !== undefined && object.placemarkIconX !== null) - message.placemarkIconX = object.placemarkIconX | 0; - if (object.placemarkIconY !== undefined && object.placemarkIconY !== null) - message.placemarkIconY = object.placemarkIconY | 0; - if (object.placemarkIconWidth !== undefined && object.placemarkIconWidth !== null) - message.placemarkIconWidth = object.placemarkIconWidth | 0; - if (object.placemarkIconHeight !== undefined && object.placemarkIconHeight !== null) - message.placemarkIconHeight = object.placemarkIconHeight | 0; - if (object.popUp !== undefined && object.popUp !== null) { - if (typeof object.popUp !== "object") - throw TypeError(".keyhole.dbroot.StyleAttributeProto.popUp: object expected"); - message.popUp = $types[14].fromObject(object.popUp); - } - if (object.drawFlag) { - if (!Array.isArray(object.drawFlag)) - throw TypeError(".keyhole.dbroot.StyleAttributeProto.drawFlag: array expected"); - message.drawFlag = []; - for (var i = 0; i < object.drawFlag.length; ++i) { - if (typeof object.drawFlag[i] !== "object") - throw TypeError(".keyhole.dbroot.StyleAttributeProto.drawFlag: object expected"); - message.drawFlag[i] = $types[15].fromObject(object.drawFlag[i]); - } - } - return message; - }; + self.emit("data", response, method); + return callback(null, response); + } + ); + } catch (err) { + self.emit("error", err, method); + setTimeout(function() { callback(err); }, 0); + return undefined; + } +}; - StyleAttributeProto.from = StyleAttributeProto.fromObject; +/** + * Ends this service and emits the `end` event. + * @param {boolean} [endedByRPC=false] Whether the service has been ended by the RPC implementation. + * @returns {rpc.Service} `this` + */ +Service.prototype.end = function end(endedByRPC) { + if (this.rpcImpl) { + if (!endedByRPC) // signal end to rpcImpl + this.rpcImpl(null, null, null); + this.rpcImpl = null; + this.emit("end").off(); + } + return this; +}; - StyleAttributeProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) - object.drawFlag = []; - if (options.defaults) { - object.styleId = ""; - object.providerId = 0; - object.polyColorAbgr = 4294967295; - object.lineColorAbgr = 4294967295; - object.lineWidth = 1; - object.labelColorAbgr = 4294967295; - object.labelScale = 1; - object.placemarkIconColorAbgr = 4294967295; - object.placemarkIconScale = 1; - object.placemarkIconPath = null; - object.placemarkIconX = 0; - object.placemarkIconY = 0; - object.placemarkIconWidth = 32; - object.placemarkIconHeight = 32; - object.popUp = null; - } - if (message.styleId !== undefined && message.styleId !== null && message.hasOwnProperty("styleId")) - object.styleId = message.styleId; - if (message.providerId !== undefined && message.providerId !== null && message.hasOwnProperty("providerId")) - object.providerId = message.providerId; - if (message.polyColorAbgr !== undefined && message.polyColorAbgr !== null && message.hasOwnProperty("polyColorAbgr")) - object.polyColorAbgr = message.polyColorAbgr; - if (message.lineColorAbgr !== undefined && message.lineColorAbgr !== null && message.hasOwnProperty("lineColorAbgr")) - object.lineColorAbgr = message.lineColorAbgr; - if (message.lineWidth !== undefined && message.lineWidth !== null && message.hasOwnProperty("lineWidth")) - object.lineWidth = message.lineWidth; - if (message.labelColorAbgr !== undefined && message.labelColorAbgr !== null && message.hasOwnProperty("labelColorAbgr")) - object.labelColorAbgr = message.labelColorAbgr; - if (message.labelScale !== undefined && message.labelScale !== null && message.hasOwnProperty("labelScale")) - object.labelScale = message.labelScale; - if (message.placemarkIconColorAbgr !== undefined && message.placemarkIconColorAbgr !== null && message.hasOwnProperty("placemarkIconColorAbgr")) - object.placemarkIconColorAbgr = message.placemarkIconColorAbgr; - if (message.placemarkIconScale !== undefined && message.placemarkIconScale !== null && message.hasOwnProperty("placemarkIconScale")) - object.placemarkIconScale = message.placemarkIconScale; - if (message.placemarkIconPath !== undefined && message.placemarkIconPath !== null && message.hasOwnProperty("placemarkIconPath")) - object.placemarkIconPath = $types[9].toObject(message.placemarkIconPath, options); - if (message.placemarkIconX !== undefined && message.placemarkIconX !== null && message.hasOwnProperty("placemarkIconX")) - object.placemarkIconX = message.placemarkIconX; - if (message.placemarkIconY !== undefined && message.placemarkIconY !== null && message.hasOwnProperty("placemarkIconY")) - object.placemarkIconY = message.placemarkIconY; - if (message.placemarkIconWidth !== undefined && message.placemarkIconWidth !== null && message.hasOwnProperty("placemarkIconWidth")) - object.placemarkIconWidth = message.placemarkIconWidth; - if (message.placemarkIconHeight !== undefined && message.placemarkIconHeight !== null && message.hasOwnProperty("placemarkIconHeight")) - object.placemarkIconHeight = message.placemarkIconHeight; - if (message.popUp !== undefined && message.popUp !== null && message.hasOwnProperty("popUp")) - object.popUp = $types[14].toObject(message.popUp, options); - if (message.drawFlag !== undefined && message.drawFlag !== null && message.hasOwnProperty("drawFlag")) { - object.drawFlag = []; - for (var j = 0; j < message.drawFlag.length; ++j) - object.drawFlag[j] = $types[15].toObject(message.drawFlag[j], options); - } - return object; - }; +},{"13":13}],12:[function(require,module,exports){ +"use strict"; +module.exports = LongBits; - StyleAttributeProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +var util = require(13); - StyleAttributeProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +/** + * Any compatible Long instance. + * + * This is a minimal stand-alone definition of a Long instance. The actual type is that exported by long.js. + * @typedef Long + * @type {Object} + * @property {number} low Low bits + * @property {number} high High bits + * @property {boolean} unsigned Whether unsigned or not + */ - return StyleAttributeProto; - })(); +/** + * Constructs new long bits. + * @classdesc Helper class for working with the low and high bits of a 64 bit value. + * @memberof util + * @constructor + * @param {number} lo Low 32 bits, unsigned + * @param {number} hi High 32 bits, unsigned + */ +function LongBits(lo, hi) { - dbroot.StyleMapProto = (function() { + // note that the casts below are theoretically unnecessary as of today, but older statically + // generated converter code might still call the ctor with signed 32bits. kept for compat. - function StyleMapProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + /** + * Low bits. + * @type {number} + */ + this.lo = lo >>> 0; - StyleMapProto.prototype.styleMapId = 0; - StyleMapProto.prototype.channelId = $util.emptyArray; - StyleMapProto.prototype.normalStyleAttribute = 0; - StyleMapProto.prototype.highlightStyleAttribute = 0; + /** + * High bits. + * @type {number} + */ + this.hi = hi >>> 0; +} - StyleMapProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.StyleMapProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.styleMapId = reader.int32(); - break; - case 2: - if (!(message.channelId && message.channelId.length)) - message.channelId = []; - if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; - while (reader.pos < end2) - message.channelId.push(reader.int32()); - } else - message.channelId.push(reader.int32()); - break; - case 3: - message.normalStyleAttribute = reader.int32(); - break; - case 4: - message.highlightStyleAttribute = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * Zero bits. + * @memberof util.LongBits + * @type {util.LongBits} + */ +var zero = LongBits.zero = new LongBits(0, 0); - StyleMapProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (!$util.isInteger(message.styleMapId)) - return "styleMapId: integer expected"; - if (message.channelId !== undefined) { - if (!Array.isArray(message.channelId)) - return "channelId: array expected"; - for (var i = 0; i < message.channelId.length; ++i) - if (!$util.isInteger(message.channelId[i])) - return "channelId: integer[] expected"; - } - if (message.normalStyleAttribute !== undefined) - if (!$util.isInteger(message.normalStyleAttribute)) - return "normalStyleAttribute: integer expected"; - if (message.highlightStyleAttribute !== undefined) - if (!$util.isInteger(message.highlightStyleAttribute)) - return "highlightStyleAttribute: integer expected"; - return null; - }; - - StyleMapProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.StyleMapProto) - return object; - var message = new $root.keyhole.dbroot.StyleMapProto(); - if (object.styleMapId !== undefined && object.styleMapId !== null) - message.styleMapId = object.styleMapId | 0; - if (object.channelId) { - if (!Array.isArray(object.channelId)) - throw TypeError(".keyhole.dbroot.StyleMapProto.channelId: array expected"); - message.channelId = []; - for (var i = 0; i < object.channelId.length; ++i) - message.channelId[i] = object.channelId[i] | 0; - } - if (object.normalStyleAttribute !== undefined && object.normalStyleAttribute !== null) - message.normalStyleAttribute = object.normalStyleAttribute | 0; - if (object.highlightStyleAttribute !== undefined && object.highlightStyleAttribute !== null) - message.highlightStyleAttribute = object.highlightStyleAttribute | 0; - return message; - }; +zero.toNumber = function() { return 0; }; +zero.zzEncode = zero.zzDecode = function() { return this; }; +zero.length = function() { return 1; }; - StyleMapProto.from = StyleMapProto.fromObject; +/** + * Zero hash. + * @memberof util.LongBits + * @type {string} + */ +var zeroHash = LongBits.zeroHash = "\0\0\0\0\0\0\0\0"; - StyleMapProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) - object.channelId = []; - if (options.defaults) { - object.styleMapId = 0; - object.normalStyleAttribute = 0; - object.highlightStyleAttribute = 0; - } - if (message.styleMapId !== undefined && message.styleMapId !== null && message.hasOwnProperty("styleMapId")) - object.styleMapId = message.styleMapId; - if (message.channelId !== undefined && message.channelId !== null && message.hasOwnProperty("channelId")) { - object.channelId = []; - for (var j = 0; j < message.channelId.length; ++j) - object.channelId[j] = message.channelId[j]; - } - if (message.normalStyleAttribute !== undefined && message.normalStyleAttribute !== null && message.hasOwnProperty("normalStyleAttribute")) - object.normalStyleAttribute = message.normalStyleAttribute; - if (message.highlightStyleAttribute !== undefined && message.highlightStyleAttribute !== null && message.hasOwnProperty("highlightStyleAttribute")) - object.highlightStyleAttribute = message.highlightStyleAttribute; - return object; - }; +/** + * Constructs new long bits from the specified number. + * @param {number} value Value + * @returns {util.LongBits} Instance + */ +LongBits.fromNumber = function fromNumber(value) { + if (value === 0) + return zero; + var sign = value < 0; + if (sign) + value = -value; + var lo = value >>> 0, + hi = (value - lo) / 4294967296 >>> 0; + if (sign) { + hi = ~hi >>> 0; + lo = ~lo >>> 0; + if (++lo > 4294967295) { + lo = 0; + if (++hi > 4294967295) + hi = 0; + } + } + return new LongBits(lo, hi); +}; - StyleMapProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +/** + * Constructs new long bits from a number, long or string. + * @param {Long|number|string} value Value + * @returns {util.LongBits} Instance + */ +LongBits.from = function from(value) { + if (typeof value === "number") + return LongBits.fromNumber(value); + if (util.isString(value)) { + /* istanbul ignore else */ + if (util.Long) + value = util.Long.fromString(value); + else + return LongBits.fromNumber(parseInt(value, 10)); + } + return value.low || value.high ? new LongBits(value.low >>> 0, value.high >>> 0) : zero; +}; - StyleMapProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +/** + * Converts this long bits to a possibly unsafe JavaScript number. + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {number} Possibly unsafe number + */ +LongBits.prototype.toNumber = function toNumber(unsigned) { + if (!unsigned && this.hi >>> 31) { + var lo = ~this.lo + 1 >>> 0, + hi = ~this.hi >>> 0; + if (!lo) + hi = hi + 1 >>> 0; + return -(lo + hi * 4294967296); + } + return this.lo + this.hi * 4294967296; +}; - return StyleMapProto; - })(); +/** + * Converts this long bits to a long. + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {Long} Long + */ +LongBits.prototype.toLong = function toLong(unsigned) { + return util.Long + ? new util.Long(this.lo | 0, this.hi | 0, Boolean(unsigned)) + /* istanbul ignore next */ + : { low: this.lo | 0, high: this.hi | 0, unsigned: Boolean(unsigned) }; +}; - dbroot.ZoomRangeProto = (function() { +var charCodeAt = String.prototype.charCodeAt; - function ZoomRangeProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +/** + * Constructs new long bits from the specified 8 characters long hash. + * @param {string} hash Hash + * @returns {util.LongBits} Bits + */ +LongBits.fromHash = function fromHash(hash) { + if (hash === zeroHash) + return zero; + return new LongBits( + ( charCodeAt.call(hash, 0) + | charCodeAt.call(hash, 1) << 8 + | charCodeAt.call(hash, 2) << 16 + | charCodeAt.call(hash, 3) << 24) >>> 0 + , + ( charCodeAt.call(hash, 4) + | charCodeAt.call(hash, 5) << 8 + | charCodeAt.call(hash, 6) << 16 + | charCodeAt.call(hash, 7) << 24) >>> 0 + ); +}; - ZoomRangeProto.prototype.minZoom = 0; - ZoomRangeProto.prototype.maxZoom = 0; +/** + * Converts this long bits to a 8 characters long hash. + * @returns {string} Hash + */ +LongBits.prototype.toHash = function toHash() { + return String.fromCharCode( + this.lo & 255, + this.lo >>> 8 & 255, + this.lo >>> 16 & 255, + this.lo >>> 24 , + this.hi & 255, + this.hi >>> 8 & 255, + this.hi >>> 16 & 255, + this.hi >>> 24 + ); +}; - ZoomRangeProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ZoomRangeProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.minZoom = reader.int32(); - break; - case 2: - message.maxZoom = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * Zig-zag encodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzEncode = function zzEncode() { + var mask = this.hi >> 31; + this.hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0; + this.lo = ( this.lo << 1 ^ mask) >>> 0; + return this; +}; - ZoomRangeProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (!$util.isInteger(message.minZoom)) - return "minZoom: integer expected"; - if (!$util.isInteger(message.maxZoom)) - return "maxZoom: integer expected"; - return null; - }; +/** + * Zig-zag decodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzDecode = function zzDecode() { + var mask = -(this.lo & 1); + this.lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0; + this.hi = ( this.hi >>> 1 ^ mask) >>> 0; + return this; +}; - ZoomRangeProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ZoomRangeProto) - return object; - var message = new $root.keyhole.dbroot.ZoomRangeProto(); - if (object.minZoom !== undefined && object.minZoom !== null) - message.minZoom = object.minZoom | 0; - if (object.maxZoom !== undefined && object.maxZoom !== null) - message.maxZoom = object.maxZoom | 0; - return message; - }; +/** + * Calculates the length of this longbits when encoded as a varint. + * @returns {number} Length + */ +LongBits.prototype.length = function length() { + var part0 = this.lo, + part1 = (this.lo >>> 28 | this.hi << 4) >>> 0, + part2 = this.hi >>> 24; + return part2 === 0 + ? part1 === 0 + ? part0 < 16384 + ? part0 < 128 ? 1 : 2 + : part0 < 2097152 ? 3 : 4 + : part1 < 16384 + ? part1 < 128 ? 5 : 6 + : part1 < 2097152 ? 7 : 8 + : part2 < 128 ? 9 : 10; +}; - ZoomRangeProto.from = ZoomRangeProto.fromObject; +},{"13":13}],13:[function(require,module,exports){ +"use strict"; +var util = exports; - ZoomRangeProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.minZoom = 0; - object.maxZoom = 0; - } - if (message.minZoom !== undefined && message.minZoom !== null && message.hasOwnProperty("minZoom")) - object.minZoom = message.minZoom; - if (message.maxZoom !== undefined && message.maxZoom !== null && message.hasOwnProperty("maxZoom")) - object.maxZoom = message.maxZoom; - return object; - }; +// used to return a Promise where callback is omitted +util.asPromise = require(1); - ZoomRangeProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +// converts to / from base64 encoded strings +util.base64 = require(2); - ZoomRangeProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +// base class of rpc.Service +util.EventEmitter = require(3); - return ZoomRangeProto; - })(); +// requires modules optionally and hides the call from bundlers +util.inquire = require(4); - dbroot.DrawFlagProto = (function() { +// converts to / from utf8 encoded strings +util.utf8 = require(6); - function DrawFlagProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +// provides a node-like buffer pool in the browser +util.pool = require(5); - DrawFlagProto.prototype.drawFlagType = 1; +// utility to work with the low and high bits of a 64 bit value +util.LongBits = require(12); - var $types = { - 0 : "keyhole.dbroot.DrawFlagProto.DrawFlagType" - }; - $lazyTypes.push($types); +/** + * An immuable empty array. + * @memberof util + * @type {Array.<*>} + */ +util.emptyArray = Object.freeze ? Object.freeze([]) : /* istanbul ignore next */ []; // used on prototypes - DrawFlagProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.DrawFlagProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.drawFlagType = reader.uint32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * An immutable empty object. + * @type {Object} + */ +util.emptyObject = Object.freeze ? Object.freeze({}) : /* istanbul ignore next */ {}; // used on prototypes - DrawFlagProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - switch (message.drawFlagType) { - default: - return "drawFlagType: enum value expected"; - case 1: - case 2: - case 3: - case 4: - case 5: - break; - } - return null; - }; +/** + * Whether running within node or not. + * @memberof util + * @type {boolean} + */ +util.isNode = Boolean(global.process && global.process.versions && global.process.versions.node); - DrawFlagProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.DrawFlagProto) - return object; - var message = new $root.keyhole.dbroot.DrawFlagProto(); - switch (object.drawFlagType) { - case "TYPE_FILL_ONLY": - case 1: - message.drawFlagType = 1; - break; - case "TYPE_OUTLINE_ONLY": - case 2: - message.drawFlagType = 2; - break; - case "TYPE_FILL_AND_OUTLINE": - case 3: - message.drawFlagType = 3; - break; - case "TYPE_ANTIALIASING": - case 4: - message.drawFlagType = 4; - break; - case "TYPE_CENTER_LABEL": - case 5: - message.drawFlagType = 5; - break; - } - return message; - }; +/** + * Tests if the specified value is an integer. + * @function + * @param {*} value Value to test + * @returns {boolean} `true` if the value is an integer + */ +util.isInteger = Number.isInteger || /* istanbul ignore next */ function isInteger(value) { + return typeof value === "number" && isFinite(value) && Math.floor(value) === value; +}; - DrawFlagProto.from = DrawFlagProto.fromObject; +/** + * Tests if the specified value is a string. + * @param {*} value Value to test + * @returns {boolean} `true` if the value is a string + */ +util.isString = function isString(value) { + return typeof value === "string" || value instanceof String; +}; - DrawFlagProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) - object.drawFlagType = options.enums === String ? "TYPE_FILL_ONLY" : 1; - if (message.drawFlagType !== undefined && message.drawFlagType !== null && message.hasOwnProperty("drawFlagType")) - object.drawFlagType = options.enums === String ? $types[0][message.drawFlagType] : message.drawFlagType; - return object; - }; +/** + * Tests if the specified value is a non-null object. + * @param {*} value Value to test + * @returns {boolean} `true` if the value is a non-null object + */ +util.isObject = function isObject(value) { + return value && typeof value === "object"; +}; - DrawFlagProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +/** + * Node's Buffer class if available. + * @type {?function(new: Buffer)} + */ +util.Buffer = (function() { + try { + var Buffer = util.inquire("buffer").Buffer; + // refuse to use non-node buffers if not explicitly assigned (perf reasons): + return Buffer.prototype.utf8Write ? Buffer : /* istanbul ignore next */ null; + } catch (e) { + /* istanbul ignore next */ + return null; + } +})(); - DrawFlagProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +/** + * Internal alias of or polyfull for Buffer.from. + * @type {?function} + * @param {string|number[]} value Value + * @param {string} [encoding] Encoding if value is a string + * @returns {Uint8Array} + * @private + */ +util._Buffer_from = null; - DrawFlagProto.DrawFlagType = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["TYPE_FILL_ONLY"] = 1; - values["TYPE_OUTLINE_ONLY"] = 2; - values["TYPE_FILL_AND_OUTLINE"] = 3; - values["TYPE_ANTIALIASING"] = 4; - values["TYPE_CENTER_LABEL"] = 5; - return values; - })(); +/** + * Internal alias of or polyfill for Buffer.allocUnsafe. + * @type {?function} + * @param {number} size Buffer size + * @returns {Uint8Array} + * @private + */ +util._Buffer_allocUnsafe = null; - return DrawFlagProto; - })(); +/** + * Creates a new buffer of whatever type supported by the environment. + * @param {number|number[]} [sizeOrArray=0] Buffer size or number array + * @returns {Uint8Array|Buffer} Buffer + */ +util.newBuffer = function newBuffer(sizeOrArray) { + /* istanbul ignore next */ + return typeof sizeOrArray === "number" + ? util.Buffer + ? util._Buffer_allocUnsafe(sizeOrArray) + : new util.Array(sizeOrArray) + : util.Buffer + ? util._Buffer_from(sizeOrArray) + : typeof Uint8Array === "undefined" + ? sizeOrArray + : new Uint8Array(sizeOrArray); +}; - dbroot.LayerProto = (function() { +/** + * Array implementation used in the browser. `Uint8Array` if supported, otherwise `Array`. + * @type {?function(new: Uint8Array, *)} + */ +util.Array = typeof Uint8Array !== "undefined" ? Uint8Array /* istanbul ignore next */ : Array; - function LayerProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +/** + * Long.js's Long class if available. + * @type {?function(new: Long)} + */ +util.Long = /* istanbul ignore next */ global.dcodeIO && /* istanbul ignore next */ global.dcodeIO.Long || util.inquire("long"); - LayerProto.prototype.zoomRange = $util.emptyArray; - LayerProto.prototype.preserveTextLevel = 30; - LayerProto.prototype.lodBeginTransition = false; - LayerProto.prototype.lodEndTransition = false; +/** + * Regular expression used to verify 2 bit (`bool`) map keys. + * @type {RegExp} + */ +util.key2Re = /^true|false|0|1$/; - var $types = { - 0 : "keyhole.dbroot.ZoomRangeProto" - }; - $lazyTypes.push($types); +/** + * Regular expression used to verify 32 bit (`int32` etc.) map keys. + * @type {RegExp} + */ +util.key32Re = /^-?(?:0|[1-9][0-9]*)$/; - LayerProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.LayerProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - if (!(message.zoomRange && message.zoomRange.length)) - message.zoomRange = []; - message.zoomRange.push($types[0].decode(reader, reader.uint32())); - break; - case 2: - message.preserveTextLevel = reader.int32(); - break; - case 4: - message.lodBeginTransition = reader.bool(); - break; - case 5: - message.lodEndTransition = reader.bool(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * Regular expression used to verify 64 bit (`int64` etc.) map keys. + * @type {RegExp} + */ +util.key64Re = /^(?:[\\x00-\\xff]{8}|-?(?:0|[1-9][0-9]*))$/; - LayerProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.zoomRange !== undefined) { - if (!Array.isArray(message.zoomRange)) - return "zoomRange: array expected"; - for (var i = 0; i < message.zoomRange.length; ++i) { - var error = $types[0].verify(message.zoomRange[i]); - if (error) - return "zoomRange." + error; - } - } - if (message.preserveTextLevel !== undefined) - if (!$util.isInteger(message.preserveTextLevel)) - return "preserveTextLevel: integer expected"; - if (message.lodBeginTransition !== undefined) - if (typeof message.lodBeginTransition !== "boolean") - return "lodBeginTransition: boolean expected"; - if (message.lodEndTransition !== undefined) - if (typeof message.lodEndTransition !== "boolean") - return "lodEndTransition: boolean expected"; - return null; - }; +/** + * Converts a number or long to an 8 characters long hash string. + * @param {Long|number} value Value to convert + * @returns {string} Hash + */ +util.longToHash = function longToHash(value) { + return value + ? util.LongBits.from(value).toHash() + : util.LongBits.zeroHash; +}; - LayerProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.LayerProto) - return object; - var message = new $root.keyhole.dbroot.LayerProto(); - if (object.zoomRange) { - if (!Array.isArray(object.zoomRange)) - throw TypeError(".keyhole.dbroot.LayerProto.zoomRange: array expected"); - message.zoomRange = []; - for (var i = 0; i < object.zoomRange.length; ++i) { - if (typeof object.zoomRange[i] !== "object") - throw TypeError(".keyhole.dbroot.LayerProto.zoomRange: object expected"); - message.zoomRange[i] = $types[0].fromObject(object.zoomRange[i]); - } - } - if (object.preserveTextLevel !== undefined && object.preserveTextLevel !== null) - message.preserveTextLevel = object.preserveTextLevel | 0; - if (object.lodBeginTransition !== undefined && object.lodBeginTransition !== null) - message.lodBeginTransition = Boolean(object.lodBeginTransition); - if (object.lodEndTransition !== undefined && object.lodEndTransition !== null) - message.lodEndTransition = Boolean(object.lodEndTransition); - return message; - }; +/** + * Converts an 8 characters long hash string to a long or number. + * @param {string} hash Hash + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {Long|number} Original value + */ +util.longFromHash = function longFromHash(hash, unsigned) { + var bits = util.LongBits.fromHash(hash); + if (util.Long) + return util.Long.fromBits(bits.lo, bits.hi, unsigned); + return bits.toNumber(Boolean(unsigned)); +}; - LayerProto.from = LayerProto.fromObject; +/** + * Merges the properties of the source object into the destination object. + * @memberof util + * @param {Object.} dst Destination object + * @param {Object.} src Source object + * @param {boolean} [ifNotSet=false] Merges only if the key is not already set + * @returns {Object.} Destination object + */ +function merge(dst, src, ifNotSet) { // used by converters + for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) + if (dst[keys[i]] === undefined || !ifNotSet) + dst[keys[i]] = src[keys[i]]; + return dst; +} - LayerProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) - object.zoomRange = []; - if (options.defaults) { - object.preserveTextLevel = 30; - object.lodBeginTransition = false; - object.lodEndTransition = false; - } - if (message.zoomRange !== undefined && message.zoomRange !== null && message.hasOwnProperty("zoomRange")) { - object.zoomRange = []; - for (var j = 0; j < message.zoomRange.length; ++j) - object.zoomRange[j] = $types[0].toObject(message.zoomRange[j], options); - } - if (message.preserveTextLevel !== undefined && message.preserveTextLevel !== null && message.hasOwnProperty("preserveTextLevel")) - object.preserveTextLevel = message.preserveTextLevel; - if (message.lodBeginTransition !== undefined && message.lodBeginTransition !== null && message.hasOwnProperty("lodBeginTransition")) - object.lodBeginTransition = message.lodBeginTransition; - if (message.lodEndTransition !== undefined && message.lodEndTransition !== null && message.hasOwnProperty("lodEndTransition")) - object.lodEndTransition = message.lodEndTransition; - return object; - }; +util.merge = merge; - LayerProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +/** + * Converts the first character of a string to lower case. + * @param {string} str String to convert + * @returns {string} Converted string + */ +util.lcFirst = function lcFirst(str) { + return str.charAt(0).toLowerCase() + str.substring(1); +}; - LayerProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +/** + * Creates a custom error constructor. + * @memberof util + * @param {string} name Error name + * @returns {function} Custom error constructor + */ +function newError(name) { - return LayerProto; - })(); + function CustomError(message, properties) { - dbroot.FolderProto = (function() { + if (!(this instanceof CustomError)) + return new CustomError(message, properties); - function FolderProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + // Error.call(this, message); + // ^ just returns a new error instance because the ctor can be called as a function - FolderProto.prototype.isExpandable = true; + Object.defineProperty(this, "message", { get: function() { return message; } }); - FolderProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.FolderProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.isExpandable = reader.bool(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + /* istanbul ignore next */ + if (Error.captureStackTrace) // node + Error.captureStackTrace(this, CustomError); + else + Object.defineProperty(this, "stack", { value: (new Error()).stack || "" }); - FolderProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.isExpandable !== undefined) - if (typeof message.isExpandable !== "boolean") - return "isExpandable: boolean expected"; - return null; - }; + if (properties) + merge(this, properties); + } - FolderProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.FolderProto) - return object; - var message = new $root.keyhole.dbroot.FolderProto(); - if (object.isExpandable !== undefined && object.isExpandable !== null) - message.isExpandable = Boolean(object.isExpandable); - return message; - }; + (CustomError.prototype = Object.create(Error.prototype)).constructor = CustomError; - FolderProto.from = FolderProto.fromObject; + Object.defineProperty(CustomError.prototype, "name", { get: function() { return name; } }); - FolderProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) - object.isExpandable = true; - if (message.isExpandable !== undefined && message.isExpandable !== null && message.hasOwnProperty("isExpandable")) - object.isExpandable = message.isExpandable; - return object; - }; + CustomError.prototype.toString = function toString() { + return this.name + ": " + this.message; + }; - FolderProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + return CustomError; +} - FolderProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +util.newError = newError; - return FolderProto; - })(); +/** + * Constructs a new protocol error. + * @classdesc Error subclass indicating a protocol specifc error. + * @memberof util + * @extends Error + * @constructor + * @param {string} message Error message + * @param {Object.=} properties Additional properties + * @example + * try { + * MyMessage.decode(someBuffer); // throws if required fields are missing + * } catch (e) { + * if (e instanceof ProtocolError && e.instance) + * console.log("decoded so far: " + JSON.stringify(e.instance)); + * } + */ +util.ProtocolError = newError("ProtocolError"); - dbroot.RequirementProto = (function() { +/** + * So far decoded message instance. + * @name util.ProtocolError#instance + * @type {Message} + */ - function RequirementProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +/** + * Builds a getter for a oneof's present field name. + * @param {string[]} fieldNames Field names + * @returns {function():string|undefined} Unbound getter + */ +util.oneOfGetter = function getOneOf(fieldNames) { + var fieldMap = {}; + for (var i = 0; i < fieldNames.length; ++i) + fieldMap[fieldNames[i]] = 1; - RequirementProto.prototype.requiredVram = ""; - RequirementProto.prototype.requiredClientVer = ""; - RequirementProto.prototype.probability = ""; - RequirementProto.prototype.requiredUserAgent = ""; - RequirementProto.prototype.requiredClientCapabilities = ""; + /** + * @returns {string|undefined} Set field name, if any + * @this Object + * @ignore + */ + return function() { // eslint-disable-line consistent-return + for (var keys = Object.keys(this), i = keys.length - 1; i > -1; --i) + if (fieldMap[keys[i]] === 1 && this[keys[i]] !== undefined && this[keys[i]] !== null) + return keys[i]; + }; +}; - RequirementProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.RequirementProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 3: - message.requiredVram = reader.string(); - break; - case 4: - message.requiredClientVer = reader.string(); - break; - case 5: - message.probability = reader.string(); - break; - case 6: - message.requiredUserAgent = reader.string(); - break; - case 7: - message.requiredClientCapabilities = reader.string(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; +/** + * Builds a setter for a oneof's present field name. + * @param {string[]} fieldNames Field names + * @returns {function(?string):undefined} Unbound setter + */ +util.oneOfSetter = function setOneOf(fieldNames) { - RequirementProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.requiredVram !== undefined) - if (!$util.isString(message.requiredVram)) - return "requiredVram: string expected"; - if (message.requiredClientVer !== undefined) - if (!$util.isString(message.requiredClientVer)) - return "requiredClientVer: string expected"; - if (message.probability !== undefined) - if (!$util.isString(message.probability)) - return "probability: string expected"; - if (message.requiredUserAgent !== undefined) - if (!$util.isString(message.requiredUserAgent)) - return "requiredUserAgent: string expected"; - if (message.requiredClientCapabilities !== undefined) - if (!$util.isString(message.requiredClientCapabilities)) - return "requiredClientCapabilities: string expected"; - return null; - }; + /** + * @param {string} name Field name + * @returns {undefined} + * @this Object + * @ignore + */ + return function(name) { + for (var i = 0; i < fieldNames.length; ++i) + if (fieldNames[i] !== name) + delete this[fieldNames[i]]; + }; +}; - RequirementProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.RequirementProto) - return object; - var message = new $root.keyhole.dbroot.RequirementProto(); - if (object.requiredVram !== undefined && object.requiredVram !== null) - message.requiredVram = String(object.requiredVram); - if (object.requiredClientVer !== undefined && object.requiredClientVer !== null) - message.requiredClientVer = String(object.requiredClientVer); - if (object.probability !== undefined && object.probability !== null) - message.probability = String(object.probability); - if (object.requiredUserAgent !== undefined && object.requiredUserAgent !== null) - message.requiredUserAgent = String(object.requiredUserAgent); - if (object.requiredClientCapabilities !== undefined && object.requiredClientCapabilities !== null) - message.requiredClientCapabilities = String(object.requiredClientCapabilities); - return message; - }; +/** + * Lazily resolves fully qualified type names against the specified root. + * @param {Root} root Root instanceof + * @param {Object.} lazyTypes Type names + * @returns {undefined} + */ +util.lazyResolve = function lazyResolve(root, lazyTypes) { + for (var i = 0; i < lazyTypes.length; ++i) { + for (var keys = Object.keys(lazyTypes[i]), j = 0; j < keys.length; ++j) { + var path = lazyTypes[i][keys[j]].split("."), + ptr = root; + while (path.length) + ptr = ptr[path.shift()]; + lazyTypes[i][keys[j]] = ptr; + } + } +}; - RequirementProto.from = RequirementProto.fromObject; +/** + * Default conversion options used for {@link Message#toJSON} implementations. Longs, enums and bytes are converted to strings by default. + * @type {ConversionOptions} + */ +util.toJSONOptions = { + longs: String, + enums: String, + bytes: String +}; - RequirementProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.requiredVram = ""; - object.requiredClientVer = ""; - object.probability = ""; - object.requiredUserAgent = ""; - object.requiredClientCapabilities = ""; - } - if (message.requiredVram !== undefined && message.requiredVram !== null && message.hasOwnProperty("requiredVram")) - object.requiredVram = message.requiredVram; - if (message.requiredClientVer !== undefined && message.requiredClientVer !== null && message.hasOwnProperty("requiredClientVer")) - object.requiredClientVer = message.requiredClientVer; - if (message.probability !== undefined && message.probability !== null && message.hasOwnProperty("probability")) - object.probability = message.probability; - if (message.requiredUserAgent !== undefined && message.requiredUserAgent !== null && message.hasOwnProperty("requiredUserAgent")) - object.requiredUserAgent = message.requiredUserAgent; - if (message.requiredClientCapabilities !== undefined && message.requiredClientCapabilities !== null && message.hasOwnProperty("requiredClientCapabilities")) - object.requiredClientCapabilities = message.requiredClientCapabilities; - return object; - }; +util._configure = function() { + var Buffer = util.Buffer; + /* istanbul ignore if */ + if (!Buffer) { + util._Buffer_from = util._Buffer_allocUnsafe = null; + return; + } + // because node 4.x buffers are incompatible & immutable + // see: https://github.com/dcodeIO/protobuf.js/pull/665 + util._Buffer_from = Buffer.from !== Uint8Array.from && Buffer.from || + /* istanbul ignore next */ + function Buffer_from(value, encoding) { + return new Buffer(value, encoding); + }; + util._Buffer_allocUnsafe = Buffer.allocUnsafe || + /* istanbul ignore next */ + function Buffer_allocUnsafe(size) { + return new Buffer(size); + }; +}; - RequirementProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; +},{"1":1,"12":12,"2":2,"3":3,"4":4,"5":5,"6":6}],14:[function(require,module,exports){ +"use strict"; +module.exports = Writer; - RequirementProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; +var util = require(13); - return RequirementProto; - })(); +var BufferWriter; // cyclic - dbroot.LookAtProto = (function() { +var LongBits = util.LongBits, + base64 = util.base64, + utf8 = util.utf8; - function LookAtProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } +/** + * Constructs a new writer operation instance. + * @classdesc Scheduled writer operation. + * @constructor + * @param {function(*, Uint8Array, number)} fn Function to call + * @param {number} len Value byte length + * @param {*} val Value to write + * @ignore + */ +function Op(fn, len, val) { - LookAtProto.prototype.longitude = 0; - LookAtProto.prototype.latitude = 0; - LookAtProto.prototype.range = 0; - LookAtProto.prototype.tilt = 0; - LookAtProto.prototype.heading = 0; + /** + * Function to call. + * @type {function(Uint8Array, number, *)} + */ + this.fn = fn; - LookAtProto.decode = function decode(reader, length) { + /** + * Value byte length. + * @type {number} + */ + this.len = len; + + /** + * Next operation. + * @type {Writer.Op|undefined} + */ + this.next = undefined; + + /** + * Value to write. + * @type {*} + */ + this.val = val; // type varies +} + +/* istanbul ignore next */ +function noop() {} // eslint-disable-line no-empty-function + +/** + * Constructs a new writer state instance. + * @classdesc Copied writer state. + * @memberof Writer + * @constructor + * @param {Writer} writer Writer to copy state from + * @private + * @ignore + */ +function State(writer) { + + /** + * Current head. + * @type {Writer.Op} + */ + this.head = writer.head; + + /** + * Current tail. + * @type {Writer.Op} + */ + this.tail = writer.tail; + + /** + * Current buffer length. + * @type {number} + */ + this.len = writer.len; + + /** + * Next state. + * @type {?State} + */ + this.next = writer.states; +} + +/** + * Constructs a new writer instance. + * @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`. + * @constructor + */ +function Writer() { + + /** + * Current length. + * @type {number} + */ + this.len = 0; + + /** + * Operations head. + * @type {Object} + */ + this.head = new Op(noop, 0, 0); + + /** + * Operations tail + * @type {Object} + */ + this.tail = this.head; + + /** + * Linked forked states. + * @type {?Object} + */ + this.states = null; + + // When a value is written, the writer calculates its byte length and puts it into a linked + // list of operations to perform when finish() is called. This both allows us to allocate + // buffers of the exact required size and reduces the amount of work we have to do compared + // to first calculating over objects and then encoding over objects. In our case, the encoding + // part is just a linked list walk calling operations with already prepared values. +} + +/** + * Creates a new writer. + * @function + * @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer} + */ +Writer.create = util.Buffer + ? function create_buffer_setup() { + return (Writer.create = function create_buffer() { + return new BufferWriter(); + })(); + } + /* istanbul ignore next */ + : function create_array() { + return new Writer(); + }; + +/** + * Allocates a buffer of the specified size. + * @param {number} size Buffer size + * @returns {Uint8Array} Buffer + */ +Writer.alloc = function alloc(size) { + return new util.Array(size); +}; + +// Use Uint8Array buffer pool in the browser, just like node does with buffers +/* istanbul ignore else */ +if (util.Array !== Array) + Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray); + +/** + * Pushes a new operation to the queue. + * @param {function(Uint8Array, number, *)} fn Function to call + * @param {number} len Value byte length + * @param {number} val Value to write + * @returns {Writer} `this` + */ +Writer.prototype.push = function push(fn, len, val) { + this.tail = this.tail.next = new Op(fn, len, val); + this.len += len; + return this; +}; + +function writeByte(val, buf, pos) { + buf[pos] = val & 255; +} + +function writeVarint32(val, buf, pos) { + while (val > 127) { + buf[pos++] = val & 127 | 128; + val >>>= 7; + } + buf[pos] = val; +} + +/** + * Constructs a new varint writer operation instance. + * @classdesc Scheduled varint writer operation. + * @extends Op + * @constructor + * @param {number} len Value byte length + * @param {number} val Value to write + * @ignore + */ +function VarintOp(len, val) { + this.len = len; + this.next = undefined; + this.val = val; +} + +VarintOp.prototype = Object.create(Op.prototype); +VarintOp.prototype.fn = writeVarint32; + +/** + * Writes an unsigned 32 bit value as a varint. + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.uint32 = function write_uint32(value) { + // here, the call to this.push has been inlined and a varint specific Op subclass is used. + // uint32 is by far the most frequently used operation and benefits significantly from this. + this.len += (this.tail = this.tail.next = new VarintOp( + (value = value >>> 0) + < 128 ? 1 + : value < 16384 ? 2 + : value < 2097152 ? 3 + : value < 268435456 ? 4 + : 5, + value)).len; + return this; +}; + +/** + * Writes a signed 32 bit value as a varint. + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.int32 = function write_int32(value) { + return value < 0 + ? this.push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec + : this.uint32(value); +}; + +/** + * Writes a 32 bit value as a varint, zig-zag encoded. + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.sint32 = function write_sint32(value) { + return this.uint32((value << 1 ^ value >> 31) >>> 0); +}; + +function writeVarint64(val, buf, pos) { + while (val.hi) { + buf[pos++] = val.lo & 127 | 128; + val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; + val.hi >>>= 7; + } + while (val.lo > 127) { + buf[pos++] = val.lo & 127 | 128; + val.lo = val.lo >>> 7; + } + buf[pos++] = val.lo; +} + +/** + * Writes an unsigned 64 bit value as a varint. + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.uint64 = function write_uint64(value) { + var bits = LongBits.from(value); + return this.push(writeVarint64, bits.length(), bits); +}; + +/** + * Writes a signed 64 bit value as a varint. + * @function + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.int64 = Writer.prototype.uint64; + +/** + * Writes a signed 64 bit value as a varint, zig-zag encoded. + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.sint64 = function write_sint64(value) { + var bits = LongBits.from(value).zzEncode(); + return this.push(writeVarint64, bits.length(), bits); +}; + +/** + * Writes a boolish value as a varint. + * @param {boolean} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.bool = function write_bool(value) { + return this.push(writeByte, 1, value ? 1 : 0); +}; + +function writeFixed32(val, buf, pos) { + buf[pos++] = val & 255; + buf[pos++] = val >>> 8 & 255; + buf[pos++] = val >>> 16 & 255; + buf[pos ] = val >>> 24; +} + +/** + * Writes an unsigned 32 bit value as fixed 32 bits. + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.fixed32 = function write_fixed32(value) { + return this.push(writeFixed32, 4, value >>> 0); +}; + +/** + * Writes a signed 32 bit value as fixed 32 bits. + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.sfixed32 = Writer.prototype.fixed32; + +/** + * Writes an unsigned 64 bit value as fixed 64 bits. + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.fixed64 = function write_fixed64(value) { + var bits = LongBits.from(value); + return this.push(writeFixed32, 4, bits.lo).push(writeFixed32, 4, bits.hi); +}; + +/** + * Writes a signed 64 bit value as fixed 64 bits. + * @function + * @param {Long|number|string} value Value to write + * @returns {Writer} `this` + * @throws {TypeError} If `value` is a string and no long library is present. + */ +Writer.prototype.sfixed64 = Writer.prototype.fixed64; + +var writeFloat = typeof Float32Array !== "undefined" + ? (function() { + var f32 = new Float32Array(1), + f8b = new Uint8Array(f32.buffer); + f32[0] = -0; + return f8b[3] // already le? + ? function writeFloat_f32(val, buf, pos) { + f32[0] = val; + buf[pos++] = f8b[0]; + buf[pos++] = f8b[1]; + buf[pos++] = f8b[2]; + buf[pos ] = f8b[3]; + } + /* istanbul ignore next */ + : function writeFloat_f32_le(val, buf, pos) { + f32[0] = val; + buf[pos++] = f8b[3]; + buf[pos++] = f8b[2]; + buf[pos++] = f8b[1]; + buf[pos ] = f8b[0]; + }; + })() + /* istanbul ignore next */ + : function writeFloat_ieee754(value, buf, pos) { + var sign = value < 0 ? 1 : 0; + if (sign) + value = -value; + if (value === 0) + writeFixed32(1 / value > 0 ? /* positive */ 0 : /* negative 0 */ 2147483648, buf, pos); + else if (isNaN(value)) + writeFixed32(2147483647, buf, pos); + else if (value > 3.4028234663852886e+38) // +-Infinity + writeFixed32((sign << 31 | 2139095040) >>> 0, buf, pos); + else if (value < 1.1754943508222875e-38) // denormal + writeFixed32((sign << 31 | Math.round(value / 1.401298464324817e-45)) >>> 0, buf, pos); + else { + var exponent = Math.floor(Math.log(value) / Math.LN2), + mantissa = Math.round(value * Math.pow(2, -exponent) * 8388608) & 8388607; + writeFixed32((sign << 31 | exponent + 127 << 23 | mantissa) >>> 0, buf, pos); + } + }; + +/** + * Writes a float (32 bit). + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.float = function write_float(value) { + return this.push(writeFloat, 4, value); +}; + +var writeDouble = typeof Float64Array !== "undefined" + ? (function() { + var f64 = new Float64Array(1), + f8b = new Uint8Array(f64.buffer); + f64[0] = -0; + return f8b[7] // already le? + ? function writeDouble_f64(val, buf, pos) { + f64[0] = val; + buf[pos++] = f8b[0]; + buf[pos++] = f8b[1]; + buf[pos++] = f8b[2]; + buf[pos++] = f8b[3]; + buf[pos++] = f8b[4]; + buf[pos++] = f8b[5]; + buf[pos++] = f8b[6]; + buf[pos ] = f8b[7]; + } + /* istanbul ignore next */ + : function writeDouble_f64_le(val, buf, pos) { + f64[0] = val; + buf[pos++] = f8b[7]; + buf[pos++] = f8b[6]; + buf[pos++] = f8b[5]; + buf[pos++] = f8b[4]; + buf[pos++] = f8b[3]; + buf[pos++] = f8b[2]; + buf[pos++] = f8b[1]; + buf[pos ] = f8b[0]; + }; + })() + /* istanbul ignore next */ + : function writeDouble_ieee754(value, buf, pos) { + var sign = value < 0 ? 1 : 0; + if (sign) + value = -value; + if (value === 0) { + writeFixed32(0, buf, pos); + writeFixed32(1 / value > 0 ? /* positive */ 0 : /* negative 0 */ 2147483648, buf, pos + 4); + } else if (isNaN(value)) { + writeFixed32(4294967295, buf, pos); + writeFixed32(2147483647, buf, pos + 4); + } else if (value > 1.7976931348623157e+308) { // +-Infinity + writeFixed32(0, buf, pos); + writeFixed32((sign << 31 | 2146435072) >>> 0, buf, pos + 4); + } else { + var mantissa; + if (value < 2.2250738585072014e-308) { // denormal + mantissa = value / 5e-324; + writeFixed32(mantissa >>> 0, buf, pos); + writeFixed32((sign << 31 | mantissa / 4294967296) >>> 0, buf, pos + 4); + } else { + var exponent = Math.floor(Math.log(value) / Math.LN2); + if (exponent === 1024) + exponent = 1023; + mantissa = value * Math.pow(2, -exponent); + writeFixed32(mantissa * 4503599627370496 >>> 0, buf, pos); + writeFixed32((sign << 31 | exponent + 1023 << 20 | mantissa * 1048576 & 1048575) >>> 0, buf, pos + 4); + } + } + }; + +/** + * Writes a double (64 bit float). + * @function + * @param {number} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.double = function write_double(value) { + return this.push(writeDouble, 8, value); +}; + +var writeBytes = util.Array.prototype.set + ? function writeBytes_set(val, buf, pos) { + buf.set(val, pos); // also works for plain array values + } + /* istanbul ignore next */ + : function writeBytes_for(val, buf, pos) { + for (var i = 0; i < val.length; ++i) + buf[pos + i] = val[i]; + }; + +/** + * Writes a sequence of bytes. + * @param {Uint8Array|string} value Buffer or base64 encoded string to write + * @returns {Writer} `this` + */ +Writer.prototype.bytes = function write_bytes(value) { + var len = value.length >>> 0; + if (!len) + return this.push(writeByte, 1, 0); + if (util.isString(value)) { + var buf = Writer.alloc(len = base64.length(value)); + base64.decode(value, buf, 0); + value = buf; + } + return this.uint32(len).push(writeBytes, len, value); +}; + +/** + * Writes a string. + * @param {string} value Value to write + * @returns {Writer} `this` + */ +Writer.prototype.string = function write_string(value) { + var len = utf8.length(value); + return len + ? this.uint32(len).push(utf8.write, len, value) + : this.push(writeByte, 1, 0); +}; + +/** + * Forks this writer's state by pushing it to a stack. + * Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state. + * @returns {Writer} `this` + */ +Writer.prototype.fork = function fork() { + this.states = new State(this); + this.head = this.tail = new Op(noop, 0, 0); + this.len = 0; + return this; +}; + +/** + * Resets this instance to the last state. + * @returns {Writer} `this` + */ +Writer.prototype.reset = function reset() { + if (this.states) { + this.head = this.states.head; + this.tail = this.states.tail; + this.len = this.states.len; + this.states = this.states.next; + } else { + this.head = this.tail = new Op(noop, 0, 0); + this.len = 0; + } + return this; +}; + +/** + * Resets to the last state and appends the fork state's current write length as a varint followed by its operations. + * @returns {Writer} `this` + */ +Writer.prototype.ldelim = function ldelim() { + var head = this.head, + tail = this.tail, + len = this.len; + this.reset().uint32(len); + if (len) { + this.tail.next = head.next; // skip noop + this.tail = tail; + this.len += len; + } + return this; +}; + +/** + * Finishes the write operation. + * @returns {Uint8Array} Finished buffer + */ +Writer.prototype.finish = function finish() { + var head = this.head.next, // skip noop + buf = this.constructor.alloc(this.len), + pos = 0; + while (head) { + head.fn(head.val, buf, pos); + pos += head.len; + head = head.next; + } + // this.head = this.tail = null; + return buf; +}; + +Writer._configure = function(BufferWriter_) { + BufferWriter = BufferWriter_; +}; + +},{"13":13}],15:[function(require,module,exports){ +"use strict"; +module.exports = BufferWriter; + +// extends Writer +var Writer = require(14); +(BufferWriter.prototype = Object.create(Writer.prototype)).constructor = BufferWriter; + +var util = require(13); + +var Buffer = util.Buffer; + +/** + * Constructs a new buffer writer instance. + * @classdesc Wire format writer using node buffers. + * @extends Writer + * @constructor + */ +function BufferWriter() { + Writer.call(this); +} + +/** + * Allocates a buffer of the specified size. + * @param {number} size Buffer size + * @returns {Buffer} Buffer + */ +BufferWriter.alloc = function alloc_buffer(size) { + return (BufferWriter.alloc = util._Buffer_allocUnsafe)(size); +}; + +var writeBytesBuffer = Buffer && Buffer.prototype instanceof Uint8Array && Buffer.prototype.set.name === "set" + ? function writeBytesBuffer_set(val, buf, pos) { + buf.set(val, pos); // faster than copy (requires node >= 4 where Buffers extend Uint8Array and set is properly inherited) + // also works for plain array values + } + /* istanbul ignore next */ + : function writeBytesBuffer_copy(val, buf, pos) { + if (val.copy) // Buffer values + val.copy(buf, pos, 0, val.length); + else for (var i = 0; i < val.length;) // plain array values + buf[pos++] = val[i++]; + }; + +/** + * @override + */ +BufferWriter.prototype.bytes = function write_bytes_buffer(value) { + if (util.isString(value)) + value = util._Buffer_from(value, "base64"); + var len = value.length >>> 0; + this.uint32(len); + if (len) + this.push(writeBytesBuffer, len, value); + return this; +}; + +function writeStringBuffer(val, buf, pos) { + if (val.length < 40) // plain js is faster for short strings (probably due to redundant assertions) + util.utf8.write(val, buf, pos); + else + buf.utf8Write(val, pos); +} + +/** + * @override + */ +BufferWriter.prototype.string = function write_string_buffer(value) { + var len = Buffer.byteLength(value); + this.uint32(len); + if (len) + this.push(writeStringBuffer, len, value); + return this; +}; + + +/** + * Finishes the write operation. + * @name BufferWriter#finish + * @function + * @returns {Buffer} Finished buffer + */ + +},{"13":13,"14":14}]},{},[7]) + +})(typeof window==="object"&&window||typeof self==="object"&&self||this); +//# sourceMappingURL=protobuf.js.map +; +define('ThirdParty/google-earth-dbroot-parser',[ + './protobuf-minimal' +], function( + $protobuf) { + /* jshint curly: false, sub: true, newcap: false, shadow: true, unused: false*/ + 'use strict'; + + // + // Creates a parser for a dbroot protocol buffer + // Below code is generated using protobufjs with the following command + // + // ./pbjs --no-encode --no-create --no-comments --no-delimited -w amd -t static dbroot_v2.proto + // + // .proto file can be found here: https://github.com/google/earthenterprise/blob/master/earth_enterprise/src/keyhole/proto/dbroot/dbroot_v2.proto + + var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + + var $lazyTypes = []; + + var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + + $root.keyhole = (function() { + + var keyhole = {}; + + keyhole.dbroot = (function() { + + var dbroot = {}; + + dbroot.StringEntryProto = (function() { + + function StringEntryProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + StringEntryProto.prototype.stringId = 0; + StringEntryProto.prototype.stringValue = ""; + + StringEntryProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.LookAtProto(); + message = new $root.keyhole.dbroot.StringEntryProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.longitude = reader.float(); + message.stringId = reader.fixed32(); break; case 2: - message.latitude = reader.float(); + message.stringValue = reader.string(); break; - case 3: - message.range = reader.float(); + default: + reader.skipType(tag & 7); break; - case 4: - message.tilt = reader.float(); + } + } + return message; + }; + + StringEntryProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isInteger(message.stringId)) + return "stringId: integer expected"; + if (!$util.isString(message.stringValue)) + return "stringValue: string expected"; + return null; + }; + + StringEntryProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.StringEntryProto) + return object; + var message = new $root.keyhole.dbroot.StringEntryProto(); + if (object.stringId !== undefined && object.stringId !== null) + message.stringId = object.stringId >>> 0; + if (object.stringValue !== undefined && object.stringValue !== null) + message.stringValue = String(object.stringValue); + return message; + }; + + StringEntryProto.from = StringEntryProto.fromObject; + + StringEntryProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.stringId = 0; + object.stringValue = ""; + } + if (message.stringId !== undefined && message.stringId !== null && message.hasOwnProperty("stringId")) + object.stringId = message.stringId; + if (message.stringValue !== undefined && message.stringValue !== null && message.hasOwnProperty("stringValue")) + object.stringValue = message.stringValue; + return object; + }; + + StringEntryProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + StringEntryProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return StringEntryProto; + })(); + + dbroot.StringIdOrValueProto = (function() { + + function StringIdOrValueProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + StringIdOrValueProto.prototype.stringId = 0; + StringIdOrValueProto.prototype.value = ""; + + StringIdOrValueProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.StringIdOrValueProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.stringId = reader.fixed32(); break; - case 5: - message.heading = reader.float(); + case 2: + message.value = reader.string(); break; default: reader.skipType(tag & 7); @@ -50740,190 +52648,93 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - LookAtProto.verify = function verify(message) { + StringIdOrValueProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (typeof message.longitude !== "number") - return "longitude: number expected"; - if (typeof message.latitude !== "number") - return "latitude: number expected"; - if (message.range !== undefined) - if (typeof message.range !== "number") - return "range: number expected"; - if (message.tilt !== undefined) - if (typeof message.tilt !== "number") - return "tilt: number expected"; - if (message.heading !== undefined) - if (typeof message.heading !== "number") - return "heading: number expected"; + if (message.stringId !== undefined) + if (!$util.isInteger(message.stringId)) + return "stringId: integer expected"; + if (message.value !== undefined) + if (!$util.isString(message.value)) + return "value: string expected"; return null; }; - LookAtProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.LookAtProto) + StringIdOrValueProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.StringIdOrValueProto) return object; - var message = new $root.keyhole.dbroot.LookAtProto(); - if (object.longitude !== undefined && object.longitude !== null) - message.longitude = Number(object.longitude); - if (object.latitude !== undefined && object.latitude !== null) - message.latitude = Number(object.latitude); - if (object.range !== undefined && object.range !== null) - message.range = Number(object.range); - if (object.tilt !== undefined && object.tilt !== null) - message.tilt = Number(object.tilt); - if (object.heading !== undefined && object.heading !== null) - message.heading = Number(object.heading); + var message = new $root.keyhole.dbroot.StringIdOrValueProto(); + if (object.stringId !== undefined && object.stringId !== null) + message.stringId = object.stringId >>> 0; + if (object.value !== undefined && object.value !== null) + message.value = String(object.value); return message; }; - LookAtProto.from = LookAtProto.fromObject; + StringIdOrValueProto.from = StringIdOrValueProto.fromObject; - LookAtProto.toObject = function toObject(message, options) { + StringIdOrValueProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { - object.longitude = 0; - object.latitude = 0; - object.range = 0; - object.tilt = 0; - object.heading = 0; + object.stringId = 0; + object.value = ""; } - if (message.longitude !== undefined && message.longitude !== null && message.hasOwnProperty("longitude")) - object.longitude = message.longitude; - if (message.latitude !== undefined && message.latitude !== null && message.hasOwnProperty("latitude")) - object.latitude = message.latitude; - if (message.range !== undefined && message.range !== null && message.hasOwnProperty("range")) - object.range = message.range; - if (message.tilt !== undefined && message.tilt !== null && message.hasOwnProperty("tilt")) - object.tilt = message.tilt; - if (message.heading !== undefined && message.heading !== null && message.hasOwnProperty("heading")) - object.heading = message.heading; + if (message.stringId !== undefined && message.stringId !== null && message.hasOwnProperty("stringId")) + object.stringId = message.stringId; + if (message.value !== undefined && message.value !== null && message.hasOwnProperty("value")) + object.value = message.value; return object; }; - LookAtProto.prototype.toObject = function toObject(options) { + StringIdOrValueProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - LookAtProto.prototype.toJSON = function toJSON() { + StringIdOrValueProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return LookAtProto; + return StringIdOrValueProto; })(); - dbroot.NestedFeatureProto = (function() { + dbroot.PlanetModelProto = (function() { - function NestedFeatureProto(properties) { + function PlanetModelProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - NestedFeatureProto.prototype.featureType = 1; - NestedFeatureProto.prototype.kmlUrl = null; - NestedFeatureProto.prototype.databaseUrl = ""; - NestedFeatureProto.prototype.layer = null; - NestedFeatureProto.prototype.folder = null; - NestedFeatureProto.prototype.requirement = null; - NestedFeatureProto.prototype.channelId = 0; - NestedFeatureProto.prototype.displayName = null; - NestedFeatureProto.prototype.isVisible = true; - NestedFeatureProto.prototype.isEnabled = true; - NestedFeatureProto.prototype.isChecked = false; - NestedFeatureProto.prototype.layerMenuIconPath = "icons/773_l.png"; - NestedFeatureProto.prototype.description = null; - NestedFeatureProto.prototype.lookAt = null; - NestedFeatureProto.prototype.assetUuid = ""; - NestedFeatureProto.prototype.isSaveLocked = true; - NestedFeatureProto.prototype.children = $util.emptyArray; - NestedFeatureProto.prototype.clientConfigScriptName = ""; - NestedFeatureProto.prototype.dioramaDataChannelBase = -1; - NestedFeatureProto.prototype.replicaDataChannelBase = -1; - - var $types = { - 0 : "keyhole.dbroot.NestedFeatureProto.FeatureType", - 1 : "keyhole.dbroot.StringIdOrValueProto", - 3 : "keyhole.dbroot.LayerProto", - 4 : "keyhole.dbroot.FolderProto", - 5 : "keyhole.dbroot.RequirementProto", - 7 : "keyhole.dbroot.StringIdOrValueProto", - 12 : "keyhole.dbroot.StringIdOrValueProto", - 13 : "keyhole.dbroot.LookAtProto", - 16 : "keyhole.dbroot.NestedFeatureProto" - }; - $lazyTypes.push($types); + PlanetModelProto.prototype.radius = 6378.137; + PlanetModelProto.prototype.flattening = 0.00335281066474748; + PlanetModelProto.prototype.elevationBias = 0; + PlanetModelProto.prototype.negativeAltitudeExponentBias = 0; + PlanetModelProto.prototype.compressedNegativeAltitudeThreshold = 0; - NestedFeatureProto.decode = function decode(reader, length) { + PlanetModelProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.NestedFeatureProto(); + message = new $root.keyhole.dbroot.PlanetModelProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.featureType = reader.uint32(); + message.radius = reader.double(); break; case 2: - message.kmlUrl = $types[1].decode(reader, reader.uint32()); - break; - case 21: - message.databaseUrl = reader.string(); - break; - case 3: - message.layer = $types[3].decode(reader, reader.uint32()); + message.flattening = reader.double(); break; case 4: - message.folder = $types[4].decode(reader, reader.uint32()); + message.elevationBias = reader.double(); break; case 5: - message.requirement = $types[5].decode(reader, reader.uint32()); + message.negativeAltitudeExponentBias = reader.int32(); break; case 6: - message.channelId = reader.int32(); - break; - case 7: - message.displayName = $types[7].decode(reader, reader.uint32()); - break; - case 8: - message.isVisible = reader.bool(); - break; - case 9: - message.isEnabled = reader.bool(); - break; - case 10: - message.isChecked = reader.bool(); - break; - case 11: - message.layerMenuIconPath = reader.string(); - break; - case 12: - message.description = $types[12].decode(reader, reader.uint32()); - break; - case 13: - message.lookAt = $types[13].decode(reader, reader.uint32()); - break; - case 15: - message.assetUuid = reader.string(); - break; - case 16: - message.isSaveLocked = reader.bool(); - break; - case 17: - if (!(message.children && message.children.length)) - message.children = []; - message.children.push($types[16].decode(reader, reader.uint32())); - break; - case 18: - message.clientConfigScriptName = reader.string(); - break; - case 19: - message.dioramaDataChannelBase = reader.int32(); - break; - case 20: - message.replicaDataChannelBase = reader.int32(); + message.compressedNegativeAltitudeThreshold = reader.double(); break; default: reader.skipType(tag & 7); @@ -50933,325 +52744,223 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - NestedFeatureProto.verify = function verify(message) { + PlanetModelProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.featureType !== undefined) - switch (message.featureType) { - default: - return "featureType: enum value expected"; - case 1: - case 2: - case 3: - case 4: - break; - } - if (message.kmlUrl !== undefined && message.kmlUrl !== null) { - var error = $types[1].verify(message.kmlUrl); - if (error) - return "kmlUrl." + error; - } - if (message.databaseUrl !== undefined) - if (!$util.isString(message.databaseUrl)) - return "databaseUrl: string expected"; - if (message.layer !== undefined && message.layer !== null) { - var error = $types[3].verify(message.layer); - if (error) - return "layer." + error; - } - if (message.folder !== undefined && message.folder !== null) { - var error = $types[4].verify(message.folder); - if (error) - return "folder." + error; - } - if (message.requirement !== undefined && message.requirement !== null) { - var error = $types[5].verify(message.requirement); - if (error) - return "requirement." + error; + if (message.radius !== undefined) + if (typeof message.radius !== "number") + return "radius: number expected"; + if (message.flattening !== undefined) + if (typeof message.flattening !== "number") + return "flattening: number expected"; + if (message.elevationBias !== undefined) + if (typeof message.elevationBias !== "number") + return "elevationBias: number expected"; + if (message.negativeAltitudeExponentBias !== undefined) + if (!$util.isInteger(message.negativeAltitudeExponentBias)) + return "negativeAltitudeExponentBias: integer expected"; + if (message.compressedNegativeAltitudeThreshold !== undefined) + if (typeof message.compressedNegativeAltitudeThreshold !== "number") + return "compressedNegativeAltitudeThreshold: number expected"; + return null; + }; + + PlanetModelProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.PlanetModelProto) + return object; + var message = new $root.keyhole.dbroot.PlanetModelProto(); + if (object.radius !== undefined && object.radius !== null) + message.radius = Number(object.radius); + if (object.flattening !== undefined && object.flattening !== null) + message.flattening = Number(object.flattening); + if (object.elevationBias !== undefined && object.elevationBias !== null) + message.elevationBias = Number(object.elevationBias); + if (object.negativeAltitudeExponentBias !== undefined && object.negativeAltitudeExponentBias !== null) + message.negativeAltitudeExponentBias = object.negativeAltitudeExponentBias | 0; + if (object.compressedNegativeAltitudeThreshold !== undefined && object.compressedNegativeAltitudeThreshold !== null) + message.compressedNegativeAltitudeThreshold = Number(object.compressedNegativeAltitudeThreshold); + return message; + }; + + PlanetModelProto.from = PlanetModelProto.fromObject; + + PlanetModelProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.radius = 6378.137; + object.flattening = 0.00335281066474748; + object.elevationBias = 0; + object.negativeAltitudeExponentBias = 0; + object.compressedNegativeAltitudeThreshold = 0; } - if (!$util.isInteger(message.channelId)) - return "channelId: integer expected"; - if (message.displayName !== undefined && message.displayName !== null) { - var error = $types[7].verify(message.displayName); - if (error) - return "displayName." + error; - } - if (message.isVisible !== undefined) - if (typeof message.isVisible !== "boolean") - return "isVisible: boolean expected"; - if (message.isEnabled !== undefined) - if (typeof message.isEnabled !== "boolean") - return "isEnabled: boolean expected"; - if (message.isChecked !== undefined) - if (typeof message.isChecked !== "boolean") - return "isChecked: boolean expected"; - if (message.layerMenuIconPath !== undefined) - if (!$util.isString(message.layerMenuIconPath)) - return "layerMenuIconPath: string expected"; - if (message.description !== undefined && message.description !== null) { - var error = $types[12].verify(message.description); - if (error) - return "description." + error; + if (message.radius !== undefined && message.radius !== null && message.hasOwnProperty("radius")) + object.radius = message.radius; + if (message.flattening !== undefined && message.flattening !== null && message.hasOwnProperty("flattening")) + object.flattening = message.flattening; + if (message.elevationBias !== undefined && message.elevationBias !== null && message.hasOwnProperty("elevationBias")) + object.elevationBias = message.elevationBias; + if (message.negativeAltitudeExponentBias !== undefined && message.negativeAltitudeExponentBias !== null && message.hasOwnProperty("negativeAltitudeExponentBias")) + object.negativeAltitudeExponentBias = message.negativeAltitudeExponentBias; + if (message.compressedNegativeAltitudeThreshold !== undefined && message.compressedNegativeAltitudeThreshold !== null && message.hasOwnProperty("compressedNegativeAltitudeThreshold")) + object.compressedNegativeAltitudeThreshold = message.compressedNegativeAltitudeThreshold; + return object; + }; + + PlanetModelProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + PlanetModelProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return PlanetModelProto; + })(); + + dbroot.ProviderInfoProto = (function() { + + function ProviderInfoProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + ProviderInfoProto.prototype.providerId = 0; + ProviderInfoProto.prototype.copyrightString = null; + ProviderInfoProto.prototype.verticalPixelOffset = -1; + + var $types = { + 1 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); + + ProviderInfoProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.ProviderInfoProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.providerId = reader.int32(); + break; + case 2: + message.copyrightString = $types[1].decode(reader, reader.uint32()); + break; + case 3: + message.verticalPixelOffset = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } } - if (message.lookAt !== undefined && message.lookAt !== null) { - var error = $types[13].verify(message.lookAt); + return message; + }; + + ProviderInfoProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isInteger(message.providerId)) + return "providerId: integer expected"; + if (message.copyrightString !== undefined && message.copyrightString !== null) { + var error = $types[1].verify(message.copyrightString); if (error) - return "lookAt." + error; - } - if (message.assetUuid !== undefined) - if (!$util.isString(message.assetUuid)) - return "assetUuid: string expected"; - if (message.isSaveLocked !== undefined) - if (typeof message.isSaveLocked !== "boolean") - return "isSaveLocked: boolean expected"; - if (message.children !== undefined) { - if (!Array.isArray(message.children)) - return "children: array expected"; - for (var i = 0; i < message.children.length; ++i) { - var error = $types[16].verify(message.children[i]); - if (error) - return "children." + error; - } + return "copyrightString." + error; } - if (message.clientConfigScriptName !== undefined) - if (!$util.isString(message.clientConfigScriptName)) - return "clientConfigScriptName: string expected"; - if (message.dioramaDataChannelBase !== undefined) - if (!$util.isInteger(message.dioramaDataChannelBase)) - return "dioramaDataChannelBase: integer expected"; - if (message.replicaDataChannelBase !== undefined) - if (!$util.isInteger(message.replicaDataChannelBase)) - return "replicaDataChannelBase: integer expected"; + if (message.verticalPixelOffset !== undefined) + if (!$util.isInteger(message.verticalPixelOffset)) + return "verticalPixelOffset: integer expected"; return null; }; - NestedFeatureProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.NestedFeatureProto) + ProviderInfoProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ProviderInfoProto) return object; - var message = new $root.keyhole.dbroot.NestedFeatureProto(); - switch (object.featureType) { - case "TYPE_POINT_Z": - case 1: - message.featureType = 1; - break; - case "TYPE_POLYGON_Z": - case 2: - message.featureType = 2; - break; - case "TYPE_LINE_Z": - case 3: - message.featureType = 3; - break; - case "TYPE_TERRAIN": - case 4: - message.featureType = 4; - break; - } - if (object.kmlUrl !== undefined && object.kmlUrl !== null) { - if (typeof object.kmlUrl !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.kmlUrl: object expected"); - message.kmlUrl = $types[1].fromObject(object.kmlUrl); - } - if (object.databaseUrl !== undefined && object.databaseUrl !== null) - message.databaseUrl = String(object.databaseUrl); - if (object.layer !== undefined && object.layer !== null) { - if (typeof object.layer !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.layer: object expected"); - message.layer = $types[3].fromObject(object.layer); - } - if (object.folder !== undefined && object.folder !== null) { - if (typeof object.folder !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.folder: object expected"); - message.folder = $types[4].fromObject(object.folder); - } - if (object.requirement !== undefined && object.requirement !== null) { - if (typeof object.requirement !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.requirement: object expected"); - message.requirement = $types[5].fromObject(object.requirement); - } - if (object.channelId !== undefined && object.channelId !== null) - message.channelId = object.channelId | 0; - if (object.displayName !== undefined && object.displayName !== null) { - if (typeof object.displayName !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.displayName: object expected"); - message.displayName = $types[7].fromObject(object.displayName); - } - if (object.isVisible !== undefined && object.isVisible !== null) - message.isVisible = Boolean(object.isVisible); - if (object.isEnabled !== undefined && object.isEnabled !== null) - message.isEnabled = Boolean(object.isEnabled); - if (object.isChecked !== undefined && object.isChecked !== null) - message.isChecked = Boolean(object.isChecked); - if (object.layerMenuIconPath !== undefined && object.layerMenuIconPath !== null) - message.layerMenuIconPath = String(object.layerMenuIconPath); - if (object.description !== undefined && object.description !== null) { - if (typeof object.description !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.description: object expected"); - message.description = $types[12].fromObject(object.description); - } - if (object.lookAt !== undefined && object.lookAt !== null) { - if (typeof object.lookAt !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.lookAt: object expected"); - message.lookAt = $types[13].fromObject(object.lookAt); - } - if (object.assetUuid !== undefined && object.assetUuid !== null) - message.assetUuid = String(object.assetUuid); - if (object.isSaveLocked !== undefined && object.isSaveLocked !== null) - message.isSaveLocked = Boolean(object.isSaveLocked); - if (object.children) { - if (!Array.isArray(object.children)) - throw TypeError(".keyhole.dbroot.NestedFeatureProto.children: array expected"); - message.children = []; - for (var i = 0; i < object.children.length; ++i) { - if (typeof object.children[i] !== "object") - throw TypeError(".keyhole.dbroot.NestedFeatureProto.children: object expected"); - message.children[i] = $types[16].fromObject(object.children[i]); - } + var message = new $root.keyhole.dbroot.ProviderInfoProto(); + if (object.providerId !== undefined && object.providerId !== null) + message.providerId = object.providerId | 0; + if (object.copyrightString !== undefined && object.copyrightString !== null) { + if (typeof object.copyrightString !== "object") + throw TypeError(".keyhole.dbroot.ProviderInfoProto.copyrightString: object expected"); + message.copyrightString = $types[1].fromObject(object.copyrightString); } - if (object.clientConfigScriptName !== undefined && object.clientConfigScriptName !== null) - message.clientConfigScriptName = String(object.clientConfigScriptName); - if (object.dioramaDataChannelBase !== undefined && object.dioramaDataChannelBase !== null) - message.dioramaDataChannelBase = object.dioramaDataChannelBase | 0; - if (object.replicaDataChannelBase !== undefined && object.replicaDataChannelBase !== null) - message.replicaDataChannelBase = object.replicaDataChannelBase | 0; + if (object.verticalPixelOffset !== undefined && object.verticalPixelOffset !== null) + message.verticalPixelOffset = object.verticalPixelOffset | 0; return message; }; - NestedFeatureProto.from = NestedFeatureProto.fromObject; + ProviderInfoProto.from = ProviderInfoProto.fromObject; - NestedFeatureProto.toObject = function toObject(message, options) { + ProviderInfoProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) - object.children = []; if (options.defaults) { - object.featureType = options.enums === String ? "TYPE_POINT_Z" : 1; - object.kmlUrl = null; - object.databaseUrl = ""; - object.layer = null; - object.folder = null; - object.requirement = null; - object.channelId = 0; - object.displayName = null; - object.isVisible = true; - object.isEnabled = true; - object.isChecked = false; - object.layerMenuIconPath = "icons/773_l.png"; - object.description = null; - object.lookAt = null; - object.assetUuid = ""; - object.isSaveLocked = true; - object.clientConfigScriptName = ""; - object.dioramaDataChannelBase = -1; - object.replicaDataChannelBase = -1; - } - if (message.featureType !== undefined && message.featureType !== null && message.hasOwnProperty("featureType")) - object.featureType = options.enums === String ? $types[0][message.featureType] : message.featureType; - if (message.kmlUrl !== undefined && message.kmlUrl !== null && message.hasOwnProperty("kmlUrl")) - object.kmlUrl = $types[1].toObject(message.kmlUrl, options); - if (message.databaseUrl !== undefined && message.databaseUrl !== null && message.hasOwnProperty("databaseUrl")) - object.databaseUrl = message.databaseUrl; - if (message.layer !== undefined && message.layer !== null && message.hasOwnProperty("layer")) - object.layer = $types[3].toObject(message.layer, options); - if (message.folder !== undefined && message.folder !== null && message.hasOwnProperty("folder")) - object.folder = $types[4].toObject(message.folder, options); - if (message.requirement !== undefined && message.requirement !== null && message.hasOwnProperty("requirement")) - object.requirement = $types[5].toObject(message.requirement, options); - if (message.channelId !== undefined && message.channelId !== null && message.hasOwnProperty("channelId")) - object.channelId = message.channelId; - if (message.displayName !== undefined && message.displayName !== null && message.hasOwnProperty("displayName")) - object.displayName = $types[7].toObject(message.displayName, options); - if (message.isVisible !== undefined && message.isVisible !== null && message.hasOwnProperty("isVisible")) - object.isVisible = message.isVisible; - if (message.isEnabled !== undefined && message.isEnabled !== null && message.hasOwnProperty("isEnabled")) - object.isEnabled = message.isEnabled; - if (message.isChecked !== undefined && message.isChecked !== null && message.hasOwnProperty("isChecked")) - object.isChecked = message.isChecked; - if (message.layerMenuIconPath !== undefined && message.layerMenuIconPath !== null && message.hasOwnProperty("layerMenuIconPath")) - object.layerMenuIconPath = message.layerMenuIconPath; - if (message.description !== undefined && message.description !== null && message.hasOwnProperty("description")) - object.description = $types[12].toObject(message.description, options); - if (message.lookAt !== undefined && message.lookAt !== null && message.hasOwnProperty("lookAt")) - object.lookAt = $types[13].toObject(message.lookAt, options); - if (message.assetUuid !== undefined && message.assetUuid !== null && message.hasOwnProperty("assetUuid")) - object.assetUuid = message.assetUuid; - if (message.isSaveLocked !== undefined && message.isSaveLocked !== null && message.hasOwnProperty("isSaveLocked")) - object.isSaveLocked = message.isSaveLocked; - if (message.children !== undefined && message.children !== null && message.hasOwnProperty("children")) { - object.children = []; - for (var j = 0; j < message.children.length; ++j) - object.children[j] = $types[16].toObject(message.children[j], options); + object.providerId = 0; + object.copyrightString = null; + object.verticalPixelOffset = -1; } - if (message.clientConfigScriptName !== undefined && message.clientConfigScriptName !== null && message.hasOwnProperty("clientConfigScriptName")) - object.clientConfigScriptName = message.clientConfigScriptName; - if (message.dioramaDataChannelBase !== undefined && message.dioramaDataChannelBase !== null && message.hasOwnProperty("dioramaDataChannelBase")) - object.dioramaDataChannelBase = message.dioramaDataChannelBase; - if (message.replicaDataChannelBase !== undefined && message.replicaDataChannelBase !== null && message.hasOwnProperty("replicaDataChannelBase")) - object.replicaDataChannelBase = message.replicaDataChannelBase; + if (message.providerId !== undefined && message.providerId !== null && message.hasOwnProperty("providerId")) + object.providerId = message.providerId; + if (message.copyrightString !== undefined && message.copyrightString !== null && message.hasOwnProperty("copyrightString")) + object.copyrightString = $types[1].toObject(message.copyrightString, options); + if (message.verticalPixelOffset !== undefined && message.verticalPixelOffset !== null && message.hasOwnProperty("verticalPixelOffset")) + object.verticalPixelOffset = message.verticalPixelOffset; return object; }; - NestedFeatureProto.prototype.toObject = function toObject(options) { + ProviderInfoProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - NestedFeatureProto.prototype.toJSON = function toJSON() { + ProviderInfoProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - NestedFeatureProto.FeatureType = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["TYPE_POINT_Z"] = 1; - values["TYPE_POLYGON_Z"] = 2; - values["TYPE_LINE_Z"] = 3; - values["TYPE_TERRAIN"] = 4; - return values; - })(); - - return NestedFeatureProto; + return ProviderInfoProto; })(); - dbroot.MfeDomainFeaturesProto = (function() { + dbroot.PopUpProto = (function() { - function MfeDomainFeaturesProto(properties) { + function PopUpProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - MfeDomainFeaturesProto.prototype.countryCode = ""; - MfeDomainFeaturesProto.prototype.domainName = ""; - MfeDomainFeaturesProto.prototype.supportedFeatures = $util.emptyArray; + PopUpProto.prototype.isBalloonStyle = false; + PopUpProto.prototype.text = null; + PopUpProto.prototype.backgroundColorAbgr = 4294967295; + PopUpProto.prototype.textColorAbgr = 4278190080; var $types = { - 2 : "keyhole.dbroot.MfeDomainFeaturesProto.SupportedFeature" + 1 : "keyhole.dbroot.StringIdOrValueProto" }; $lazyTypes.push($types); - MfeDomainFeaturesProto.decode = function decode(reader, length) { + PopUpProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.MfeDomainFeaturesProto(); + message = new $root.keyhole.dbroot.PopUpProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.countryCode = reader.string(); + message.isBalloonStyle = reader.bool(); break; case 2: - message.domainName = reader.string(); + message.text = $types[1].decode(reader, reader.uint32()); break; case 3: - if (!(message.supportedFeatures && message.supportedFeatures.length)) - message.supportedFeatures = []; - if ((tag & 7) === 2) { - var end2 = reader.uint32() + reader.pos; - while (reader.pos < end2) - message.supportedFeatures.push(reader.uint32()); - } else - message.supportedFeatures.push(reader.uint32()); + message.backgroundColorAbgr = reader.fixed32(); + break; + case 4: + message.textColorAbgr = reader.fixed32(); break; default: reader.skipType(tag & 7); @@ -51261,191 +52970,167 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - MfeDomainFeaturesProto.verify = function verify(message) { + PopUpProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (!$util.isString(message.countryCode)) - return "countryCode: string expected"; - if (!$util.isString(message.domainName)) - return "domainName: string expected"; - if (message.supportedFeatures !== undefined) { - if (!Array.isArray(message.supportedFeatures)) - return "supportedFeatures: array expected"; - for (var i = 0; i < message.supportedFeatures.length; ++i) - switch (message.supportedFeatures[i]) { - default: - return "supportedFeatures: enum value[] expected"; - case 0: - case 1: - case 2: - break; - } + if (message.isBalloonStyle !== undefined) + if (typeof message.isBalloonStyle !== "boolean") + return "isBalloonStyle: boolean expected"; + if (message.text !== undefined && message.text !== null) { + var error = $types[1].verify(message.text); + if (error) + return "text." + error; } + if (message.backgroundColorAbgr !== undefined) + if (!$util.isInteger(message.backgroundColorAbgr)) + return "backgroundColorAbgr: integer expected"; + if (message.textColorAbgr !== undefined) + if (!$util.isInteger(message.textColorAbgr)) + return "textColorAbgr: integer expected"; return null; }; - MfeDomainFeaturesProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.MfeDomainFeaturesProto) + PopUpProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.PopUpProto) return object; - var message = new $root.keyhole.dbroot.MfeDomainFeaturesProto(); - if (object.countryCode !== undefined && object.countryCode !== null) - message.countryCode = String(object.countryCode); - if (object.domainName !== undefined && object.domainName !== null) - message.domainName = String(object.domainName); - if (object.supportedFeatures) { - if (!Array.isArray(object.supportedFeatures)) - throw TypeError(".keyhole.dbroot.MfeDomainFeaturesProto.supportedFeatures: array expected"); - message.supportedFeatures = []; - for (var i = 0; i < object.supportedFeatures.length; ++i) - switch (object.supportedFeatures[i]) { - default: - case "GEOCODING": - case 0: - message.supportedFeatures[i] = 0; - break; - case "LOCAL_SEARCH": - case 1: - message.supportedFeatures[i] = 1; - break; - case "DRIVING_DIRECTIONS": - case 2: - message.supportedFeatures[i] = 2; - break; - } + var message = new $root.keyhole.dbroot.PopUpProto(); + if (object.isBalloonStyle !== undefined && object.isBalloonStyle !== null) + message.isBalloonStyle = Boolean(object.isBalloonStyle); + if (object.text !== undefined && object.text !== null) { + if (typeof object.text !== "object") + throw TypeError(".keyhole.dbroot.PopUpProto.text: object expected"); + message.text = $types[1].fromObject(object.text); } + if (object.backgroundColorAbgr !== undefined && object.backgroundColorAbgr !== null) + message.backgroundColorAbgr = object.backgroundColorAbgr >>> 0; + if (object.textColorAbgr !== undefined && object.textColorAbgr !== null) + message.textColorAbgr = object.textColorAbgr >>> 0; return message; }; - MfeDomainFeaturesProto.from = MfeDomainFeaturesProto.fromObject; + PopUpProto.from = PopUpProto.fromObject; - MfeDomainFeaturesProto.toObject = function toObject(message, options) { + PopUpProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) - object.supportedFeatures = []; if (options.defaults) { - object.countryCode = ""; - object.domainName = ""; - } - if (message.countryCode !== undefined && message.countryCode !== null && message.hasOwnProperty("countryCode")) - object.countryCode = message.countryCode; - if (message.domainName !== undefined && message.domainName !== null && message.hasOwnProperty("domainName")) - object.domainName = message.domainName; - if (message.supportedFeatures !== undefined && message.supportedFeatures !== null && message.hasOwnProperty("supportedFeatures")) { - object.supportedFeatures = []; - for (var j = 0; j < message.supportedFeatures.length; ++j) - object.supportedFeatures[j] = options.enums === String ? $types[2][message.supportedFeatures[j]] : message.supportedFeatures[j]; + object.isBalloonStyle = false; + object.text = null; + object.backgroundColorAbgr = 4294967295; + object.textColorAbgr = 4278190080; } + if (message.isBalloonStyle !== undefined && message.isBalloonStyle !== null && message.hasOwnProperty("isBalloonStyle")) + object.isBalloonStyle = message.isBalloonStyle; + if (message.text !== undefined && message.text !== null && message.hasOwnProperty("text")) + object.text = $types[1].toObject(message.text, options); + if (message.backgroundColorAbgr !== undefined && message.backgroundColorAbgr !== null && message.hasOwnProperty("backgroundColorAbgr")) + object.backgroundColorAbgr = message.backgroundColorAbgr; + if (message.textColorAbgr !== undefined && message.textColorAbgr !== null && message.hasOwnProperty("textColorAbgr")) + object.textColorAbgr = message.textColorAbgr; return object; }; - MfeDomainFeaturesProto.prototype.toObject = function toObject(options) { + PopUpProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - MfeDomainFeaturesProto.prototype.toJSON = function toJSON() { + PopUpProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - MfeDomainFeaturesProto.SupportedFeature = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["GEOCODING"] = 0; - values["LOCAL_SEARCH"] = 1; - values["DRIVING_DIRECTIONS"] = 2; - return values; - })(); - - return MfeDomainFeaturesProto; + return PopUpProto; })(); - dbroot.ClientOptionsProto = (function() { + dbroot.StyleAttributeProto = (function() { - function ClientOptionsProto(properties) { + function StyleAttributeProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - ClientOptionsProto.prototype.disableDiskCache = false; - ClientOptionsProto.prototype.disableEmbeddedBrowserVista = false; - ClientOptionsProto.prototype.drawAtmosphere = true; - ClientOptionsProto.prototype.drawStars = true; - ClientOptionsProto.prototype.shaderFilePrefix = ""; - ClientOptionsProto.prototype.useProtobufQuadtreePackets = false; - ClientOptionsProto.prototype.useExtendedCopyrightIds = true; - ClientOptionsProto.prototype.precipitationsOptions = null; - ClientOptionsProto.prototype.captureOptions = null; - ClientOptionsProto.prototype.show_2dMapsIcon = true; - ClientOptionsProto.prototype.disableInternalBrowser = false; - ClientOptionsProto.prototype.internalBrowserBlacklist = ""; - ClientOptionsProto.prototype.internalBrowserOriginWhitelist = "*"; - ClientOptionsProto.prototype.polarTileMergingLevel = 0; - ClientOptionsProto.prototype.jsBridgeRequestWhitelist = "http://*.google.com/*"; - ClientOptionsProto.prototype.mapsOptions = null; + StyleAttributeProto.prototype.styleId = ""; + StyleAttributeProto.prototype.providerId = 0; + StyleAttributeProto.prototype.polyColorAbgr = 4294967295; + StyleAttributeProto.prototype.lineColorAbgr = 4294967295; + StyleAttributeProto.prototype.lineWidth = 1; + StyleAttributeProto.prototype.labelColorAbgr = 4294967295; + StyleAttributeProto.prototype.labelScale = 1; + StyleAttributeProto.prototype.placemarkIconColorAbgr = 4294967295; + StyleAttributeProto.prototype.placemarkIconScale = 1; + StyleAttributeProto.prototype.placemarkIconPath = null; + StyleAttributeProto.prototype.placemarkIconX = 0; + StyleAttributeProto.prototype.placemarkIconY = 0; + StyleAttributeProto.prototype.placemarkIconWidth = 32; + StyleAttributeProto.prototype.placemarkIconHeight = 32; + StyleAttributeProto.prototype.popUp = null; + StyleAttributeProto.prototype.drawFlag = $util.emptyArray; var $types = { - 7 : "keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions", - 8 : "keyhole.dbroot.ClientOptionsProto.CaptureOptions", - 15 : "keyhole.dbroot.ClientOptionsProto.MapsOptions" + 9 : "keyhole.dbroot.StringIdOrValueProto", + 14 : "keyhole.dbroot.PopUpProto", + 15 : "keyhole.dbroot.DrawFlagProto" }; $lazyTypes.push($types); - ClientOptionsProto.decode = function decode(reader, length) { + StyleAttributeProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ClientOptionsProto(); + message = new $root.keyhole.dbroot.StyleAttributeProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.disableDiskCache = reader.bool(); - break; - case 2: - message.disableEmbeddedBrowserVista = reader.bool(); + message.styleId = reader.string(); break; case 3: - message.drawAtmosphere = reader.bool(); + message.providerId = reader.int32(); break; case 4: - message.drawStars = reader.bool(); + message.polyColorAbgr = reader.fixed32(); break; case 5: - message.shaderFilePrefix = reader.string(); + message.lineColorAbgr = reader.fixed32(); break; case 6: - message.useProtobufQuadtreePackets = reader.bool(); + message.lineWidth = reader.float(); break; case 7: - message.useExtendedCopyrightIds = reader.bool(); + message.labelColorAbgr = reader.fixed32(); break; case 8: - message.precipitationsOptions = $types[7].decode(reader, reader.uint32()); + message.labelScale = reader.float(); break; case 9: - message.captureOptions = $types[8].decode(reader, reader.uint32()); + message.placemarkIconColorAbgr = reader.fixed32(); break; case 10: - message.show_2dMapsIcon = reader.bool(); + message.placemarkIconScale = reader.float(); break; case 11: - message.disableInternalBrowser = reader.bool(); + message.placemarkIconPath = $types[9].decode(reader, reader.uint32()); break; case 12: - message.internalBrowserBlacklist = reader.string(); + message.placemarkIconX = reader.int32(); break; case 13: - message.internalBrowserOriginWhitelist = reader.string(); + message.placemarkIconY = reader.int32(); break; case 14: - message.polarTileMergingLevel = reader.int32(); + message.placemarkIconWidth = reader.int32(); break; case 15: - message.jsBridgeRequestWhitelist = reader.string(); + message.placemarkIconHeight = reader.int32(); break; case 16: - message.mapsOptions = $types[15].decode(reader, reader.uint32()); + message.popUp = $types[14].decode(reader, reader.uint32()); + break; + case 17: + if (!(message.drawFlag && message.drawFlag.length)) + message.drawFlag = []; + message.drawFlag.push($types[15].decode(reader, reader.uint32())); break; default: reader.skipType(tag & 7); @@ -51455,842 +53140,347 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - ClientOptionsProto.verify = function verify(message) { + StyleAttributeProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.disableDiskCache !== undefined) - if (typeof message.disableDiskCache !== "boolean") - return "disableDiskCache: boolean expected"; - if (message.disableEmbeddedBrowserVista !== undefined) - if (typeof message.disableEmbeddedBrowserVista !== "boolean") - return "disableEmbeddedBrowserVista: boolean expected"; - if (message.drawAtmosphere !== undefined) - if (typeof message.drawAtmosphere !== "boolean") - return "drawAtmosphere: boolean expected"; - if (message.drawStars !== undefined) - if (typeof message.drawStars !== "boolean") - return "drawStars: boolean expected"; - if (message.shaderFilePrefix !== undefined) - if (!$util.isString(message.shaderFilePrefix)) - return "shaderFilePrefix: string expected"; - if (message.useProtobufQuadtreePackets !== undefined) - if (typeof message.useProtobufQuadtreePackets !== "boolean") - return "useProtobufQuadtreePackets: boolean expected"; - if (message.useExtendedCopyrightIds !== undefined) - if (typeof message.useExtendedCopyrightIds !== "boolean") - return "useExtendedCopyrightIds: boolean expected"; - if (message.precipitationsOptions !== undefined && message.precipitationsOptions !== null) { - var error = $types[7].verify(message.precipitationsOptions); + if (!$util.isString(message.styleId)) + return "styleId: string expected"; + if (message.providerId !== undefined) + if (!$util.isInteger(message.providerId)) + return "providerId: integer expected"; + if (message.polyColorAbgr !== undefined) + if (!$util.isInteger(message.polyColorAbgr)) + return "polyColorAbgr: integer expected"; + if (message.lineColorAbgr !== undefined) + if (!$util.isInteger(message.lineColorAbgr)) + return "lineColorAbgr: integer expected"; + if (message.lineWidth !== undefined) + if (typeof message.lineWidth !== "number") + return "lineWidth: number expected"; + if (message.labelColorAbgr !== undefined) + if (!$util.isInteger(message.labelColorAbgr)) + return "labelColorAbgr: integer expected"; + if (message.labelScale !== undefined) + if (typeof message.labelScale !== "number") + return "labelScale: number expected"; + if (message.placemarkIconColorAbgr !== undefined) + if (!$util.isInteger(message.placemarkIconColorAbgr)) + return "placemarkIconColorAbgr: integer expected"; + if (message.placemarkIconScale !== undefined) + if (typeof message.placemarkIconScale !== "number") + return "placemarkIconScale: number expected"; + if (message.placemarkIconPath !== undefined && message.placemarkIconPath !== null) { + var error = $types[9].verify(message.placemarkIconPath); if (error) - return "precipitationsOptions." + error; + return "placemarkIconPath." + error; } - if (message.captureOptions !== undefined && message.captureOptions !== null) { - var error = $types[8].verify(message.captureOptions); + if (message.placemarkIconX !== undefined) + if (!$util.isInteger(message.placemarkIconX)) + return "placemarkIconX: integer expected"; + if (message.placemarkIconY !== undefined) + if (!$util.isInteger(message.placemarkIconY)) + return "placemarkIconY: integer expected"; + if (message.placemarkIconWidth !== undefined) + if (!$util.isInteger(message.placemarkIconWidth)) + return "placemarkIconWidth: integer expected"; + if (message.placemarkIconHeight !== undefined) + if (!$util.isInteger(message.placemarkIconHeight)) + return "placemarkIconHeight: integer expected"; + if (message.popUp !== undefined && message.popUp !== null) { + var error = $types[14].verify(message.popUp); if (error) - return "captureOptions." + error; + return "popUp." + error; } - if (message.show_2dMapsIcon !== undefined) - if (typeof message.show_2dMapsIcon !== "boolean") - return "show_2dMapsIcon: boolean expected"; - if (message.disableInternalBrowser !== undefined) - if (typeof message.disableInternalBrowser !== "boolean") - return "disableInternalBrowser: boolean expected"; - if (message.internalBrowserBlacklist !== undefined) - if (!$util.isString(message.internalBrowserBlacklist)) - return "internalBrowserBlacklist: string expected"; - if (message.internalBrowserOriginWhitelist !== undefined) - if (!$util.isString(message.internalBrowserOriginWhitelist)) - return "internalBrowserOriginWhitelist: string expected"; - if (message.polarTileMergingLevel !== undefined) - if (!$util.isInteger(message.polarTileMergingLevel)) - return "polarTileMergingLevel: integer expected"; - if (message.jsBridgeRequestWhitelist !== undefined) - if (!$util.isString(message.jsBridgeRequestWhitelist)) - return "jsBridgeRequestWhitelist: string expected"; - if (message.mapsOptions !== undefined && message.mapsOptions !== null) { - var error = $types[15].verify(message.mapsOptions); - if (error) - return "mapsOptions." + error; + if (message.drawFlag !== undefined) { + if (!Array.isArray(message.drawFlag)) + return "drawFlag: array expected"; + for (var i = 0; i < message.drawFlag.length; ++i) { + var error = $types[15].verify(message.drawFlag[i]); + if (error) + return "drawFlag." + error; + } } return null; }; - ClientOptionsProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ClientOptionsProto) + StyleAttributeProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.StyleAttributeProto) return object; - var message = new $root.keyhole.dbroot.ClientOptionsProto(); - if (object.disableDiskCache !== undefined && object.disableDiskCache !== null) - message.disableDiskCache = Boolean(object.disableDiskCache); - if (object.disableEmbeddedBrowserVista !== undefined && object.disableEmbeddedBrowserVista !== null) - message.disableEmbeddedBrowserVista = Boolean(object.disableEmbeddedBrowserVista); - if (object.drawAtmosphere !== undefined && object.drawAtmosphere !== null) - message.drawAtmosphere = Boolean(object.drawAtmosphere); - if (object.drawStars !== undefined && object.drawStars !== null) - message.drawStars = Boolean(object.drawStars); - if (object.shaderFilePrefix !== undefined && object.shaderFilePrefix !== null) - message.shaderFilePrefix = String(object.shaderFilePrefix); - if (object.useProtobufQuadtreePackets !== undefined && object.useProtobufQuadtreePackets !== null) - message.useProtobufQuadtreePackets = Boolean(object.useProtobufQuadtreePackets); - if (object.useExtendedCopyrightIds !== undefined && object.useExtendedCopyrightIds !== null) - message.useExtendedCopyrightIds = Boolean(object.useExtendedCopyrightIds); - if (object.precipitationsOptions !== undefined && object.precipitationsOptions !== null) { - if (typeof object.precipitationsOptions !== "object") - throw TypeError(".keyhole.dbroot.ClientOptionsProto.precipitationsOptions: object expected"); - message.precipitationsOptions = $types[7].fromObject(object.precipitationsOptions); + var message = new $root.keyhole.dbroot.StyleAttributeProto(); + if (object.styleId !== undefined && object.styleId !== null) + message.styleId = String(object.styleId); + if (object.providerId !== undefined && object.providerId !== null) + message.providerId = object.providerId | 0; + if (object.polyColorAbgr !== undefined && object.polyColorAbgr !== null) + message.polyColorAbgr = object.polyColorAbgr >>> 0; + if (object.lineColorAbgr !== undefined && object.lineColorAbgr !== null) + message.lineColorAbgr = object.lineColorAbgr >>> 0; + if (object.lineWidth !== undefined && object.lineWidth !== null) + message.lineWidth = Number(object.lineWidth); + if (object.labelColorAbgr !== undefined && object.labelColorAbgr !== null) + message.labelColorAbgr = object.labelColorAbgr >>> 0; + if (object.labelScale !== undefined && object.labelScale !== null) + message.labelScale = Number(object.labelScale); + if (object.placemarkIconColorAbgr !== undefined && object.placemarkIconColorAbgr !== null) + message.placemarkIconColorAbgr = object.placemarkIconColorAbgr >>> 0; + if (object.placemarkIconScale !== undefined && object.placemarkIconScale !== null) + message.placemarkIconScale = Number(object.placemarkIconScale); + if (object.placemarkIconPath !== undefined && object.placemarkIconPath !== null) { + if (typeof object.placemarkIconPath !== "object") + throw TypeError(".keyhole.dbroot.StyleAttributeProto.placemarkIconPath: object expected"); + message.placemarkIconPath = $types[9].fromObject(object.placemarkIconPath); } - if (object.captureOptions !== undefined && object.captureOptions !== null) { - if (typeof object.captureOptions !== "object") - throw TypeError(".keyhole.dbroot.ClientOptionsProto.captureOptions: object expected"); - message.captureOptions = $types[8].fromObject(object.captureOptions); + if (object.placemarkIconX !== undefined && object.placemarkIconX !== null) + message.placemarkIconX = object.placemarkIconX | 0; + if (object.placemarkIconY !== undefined && object.placemarkIconY !== null) + message.placemarkIconY = object.placemarkIconY | 0; + if (object.placemarkIconWidth !== undefined && object.placemarkIconWidth !== null) + message.placemarkIconWidth = object.placemarkIconWidth | 0; + if (object.placemarkIconHeight !== undefined && object.placemarkIconHeight !== null) + message.placemarkIconHeight = object.placemarkIconHeight | 0; + if (object.popUp !== undefined && object.popUp !== null) { + if (typeof object.popUp !== "object") + throw TypeError(".keyhole.dbroot.StyleAttributeProto.popUp: object expected"); + message.popUp = $types[14].fromObject(object.popUp); } - if (object.show_2dMapsIcon !== undefined && object.show_2dMapsIcon !== null) - message.show_2dMapsIcon = Boolean(object.show_2dMapsIcon); - if (object.disableInternalBrowser !== undefined && object.disableInternalBrowser !== null) - message.disableInternalBrowser = Boolean(object.disableInternalBrowser); - if (object.internalBrowserBlacklist !== undefined && object.internalBrowserBlacklist !== null) - message.internalBrowserBlacklist = String(object.internalBrowserBlacklist); - if (object.internalBrowserOriginWhitelist !== undefined && object.internalBrowserOriginWhitelist !== null) - message.internalBrowserOriginWhitelist = String(object.internalBrowserOriginWhitelist); - if (object.polarTileMergingLevel !== undefined && object.polarTileMergingLevel !== null) - message.polarTileMergingLevel = object.polarTileMergingLevel | 0; - if (object.jsBridgeRequestWhitelist !== undefined && object.jsBridgeRequestWhitelist !== null) - message.jsBridgeRequestWhitelist = String(object.jsBridgeRequestWhitelist); - if (object.mapsOptions !== undefined && object.mapsOptions !== null) { - if (typeof object.mapsOptions !== "object") - throw TypeError(".keyhole.dbroot.ClientOptionsProto.mapsOptions: object expected"); - message.mapsOptions = $types[15].fromObject(object.mapsOptions); + if (object.drawFlag) { + if (!Array.isArray(object.drawFlag)) + throw TypeError(".keyhole.dbroot.StyleAttributeProto.drawFlag: array expected"); + message.drawFlag = []; + for (var i = 0; i < object.drawFlag.length; ++i) { + if (typeof object.drawFlag[i] !== "object") + throw TypeError(".keyhole.dbroot.StyleAttributeProto.drawFlag: object expected"); + message.drawFlag[i] = $types[15].fromObject(object.drawFlag[i]); + } } return message; }; - ClientOptionsProto.from = ClientOptionsProto.fromObject; + StyleAttributeProto.from = StyleAttributeProto.fromObject; - ClientOptionsProto.toObject = function toObject(message, options) { + StyleAttributeProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.drawFlag = []; if (options.defaults) { - object.disableDiskCache = false; - object.disableEmbeddedBrowserVista = false; - object.drawAtmosphere = true; - object.drawStars = true; - object.shaderFilePrefix = ""; - object.useProtobufQuadtreePackets = false; - object.useExtendedCopyrightIds = true; - object.precipitationsOptions = null; - object.captureOptions = null; - object.show_2dMapsIcon = true; - object.disableInternalBrowser = false; - object.internalBrowserBlacklist = ""; - object.internalBrowserOriginWhitelist = "*"; - object.polarTileMergingLevel = 0; - object.jsBridgeRequestWhitelist = "http://*.google.com/*"; - object.mapsOptions = null; + object.styleId = ""; + object.providerId = 0; + object.polyColorAbgr = 4294967295; + object.lineColorAbgr = 4294967295; + object.lineWidth = 1; + object.labelColorAbgr = 4294967295; + object.labelScale = 1; + object.placemarkIconColorAbgr = 4294967295; + object.placemarkIconScale = 1; + object.placemarkIconPath = null; + object.placemarkIconX = 0; + object.placemarkIconY = 0; + object.placemarkIconWidth = 32; + object.placemarkIconHeight = 32; + object.popUp = null; + } + if (message.styleId !== undefined && message.styleId !== null && message.hasOwnProperty("styleId")) + object.styleId = message.styleId; + if (message.providerId !== undefined && message.providerId !== null && message.hasOwnProperty("providerId")) + object.providerId = message.providerId; + if (message.polyColorAbgr !== undefined && message.polyColorAbgr !== null && message.hasOwnProperty("polyColorAbgr")) + object.polyColorAbgr = message.polyColorAbgr; + if (message.lineColorAbgr !== undefined && message.lineColorAbgr !== null && message.hasOwnProperty("lineColorAbgr")) + object.lineColorAbgr = message.lineColorAbgr; + if (message.lineWidth !== undefined && message.lineWidth !== null && message.hasOwnProperty("lineWidth")) + object.lineWidth = message.lineWidth; + if (message.labelColorAbgr !== undefined && message.labelColorAbgr !== null && message.hasOwnProperty("labelColorAbgr")) + object.labelColorAbgr = message.labelColorAbgr; + if (message.labelScale !== undefined && message.labelScale !== null && message.hasOwnProperty("labelScale")) + object.labelScale = message.labelScale; + if (message.placemarkIconColorAbgr !== undefined && message.placemarkIconColorAbgr !== null && message.hasOwnProperty("placemarkIconColorAbgr")) + object.placemarkIconColorAbgr = message.placemarkIconColorAbgr; + if (message.placemarkIconScale !== undefined && message.placemarkIconScale !== null && message.hasOwnProperty("placemarkIconScale")) + object.placemarkIconScale = message.placemarkIconScale; + if (message.placemarkIconPath !== undefined && message.placemarkIconPath !== null && message.hasOwnProperty("placemarkIconPath")) + object.placemarkIconPath = $types[9].toObject(message.placemarkIconPath, options); + if (message.placemarkIconX !== undefined && message.placemarkIconX !== null && message.hasOwnProperty("placemarkIconX")) + object.placemarkIconX = message.placemarkIconX; + if (message.placemarkIconY !== undefined && message.placemarkIconY !== null && message.hasOwnProperty("placemarkIconY")) + object.placemarkIconY = message.placemarkIconY; + if (message.placemarkIconWidth !== undefined && message.placemarkIconWidth !== null && message.hasOwnProperty("placemarkIconWidth")) + object.placemarkIconWidth = message.placemarkIconWidth; + if (message.placemarkIconHeight !== undefined && message.placemarkIconHeight !== null && message.hasOwnProperty("placemarkIconHeight")) + object.placemarkIconHeight = message.placemarkIconHeight; + if (message.popUp !== undefined && message.popUp !== null && message.hasOwnProperty("popUp")) + object.popUp = $types[14].toObject(message.popUp, options); + if (message.drawFlag !== undefined && message.drawFlag !== null && message.hasOwnProperty("drawFlag")) { + object.drawFlag = []; + for (var j = 0; j < message.drawFlag.length; ++j) + object.drawFlag[j] = $types[15].toObject(message.drawFlag[j], options); } - if (message.disableDiskCache !== undefined && message.disableDiskCache !== null && message.hasOwnProperty("disableDiskCache")) - object.disableDiskCache = message.disableDiskCache; - if (message.disableEmbeddedBrowserVista !== undefined && message.disableEmbeddedBrowserVista !== null && message.hasOwnProperty("disableEmbeddedBrowserVista")) - object.disableEmbeddedBrowserVista = message.disableEmbeddedBrowserVista; - if (message.drawAtmosphere !== undefined && message.drawAtmosphere !== null && message.hasOwnProperty("drawAtmosphere")) - object.drawAtmosphere = message.drawAtmosphere; - if (message.drawStars !== undefined && message.drawStars !== null && message.hasOwnProperty("drawStars")) - object.drawStars = message.drawStars; - if (message.shaderFilePrefix !== undefined && message.shaderFilePrefix !== null && message.hasOwnProperty("shaderFilePrefix")) - object.shaderFilePrefix = message.shaderFilePrefix; - if (message.useProtobufQuadtreePackets !== undefined && message.useProtobufQuadtreePackets !== null && message.hasOwnProperty("useProtobufQuadtreePackets")) - object.useProtobufQuadtreePackets = message.useProtobufQuadtreePackets; - if (message.useExtendedCopyrightIds !== undefined && message.useExtendedCopyrightIds !== null && message.hasOwnProperty("useExtendedCopyrightIds")) - object.useExtendedCopyrightIds = message.useExtendedCopyrightIds; - if (message.precipitationsOptions !== undefined && message.precipitationsOptions !== null && message.hasOwnProperty("precipitationsOptions")) - object.precipitationsOptions = $types[7].toObject(message.precipitationsOptions, options); - if (message.captureOptions !== undefined && message.captureOptions !== null && message.hasOwnProperty("captureOptions")) - object.captureOptions = $types[8].toObject(message.captureOptions, options); - if (message.show_2dMapsIcon !== undefined && message.show_2dMapsIcon !== null && message.hasOwnProperty("show_2dMapsIcon")) - object.show_2dMapsIcon = message.show_2dMapsIcon; - if (message.disableInternalBrowser !== undefined && message.disableInternalBrowser !== null && message.hasOwnProperty("disableInternalBrowser")) - object.disableInternalBrowser = message.disableInternalBrowser; - if (message.internalBrowserBlacklist !== undefined && message.internalBrowserBlacklist !== null && message.hasOwnProperty("internalBrowserBlacklist")) - object.internalBrowserBlacklist = message.internalBrowserBlacklist; - if (message.internalBrowserOriginWhitelist !== undefined && message.internalBrowserOriginWhitelist !== null && message.hasOwnProperty("internalBrowserOriginWhitelist")) - object.internalBrowserOriginWhitelist = message.internalBrowserOriginWhitelist; - if (message.polarTileMergingLevel !== undefined && message.polarTileMergingLevel !== null && message.hasOwnProperty("polarTileMergingLevel")) - object.polarTileMergingLevel = message.polarTileMergingLevel; - if (message.jsBridgeRequestWhitelist !== undefined && message.jsBridgeRequestWhitelist !== null && message.hasOwnProperty("jsBridgeRequestWhitelist")) - object.jsBridgeRequestWhitelist = message.jsBridgeRequestWhitelist; - if (message.mapsOptions !== undefined && message.mapsOptions !== null && message.hasOwnProperty("mapsOptions")) - object.mapsOptions = $types[15].toObject(message.mapsOptions, options); return object; }; - ClientOptionsProto.prototype.toObject = function toObject(options) { + StyleAttributeProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - ClientOptionsProto.prototype.toJSON = function toJSON() { + StyleAttributeProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - ClientOptionsProto.PrecipitationsOptions = (function() { - - function PrecipitationsOptions(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - PrecipitationsOptions.prototype.imageUrl = ""; - PrecipitationsOptions.prototype.imageExpireTime = 900; - PrecipitationsOptions.prototype.maxColorDistance = 20; - PrecipitationsOptions.prototype.imageLevel = 5; - PrecipitationsOptions.prototype.weatherMapping = $util.emptyArray; - PrecipitationsOptions.prototype.cloudsLayerUrl = ""; - PrecipitationsOptions.prototype.animationDecelerationDelay = 20; + return StyleAttributeProto; + })(); - var $types = { - 4 : "keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping" - }; - $lazyTypes.push($types); + dbroot.StyleMapProto = (function() { - PrecipitationsOptions.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.imageUrl = reader.string(); - break; - case 2: - message.imageExpireTime = reader.int32(); - break; - case 3: - message.maxColorDistance = reader.int32(); - break; - case 4: - message.imageLevel = reader.int32(); - break; - case 5: - if (!(message.weatherMapping && message.weatherMapping.length)) - message.weatherMapping = []; - message.weatherMapping.push($types[4].decode(reader, reader.uint32())); - break; - case 6: - message.cloudsLayerUrl = reader.string(); - break; - case 7: - message.animationDecelerationDelay = reader.float(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + function StyleMapProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - PrecipitationsOptions.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.imageUrl !== undefined) - if (!$util.isString(message.imageUrl)) - return "imageUrl: string expected"; - if (message.imageExpireTime !== undefined) - if (!$util.isInteger(message.imageExpireTime)) - return "imageExpireTime: integer expected"; - if (message.maxColorDistance !== undefined) - if (!$util.isInteger(message.maxColorDistance)) - return "maxColorDistance: integer expected"; - if (message.imageLevel !== undefined) - if (!$util.isInteger(message.imageLevel)) - return "imageLevel: integer expected"; - if (message.weatherMapping !== undefined) { - if (!Array.isArray(message.weatherMapping)) - return "weatherMapping: array expected"; - for (var i = 0; i < message.weatherMapping.length; ++i) { - var error = $types[4].verify(message.weatherMapping[i]); - if (error) - return "weatherMapping." + error; - } - } - if (message.cloudsLayerUrl !== undefined) - if (!$util.isString(message.cloudsLayerUrl)) - return "cloudsLayerUrl: string expected"; - if (message.animationDecelerationDelay !== undefined) - if (typeof message.animationDecelerationDelay !== "number") - return "animationDecelerationDelay: number expected"; - return null; - }; + StyleMapProto.prototype.styleMapId = 0; + StyleMapProto.prototype.channelId = $util.emptyArray; + StyleMapProto.prototype.normalStyleAttribute = 0; + StyleMapProto.prototype.highlightStyleAttribute = 0; - PrecipitationsOptions.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions) - return object; - var message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions(); - if (object.imageUrl !== undefined && object.imageUrl !== null) - message.imageUrl = String(object.imageUrl); - if (object.imageExpireTime !== undefined && object.imageExpireTime !== null) - message.imageExpireTime = object.imageExpireTime | 0; - if (object.maxColorDistance !== undefined && object.maxColorDistance !== null) - message.maxColorDistance = object.maxColorDistance | 0; - if (object.imageLevel !== undefined && object.imageLevel !== null) - message.imageLevel = object.imageLevel | 0; - if (object.weatherMapping) { - if (!Array.isArray(object.weatherMapping)) - throw TypeError(".keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.weatherMapping: array expected"); - message.weatherMapping = []; - for (var i = 0; i < object.weatherMapping.length; ++i) { - if (typeof object.weatherMapping[i] !== "object") - throw TypeError(".keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.weatherMapping: object expected"); - message.weatherMapping[i] = $types[4].fromObject(object.weatherMapping[i]); - } + StyleMapProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.StyleMapProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.styleMapId = reader.int32(); + break; + case 2: + if (!(message.channelId && message.channelId.length)) + message.channelId = []; + if ((tag & 7) === 2) { + var end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) + message.channelId.push(reader.int32()); + } else + message.channelId.push(reader.int32()); + break; + case 3: + message.normalStyleAttribute = reader.int32(); + break; + case 4: + message.highlightStyleAttribute = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; } - if (object.cloudsLayerUrl !== undefined && object.cloudsLayerUrl !== null) - message.cloudsLayerUrl = String(object.cloudsLayerUrl); - if (object.animationDecelerationDelay !== undefined && object.animationDecelerationDelay !== null) - message.animationDecelerationDelay = Number(object.animationDecelerationDelay); - return message; - }; + } + return message; + }; - PrecipitationsOptions.from = PrecipitationsOptions.fromObject; + StyleMapProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isInteger(message.styleMapId)) + return "styleMapId: integer expected"; + if (message.channelId !== undefined) { + if (!Array.isArray(message.channelId)) + return "channelId: array expected"; + for (var i = 0; i < message.channelId.length; ++i) + if (!$util.isInteger(message.channelId[i])) + return "channelId: integer[] expected"; + } + if (message.normalStyleAttribute !== undefined) + if (!$util.isInteger(message.normalStyleAttribute)) + return "normalStyleAttribute: integer expected"; + if (message.highlightStyleAttribute !== undefined) + if (!$util.isInteger(message.highlightStyleAttribute)) + return "highlightStyleAttribute: integer expected"; + return null; + }; - PrecipitationsOptions.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) - object.weatherMapping = []; - if (options.defaults) { - object.imageUrl = ""; - object.imageExpireTime = 900; - object.maxColorDistance = 20; - object.imageLevel = 5; - object.cloudsLayerUrl = ""; - object.animationDecelerationDelay = 20; - } - if (message.imageUrl !== undefined && message.imageUrl !== null && message.hasOwnProperty("imageUrl")) - object.imageUrl = message.imageUrl; - if (message.imageExpireTime !== undefined && message.imageExpireTime !== null && message.hasOwnProperty("imageExpireTime")) - object.imageExpireTime = message.imageExpireTime; - if (message.maxColorDistance !== undefined && message.maxColorDistance !== null && message.hasOwnProperty("maxColorDistance")) - object.maxColorDistance = message.maxColorDistance; - if (message.imageLevel !== undefined && message.imageLevel !== null && message.hasOwnProperty("imageLevel")) - object.imageLevel = message.imageLevel; - if (message.weatherMapping !== undefined && message.weatherMapping !== null && message.hasOwnProperty("weatherMapping")) { - object.weatherMapping = []; - for (var j = 0; j < message.weatherMapping.length; ++j) - object.weatherMapping[j] = $types[4].toObject(message.weatherMapping[j], options); - } - if (message.cloudsLayerUrl !== undefined && message.cloudsLayerUrl !== null && message.hasOwnProperty("cloudsLayerUrl")) - object.cloudsLayerUrl = message.cloudsLayerUrl; - if (message.animationDecelerationDelay !== undefined && message.animationDecelerationDelay !== null && message.hasOwnProperty("animationDecelerationDelay")) - object.animationDecelerationDelay = message.animationDecelerationDelay; + StyleMapProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.StyleMapProto) return object; - }; - - PrecipitationsOptions.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - PrecipitationsOptions.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - PrecipitationsOptions.WeatherMapping = (function() { - - function WeatherMapping(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - WeatherMapping.prototype.colorAbgr = 0; - WeatherMapping.prototype.weatherType = 0; - WeatherMapping.prototype.elongation = 1; - WeatherMapping.prototype.opacity = 0; - WeatherMapping.prototype.fogDensity = 0; - WeatherMapping.prototype.speed0 = 0; - WeatherMapping.prototype.speed1 = 0; - WeatherMapping.prototype.speed2 = 0; - WeatherMapping.prototype.speed3 = 0; - - var $types = { - 1 : "keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping.WeatherType" - }; - $lazyTypes.push($types); - - WeatherMapping.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.colorAbgr = reader.uint32(); - break; - case 2: - message.weatherType = reader.uint32(); - break; - case 3: - message.elongation = reader.float(); - break; - case 4: - message.opacity = reader.float(); - break; - case 5: - message.fogDensity = reader.float(); - break; - case 6: - message.speed0 = reader.float(); - break; - case 7: - message.speed1 = reader.float(); - break; - case 8: - message.speed2 = reader.float(); - break; - case 9: - message.speed3 = reader.float(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - WeatherMapping.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (!$util.isInteger(message.colorAbgr)) - return "colorAbgr: integer expected"; - switch (message.weatherType) { - default: - return "weatherType: enum value expected"; - case 0: - case 1: - case 2: - break; - } - if (message.elongation !== undefined) - if (typeof message.elongation !== "number") - return "elongation: number expected"; - if (message.opacity !== undefined) - if (typeof message.opacity !== "number") - return "opacity: number expected"; - if (message.fogDensity !== undefined) - if (typeof message.fogDensity !== "number") - return "fogDensity: number expected"; - if (message.speed0 !== undefined) - if (typeof message.speed0 !== "number") - return "speed0: number expected"; - if (message.speed1 !== undefined) - if (typeof message.speed1 !== "number") - return "speed1: number expected"; - if (message.speed2 !== undefined) - if (typeof message.speed2 !== "number") - return "speed2: number expected"; - if (message.speed3 !== undefined) - if (typeof message.speed3 !== "number") - return "speed3: number expected"; - return null; - }; - - WeatherMapping.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping) - return object; - var message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping(); - if (object.colorAbgr !== undefined && object.colorAbgr !== null) - message.colorAbgr = object.colorAbgr >>> 0; - switch (object.weatherType) { - case "NO_PRECIPITATION": - case 0: - message.weatherType = 0; - break; - case "RAIN": - case 1: - message.weatherType = 1; - break; - case "SNOW": - case 2: - message.weatherType = 2; - break; - } - if (object.elongation !== undefined && object.elongation !== null) - message.elongation = Number(object.elongation); - if (object.opacity !== undefined && object.opacity !== null) - message.opacity = Number(object.opacity); - if (object.fogDensity !== undefined && object.fogDensity !== null) - message.fogDensity = Number(object.fogDensity); - if (object.speed0 !== undefined && object.speed0 !== null) - message.speed0 = Number(object.speed0); - if (object.speed1 !== undefined && object.speed1 !== null) - message.speed1 = Number(object.speed1); - if (object.speed2 !== undefined && object.speed2 !== null) - message.speed2 = Number(object.speed2); - if (object.speed3 !== undefined && object.speed3 !== null) - message.speed3 = Number(object.speed3); - return message; - }; - - WeatherMapping.from = WeatherMapping.fromObject; - - WeatherMapping.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.colorAbgr = 0; - object.weatherType = options.enums === String ? "NO_PRECIPITATION" : 0; - object.elongation = 1; - object.opacity = 0; - object.fogDensity = 0; - object.speed0 = 0; - object.speed1 = 0; - object.speed2 = 0; - object.speed3 = 0; - } - if (message.colorAbgr !== undefined && message.colorAbgr !== null && message.hasOwnProperty("colorAbgr")) - object.colorAbgr = message.colorAbgr; - if (message.weatherType !== undefined && message.weatherType !== null && message.hasOwnProperty("weatherType")) - object.weatherType = options.enums === String ? $types[1][message.weatherType] : message.weatherType; - if (message.elongation !== undefined && message.elongation !== null && message.hasOwnProperty("elongation")) - object.elongation = message.elongation; - if (message.opacity !== undefined && message.opacity !== null && message.hasOwnProperty("opacity")) - object.opacity = message.opacity; - if (message.fogDensity !== undefined && message.fogDensity !== null && message.hasOwnProperty("fogDensity")) - object.fogDensity = message.fogDensity; - if (message.speed0 !== undefined && message.speed0 !== null && message.hasOwnProperty("speed0")) - object.speed0 = message.speed0; - if (message.speed1 !== undefined && message.speed1 !== null && message.hasOwnProperty("speed1")) - object.speed1 = message.speed1; - if (message.speed2 !== undefined && message.speed2 !== null && message.hasOwnProperty("speed2")) - object.speed2 = message.speed2; - if (message.speed3 !== undefined && message.speed3 !== null && message.hasOwnProperty("speed3")) - object.speed3 = message.speed3; - return object; - }; - - WeatherMapping.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - WeatherMapping.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - WeatherMapping.WeatherType = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["NO_PRECIPITATION"] = 0; - values["RAIN"] = 1; - values["SNOW"] = 2; - return values; - })(); - - return WeatherMapping; - })(); - - return PrecipitationsOptions; - })(); - - ClientOptionsProto.CaptureOptions = (function() { - - function CaptureOptions(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; + var message = new $root.keyhole.dbroot.StyleMapProto(); + if (object.styleMapId !== undefined && object.styleMapId !== null) + message.styleMapId = object.styleMapId | 0; + if (object.channelId) { + if (!Array.isArray(object.channelId)) + throw TypeError(".keyhole.dbroot.StyleMapProto.channelId: array expected"); + message.channelId = []; + for (var i = 0; i < object.channelId.length; ++i) + message.channelId[i] = object.channelId[i] | 0; } + if (object.normalStyleAttribute !== undefined && object.normalStyleAttribute !== null) + message.normalStyleAttribute = object.normalStyleAttribute | 0; + if (object.highlightStyleAttribute !== undefined && object.highlightStyleAttribute !== null) + message.highlightStyleAttribute = object.highlightStyleAttribute | 0; + return message; + }; - CaptureOptions.prototype.allowSaveAsImage = true; - CaptureOptions.prototype.maxFreeCaptureRes = 2400; - CaptureOptions.prototype.maxPremiumCaptureRes = 4800; - - CaptureOptions.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ClientOptionsProto.CaptureOptions(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.allowSaveAsImage = reader.bool(); - break; - case 2: - message.maxFreeCaptureRes = reader.int32(); - break; - case 3: - message.maxPremiumCaptureRes = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - CaptureOptions.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.allowSaveAsImage !== undefined) - if (typeof message.allowSaveAsImage !== "boolean") - return "allowSaveAsImage: boolean expected"; - if (message.maxFreeCaptureRes !== undefined) - if (!$util.isInteger(message.maxFreeCaptureRes)) - return "maxFreeCaptureRes: integer expected"; - if (message.maxPremiumCaptureRes !== undefined) - if (!$util.isInteger(message.maxPremiumCaptureRes)) - return "maxPremiumCaptureRes: integer expected"; - return null; - }; - - CaptureOptions.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.CaptureOptions) - return object; - var message = new $root.keyhole.dbroot.ClientOptionsProto.CaptureOptions(); - if (object.allowSaveAsImage !== undefined && object.allowSaveAsImage !== null) - message.allowSaveAsImage = Boolean(object.allowSaveAsImage); - if (object.maxFreeCaptureRes !== undefined && object.maxFreeCaptureRes !== null) - message.maxFreeCaptureRes = object.maxFreeCaptureRes | 0; - if (object.maxPremiumCaptureRes !== undefined && object.maxPremiumCaptureRes !== null) - message.maxPremiumCaptureRes = object.maxPremiumCaptureRes | 0; - return message; - }; - - CaptureOptions.from = CaptureOptions.fromObject; - - CaptureOptions.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.allowSaveAsImage = true; - object.maxFreeCaptureRes = 2400; - object.maxPremiumCaptureRes = 4800; - } - if (message.allowSaveAsImage !== undefined && message.allowSaveAsImage !== null && message.hasOwnProperty("allowSaveAsImage")) - object.allowSaveAsImage = message.allowSaveAsImage; - if (message.maxFreeCaptureRes !== undefined && message.maxFreeCaptureRes !== null && message.hasOwnProperty("maxFreeCaptureRes")) - object.maxFreeCaptureRes = message.maxFreeCaptureRes; - if (message.maxPremiumCaptureRes !== undefined && message.maxPremiumCaptureRes !== null && message.hasOwnProperty("maxPremiumCaptureRes")) - object.maxPremiumCaptureRes = message.maxPremiumCaptureRes; - return object; - }; - - CaptureOptions.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - CaptureOptions.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return CaptureOptions; - })(); - - ClientOptionsProto.MapsOptions = (function() { + StyleMapProto.from = StyleMapProto.fromObject; - function MapsOptions(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; + StyleMapProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.channelId = []; + if (options.defaults) { + object.styleMapId = 0; + object.normalStyleAttribute = 0; + object.highlightStyleAttribute = 0; } + if (message.styleMapId !== undefined && message.styleMapId !== null && message.hasOwnProperty("styleMapId")) + object.styleMapId = message.styleMapId; + if (message.channelId !== undefined && message.channelId !== null && message.hasOwnProperty("channelId")) { + object.channelId = []; + for (var j = 0; j < message.channelId.length; ++j) + object.channelId[j] = message.channelId[j]; + } + if (message.normalStyleAttribute !== undefined && message.normalStyleAttribute !== null && message.hasOwnProperty("normalStyleAttribute")) + object.normalStyleAttribute = message.normalStyleAttribute; + if (message.highlightStyleAttribute !== undefined && message.highlightStyleAttribute !== null && message.hasOwnProperty("highlightStyleAttribute")) + object.highlightStyleAttribute = message.highlightStyleAttribute; + return object; + }; - MapsOptions.prototype.enableMaps = false; - MapsOptions.prototype.docsAutoDownloadEnabled = false; - MapsOptions.prototype.docsAutoDownloadInterval = 0; - MapsOptions.prototype.docsAutoUploadEnabled = false; - MapsOptions.prototype.docsAutoUploadDelay = 0; - - MapsOptions.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ClientOptionsProto.MapsOptions(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.enableMaps = reader.bool(); - break; - case 2: - message.docsAutoDownloadEnabled = reader.bool(); - break; - case 3: - message.docsAutoDownloadInterval = reader.int32(); - break; - case 4: - message.docsAutoUploadEnabled = reader.bool(); - break; - case 5: - message.docsAutoUploadDelay = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - MapsOptions.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.enableMaps !== undefined) - if (typeof message.enableMaps !== "boolean") - return "enableMaps: boolean expected"; - if (message.docsAutoDownloadEnabled !== undefined) - if (typeof message.docsAutoDownloadEnabled !== "boolean") - return "docsAutoDownloadEnabled: boolean expected"; - if (message.docsAutoDownloadInterval !== undefined) - if (!$util.isInteger(message.docsAutoDownloadInterval)) - return "docsAutoDownloadInterval: integer expected"; - if (message.docsAutoUploadEnabled !== undefined) - if (typeof message.docsAutoUploadEnabled !== "boolean") - return "docsAutoUploadEnabled: boolean expected"; - if (message.docsAutoUploadDelay !== undefined) - if (!$util.isInteger(message.docsAutoUploadDelay)) - return "docsAutoUploadDelay: integer expected"; - return null; - }; - - MapsOptions.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.MapsOptions) - return object; - var message = new $root.keyhole.dbroot.ClientOptionsProto.MapsOptions(); - if (object.enableMaps !== undefined && object.enableMaps !== null) - message.enableMaps = Boolean(object.enableMaps); - if (object.docsAutoDownloadEnabled !== undefined && object.docsAutoDownloadEnabled !== null) - message.docsAutoDownloadEnabled = Boolean(object.docsAutoDownloadEnabled); - if (object.docsAutoDownloadInterval !== undefined && object.docsAutoDownloadInterval !== null) - message.docsAutoDownloadInterval = object.docsAutoDownloadInterval | 0; - if (object.docsAutoUploadEnabled !== undefined && object.docsAutoUploadEnabled !== null) - message.docsAutoUploadEnabled = Boolean(object.docsAutoUploadEnabled); - if (object.docsAutoUploadDelay !== undefined && object.docsAutoUploadDelay !== null) - message.docsAutoUploadDelay = object.docsAutoUploadDelay | 0; - return message; - }; - - MapsOptions.from = MapsOptions.fromObject; - - MapsOptions.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.enableMaps = false; - object.docsAutoDownloadEnabled = false; - object.docsAutoDownloadInterval = 0; - object.docsAutoUploadEnabled = false; - object.docsAutoUploadDelay = 0; - } - if (message.enableMaps !== undefined && message.enableMaps !== null && message.hasOwnProperty("enableMaps")) - object.enableMaps = message.enableMaps; - if (message.docsAutoDownloadEnabled !== undefined && message.docsAutoDownloadEnabled !== null && message.hasOwnProperty("docsAutoDownloadEnabled")) - object.docsAutoDownloadEnabled = message.docsAutoDownloadEnabled; - if (message.docsAutoDownloadInterval !== undefined && message.docsAutoDownloadInterval !== null && message.hasOwnProperty("docsAutoDownloadInterval")) - object.docsAutoDownloadInterval = message.docsAutoDownloadInterval; - if (message.docsAutoUploadEnabled !== undefined && message.docsAutoUploadEnabled !== null && message.hasOwnProperty("docsAutoUploadEnabled")) - object.docsAutoUploadEnabled = message.docsAutoUploadEnabled; - if (message.docsAutoUploadDelay !== undefined && message.docsAutoUploadDelay !== null && message.hasOwnProperty("docsAutoUploadDelay")) - object.docsAutoUploadDelay = message.docsAutoUploadDelay; - return object; - }; - - MapsOptions.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - MapsOptions.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + StyleMapProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - return MapsOptions; - })(); + StyleMapProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - return ClientOptionsProto; + return StyleMapProto; })(); - dbroot.FetchingOptionsProto = (function() { + dbroot.ZoomRangeProto = (function() { - function FetchingOptionsProto(properties) { + function ZoomRangeProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - FetchingOptionsProto.prototype.maxRequestsPerQuery = 1; - FetchingOptionsProto.prototype.forceMaxRequestsPerQuery = false; - FetchingOptionsProto.prototype.sortBatches = false; - FetchingOptionsProto.prototype.maxDrawable = 2; - FetchingOptionsProto.prototype.maxImagery = 2; - FetchingOptionsProto.prototype.maxTerrain = 5; - FetchingOptionsProto.prototype.maxQuadtree = 5; - FetchingOptionsProto.prototype.maxDioramaMetadata = 1; - FetchingOptionsProto.prototype.maxDioramaData = 0; - FetchingOptionsProto.prototype.maxConsumerFetchRatio = 1; - FetchingOptionsProto.prototype.maxProEcFetchRatio = 0; - FetchingOptionsProto.prototype.safeOverallQps = 0; - FetchingOptionsProto.prototype.safeImageryQps = 0; - FetchingOptionsProto.prototype.domainsForHttps = "google.com gstatic.com"; - FetchingOptionsProto.prototype.hostsForHttp = ""; + ZoomRangeProto.prototype.minZoom = 0; + ZoomRangeProto.prototype.maxZoom = 0; - FetchingOptionsProto.decode = function decode(reader, length) { + ZoomRangeProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.FetchingOptionsProto(); + message = new $root.keyhole.dbroot.ZoomRangeProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.maxRequestsPerQuery = reader.int32(); - break; - case 12: - message.forceMaxRequestsPerQuery = reader.bool(); - break; - case 13: - message.sortBatches = reader.bool(); + message.minZoom = reader.int32(); break; case 2: - message.maxDrawable = reader.int32(); - break; - case 3: - message.maxImagery = reader.int32(); - break; - case 4: - message.maxTerrain = reader.int32(); - break; - case 5: - message.maxQuadtree = reader.int32(); - break; - case 6: - message.maxDioramaMetadata = reader.int32(); - break; - case 7: - message.maxDioramaData = reader.int32(); - break; - case 8: - message.maxConsumerFetchRatio = reader.float(); - break; - case 9: - message.maxProEcFetchRatio = reader.float(); - break; - case 10: - message.safeOverallQps = reader.float(); - break; - case 11: - message.safeImageryQps = reader.float(); - break; - case 14: - message.domainsForHttps = reader.string(); - break; - case 15: - message.hostsForHttp = reader.string(); + message.maxZoom = reader.int32(); break; default: reader.skipType(tag & 7); @@ -52300,193 +53490,80 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - FetchingOptionsProto.verify = function verify(message) { + ZoomRangeProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.maxRequestsPerQuery !== undefined) - if (!$util.isInteger(message.maxRequestsPerQuery)) - return "maxRequestsPerQuery: integer expected"; - if (message.forceMaxRequestsPerQuery !== undefined) - if (typeof message.forceMaxRequestsPerQuery !== "boolean") - return "forceMaxRequestsPerQuery: boolean expected"; - if (message.sortBatches !== undefined) - if (typeof message.sortBatches !== "boolean") - return "sortBatches: boolean expected"; - if (message.maxDrawable !== undefined) - if (!$util.isInteger(message.maxDrawable)) - return "maxDrawable: integer expected"; - if (message.maxImagery !== undefined) - if (!$util.isInteger(message.maxImagery)) - return "maxImagery: integer expected"; - if (message.maxTerrain !== undefined) - if (!$util.isInteger(message.maxTerrain)) - return "maxTerrain: integer expected"; - if (message.maxQuadtree !== undefined) - if (!$util.isInteger(message.maxQuadtree)) - return "maxQuadtree: integer expected"; - if (message.maxDioramaMetadata !== undefined) - if (!$util.isInteger(message.maxDioramaMetadata)) - return "maxDioramaMetadata: integer expected"; - if (message.maxDioramaData !== undefined) - if (!$util.isInteger(message.maxDioramaData)) - return "maxDioramaData: integer expected"; - if (message.maxConsumerFetchRatio !== undefined) - if (typeof message.maxConsumerFetchRatio !== "number") - return "maxConsumerFetchRatio: number expected"; - if (message.maxProEcFetchRatio !== undefined) - if (typeof message.maxProEcFetchRatio !== "number") - return "maxProEcFetchRatio: number expected"; - if (message.safeOverallQps !== undefined) - if (typeof message.safeOverallQps !== "number") - return "safeOverallQps: number expected"; - if (message.safeImageryQps !== undefined) - if (typeof message.safeImageryQps !== "number") - return "safeImageryQps: number expected"; - if (message.domainsForHttps !== undefined) - if (!$util.isString(message.domainsForHttps)) - return "domainsForHttps: string expected"; - if (message.hostsForHttp !== undefined) - if (!$util.isString(message.hostsForHttp)) - return "hostsForHttp: string expected"; + if (!$util.isInteger(message.minZoom)) + return "minZoom: integer expected"; + if (!$util.isInteger(message.maxZoom)) + return "maxZoom: integer expected"; return null; }; - FetchingOptionsProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.FetchingOptionsProto) + ZoomRangeProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ZoomRangeProto) return object; - var message = new $root.keyhole.dbroot.FetchingOptionsProto(); - if (object.maxRequestsPerQuery !== undefined && object.maxRequestsPerQuery !== null) - message.maxRequestsPerQuery = object.maxRequestsPerQuery | 0; - if (object.forceMaxRequestsPerQuery !== undefined && object.forceMaxRequestsPerQuery !== null) - message.forceMaxRequestsPerQuery = Boolean(object.forceMaxRequestsPerQuery); - if (object.sortBatches !== undefined && object.sortBatches !== null) - message.sortBatches = Boolean(object.sortBatches); - if (object.maxDrawable !== undefined && object.maxDrawable !== null) - message.maxDrawable = object.maxDrawable | 0; - if (object.maxImagery !== undefined && object.maxImagery !== null) - message.maxImagery = object.maxImagery | 0; - if (object.maxTerrain !== undefined && object.maxTerrain !== null) - message.maxTerrain = object.maxTerrain | 0; - if (object.maxQuadtree !== undefined && object.maxQuadtree !== null) - message.maxQuadtree = object.maxQuadtree | 0; - if (object.maxDioramaMetadata !== undefined && object.maxDioramaMetadata !== null) - message.maxDioramaMetadata = object.maxDioramaMetadata | 0; - if (object.maxDioramaData !== undefined && object.maxDioramaData !== null) - message.maxDioramaData = object.maxDioramaData | 0; - if (object.maxConsumerFetchRatio !== undefined && object.maxConsumerFetchRatio !== null) - message.maxConsumerFetchRatio = Number(object.maxConsumerFetchRatio); - if (object.maxProEcFetchRatio !== undefined && object.maxProEcFetchRatio !== null) - message.maxProEcFetchRatio = Number(object.maxProEcFetchRatio); - if (object.safeOverallQps !== undefined && object.safeOverallQps !== null) - message.safeOverallQps = Number(object.safeOverallQps); - if (object.safeImageryQps !== undefined && object.safeImageryQps !== null) - message.safeImageryQps = Number(object.safeImageryQps); - if (object.domainsForHttps !== undefined && object.domainsForHttps !== null) - message.domainsForHttps = String(object.domainsForHttps); - if (object.hostsForHttp !== undefined && object.hostsForHttp !== null) - message.hostsForHttp = String(object.hostsForHttp); + var message = new $root.keyhole.dbroot.ZoomRangeProto(); + if (object.minZoom !== undefined && object.minZoom !== null) + message.minZoom = object.minZoom | 0; + if (object.maxZoom !== undefined && object.maxZoom !== null) + message.maxZoom = object.maxZoom | 0; return message; }; - FetchingOptionsProto.from = FetchingOptionsProto.fromObject; + ZoomRangeProto.from = ZoomRangeProto.fromObject; - FetchingOptionsProto.toObject = function toObject(message, options) { + ZoomRangeProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { - object.maxRequestsPerQuery = 1; - object.forceMaxRequestsPerQuery = false; - object.sortBatches = false; - object.maxDrawable = 2; - object.maxImagery = 2; - object.maxTerrain = 5; - object.maxQuadtree = 5; - object.maxDioramaMetadata = 1; - object.maxDioramaData = 0; - object.maxConsumerFetchRatio = 1; - object.maxProEcFetchRatio = 0; - object.safeOverallQps = 0; - object.safeImageryQps = 0; - object.domainsForHttps = "google.com gstatic.com"; - object.hostsForHttp = ""; + object.minZoom = 0; + object.maxZoom = 0; } - if (message.maxRequestsPerQuery !== undefined && message.maxRequestsPerQuery !== null && message.hasOwnProperty("maxRequestsPerQuery")) - object.maxRequestsPerQuery = message.maxRequestsPerQuery; - if (message.forceMaxRequestsPerQuery !== undefined && message.forceMaxRequestsPerQuery !== null && message.hasOwnProperty("forceMaxRequestsPerQuery")) - object.forceMaxRequestsPerQuery = message.forceMaxRequestsPerQuery; - if (message.sortBatches !== undefined && message.sortBatches !== null && message.hasOwnProperty("sortBatches")) - object.sortBatches = message.sortBatches; - if (message.maxDrawable !== undefined && message.maxDrawable !== null && message.hasOwnProperty("maxDrawable")) - object.maxDrawable = message.maxDrawable; - if (message.maxImagery !== undefined && message.maxImagery !== null && message.hasOwnProperty("maxImagery")) - object.maxImagery = message.maxImagery; - if (message.maxTerrain !== undefined && message.maxTerrain !== null && message.hasOwnProperty("maxTerrain")) - object.maxTerrain = message.maxTerrain; - if (message.maxQuadtree !== undefined && message.maxQuadtree !== null && message.hasOwnProperty("maxQuadtree")) - object.maxQuadtree = message.maxQuadtree; - if (message.maxDioramaMetadata !== undefined && message.maxDioramaMetadata !== null && message.hasOwnProperty("maxDioramaMetadata")) - object.maxDioramaMetadata = message.maxDioramaMetadata; - if (message.maxDioramaData !== undefined && message.maxDioramaData !== null && message.hasOwnProperty("maxDioramaData")) - object.maxDioramaData = message.maxDioramaData; - if (message.maxConsumerFetchRatio !== undefined && message.maxConsumerFetchRatio !== null && message.hasOwnProperty("maxConsumerFetchRatio")) - object.maxConsumerFetchRatio = message.maxConsumerFetchRatio; - if (message.maxProEcFetchRatio !== undefined && message.maxProEcFetchRatio !== null && message.hasOwnProperty("maxProEcFetchRatio")) - object.maxProEcFetchRatio = message.maxProEcFetchRatio; - if (message.safeOverallQps !== undefined && message.safeOverallQps !== null && message.hasOwnProperty("safeOverallQps")) - object.safeOverallQps = message.safeOverallQps; - if (message.safeImageryQps !== undefined && message.safeImageryQps !== null && message.hasOwnProperty("safeImageryQps")) - object.safeImageryQps = message.safeImageryQps; - if (message.domainsForHttps !== undefined && message.domainsForHttps !== null && message.hasOwnProperty("domainsForHttps")) - object.domainsForHttps = message.domainsForHttps; - if (message.hostsForHttp !== undefined && message.hostsForHttp !== null && message.hasOwnProperty("hostsForHttp")) - object.hostsForHttp = message.hostsForHttp; + if (message.minZoom !== undefined && message.minZoom !== null && message.hasOwnProperty("minZoom")) + object.minZoom = message.minZoom; + if (message.maxZoom !== undefined && message.maxZoom !== null && message.hasOwnProperty("maxZoom")) + object.maxZoom = message.maxZoom; return object; }; - FetchingOptionsProto.prototype.toObject = function toObject(options) { + ZoomRangeProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - FetchingOptionsProto.prototype.toJSON = function toJSON() { + ZoomRangeProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return FetchingOptionsProto; + return ZoomRangeProto; })(); - dbroot.TimeMachineOptionsProto = (function() { + dbroot.DrawFlagProto = (function() { - function TimeMachineOptionsProto(properties) { + function DrawFlagProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - TimeMachineOptionsProto.prototype.serverUrl = ""; - TimeMachineOptionsProto.prototype.isTimemachine = false; - TimeMachineOptionsProto.prototype.dwellTimeMs = 500; - TimeMachineOptionsProto.prototype.discoverabilityAltitudeMeters = 15000; + DrawFlagProto.prototype.drawFlagType = 1; - TimeMachineOptionsProto.decode = function decode(reader, length) { + var $types = { + 0 : "keyhole.dbroot.DrawFlagProto.DrawFlagType" + }; + $lazyTypes.push($types); + + DrawFlagProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.TimeMachineOptionsProto(); + message = new $root.keyhole.dbroot.DrawFlagProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.serverUrl = reader.string(); - break; - case 2: - message.isTimemachine = reader.bool(); - break; - case 3: - message.dwellTimeMs = reader.int32(); - break; - case 4: - message.discoverabilityAltitudeMeters = reader.int32(); + message.drawFlagType = reader.uint32(); break; default: reader.skipType(tag & 7); @@ -52496,109 +53573,124 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - TimeMachineOptionsProto.verify = function verify(message) { + DrawFlagProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.serverUrl !== undefined) - if (!$util.isString(message.serverUrl)) - return "serverUrl: string expected"; - if (message.isTimemachine !== undefined) - if (typeof message.isTimemachine !== "boolean") - return "isTimemachine: boolean expected"; - if (message.dwellTimeMs !== undefined) - if (!$util.isInteger(message.dwellTimeMs)) - return "dwellTimeMs: integer expected"; - if (message.discoverabilityAltitudeMeters !== undefined) - if (!$util.isInteger(message.discoverabilityAltitudeMeters)) - return "discoverabilityAltitudeMeters: integer expected"; + switch (message.drawFlagType) { + default: + return "drawFlagType: enum value expected"; + case 1: + case 2: + case 3: + case 4: + case 5: + break; + } return null; }; - TimeMachineOptionsProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.TimeMachineOptionsProto) + DrawFlagProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.DrawFlagProto) return object; - var message = new $root.keyhole.dbroot.TimeMachineOptionsProto(); - if (object.serverUrl !== undefined && object.serverUrl !== null) - message.serverUrl = String(object.serverUrl); - if (object.isTimemachine !== undefined && object.isTimemachine !== null) - message.isTimemachine = Boolean(object.isTimemachine); - if (object.dwellTimeMs !== undefined && object.dwellTimeMs !== null) - message.dwellTimeMs = object.dwellTimeMs | 0; - if (object.discoverabilityAltitudeMeters !== undefined && object.discoverabilityAltitudeMeters !== null) - message.discoverabilityAltitudeMeters = object.discoverabilityAltitudeMeters | 0; + var message = new $root.keyhole.dbroot.DrawFlagProto(); + switch (object.drawFlagType) { + case "TYPE_FILL_ONLY": + case 1: + message.drawFlagType = 1; + break; + case "TYPE_OUTLINE_ONLY": + case 2: + message.drawFlagType = 2; + break; + case "TYPE_FILL_AND_OUTLINE": + case 3: + message.drawFlagType = 3; + break; + case "TYPE_ANTIALIASING": + case 4: + message.drawFlagType = 4; + break; + case "TYPE_CENTER_LABEL": + case 5: + message.drawFlagType = 5; + break; + } return message; }; - TimeMachineOptionsProto.from = TimeMachineOptionsProto.fromObject; + DrawFlagProto.from = DrawFlagProto.fromObject; - TimeMachineOptionsProto.toObject = function toObject(message, options) { + DrawFlagProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) { - object.serverUrl = ""; - object.isTimemachine = false; - object.dwellTimeMs = 500; - object.discoverabilityAltitudeMeters = 15000; - } - if (message.serverUrl !== undefined && message.serverUrl !== null && message.hasOwnProperty("serverUrl")) - object.serverUrl = message.serverUrl; - if (message.isTimemachine !== undefined && message.isTimemachine !== null && message.hasOwnProperty("isTimemachine")) - object.isTimemachine = message.isTimemachine; - if (message.dwellTimeMs !== undefined && message.dwellTimeMs !== null && message.hasOwnProperty("dwellTimeMs")) - object.dwellTimeMs = message.dwellTimeMs; - if (message.discoverabilityAltitudeMeters !== undefined && message.discoverabilityAltitudeMeters !== null && message.hasOwnProperty("discoverabilityAltitudeMeters")) - object.discoverabilityAltitudeMeters = message.discoverabilityAltitudeMeters; + if (options.defaults) + object.drawFlagType = options.enums === String ? "TYPE_FILL_ONLY" : 1; + if (message.drawFlagType !== undefined && message.drawFlagType !== null && message.hasOwnProperty("drawFlagType")) + object.drawFlagType = options.enums === String ? $types[0][message.drawFlagType] : message.drawFlagType; return object; }; - TimeMachineOptionsProto.prototype.toObject = function toObject(options) { + DrawFlagProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - TimeMachineOptionsProto.prototype.toJSON = function toJSON() { + DrawFlagProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return TimeMachineOptionsProto; + DrawFlagProto.DrawFlagType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["TYPE_FILL_ONLY"] = 1; + values["TYPE_OUTLINE_ONLY"] = 2; + values["TYPE_FILL_AND_OUTLINE"] = 3; + values["TYPE_ANTIALIASING"] = 4; + values["TYPE_CENTER_LABEL"] = 5; + return values; + })(); + + return DrawFlagProto; })(); - dbroot.AutopiaOptionsProto = (function() { + dbroot.LayerProto = (function() { - function AutopiaOptionsProto(properties) { + function LayerProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - AutopiaOptionsProto.prototype.metadataServerUrl = "http://cbk0.google.com/cbk"; - AutopiaOptionsProto.prototype.depthmapServerUrl = "http://cbk0.google.com/cbk"; - AutopiaOptionsProto.prototype.coverageOverlayUrl = ""; - AutopiaOptionsProto.prototype.maxImageryQps = 0; - AutopiaOptionsProto.prototype.maxMetadataDepthmapQps = 0; + LayerProto.prototype.zoomRange = $util.emptyArray; + LayerProto.prototype.preserveTextLevel = 30; + LayerProto.prototype.lodBeginTransition = false; + LayerProto.prototype.lodEndTransition = false; - AutopiaOptionsProto.decode = function decode(reader, length) { + var $types = { + 0 : "keyhole.dbroot.ZoomRangeProto" + }; + $lazyTypes.push($types); + + LayerProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.AutopiaOptionsProto(); + message = new $root.keyhole.dbroot.LayerProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.metadataServerUrl = reader.string(); + if (!(message.zoomRange && message.zoomRange.length)) + message.zoomRange = []; + message.zoomRange.push($types[0].decode(reader, reader.uint32())); break; case 2: - message.depthmapServerUrl = reader.string(); - break; - case 3: - message.coverageOverlayUrl = reader.string(); + message.preserveTextLevel = reader.int32(); break; case 4: - message.maxImageryQps = reader.float(); + message.lodBeginTransition = reader.bool(); break; case 5: - message.maxMetadataDepthmapQps = reader.float(); + message.lodEndTransition = reader.bool(); break; default: reader.skipType(tag & 7); @@ -52608,105 +53700,111 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - AutopiaOptionsProto.verify = function verify(message) { + LayerProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.metadataServerUrl !== undefined) - if (!$util.isString(message.metadataServerUrl)) - return "metadataServerUrl: string expected"; - if (message.depthmapServerUrl !== undefined) - if (!$util.isString(message.depthmapServerUrl)) - return "depthmapServerUrl: string expected"; - if (message.coverageOverlayUrl !== undefined) - if (!$util.isString(message.coverageOverlayUrl)) - return "coverageOverlayUrl: string expected"; - if (message.maxImageryQps !== undefined) - if (typeof message.maxImageryQps !== "number") - return "maxImageryQps: number expected"; - if (message.maxMetadataDepthmapQps !== undefined) - if (typeof message.maxMetadataDepthmapQps !== "number") - return "maxMetadataDepthmapQps: number expected"; + if (message.zoomRange !== undefined) { + if (!Array.isArray(message.zoomRange)) + return "zoomRange: array expected"; + for (var i = 0; i < message.zoomRange.length; ++i) { + var error = $types[0].verify(message.zoomRange[i]); + if (error) + return "zoomRange." + error; + } + } + if (message.preserveTextLevel !== undefined) + if (!$util.isInteger(message.preserveTextLevel)) + return "preserveTextLevel: integer expected"; + if (message.lodBeginTransition !== undefined) + if (typeof message.lodBeginTransition !== "boolean") + return "lodBeginTransition: boolean expected"; + if (message.lodEndTransition !== undefined) + if (typeof message.lodEndTransition !== "boolean") + return "lodEndTransition: boolean expected"; return null; }; - AutopiaOptionsProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.AutopiaOptionsProto) + LayerProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.LayerProto) return object; - var message = new $root.keyhole.dbroot.AutopiaOptionsProto(); - if (object.metadataServerUrl !== undefined && object.metadataServerUrl !== null) - message.metadataServerUrl = String(object.metadataServerUrl); - if (object.depthmapServerUrl !== undefined && object.depthmapServerUrl !== null) - message.depthmapServerUrl = String(object.depthmapServerUrl); - if (object.coverageOverlayUrl !== undefined && object.coverageOverlayUrl !== null) - message.coverageOverlayUrl = String(object.coverageOverlayUrl); - if (object.maxImageryQps !== undefined && object.maxImageryQps !== null) - message.maxImageryQps = Number(object.maxImageryQps); - if (object.maxMetadataDepthmapQps !== undefined && object.maxMetadataDepthmapQps !== null) - message.maxMetadataDepthmapQps = Number(object.maxMetadataDepthmapQps); + var message = new $root.keyhole.dbroot.LayerProto(); + if (object.zoomRange) { + if (!Array.isArray(object.zoomRange)) + throw TypeError(".keyhole.dbroot.LayerProto.zoomRange: array expected"); + message.zoomRange = []; + for (var i = 0; i < object.zoomRange.length; ++i) { + if (typeof object.zoomRange[i] !== "object") + throw TypeError(".keyhole.dbroot.LayerProto.zoomRange: object expected"); + message.zoomRange[i] = $types[0].fromObject(object.zoomRange[i]); + } + } + if (object.preserveTextLevel !== undefined && object.preserveTextLevel !== null) + message.preserveTextLevel = object.preserveTextLevel | 0; + if (object.lodBeginTransition !== undefined && object.lodBeginTransition !== null) + message.lodBeginTransition = Boolean(object.lodBeginTransition); + if (object.lodEndTransition !== undefined && object.lodEndTransition !== null) + message.lodEndTransition = Boolean(object.lodEndTransition); return message; }; - AutopiaOptionsProto.from = AutopiaOptionsProto.fromObject; + LayerProto.from = LayerProto.fromObject; - AutopiaOptionsProto.toObject = function toObject(message, options) { + LayerProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.zoomRange = []; if (options.defaults) { - object.metadataServerUrl = "http://cbk0.google.com/cbk"; - object.depthmapServerUrl = "http://cbk0.google.com/cbk"; - object.coverageOverlayUrl = ""; - object.maxImageryQps = 0; - object.maxMetadataDepthmapQps = 0; + object.preserveTextLevel = 30; + object.lodBeginTransition = false; + object.lodEndTransition = false; } - if (message.metadataServerUrl !== undefined && message.metadataServerUrl !== null && message.hasOwnProperty("metadataServerUrl")) - object.metadataServerUrl = message.metadataServerUrl; - if (message.depthmapServerUrl !== undefined && message.depthmapServerUrl !== null && message.hasOwnProperty("depthmapServerUrl")) - object.depthmapServerUrl = message.depthmapServerUrl; - if (message.coverageOverlayUrl !== undefined && message.coverageOverlayUrl !== null && message.hasOwnProperty("coverageOverlayUrl")) - object.coverageOverlayUrl = message.coverageOverlayUrl; - if (message.maxImageryQps !== undefined && message.maxImageryQps !== null && message.hasOwnProperty("maxImageryQps")) - object.maxImageryQps = message.maxImageryQps; - if (message.maxMetadataDepthmapQps !== undefined && message.maxMetadataDepthmapQps !== null && message.hasOwnProperty("maxMetadataDepthmapQps")) - object.maxMetadataDepthmapQps = message.maxMetadataDepthmapQps; + if (message.zoomRange !== undefined && message.zoomRange !== null && message.hasOwnProperty("zoomRange")) { + object.zoomRange = []; + for (var j = 0; j < message.zoomRange.length; ++j) + object.zoomRange[j] = $types[0].toObject(message.zoomRange[j], options); + } + if (message.preserveTextLevel !== undefined && message.preserveTextLevel !== null && message.hasOwnProperty("preserveTextLevel")) + object.preserveTextLevel = message.preserveTextLevel; + if (message.lodBeginTransition !== undefined && message.lodBeginTransition !== null && message.hasOwnProperty("lodBeginTransition")) + object.lodBeginTransition = message.lodBeginTransition; + if (message.lodEndTransition !== undefined && message.lodEndTransition !== null && message.hasOwnProperty("lodEndTransition")) + object.lodEndTransition = message.lodEndTransition; return object; }; - AutopiaOptionsProto.prototype.toObject = function toObject(options) { + LayerProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - AutopiaOptionsProto.prototype.toJSON = function toJSON() { + LayerProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return AutopiaOptionsProto; + return LayerProto; })(); - dbroot.CSIOptionsProto = (function() { + dbroot.FolderProto = (function() { - function CSIOptionsProto(properties) { + function FolderProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - CSIOptionsProto.prototype.samplingPercentage = 0; - CSIOptionsProto.prototype.experimentId = ""; + FolderProto.prototype.isExpandable = true; - CSIOptionsProto.decode = function decode(reader, length) { + FolderProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.CSIOptionsProto(); + message = new $root.keyhole.dbroot.FolderProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.samplingPercentage = reader.int32(); - break; - case 2: - message.experimentId = reader.string(); + message.isExpandable = reader.bool(); break; default: reader.skipType(tag & 7); @@ -52716,106 +53814,84 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - CSIOptionsProto.verify = function verify(message) { + FolderProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.samplingPercentage !== undefined) - if (!$util.isInteger(message.samplingPercentage)) - return "samplingPercentage: integer expected"; - if (message.experimentId !== undefined) - if (!$util.isString(message.experimentId)) - return "experimentId: string expected"; + if (message.isExpandable !== undefined) + if (typeof message.isExpandable !== "boolean") + return "isExpandable: boolean expected"; return null; }; - CSIOptionsProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.CSIOptionsProto) + FolderProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.FolderProto) return object; - var message = new $root.keyhole.dbroot.CSIOptionsProto(); - if (object.samplingPercentage !== undefined && object.samplingPercentage !== null) - message.samplingPercentage = object.samplingPercentage | 0; - if (object.experimentId !== undefined && object.experimentId !== null) - message.experimentId = String(object.experimentId); - return message; + var message = new $root.keyhole.dbroot.FolderProto(); + if (object.isExpandable !== undefined && object.isExpandable !== null) + message.isExpandable = Boolean(object.isExpandable); + return message; }; - CSIOptionsProto.from = CSIOptionsProto.fromObject; + FolderProto.from = FolderProto.fromObject; - CSIOptionsProto.toObject = function toObject(message, options) { + FolderProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) { - object.samplingPercentage = 0; - object.experimentId = ""; - } - if (message.samplingPercentage !== undefined && message.samplingPercentage !== null && message.hasOwnProperty("samplingPercentage")) - object.samplingPercentage = message.samplingPercentage; - if (message.experimentId !== undefined && message.experimentId !== null && message.hasOwnProperty("experimentId")) - object.experimentId = message.experimentId; + if (options.defaults) + object.isExpandable = true; + if (message.isExpandable !== undefined && message.isExpandable !== null && message.hasOwnProperty("isExpandable")) + object.isExpandable = message.isExpandable; return object; }; - CSIOptionsProto.prototype.toObject = function toObject(options) { + FolderProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - CSIOptionsProto.prototype.toJSON = function toJSON() { + FolderProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return CSIOptionsProto; + return FolderProto; })(); - dbroot.SearchTabProto = (function() { + dbroot.RequirementProto = (function() { - function SearchTabProto(properties) { + function RequirementProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - SearchTabProto.prototype.isVisible = false; - SearchTabProto.prototype.tabLabel = null; - SearchTabProto.prototype.baseUrl = ""; - SearchTabProto.prototype.viewportPrefix = ""; - SearchTabProto.prototype.inputBox = $util.emptyArray; - SearchTabProto.prototype.requirement = null; - - var $types = { - 1 : "keyhole.dbroot.StringIdOrValueProto", - 4 : "keyhole.dbroot.SearchTabProto.InputBoxInfo", - 5 : "keyhole.dbroot.RequirementProto" - }; - $lazyTypes.push($types); + RequirementProto.prototype.requiredVram = ""; + RequirementProto.prototype.requiredClientVer = ""; + RequirementProto.prototype.probability = ""; + RequirementProto.prototype.requiredUserAgent = ""; + RequirementProto.prototype.requiredClientCapabilities = ""; - SearchTabProto.decode = function decode(reader, length) { + RequirementProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.SearchTabProto(); + message = new $root.keyhole.dbroot.RequirementProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { - case 1: - message.isVisible = reader.bool(); - break; - case 2: - message.tabLabel = $types[1].decode(reader, reader.uint32()); - break; case 3: - message.baseUrl = reader.string(); + message.requiredVram = reader.string(); break; case 4: - message.viewportPrefix = reader.string(); + message.requiredClientVer = reader.string(); break; case 5: - if (!(message.inputBox && message.inputBox.length)) - message.inputBox = []; - message.inputBox.push($types[4].decode(reader, reader.uint32())); + message.probability = reader.string(); break; case 6: - message.requirement = $types[5].decode(reader, reader.uint32()); + message.requiredUserAgent = reader.string(); + break; + case 7: + message.requiredClientCapabilities = reader.string(); break; default: reader.skipType(tag & 7); @@ -52825,262 +53901,117 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - SearchTabProto.verify = function verify(message) { + RequirementProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (typeof message.isVisible !== "boolean") - return "isVisible: boolean expected"; - if (message.tabLabel !== undefined && message.tabLabel !== null) { - var error = $types[1].verify(message.tabLabel); - if (error) - return "tabLabel." + error; - } - if (message.baseUrl !== undefined) - if (!$util.isString(message.baseUrl)) - return "baseUrl: string expected"; - if (message.viewportPrefix !== undefined) - if (!$util.isString(message.viewportPrefix)) - return "viewportPrefix: string expected"; - if (message.inputBox !== undefined) { - if (!Array.isArray(message.inputBox)) - return "inputBox: array expected"; - for (var i = 0; i < message.inputBox.length; ++i) { - var error = $types[4].verify(message.inputBox[i]); - if (error) - return "inputBox." + error; - } - } - if (message.requirement !== undefined && message.requirement !== null) { - var error = $types[5].verify(message.requirement); - if (error) - return "requirement." + error; - } + if (message.requiredVram !== undefined) + if (!$util.isString(message.requiredVram)) + return "requiredVram: string expected"; + if (message.requiredClientVer !== undefined) + if (!$util.isString(message.requiredClientVer)) + return "requiredClientVer: string expected"; + if (message.probability !== undefined) + if (!$util.isString(message.probability)) + return "probability: string expected"; + if (message.requiredUserAgent !== undefined) + if (!$util.isString(message.requiredUserAgent)) + return "requiredUserAgent: string expected"; + if (message.requiredClientCapabilities !== undefined) + if (!$util.isString(message.requiredClientCapabilities)) + return "requiredClientCapabilities: string expected"; return null; }; - SearchTabProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.SearchTabProto) + RequirementProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.RequirementProto) return object; - var message = new $root.keyhole.dbroot.SearchTabProto(); - if (object.isVisible !== undefined && object.isVisible !== null) - message.isVisible = Boolean(object.isVisible); - if (object.tabLabel !== undefined && object.tabLabel !== null) { - if (typeof object.tabLabel !== "object") - throw TypeError(".keyhole.dbroot.SearchTabProto.tabLabel: object expected"); - message.tabLabel = $types[1].fromObject(object.tabLabel); - } - if (object.baseUrl !== undefined && object.baseUrl !== null) - message.baseUrl = String(object.baseUrl); - if (object.viewportPrefix !== undefined && object.viewportPrefix !== null) - message.viewportPrefix = String(object.viewportPrefix); - if (object.inputBox) { - if (!Array.isArray(object.inputBox)) - throw TypeError(".keyhole.dbroot.SearchTabProto.inputBox: array expected"); - message.inputBox = []; - for (var i = 0; i < object.inputBox.length; ++i) { - if (typeof object.inputBox[i] !== "object") - throw TypeError(".keyhole.dbroot.SearchTabProto.inputBox: object expected"); - message.inputBox[i] = $types[4].fromObject(object.inputBox[i]); - } - } - if (object.requirement !== undefined && object.requirement !== null) { - if (typeof object.requirement !== "object") - throw TypeError(".keyhole.dbroot.SearchTabProto.requirement: object expected"); - message.requirement = $types[5].fromObject(object.requirement); - } + var message = new $root.keyhole.dbroot.RequirementProto(); + if (object.requiredVram !== undefined && object.requiredVram !== null) + message.requiredVram = String(object.requiredVram); + if (object.requiredClientVer !== undefined && object.requiredClientVer !== null) + message.requiredClientVer = String(object.requiredClientVer); + if (object.probability !== undefined && object.probability !== null) + message.probability = String(object.probability); + if (object.requiredUserAgent !== undefined && object.requiredUserAgent !== null) + message.requiredUserAgent = String(object.requiredUserAgent); + if (object.requiredClientCapabilities !== undefined && object.requiredClientCapabilities !== null) + message.requiredClientCapabilities = String(object.requiredClientCapabilities); return message; }; - SearchTabProto.from = SearchTabProto.fromObject; + RequirementProto.from = RequirementProto.fromObject; - SearchTabProto.toObject = function toObject(message, options) { + RequirementProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) - object.inputBox = []; if (options.defaults) { - object.isVisible = false; - object.tabLabel = null; - object.baseUrl = ""; - object.viewportPrefix = ""; - object.requirement = null; - } - if (message.isVisible !== undefined && message.isVisible !== null && message.hasOwnProperty("isVisible")) - object.isVisible = message.isVisible; - if (message.tabLabel !== undefined && message.tabLabel !== null && message.hasOwnProperty("tabLabel")) - object.tabLabel = $types[1].toObject(message.tabLabel, options); - if (message.baseUrl !== undefined && message.baseUrl !== null && message.hasOwnProperty("baseUrl")) - object.baseUrl = message.baseUrl; - if (message.viewportPrefix !== undefined && message.viewportPrefix !== null && message.hasOwnProperty("viewportPrefix")) - object.viewportPrefix = message.viewportPrefix; - if (message.inputBox !== undefined && message.inputBox !== null && message.hasOwnProperty("inputBox")) { - object.inputBox = []; - for (var j = 0; j < message.inputBox.length; ++j) - object.inputBox[j] = $types[4].toObject(message.inputBox[j], options); + object.requiredVram = ""; + object.requiredClientVer = ""; + object.probability = ""; + object.requiredUserAgent = ""; + object.requiredClientCapabilities = ""; } - if (message.requirement !== undefined && message.requirement !== null && message.hasOwnProperty("requirement")) - object.requirement = $types[5].toObject(message.requirement, options); + if (message.requiredVram !== undefined && message.requiredVram !== null && message.hasOwnProperty("requiredVram")) + object.requiredVram = message.requiredVram; + if (message.requiredClientVer !== undefined && message.requiredClientVer !== null && message.hasOwnProperty("requiredClientVer")) + object.requiredClientVer = message.requiredClientVer; + if (message.probability !== undefined && message.probability !== null && message.hasOwnProperty("probability")) + object.probability = message.probability; + if (message.requiredUserAgent !== undefined && message.requiredUserAgent !== null && message.hasOwnProperty("requiredUserAgent")) + object.requiredUserAgent = message.requiredUserAgent; + if (message.requiredClientCapabilities !== undefined && message.requiredClientCapabilities !== null && message.hasOwnProperty("requiredClientCapabilities")) + object.requiredClientCapabilities = message.requiredClientCapabilities; return object; }; - SearchTabProto.prototype.toObject = function toObject(options) { + RequirementProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - SearchTabProto.prototype.toJSON = function toJSON() { + RequirementProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - SearchTabProto.InputBoxInfo = (function() { - - function InputBoxInfo(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - InputBoxInfo.prototype.label = null; - InputBoxInfo.prototype.queryVerb = ""; - InputBoxInfo.prototype.queryPrepend = ""; - - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); - - InputBoxInfo.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.SearchTabProto.InputBoxInfo(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.label = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.queryVerb = reader.string(); - break; - case 3: - message.queryPrepend = reader.string(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - InputBoxInfo.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - var error = $types[0].verify(message.label); - if (error) - return "label." + error; - if (!$util.isString(message.queryVerb)) - return "queryVerb: string expected"; - if (message.queryPrepend !== undefined) - if (!$util.isString(message.queryPrepend)) - return "queryPrepend: string expected"; - return null; - }; - - InputBoxInfo.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.SearchTabProto.InputBoxInfo) - return object; - var message = new $root.keyhole.dbroot.SearchTabProto.InputBoxInfo(); - if (object.label !== undefined && object.label !== null) { - if (typeof object.label !== "object") - throw TypeError(".keyhole.dbroot.SearchTabProto.InputBoxInfo.label: object expected"); - message.label = $types[0].fromObject(object.label); - } - if (object.queryVerb !== undefined && object.queryVerb !== null) - message.queryVerb = String(object.queryVerb); - if (object.queryPrepend !== undefined && object.queryPrepend !== null) - message.queryPrepend = String(object.queryPrepend); - return message; - }; - - InputBoxInfo.from = InputBoxInfo.fromObject; - - InputBoxInfo.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.label = null; - object.queryVerb = ""; - object.queryPrepend = ""; - } - if (message.label !== undefined && message.label !== null && message.hasOwnProperty("label")) - object.label = $types[0].toObject(message.label, options); - if (message.queryVerb !== undefined && message.queryVerb !== null && message.hasOwnProperty("queryVerb")) - object.queryVerb = message.queryVerb; - if (message.queryPrepend !== undefined && message.queryPrepend !== null && message.hasOwnProperty("queryPrepend")) - object.queryPrepend = message.queryPrepend; - return object; - }; - - InputBoxInfo.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - InputBoxInfo.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return InputBoxInfo; - })(); - - return SearchTabProto; + return RequirementProto; })(); - dbroot.CobrandProto = (function() { + dbroot.LookAtProto = (function() { - function CobrandProto(properties) { + function LookAtProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - CobrandProto.prototype.logoUrl = ""; - CobrandProto.prototype.xCoord = null; - CobrandProto.prototype.yCoord = null; - CobrandProto.prototype.tiePoint = 6; - CobrandProto.prototype.screenSize = 0; - - var $types = { - 1 : "keyhole.dbroot.CobrandProto.Coord", - 2 : "keyhole.dbroot.CobrandProto.Coord", - 3 : "keyhole.dbroot.CobrandProto.TiePoint" - }; - $lazyTypes.push($types); + LookAtProto.prototype.longitude = 0; + LookAtProto.prototype.latitude = 0; + LookAtProto.prototype.range = 0; + LookAtProto.prototype.tilt = 0; + LookAtProto.prototype.heading = 0; - CobrandProto.decode = function decode(reader, length) { + LookAtProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.CobrandProto(); + message = new $root.keyhole.dbroot.LookAtProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.logoUrl = reader.string(); + message.longitude = reader.float(); break; case 2: - message.xCoord = $types[1].decode(reader, reader.uint32()); + message.latitude = reader.float(); break; case 3: - message.yCoord = $types[2].decode(reader, reader.uint32()); + message.range = reader.float(); break; case 4: - message.tiePoint = reader.uint32(); + message.tilt = reader.float(); break; case 5: - message.screenSize = reader.double(); + message.heading = reader.float(); break; default: reader.skipType(tag & 7); @@ -53090,265 +54021,191 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - CobrandProto.verify = function verify(message) { + LookAtProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (!$util.isString(message.logoUrl)) - return "logoUrl: string expected"; - if (message.xCoord !== undefined && message.xCoord !== null) { - var error = $types[1].verify(message.xCoord); - if (error) - return "xCoord." + error; - } - if (message.yCoord !== undefined && message.yCoord !== null) { - var error = $types[2].verify(message.yCoord); - if (error) - return "yCoord." + error; - } - if (message.tiePoint !== undefined) - switch (message.tiePoint) { - default: - return "tiePoint: enum value expected"; - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - break; - } - if (message.screenSize !== undefined) - if (typeof message.screenSize !== "number") - return "screenSize: number expected"; + if (typeof message.longitude !== "number") + return "longitude: number expected"; + if (typeof message.latitude !== "number") + return "latitude: number expected"; + if (message.range !== undefined) + if (typeof message.range !== "number") + return "range: number expected"; + if (message.tilt !== undefined) + if (typeof message.tilt !== "number") + return "tilt: number expected"; + if (message.heading !== undefined) + if (typeof message.heading !== "number") + return "heading: number expected"; return null; }; - CobrandProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.CobrandProto) + LookAtProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.LookAtProto) return object; - var message = new $root.keyhole.dbroot.CobrandProto(); - if (object.logoUrl !== undefined && object.logoUrl !== null) - message.logoUrl = String(object.logoUrl); - if (object.xCoord !== undefined && object.xCoord !== null) { - if (typeof object.xCoord !== "object") - throw TypeError(".keyhole.dbroot.CobrandProto.xCoord: object expected"); - message.xCoord = $types[1].fromObject(object.xCoord); - } - if (object.yCoord !== undefined && object.yCoord !== null) { - if (typeof object.yCoord !== "object") - throw TypeError(".keyhole.dbroot.CobrandProto.yCoord: object expected"); - message.yCoord = $types[2].fromObject(object.yCoord); - } - switch (object.tiePoint) { - case "TOP_LEFT": - case 0: - message.tiePoint = 0; - break; - case "TOP_CENTER": - case 1: - message.tiePoint = 1; - break; - case "TOP_RIGHT": - case 2: - message.tiePoint = 2; - break; - case "MID_LEFT": - case 3: - message.tiePoint = 3; - break; - case "MID_CENTER": - case 4: - message.tiePoint = 4; - break; - case "MID_RIGHT": - case 5: - message.tiePoint = 5; - break; - case "BOTTOM_LEFT": - case 6: - message.tiePoint = 6; - break; - case "BOTTOM_CENTER": - case 7: - message.tiePoint = 7; - break; - case "BOTTOM_RIGHT": - case 8: - message.tiePoint = 8; - break; - } - if (object.screenSize !== undefined && object.screenSize !== null) - message.screenSize = Number(object.screenSize); + var message = new $root.keyhole.dbroot.LookAtProto(); + if (object.longitude !== undefined && object.longitude !== null) + message.longitude = Number(object.longitude); + if (object.latitude !== undefined && object.latitude !== null) + message.latitude = Number(object.latitude); + if (object.range !== undefined && object.range !== null) + message.range = Number(object.range); + if (object.tilt !== undefined && object.tilt !== null) + message.tilt = Number(object.tilt); + if (object.heading !== undefined && object.heading !== null) + message.heading = Number(object.heading); return message; }; - CobrandProto.from = CobrandProto.fromObject; + LookAtProto.from = LookAtProto.fromObject; - CobrandProto.toObject = function toObject(message, options) { + LookAtProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { - object.logoUrl = ""; - object.xCoord = null; - object.yCoord = null; - object.tiePoint = options.enums === String ? "BOTTOM_LEFT" : 6; - object.screenSize = 0; + object.longitude = 0; + object.latitude = 0; + object.range = 0; + object.tilt = 0; + object.heading = 0; } - if (message.logoUrl !== undefined && message.logoUrl !== null && message.hasOwnProperty("logoUrl")) - object.logoUrl = message.logoUrl; - if (message.xCoord !== undefined && message.xCoord !== null && message.hasOwnProperty("xCoord")) - object.xCoord = $types[1].toObject(message.xCoord, options); - if (message.yCoord !== undefined && message.yCoord !== null && message.hasOwnProperty("yCoord")) - object.yCoord = $types[2].toObject(message.yCoord, options); - if (message.tiePoint !== undefined && message.tiePoint !== null && message.hasOwnProperty("tiePoint")) - object.tiePoint = options.enums === String ? $types[3][message.tiePoint] : message.tiePoint; - if (message.screenSize !== undefined && message.screenSize !== null && message.hasOwnProperty("screenSize")) - object.screenSize = message.screenSize; + if (message.longitude !== undefined && message.longitude !== null && message.hasOwnProperty("longitude")) + object.longitude = message.longitude; + if (message.latitude !== undefined && message.latitude !== null && message.hasOwnProperty("latitude")) + object.latitude = message.latitude; + if (message.range !== undefined && message.range !== null && message.hasOwnProperty("range")) + object.range = message.range; + if (message.tilt !== undefined && message.tilt !== null && message.hasOwnProperty("tilt")) + object.tilt = message.tilt; + if (message.heading !== undefined && message.heading !== null && message.hasOwnProperty("heading")) + object.heading = message.heading; return object; }; - CobrandProto.prototype.toObject = function toObject(options) { + LookAtProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - CobrandProto.prototype.toJSON = function toJSON() { + LookAtProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - CobrandProto.Coord = (function() { - - function Coord(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - Coord.prototype.value = 0; - Coord.prototype.isRelative = false; - - Coord.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.CobrandProto.Coord(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.value = reader.double(); - break; - case 2: - message.isRelative = reader.bool(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - Coord.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (typeof message.value !== "number") - return "value: number expected"; - if (message.isRelative !== undefined) - if (typeof message.isRelative !== "boolean") - return "isRelative: boolean expected"; - return null; - }; - - Coord.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.CobrandProto.Coord) - return object; - var message = new $root.keyhole.dbroot.CobrandProto.Coord(); - if (object.value !== undefined && object.value !== null) - message.value = Number(object.value); - if (object.isRelative !== undefined && object.isRelative !== null) - message.isRelative = Boolean(object.isRelative); - return message; - }; - - Coord.from = Coord.fromObject; - - Coord.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.value = 0; - object.isRelative = false; - } - if (message.value !== undefined && message.value !== null && message.hasOwnProperty("value")) - object.value = message.value; - if (message.isRelative !== undefined && message.isRelative !== null && message.hasOwnProperty("isRelative")) - object.isRelative = message.isRelative; - return object; - }; - - Coord.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - Coord.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Coord; - })(); - - CobrandProto.TiePoint = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["TOP_LEFT"] = 0; - values["TOP_CENTER"] = 1; - values["TOP_RIGHT"] = 2; - values["MID_LEFT"] = 3; - values["MID_CENTER"] = 4; - values["MID_RIGHT"] = 5; - values["BOTTOM_LEFT"] = 6; - values["BOTTOM_CENTER"] = 7; - values["BOTTOM_RIGHT"] = 8; - return values; - })(); - - return CobrandProto; + return LookAtProto; })(); - dbroot.DatabaseDescriptionProto = (function() { + dbroot.NestedFeatureProto = (function() { - function DatabaseDescriptionProto(properties) { + function NestedFeatureProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - DatabaseDescriptionProto.prototype.databaseName = null; - DatabaseDescriptionProto.prototype.databaseUrl = ""; + NestedFeatureProto.prototype.featureType = 1; + NestedFeatureProto.prototype.kmlUrl = null; + NestedFeatureProto.prototype.databaseUrl = ""; + NestedFeatureProto.prototype.layer = null; + NestedFeatureProto.prototype.folder = null; + NestedFeatureProto.prototype.requirement = null; + NestedFeatureProto.prototype.channelId = 0; + NestedFeatureProto.prototype.displayName = null; + NestedFeatureProto.prototype.isVisible = true; + NestedFeatureProto.prototype.isEnabled = true; + NestedFeatureProto.prototype.isChecked = false; + NestedFeatureProto.prototype.layerMenuIconPath = "icons/773_l.png"; + NestedFeatureProto.prototype.description = null; + NestedFeatureProto.prototype.lookAt = null; + NestedFeatureProto.prototype.assetUuid = ""; + NestedFeatureProto.prototype.isSaveLocked = true; + NestedFeatureProto.prototype.children = $util.emptyArray; + NestedFeatureProto.prototype.clientConfigScriptName = ""; + NestedFeatureProto.prototype.dioramaDataChannelBase = -1; + NestedFeatureProto.prototype.replicaDataChannelBase = -1; var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto" + 0 : "keyhole.dbroot.NestedFeatureProto.FeatureType", + 1 : "keyhole.dbroot.StringIdOrValueProto", + 3 : "keyhole.dbroot.LayerProto", + 4 : "keyhole.dbroot.FolderProto", + 5 : "keyhole.dbroot.RequirementProto", + 7 : "keyhole.dbroot.StringIdOrValueProto", + 12 : "keyhole.dbroot.StringIdOrValueProto", + 13 : "keyhole.dbroot.LookAtProto", + 16 : "keyhole.dbroot.NestedFeatureProto" }; $lazyTypes.push($types); - DatabaseDescriptionProto.decode = function decode(reader, length) { + NestedFeatureProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.DatabaseDescriptionProto(); + message = new $root.keyhole.dbroot.NestedFeatureProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.databaseName = $types[0].decode(reader, reader.uint32()); + message.featureType = reader.uint32(); break; case 2: + message.kmlUrl = $types[1].decode(reader, reader.uint32()); + break; + case 21: message.databaseUrl = reader.string(); break; + case 3: + message.layer = $types[3].decode(reader, reader.uint32()); + break; + case 4: + message.folder = $types[4].decode(reader, reader.uint32()); + break; + case 5: + message.requirement = $types[5].decode(reader, reader.uint32()); + break; + case 6: + message.channelId = reader.int32(); + break; + case 7: + message.displayName = $types[7].decode(reader, reader.uint32()); + break; + case 8: + message.isVisible = reader.bool(); + break; + case 9: + message.isEnabled = reader.bool(); + break; + case 10: + message.isChecked = reader.bool(); + break; + case 11: + message.layerMenuIconPath = reader.string(); + break; + case 12: + message.description = $types[12].decode(reader, reader.uint32()); + break; + case 13: + message.lookAt = $types[13].decode(reader, reader.uint32()); + break; + case 15: + message.assetUuid = reader.string(); + break; + case 16: + message.isSaveLocked = reader.bool(); + break; + case 17: + if (!(message.children && message.children.length)) + message.children = []; + message.children.push($types[16].decode(reader, reader.uint32())); + break; + case 18: + message.clientConfigScriptName = reader.string(); + break; + case 19: + message.dioramaDataChannelBase = reader.int32(); + break; + case 20: + message.replicaDataChannelBase = reader.int32(); + break; default: reader.skipType(tag & 7); break; @@ -53357,85 +54214,325 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - DatabaseDescriptionProto.verify = function verify(message) { + NestedFeatureProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.databaseName !== undefined && message.databaseName !== null) { - var error = $types[0].verify(message.databaseName); + if (message.featureType !== undefined) + switch (message.featureType) { + default: + return "featureType: enum value expected"; + case 1: + case 2: + case 3: + case 4: + break; + } + if (message.kmlUrl !== undefined && message.kmlUrl !== null) { + var error = $types[1].verify(message.kmlUrl); if (error) - return "databaseName." + error; + return "kmlUrl." + error; } - if (!$util.isString(message.databaseUrl)) - return "databaseUrl: string expected"; + if (message.databaseUrl !== undefined) + if (!$util.isString(message.databaseUrl)) + return "databaseUrl: string expected"; + if (message.layer !== undefined && message.layer !== null) { + var error = $types[3].verify(message.layer); + if (error) + return "layer." + error; + } + if (message.folder !== undefined && message.folder !== null) { + var error = $types[4].verify(message.folder); + if (error) + return "folder." + error; + } + if (message.requirement !== undefined && message.requirement !== null) { + var error = $types[5].verify(message.requirement); + if (error) + return "requirement." + error; + } + if (!$util.isInteger(message.channelId)) + return "channelId: integer expected"; + if (message.displayName !== undefined && message.displayName !== null) { + var error = $types[7].verify(message.displayName); + if (error) + return "displayName." + error; + } + if (message.isVisible !== undefined) + if (typeof message.isVisible !== "boolean") + return "isVisible: boolean expected"; + if (message.isEnabled !== undefined) + if (typeof message.isEnabled !== "boolean") + return "isEnabled: boolean expected"; + if (message.isChecked !== undefined) + if (typeof message.isChecked !== "boolean") + return "isChecked: boolean expected"; + if (message.layerMenuIconPath !== undefined) + if (!$util.isString(message.layerMenuIconPath)) + return "layerMenuIconPath: string expected"; + if (message.description !== undefined && message.description !== null) { + var error = $types[12].verify(message.description); + if (error) + return "description." + error; + } + if (message.lookAt !== undefined && message.lookAt !== null) { + var error = $types[13].verify(message.lookAt); + if (error) + return "lookAt." + error; + } + if (message.assetUuid !== undefined) + if (!$util.isString(message.assetUuid)) + return "assetUuid: string expected"; + if (message.isSaveLocked !== undefined) + if (typeof message.isSaveLocked !== "boolean") + return "isSaveLocked: boolean expected"; + if (message.children !== undefined) { + if (!Array.isArray(message.children)) + return "children: array expected"; + for (var i = 0; i < message.children.length; ++i) { + var error = $types[16].verify(message.children[i]); + if (error) + return "children." + error; + } + } + if (message.clientConfigScriptName !== undefined) + if (!$util.isString(message.clientConfigScriptName)) + return "clientConfigScriptName: string expected"; + if (message.dioramaDataChannelBase !== undefined) + if (!$util.isInteger(message.dioramaDataChannelBase)) + return "dioramaDataChannelBase: integer expected"; + if (message.replicaDataChannelBase !== undefined) + if (!$util.isInteger(message.replicaDataChannelBase)) + return "replicaDataChannelBase: integer expected"; return null; }; - DatabaseDescriptionProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.DatabaseDescriptionProto) + NestedFeatureProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.NestedFeatureProto) return object; - var message = new $root.keyhole.dbroot.DatabaseDescriptionProto(); - if (object.databaseName !== undefined && object.databaseName !== null) { - if (typeof object.databaseName !== "object") - throw TypeError(".keyhole.dbroot.DatabaseDescriptionProto.databaseName: object expected"); - message.databaseName = $types[0].fromObject(object.databaseName); + var message = new $root.keyhole.dbroot.NestedFeatureProto(); + switch (object.featureType) { + case "TYPE_POINT_Z": + case 1: + message.featureType = 1; + break; + case "TYPE_POLYGON_Z": + case 2: + message.featureType = 2; + break; + case "TYPE_LINE_Z": + case 3: + message.featureType = 3; + break; + case "TYPE_TERRAIN": + case 4: + message.featureType = 4; + break; + } + if (object.kmlUrl !== undefined && object.kmlUrl !== null) { + if (typeof object.kmlUrl !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.kmlUrl: object expected"); + message.kmlUrl = $types[1].fromObject(object.kmlUrl); } if (object.databaseUrl !== undefined && object.databaseUrl !== null) message.databaseUrl = String(object.databaseUrl); + if (object.layer !== undefined && object.layer !== null) { + if (typeof object.layer !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.layer: object expected"); + message.layer = $types[3].fromObject(object.layer); + } + if (object.folder !== undefined && object.folder !== null) { + if (typeof object.folder !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.folder: object expected"); + message.folder = $types[4].fromObject(object.folder); + } + if (object.requirement !== undefined && object.requirement !== null) { + if (typeof object.requirement !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.requirement: object expected"); + message.requirement = $types[5].fromObject(object.requirement); + } + if (object.channelId !== undefined && object.channelId !== null) + message.channelId = object.channelId | 0; + if (object.displayName !== undefined && object.displayName !== null) { + if (typeof object.displayName !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.displayName: object expected"); + message.displayName = $types[7].fromObject(object.displayName); + } + if (object.isVisible !== undefined && object.isVisible !== null) + message.isVisible = Boolean(object.isVisible); + if (object.isEnabled !== undefined && object.isEnabled !== null) + message.isEnabled = Boolean(object.isEnabled); + if (object.isChecked !== undefined && object.isChecked !== null) + message.isChecked = Boolean(object.isChecked); + if (object.layerMenuIconPath !== undefined && object.layerMenuIconPath !== null) + message.layerMenuIconPath = String(object.layerMenuIconPath); + if (object.description !== undefined && object.description !== null) { + if (typeof object.description !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.description: object expected"); + message.description = $types[12].fromObject(object.description); + } + if (object.lookAt !== undefined && object.lookAt !== null) { + if (typeof object.lookAt !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.lookAt: object expected"); + message.lookAt = $types[13].fromObject(object.lookAt); + } + if (object.assetUuid !== undefined && object.assetUuid !== null) + message.assetUuid = String(object.assetUuid); + if (object.isSaveLocked !== undefined && object.isSaveLocked !== null) + message.isSaveLocked = Boolean(object.isSaveLocked); + if (object.children) { + if (!Array.isArray(object.children)) + throw TypeError(".keyhole.dbroot.NestedFeatureProto.children: array expected"); + message.children = []; + for (var i = 0; i < object.children.length; ++i) { + if (typeof object.children[i] !== "object") + throw TypeError(".keyhole.dbroot.NestedFeatureProto.children: object expected"); + message.children[i] = $types[16].fromObject(object.children[i]); + } + } + if (object.clientConfigScriptName !== undefined && object.clientConfigScriptName !== null) + message.clientConfigScriptName = String(object.clientConfigScriptName); + if (object.dioramaDataChannelBase !== undefined && object.dioramaDataChannelBase !== null) + message.dioramaDataChannelBase = object.dioramaDataChannelBase | 0; + if (object.replicaDataChannelBase !== undefined && object.replicaDataChannelBase !== null) + message.replicaDataChannelBase = object.replicaDataChannelBase | 0; return message; }; - DatabaseDescriptionProto.from = DatabaseDescriptionProto.fromObject; + NestedFeatureProto.from = NestedFeatureProto.fromObject; - DatabaseDescriptionProto.toObject = function toObject(message, options) { + NestedFeatureProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.children = []; if (options.defaults) { - object.databaseName = null; + object.featureType = options.enums === String ? "TYPE_POINT_Z" : 1; + object.kmlUrl = null; object.databaseUrl = ""; + object.layer = null; + object.folder = null; + object.requirement = null; + object.channelId = 0; + object.displayName = null; + object.isVisible = true; + object.isEnabled = true; + object.isChecked = false; + object.layerMenuIconPath = "icons/773_l.png"; + object.description = null; + object.lookAt = null; + object.assetUuid = ""; + object.isSaveLocked = true; + object.clientConfigScriptName = ""; + object.dioramaDataChannelBase = -1; + object.replicaDataChannelBase = -1; } - if (message.databaseName !== undefined && message.databaseName !== null && message.hasOwnProperty("databaseName")) - object.databaseName = $types[0].toObject(message.databaseName, options); + if (message.featureType !== undefined && message.featureType !== null && message.hasOwnProperty("featureType")) + object.featureType = options.enums === String ? $types[0][message.featureType] : message.featureType; + if (message.kmlUrl !== undefined && message.kmlUrl !== null && message.hasOwnProperty("kmlUrl")) + object.kmlUrl = $types[1].toObject(message.kmlUrl, options); if (message.databaseUrl !== undefined && message.databaseUrl !== null && message.hasOwnProperty("databaseUrl")) object.databaseUrl = message.databaseUrl; + if (message.layer !== undefined && message.layer !== null && message.hasOwnProperty("layer")) + object.layer = $types[3].toObject(message.layer, options); + if (message.folder !== undefined && message.folder !== null && message.hasOwnProperty("folder")) + object.folder = $types[4].toObject(message.folder, options); + if (message.requirement !== undefined && message.requirement !== null && message.hasOwnProperty("requirement")) + object.requirement = $types[5].toObject(message.requirement, options); + if (message.channelId !== undefined && message.channelId !== null && message.hasOwnProperty("channelId")) + object.channelId = message.channelId; + if (message.displayName !== undefined && message.displayName !== null && message.hasOwnProperty("displayName")) + object.displayName = $types[7].toObject(message.displayName, options); + if (message.isVisible !== undefined && message.isVisible !== null && message.hasOwnProperty("isVisible")) + object.isVisible = message.isVisible; + if (message.isEnabled !== undefined && message.isEnabled !== null && message.hasOwnProperty("isEnabled")) + object.isEnabled = message.isEnabled; + if (message.isChecked !== undefined && message.isChecked !== null && message.hasOwnProperty("isChecked")) + object.isChecked = message.isChecked; + if (message.layerMenuIconPath !== undefined && message.layerMenuIconPath !== null && message.hasOwnProperty("layerMenuIconPath")) + object.layerMenuIconPath = message.layerMenuIconPath; + if (message.description !== undefined && message.description !== null && message.hasOwnProperty("description")) + object.description = $types[12].toObject(message.description, options); + if (message.lookAt !== undefined && message.lookAt !== null && message.hasOwnProperty("lookAt")) + object.lookAt = $types[13].toObject(message.lookAt, options); + if (message.assetUuid !== undefined && message.assetUuid !== null && message.hasOwnProperty("assetUuid")) + object.assetUuid = message.assetUuid; + if (message.isSaveLocked !== undefined && message.isSaveLocked !== null && message.hasOwnProperty("isSaveLocked")) + object.isSaveLocked = message.isSaveLocked; + if (message.children !== undefined && message.children !== null && message.hasOwnProperty("children")) { + object.children = []; + for (var j = 0; j < message.children.length; ++j) + object.children[j] = $types[16].toObject(message.children[j], options); + } + if (message.clientConfigScriptName !== undefined && message.clientConfigScriptName !== null && message.hasOwnProperty("clientConfigScriptName")) + object.clientConfigScriptName = message.clientConfigScriptName; + if (message.dioramaDataChannelBase !== undefined && message.dioramaDataChannelBase !== null && message.hasOwnProperty("dioramaDataChannelBase")) + object.dioramaDataChannelBase = message.dioramaDataChannelBase; + if (message.replicaDataChannelBase !== undefined && message.replicaDataChannelBase !== null && message.hasOwnProperty("replicaDataChannelBase")) + object.replicaDataChannelBase = message.replicaDataChannelBase; return object; }; - DatabaseDescriptionProto.prototype.toObject = function toObject(options) { + NestedFeatureProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - DatabaseDescriptionProto.prototype.toJSON = function toJSON() { + NestedFeatureProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return DatabaseDescriptionProto; + NestedFeatureProto.FeatureType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["TYPE_POINT_Z"] = 1; + values["TYPE_POLYGON_Z"] = 2; + values["TYPE_LINE_Z"] = 3; + values["TYPE_TERRAIN"] = 4; + return values; + })(); + + return NestedFeatureProto; })(); - dbroot.ConfigScriptProto = (function() { + dbroot.MfeDomainFeaturesProto = (function() { - function ConfigScriptProto(properties) { + function MfeDomainFeaturesProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - ConfigScriptProto.prototype.scriptName = ""; - ConfigScriptProto.prototype.scriptData = ""; + MfeDomainFeaturesProto.prototype.countryCode = ""; + MfeDomainFeaturesProto.prototype.domainName = ""; + MfeDomainFeaturesProto.prototype.supportedFeatures = $util.emptyArray; - ConfigScriptProto.decode = function decode(reader, length) { + var $types = { + 2 : "keyhole.dbroot.MfeDomainFeaturesProto.SupportedFeature" + }; + $lazyTypes.push($types); + + MfeDomainFeaturesProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.ConfigScriptProto(); + message = new $root.keyhole.dbroot.MfeDomainFeaturesProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.scriptName = reader.string(); + message.countryCode = reader.string(); break; case 2: - message.scriptData = reader.string(); + message.domainName = reader.string(); + break; + case 3: + if (!(message.supportedFeatures && message.supportedFeatures.length)) + message.supportedFeatures = []; + if ((tag & 7) === 2) { + var end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) + message.supportedFeatures.push(reader.uint32()); + } else + message.supportedFeatures.push(reader.uint32()); break; default: reader.skipType(tag & 7); @@ -53445,166 +54542,191 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - ConfigScriptProto.verify = function verify(message) { + MfeDomainFeaturesProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (!$util.isString(message.scriptName)) - return "scriptName: string expected"; - if (!$util.isString(message.scriptData)) - return "scriptData: string expected"; + if (!$util.isString(message.countryCode)) + return "countryCode: string expected"; + if (!$util.isString(message.domainName)) + return "domainName: string expected"; + if (message.supportedFeatures !== undefined) { + if (!Array.isArray(message.supportedFeatures)) + return "supportedFeatures: array expected"; + for (var i = 0; i < message.supportedFeatures.length; ++i) + switch (message.supportedFeatures[i]) { + default: + return "supportedFeatures: enum value[] expected"; + case 0: + case 1: + case 2: + break; + } + } return null; }; - ConfigScriptProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.ConfigScriptProto) + MfeDomainFeaturesProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.MfeDomainFeaturesProto) return object; - var message = new $root.keyhole.dbroot.ConfigScriptProto(); - if (object.scriptName !== undefined && object.scriptName !== null) - message.scriptName = String(object.scriptName); - if (object.scriptData !== undefined && object.scriptData !== null) - message.scriptData = String(object.scriptData); - return message; - }; - - ConfigScriptProto.from = ConfigScriptProto.fromObject; - - ConfigScriptProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.scriptName = ""; - object.scriptData = ""; - } - if (message.scriptName !== undefined && message.scriptName !== null && message.hasOwnProperty("scriptName")) - object.scriptName = message.scriptName; - if (message.scriptData !== undefined && message.scriptData !== null && message.hasOwnProperty("scriptData")) - object.scriptData = message.scriptData; - return object; - }; - - ConfigScriptProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - ConfigScriptProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ConfigScriptProto; - })(); - - dbroot.SwoopParamsProto = (function() { - - function SwoopParamsProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - SwoopParamsProto.prototype.startDistInMeters = 0; - - SwoopParamsProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.SwoopParamsProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.startDistInMeters = reader.double(); - break; - default: - reader.skipType(tag & 7); - break; - } + var message = new $root.keyhole.dbroot.MfeDomainFeaturesProto(); + if (object.countryCode !== undefined && object.countryCode !== null) + message.countryCode = String(object.countryCode); + if (object.domainName !== undefined && object.domainName !== null) + message.domainName = String(object.domainName); + if (object.supportedFeatures) { + if (!Array.isArray(object.supportedFeatures)) + throw TypeError(".keyhole.dbroot.MfeDomainFeaturesProto.supportedFeatures: array expected"); + message.supportedFeatures = []; + for (var i = 0; i < object.supportedFeatures.length; ++i) + switch (object.supportedFeatures[i]) { + default: + case "GEOCODING": + case 0: + message.supportedFeatures[i] = 0; + break; + case "LOCAL_SEARCH": + case 1: + message.supportedFeatures[i] = 1; + break; + case "DRIVING_DIRECTIONS": + case 2: + message.supportedFeatures[i] = 2; + break; + } } return message; }; - SwoopParamsProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.startDistInMeters !== undefined) - if (typeof message.startDistInMeters !== "number") - return "startDistInMeters: number expected"; - return null; - }; - - SwoopParamsProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.SwoopParamsProto) - return object; - var message = new $root.keyhole.dbroot.SwoopParamsProto(); - if (object.startDistInMeters !== undefined && object.startDistInMeters !== null) - message.startDistInMeters = Number(object.startDistInMeters); - return message; - }; - - SwoopParamsProto.from = SwoopParamsProto.fromObject; + MfeDomainFeaturesProto.from = MfeDomainFeaturesProto.fromObject; - SwoopParamsProto.toObject = function toObject(message, options) { + MfeDomainFeaturesProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) - object.startDistInMeters = 0; - if (message.startDistInMeters !== undefined && message.startDistInMeters !== null && message.hasOwnProperty("startDistInMeters")) - object.startDistInMeters = message.startDistInMeters; + if (options.arrays || options.defaults) + object.supportedFeatures = []; + if (options.defaults) { + object.countryCode = ""; + object.domainName = ""; + } + if (message.countryCode !== undefined && message.countryCode !== null && message.hasOwnProperty("countryCode")) + object.countryCode = message.countryCode; + if (message.domainName !== undefined && message.domainName !== null && message.hasOwnProperty("domainName")) + object.domainName = message.domainName; + if (message.supportedFeatures !== undefined && message.supportedFeatures !== null && message.hasOwnProperty("supportedFeatures")) { + object.supportedFeatures = []; + for (var j = 0; j < message.supportedFeatures.length; ++j) + object.supportedFeatures[j] = options.enums === String ? $types[2][message.supportedFeatures[j]] : message.supportedFeatures[j]; + } return object; }; - SwoopParamsProto.prototype.toObject = function toObject(options) { + MfeDomainFeaturesProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - SwoopParamsProto.prototype.toJSON = function toJSON() { + MfeDomainFeaturesProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return SwoopParamsProto; + MfeDomainFeaturesProto.SupportedFeature = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["GEOCODING"] = 0; + values["LOCAL_SEARCH"] = 1; + values["DRIVING_DIRECTIONS"] = 2; + return values; + })(); + + return MfeDomainFeaturesProto; })(); - dbroot.PostingServerProto = (function() { + dbroot.ClientOptionsProto = (function() { - function PostingServerProto(properties) { + function ClientOptionsProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - PostingServerProto.prototype.name = null; - PostingServerProto.prototype.baseUrl = null; - PostingServerProto.prototype.postWizardPath = null; - PostingServerProto.prototype.fileSubmitPath = null; + ClientOptionsProto.prototype.disableDiskCache = false; + ClientOptionsProto.prototype.disableEmbeddedBrowserVista = false; + ClientOptionsProto.prototype.drawAtmosphere = true; + ClientOptionsProto.prototype.drawStars = true; + ClientOptionsProto.prototype.shaderFilePrefix = ""; + ClientOptionsProto.prototype.useProtobufQuadtreePackets = false; + ClientOptionsProto.prototype.useExtendedCopyrightIds = true; + ClientOptionsProto.prototype.precipitationsOptions = null; + ClientOptionsProto.prototype.captureOptions = null; + ClientOptionsProto.prototype.show_2dMapsIcon = true; + ClientOptionsProto.prototype.disableInternalBrowser = false; + ClientOptionsProto.prototype.internalBrowserBlacklist = ""; + ClientOptionsProto.prototype.internalBrowserOriginWhitelist = "*"; + ClientOptionsProto.prototype.polarTileMergingLevel = 0; + ClientOptionsProto.prototype.jsBridgeRequestWhitelist = "http://*.google.com/*"; + ClientOptionsProto.prototype.mapsOptions = null; var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 1 : "keyhole.dbroot.StringIdOrValueProto", - 2 : "keyhole.dbroot.StringIdOrValueProto", - 3 : "keyhole.dbroot.StringIdOrValueProto" + 7 : "keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions", + 8 : "keyhole.dbroot.ClientOptionsProto.CaptureOptions", + 15 : "keyhole.dbroot.ClientOptionsProto.MapsOptions" }; $lazyTypes.push($types); - PostingServerProto.decode = function decode(reader, length) { + ClientOptionsProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.PostingServerProto(); + message = new $root.keyhole.dbroot.ClientOptionsProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.name = $types[0].decode(reader, reader.uint32()); + message.disableDiskCache = reader.bool(); break; case 2: - message.baseUrl = $types[1].decode(reader, reader.uint32()); + message.disableEmbeddedBrowserVista = reader.bool(); break; case 3: - message.postWizardPath = $types[2].decode(reader, reader.uint32()); + message.drawAtmosphere = reader.bool(); break; case 4: - message.fileSubmitPath = $types[3].decode(reader, reader.uint32()); + message.drawStars = reader.bool(); + break; + case 5: + message.shaderFilePrefix = reader.string(); + break; + case 6: + message.useProtobufQuadtreePackets = reader.bool(); + break; + case 7: + message.useExtendedCopyrightIds = reader.bool(); + break; + case 8: + message.precipitationsOptions = $types[7].decode(reader, reader.uint32()); + break; + case 9: + message.captureOptions = $types[8].decode(reader, reader.uint32()); + break; + case 10: + message.show_2dMapsIcon = reader.bool(); + break; + case 11: + message.disableInternalBrowser = reader.bool(); + break; + case 12: + message.internalBrowserBlacklist = reader.string(); + break; + case 13: + message.internalBrowserOriginWhitelist = reader.string(); + break; + case 14: + message.polarTileMergingLevel = reader.int32(); + break; + case 15: + message.jsBridgeRequestWhitelist = reader.string(); + break; + case 16: + message.mapsOptions = $types[15].decode(reader, reader.uint32()); break; default: reader.skipType(tag & 7); @@ -53614,660 +54736,1367 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - PostingServerProto.verify = function verify(message) { + ClientOptionsProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.name !== undefined && message.name !== null) { - var error = $types[0].verify(message.name); - if (error) - return "name." + error; - } - if (message.baseUrl !== undefined && message.baseUrl !== null) { - var error = $types[1].verify(message.baseUrl); + if (message.disableDiskCache !== undefined) + if (typeof message.disableDiskCache !== "boolean") + return "disableDiskCache: boolean expected"; + if (message.disableEmbeddedBrowserVista !== undefined) + if (typeof message.disableEmbeddedBrowserVista !== "boolean") + return "disableEmbeddedBrowserVista: boolean expected"; + if (message.drawAtmosphere !== undefined) + if (typeof message.drawAtmosphere !== "boolean") + return "drawAtmosphere: boolean expected"; + if (message.drawStars !== undefined) + if (typeof message.drawStars !== "boolean") + return "drawStars: boolean expected"; + if (message.shaderFilePrefix !== undefined) + if (!$util.isString(message.shaderFilePrefix)) + return "shaderFilePrefix: string expected"; + if (message.useProtobufQuadtreePackets !== undefined) + if (typeof message.useProtobufQuadtreePackets !== "boolean") + return "useProtobufQuadtreePackets: boolean expected"; + if (message.useExtendedCopyrightIds !== undefined) + if (typeof message.useExtendedCopyrightIds !== "boolean") + return "useExtendedCopyrightIds: boolean expected"; + if (message.precipitationsOptions !== undefined && message.precipitationsOptions !== null) { + var error = $types[7].verify(message.precipitationsOptions); if (error) - return "baseUrl." + error; + return "precipitationsOptions." + error; } - if (message.postWizardPath !== undefined && message.postWizardPath !== null) { - var error = $types[2].verify(message.postWizardPath); + if (message.captureOptions !== undefined && message.captureOptions !== null) { + var error = $types[8].verify(message.captureOptions); if (error) - return "postWizardPath." + error; + return "captureOptions." + error; } - if (message.fileSubmitPath !== undefined && message.fileSubmitPath !== null) { - var error = $types[3].verify(message.fileSubmitPath); + if (message.show_2dMapsIcon !== undefined) + if (typeof message.show_2dMapsIcon !== "boolean") + return "show_2dMapsIcon: boolean expected"; + if (message.disableInternalBrowser !== undefined) + if (typeof message.disableInternalBrowser !== "boolean") + return "disableInternalBrowser: boolean expected"; + if (message.internalBrowserBlacklist !== undefined) + if (!$util.isString(message.internalBrowserBlacklist)) + return "internalBrowserBlacklist: string expected"; + if (message.internalBrowserOriginWhitelist !== undefined) + if (!$util.isString(message.internalBrowserOriginWhitelist)) + return "internalBrowserOriginWhitelist: string expected"; + if (message.polarTileMergingLevel !== undefined) + if (!$util.isInteger(message.polarTileMergingLevel)) + return "polarTileMergingLevel: integer expected"; + if (message.jsBridgeRequestWhitelist !== undefined) + if (!$util.isString(message.jsBridgeRequestWhitelist)) + return "jsBridgeRequestWhitelist: string expected"; + if (message.mapsOptions !== undefined && message.mapsOptions !== null) { + var error = $types[15].verify(message.mapsOptions); if (error) - return "fileSubmitPath." + error; + return "mapsOptions." + error; } return null; }; - PostingServerProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.PostingServerProto) + ClientOptionsProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ClientOptionsProto) return object; - var message = new $root.keyhole.dbroot.PostingServerProto(); - if (object.name !== undefined && object.name !== null) { - if (typeof object.name !== "object") - throw TypeError(".keyhole.dbroot.PostingServerProto.name: object expected"); - message.name = $types[0].fromObject(object.name); - } - if (object.baseUrl !== undefined && object.baseUrl !== null) { - if (typeof object.baseUrl !== "object") - throw TypeError(".keyhole.dbroot.PostingServerProto.baseUrl: object expected"); - message.baseUrl = $types[1].fromObject(object.baseUrl); + var message = new $root.keyhole.dbroot.ClientOptionsProto(); + if (object.disableDiskCache !== undefined && object.disableDiskCache !== null) + message.disableDiskCache = Boolean(object.disableDiskCache); + if (object.disableEmbeddedBrowserVista !== undefined && object.disableEmbeddedBrowserVista !== null) + message.disableEmbeddedBrowserVista = Boolean(object.disableEmbeddedBrowserVista); + if (object.drawAtmosphere !== undefined && object.drawAtmosphere !== null) + message.drawAtmosphere = Boolean(object.drawAtmosphere); + if (object.drawStars !== undefined && object.drawStars !== null) + message.drawStars = Boolean(object.drawStars); + if (object.shaderFilePrefix !== undefined && object.shaderFilePrefix !== null) + message.shaderFilePrefix = String(object.shaderFilePrefix); + if (object.useProtobufQuadtreePackets !== undefined && object.useProtobufQuadtreePackets !== null) + message.useProtobufQuadtreePackets = Boolean(object.useProtobufQuadtreePackets); + if (object.useExtendedCopyrightIds !== undefined && object.useExtendedCopyrightIds !== null) + message.useExtendedCopyrightIds = Boolean(object.useExtendedCopyrightIds); + if (object.precipitationsOptions !== undefined && object.precipitationsOptions !== null) { + if (typeof object.precipitationsOptions !== "object") + throw TypeError(".keyhole.dbroot.ClientOptionsProto.precipitationsOptions: object expected"); + message.precipitationsOptions = $types[7].fromObject(object.precipitationsOptions); } - if (object.postWizardPath !== undefined && object.postWizardPath !== null) { - if (typeof object.postWizardPath !== "object") - throw TypeError(".keyhole.dbroot.PostingServerProto.postWizardPath: object expected"); - message.postWizardPath = $types[2].fromObject(object.postWizardPath); + if (object.captureOptions !== undefined && object.captureOptions !== null) { + if (typeof object.captureOptions !== "object") + throw TypeError(".keyhole.dbroot.ClientOptionsProto.captureOptions: object expected"); + message.captureOptions = $types[8].fromObject(object.captureOptions); } - if (object.fileSubmitPath !== undefined && object.fileSubmitPath !== null) { - if (typeof object.fileSubmitPath !== "object") - throw TypeError(".keyhole.dbroot.PostingServerProto.fileSubmitPath: object expected"); - message.fileSubmitPath = $types[3].fromObject(object.fileSubmitPath); + if (object.show_2dMapsIcon !== undefined && object.show_2dMapsIcon !== null) + message.show_2dMapsIcon = Boolean(object.show_2dMapsIcon); + if (object.disableInternalBrowser !== undefined && object.disableInternalBrowser !== null) + message.disableInternalBrowser = Boolean(object.disableInternalBrowser); + if (object.internalBrowserBlacklist !== undefined && object.internalBrowserBlacklist !== null) + message.internalBrowserBlacklist = String(object.internalBrowserBlacklist); + if (object.internalBrowserOriginWhitelist !== undefined && object.internalBrowserOriginWhitelist !== null) + message.internalBrowserOriginWhitelist = String(object.internalBrowserOriginWhitelist); + if (object.polarTileMergingLevel !== undefined && object.polarTileMergingLevel !== null) + message.polarTileMergingLevel = object.polarTileMergingLevel | 0; + if (object.jsBridgeRequestWhitelist !== undefined && object.jsBridgeRequestWhitelist !== null) + message.jsBridgeRequestWhitelist = String(object.jsBridgeRequestWhitelist); + if (object.mapsOptions !== undefined && object.mapsOptions !== null) { + if (typeof object.mapsOptions !== "object") + throw TypeError(".keyhole.dbroot.ClientOptionsProto.mapsOptions: object expected"); + message.mapsOptions = $types[15].fromObject(object.mapsOptions); } return message; }; - PostingServerProto.from = PostingServerProto.fromObject; + ClientOptionsProto.from = ClientOptionsProto.fromObject; - PostingServerProto.toObject = function toObject(message, options) { + ClientOptionsProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { - object.name = null; - object.baseUrl = null; - object.postWizardPath = null; - object.fileSubmitPath = null; + object.disableDiskCache = false; + object.disableEmbeddedBrowserVista = false; + object.drawAtmosphere = true; + object.drawStars = true; + object.shaderFilePrefix = ""; + object.useProtobufQuadtreePackets = false; + object.useExtendedCopyrightIds = true; + object.precipitationsOptions = null; + object.captureOptions = null; + object.show_2dMapsIcon = true; + object.disableInternalBrowser = false; + object.internalBrowserBlacklist = ""; + object.internalBrowserOriginWhitelist = "*"; + object.polarTileMergingLevel = 0; + object.jsBridgeRequestWhitelist = "http://*.google.com/*"; + object.mapsOptions = null; } - if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) - object.name = $types[0].toObject(message.name, options); - if (message.baseUrl !== undefined && message.baseUrl !== null && message.hasOwnProperty("baseUrl")) - object.baseUrl = $types[1].toObject(message.baseUrl, options); - if (message.postWizardPath !== undefined && message.postWizardPath !== null && message.hasOwnProperty("postWizardPath")) - object.postWizardPath = $types[2].toObject(message.postWizardPath, options); - if (message.fileSubmitPath !== undefined && message.fileSubmitPath !== null && message.hasOwnProperty("fileSubmitPath")) - object.fileSubmitPath = $types[3].toObject(message.fileSubmitPath, options); + if (message.disableDiskCache !== undefined && message.disableDiskCache !== null && message.hasOwnProperty("disableDiskCache")) + object.disableDiskCache = message.disableDiskCache; + if (message.disableEmbeddedBrowserVista !== undefined && message.disableEmbeddedBrowserVista !== null && message.hasOwnProperty("disableEmbeddedBrowserVista")) + object.disableEmbeddedBrowserVista = message.disableEmbeddedBrowserVista; + if (message.drawAtmosphere !== undefined && message.drawAtmosphere !== null && message.hasOwnProperty("drawAtmosphere")) + object.drawAtmosphere = message.drawAtmosphere; + if (message.drawStars !== undefined && message.drawStars !== null && message.hasOwnProperty("drawStars")) + object.drawStars = message.drawStars; + if (message.shaderFilePrefix !== undefined && message.shaderFilePrefix !== null && message.hasOwnProperty("shaderFilePrefix")) + object.shaderFilePrefix = message.shaderFilePrefix; + if (message.useProtobufQuadtreePackets !== undefined && message.useProtobufQuadtreePackets !== null && message.hasOwnProperty("useProtobufQuadtreePackets")) + object.useProtobufQuadtreePackets = message.useProtobufQuadtreePackets; + if (message.useExtendedCopyrightIds !== undefined && message.useExtendedCopyrightIds !== null && message.hasOwnProperty("useExtendedCopyrightIds")) + object.useExtendedCopyrightIds = message.useExtendedCopyrightIds; + if (message.precipitationsOptions !== undefined && message.precipitationsOptions !== null && message.hasOwnProperty("precipitationsOptions")) + object.precipitationsOptions = $types[7].toObject(message.precipitationsOptions, options); + if (message.captureOptions !== undefined && message.captureOptions !== null && message.hasOwnProperty("captureOptions")) + object.captureOptions = $types[8].toObject(message.captureOptions, options); + if (message.show_2dMapsIcon !== undefined && message.show_2dMapsIcon !== null && message.hasOwnProperty("show_2dMapsIcon")) + object.show_2dMapsIcon = message.show_2dMapsIcon; + if (message.disableInternalBrowser !== undefined && message.disableInternalBrowser !== null && message.hasOwnProperty("disableInternalBrowser")) + object.disableInternalBrowser = message.disableInternalBrowser; + if (message.internalBrowserBlacklist !== undefined && message.internalBrowserBlacklist !== null && message.hasOwnProperty("internalBrowserBlacklist")) + object.internalBrowserBlacklist = message.internalBrowserBlacklist; + if (message.internalBrowserOriginWhitelist !== undefined && message.internalBrowserOriginWhitelist !== null && message.hasOwnProperty("internalBrowserOriginWhitelist")) + object.internalBrowserOriginWhitelist = message.internalBrowserOriginWhitelist; + if (message.polarTileMergingLevel !== undefined && message.polarTileMergingLevel !== null && message.hasOwnProperty("polarTileMergingLevel")) + object.polarTileMergingLevel = message.polarTileMergingLevel; + if (message.jsBridgeRequestWhitelist !== undefined && message.jsBridgeRequestWhitelist !== null && message.hasOwnProperty("jsBridgeRequestWhitelist")) + object.jsBridgeRequestWhitelist = message.jsBridgeRequestWhitelist; + if (message.mapsOptions !== undefined && message.mapsOptions !== null && message.hasOwnProperty("mapsOptions")) + object.mapsOptions = $types[15].toObject(message.mapsOptions, options); return object; }; - PostingServerProto.prototype.toObject = function toObject(options) { + ClientOptionsProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - PostingServerProto.prototype.toJSON = function toJSON() { + ClientOptionsProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return PostingServerProto; - })(); + ClientOptionsProto.PrecipitationsOptions = (function() { - dbroot.PlanetaryDatabaseProto = (function() { + function PrecipitationsOptions(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - function PlanetaryDatabaseProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + PrecipitationsOptions.prototype.imageUrl = ""; + PrecipitationsOptions.prototype.imageExpireTime = 900; + PrecipitationsOptions.prototype.maxColorDistance = 20; + PrecipitationsOptions.prototype.imageLevel = 5; + PrecipitationsOptions.prototype.weatherMapping = $util.emptyArray; + PrecipitationsOptions.prototype.cloudsLayerUrl = ""; + PrecipitationsOptions.prototype.animationDecelerationDelay = 20; - PlanetaryDatabaseProto.prototype.url = null; - PlanetaryDatabaseProto.prototype.name = null; + var $types = { + 4 : "keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping" + }; + $lazyTypes.push($types); - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 1 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); + PrecipitationsOptions.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.imageUrl = reader.string(); + break; + case 2: + message.imageExpireTime = reader.int32(); + break; + case 3: + message.maxColorDistance = reader.int32(); + break; + case 4: + message.imageLevel = reader.int32(); + break; + case 5: + if (!(message.weatherMapping && message.weatherMapping.length)) + message.weatherMapping = []; + message.weatherMapping.push($types[4].decode(reader, reader.uint32())); + break; + case 6: + message.cloudsLayerUrl = reader.string(); + break; + case 7: + message.animationDecelerationDelay = reader.float(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - PlanetaryDatabaseProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.PlanetaryDatabaseProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.url = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.name = $types[1].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; + PrecipitationsOptions.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.imageUrl !== undefined) + if (!$util.isString(message.imageUrl)) + return "imageUrl: string expected"; + if (message.imageExpireTime !== undefined) + if (!$util.isInteger(message.imageExpireTime)) + return "imageExpireTime: integer expected"; + if (message.maxColorDistance !== undefined) + if (!$util.isInteger(message.maxColorDistance)) + return "maxColorDistance: integer expected"; + if (message.imageLevel !== undefined) + if (!$util.isInteger(message.imageLevel)) + return "imageLevel: integer expected"; + if (message.weatherMapping !== undefined) { + if (!Array.isArray(message.weatherMapping)) + return "weatherMapping: array expected"; + for (var i = 0; i < message.weatherMapping.length; ++i) { + var error = $types[4].verify(message.weatherMapping[i]); + if (error) + return "weatherMapping." + error; + } } - } - return message; - }; + if (message.cloudsLayerUrl !== undefined) + if (!$util.isString(message.cloudsLayerUrl)) + return "cloudsLayerUrl: string expected"; + if (message.animationDecelerationDelay !== undefined) + if (typeof message.animationDecelerationDelay !== "number") + return "animationDecelerationDelay: number expected"; + return null; + }; - PlanetaryDatabaseProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - var error = $types[0].verify(message.url); - if (error) - return "url." + error; - var error = $types[1].verify(message.name); - if (error) - return "name." + error; - return null; - }; + PrecipitationsOptions.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions) + return object; + var message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions(); + if (object.imageUrl !== undefined && object.imageUrl !== null) + message.imageUrl = String(object.imageUrl); + if (object.imageExpireTime !== undefined && object.imageExpireTime !== null) + message.imageExpireTime = object.imageExpireTime | 0; + if (object.maxColorDistance !== undefined && object.maxColorDistance !== null) + message.maxColorDistance = object.maxColorDistance | 0; + if (object.imageLevel !== undefined && object.imageLevel !== null) + message.imageLevel = object.imageLevel | 0; + if (object.weatherMapping) { + if (!Array.isArray(object.weatherMapping)) + throw TypeError(".keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.weatherMapping: array expected"); + message.weatherMapping = []; + for (var i = 0; i < object.weatherMapping.length; ++i) { + if (typeof object.weatherMapping[i] !== "object") + throw TypeError(".keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.weatherMapping: object expected"); + message.weatherMapping[i] = $types[4].fromObject(object.weatherMapping[i]); + } + } + if (object.cloudsLayerUrl !== undefined && object.cloudsLayerUrl !== null) + message.cloudsLayerUrl = String(object.cloudsLayerUrl); + if (object.animationDecelerationDelay !== undefined && object.animationDecelerationDelay !== null) + message.animationDecelerationDelay = Number(object.animationDecelerationDelay); + return message; + }; - PlanetaryDatabaseProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.PlanetaryDatabaseProto) + PrecipitationsOptions.from = PrecipitationsOptions.fromObject; + + PrecipitationsOptions.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.weatherMapping = []; + if (options.defaults) { + object.imageUrl = ""; + object.imageExpireTime = 900; + object.maxColorDistance = 20; + object.imageLevel = 5; + object.cloudsLayerUrl = ""; + object.animationDecelerationDelay = 20; + } + if (message.imageUrl !== undefined && message.imageUrl !== null && message.hasOwnProperty("imageUrl")) + object.imageUrl = message.imageUrl; + if (message.imageExpireTime !== undefined && message.imageExpireTime !== null && message.hasOwnProperty("imageExpireTime")) + object.imageExpireTime = message.imageExpireTime; + if (message.maxColorDistance !== undefined && message.maxColorDistance !== null && message.hasOwnProperty("maxColorDistance")) + object.maxColorDistance = message.maxColorDistance; + if (message.imageLevel !== undefined && message.imageLevel !== null && message.hasOwnProperty("imageLevel")) + object.imageLevel = message.imageLevel; + if (message.weatherMapping !== undefined && message.weatherMapping !== null && message.hasOwnProperty("weatherMapping")) { + object.weatherMapping = []; + for (var j = 0; j < message.weatherMapping.length; ++j) + object.weatherMapping[j] = $types[4].toObject(message.weatherMapping[j], options); + } + if (message.cloudsLayerUrl !== undefined && message.cloudsLayerUrl !== null && message.hasOwnProperty("cloudsLayerUrl")) + object.cloudsLayerUrl = message.cloudsLayerUrl; + if (message.animationDecelerationDelay !== undefined && message.animationDecelerationDelay !== null && message.hasOwnProperty("animationDecelerationDelay")) + object.animationDecelerationDelay = message.animationDecelerationDelay; return object; - var message = new $root.keyhole.dbroot.PlanetaryDatabaseProto(); - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.PlanetaryDatabaseProto.url: object expected"); - message.url = $types[0].fromObject(object.url); - } - if (object.name !== undefined && object.name !== null) { - if (typeof object.name !== "object") - throw TypeError(".keyhole.dbroot.PlanetaryDatabaseProto.name: object expected"); - message.name = $types[1].fromObject(object.name); - } - return message; - }; + }; - PlanetaryDatabaseProto.from = PlanetaryDatabaseProto.fromObject; + PrecipitationsOptions.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - PlanetaryDatabaseProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.url = null; - object.name = null; - } - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[0].toObject(message.url, options); - if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) - object.name = $types[1].toObject(message.name, options); - return object; - }; + PrecipitationsOptions.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - PlanetaryDatabaseProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + PrecipitationsOptions.WeatherMapping = (function() { - PlanetaryDatabaseProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + function WeatherMapping(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - return PlanetaryDatabaseProto; - })(); + WeatherMapping.prototype.colorAbgr = 0; + WeatherMapping.prototype.weatherType = 0; + WeatherMapping.prototype.elongation = 1; + WeatherMapping.prototype.opacity = 0; + WeatherMapping.prototype.fogDensity = 0; + WeatherMapping.prototype.speed0 = 0; + WeatherMapping.prototype.speed1 = 0; + WeatherMapping.prototype.speed2 = 0; + WeatherMapping.prototype.speed3 = 0; - dbroot.LogServerProto = (function() { + var $types = { + 1 : "keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping.WeatherType" + }; + $lazyTypes.push($types); - function LogServerProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + WeatherMapping.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.colorAbgr = reader.uint32(); + break; + case 2: + message.weatherType = reader.uint32(); + break; + case 3: + message.elongation = reader.float(); + break; + case 4: + message.opacity = reader.float(); + break; + case 5: + message.fogDensity = reader.float(); + break; + case 6: + message.speed0 = reader.float(); + break; + case 7: + message.speed1 = reader.float(); + break; + case 8: + message.speed2 = reader.float(); + break; + case 9: + message.speed3 = reader.float(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - LogServerProto.prototype.url = null; - LogServerProto.prototype.enable = false; - LogServerProto.prototype.throttlingFactor = 1; + WeatherMapping.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isInteger(message.colorAbgr)) + return "colorAbgr: integer expected"; + switch (message.weatherType) { + default: + return "weatherType: enum value expected"; + case 0: + case 1: + case 2: + break; + } + if (message.elongation !== undefined) + if (typeof message.elongation !== "number") + return "elongation: number expected"; + if (message.opacity !== undefined) + if (typeof message.opacity !== "number") + return "opacity: number expected"; + if (message.fogDensity !== undefined) + if (typeof message.fogDensity !== "number") + return "fogDensity: number expected"; + if (message.speed0 !== undefined) + if (typeof message.speed0 !== "number") + return "speed0: number expected"; + if (message.speed1 !== undefined) + if (typeof message.speed1 !== "number") + return "speed1: number expected"; + if (message.speed2 !== undefined) + if (typeof message.speed2 !== "number") + return "speed2: number expected"; + if (message.speed3 !== undefined) + if (typeof message.speed3 !== "number") + return "speed3: number expected"; + return null; + }; - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); + WeatherMapping.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping) + return object; + var message = new $root.keyhole.dbroot.ClientOptionsProto.PrecipitationsOptions.WeatherMapping(); + if (object.colorAbgr !== undefined && object.colorAbgr !== null) + message.colorAbgr = object.colorAbgr >>> 0; + switch (object.weatherType) { + case "NO_PRECIPITATION": + case 0: + message.weatherType = 0; + break; + case "RAIN": + case 1: + message.weatherType = 1; + break; + case "SNOW": + case 2: + message.weatherType = 2; + break; + } + if (object.elongation !== undefined && object.elongation !== null) + message.elongation = Number(object.elongation); + if (object.opacity !== undefined && object.opacity !== null) + message.opacity = Number(object.opacity); + if (object.fogDensity !== undefined && object.fogDensity !== null) + message.fogDensity = Number(object.fogDensity); + if (object.speed0 !== undefined && object.speed0 !== null) + message.speed0 = Number(object.speed0); + if (object.speed1 !== undefined && object.speed1 !== null) + message.speed1 = Number(object.speed1); + if (object.speed2 !== undefined && object.speed2 !== null) + message.speed2 = Number(object.speed2); + if (object.speed3 !== undefined && object.speed3 !== null) + message.speed3 = Number(object.speed3); + return message; + }; - LogServerProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.LogServerProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.url = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.enable = reader.bool(); - break; - case 3: - message.throttlingFactor = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + WeatherMapping.from = WeatherMapping.fromObject; - LogServerProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.url !== undefined && message.url !== null) { - var error = $types[0].verify(message.url); - if (error) - return "url." + error; - } - if (message.enable !== undefined) - if (typeof message.enable !== "boolean") - return "enable: boolean expected"; - if (message.throttlingFactor !== undefined) - if (!$util.isInteger(message.throttlingFactor)) - return "throttlingFactor: integer expected"; - return null; - }; + WeatherMapping.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.colorAbgr = 0; + object.weatherType = options.enums === String ? "NO_PRECIPITATION" : 0; + object.elongation = 1; + object.opacity = 0; + object.fogDensity = 0; + object.speed0 = 0; + object.speed1 = 0; + object.speed2 = 0; + object.speed3 = 0; + } + if (message.colorAbgr !== undefined && message.colorAbgr !== null && message.hasOwnProperty("colorAbgr")) + object.colorAbgr = message.colorAbgr; + if (message.weatherType !== undefined && message.weatherType !== null && message.hasOwnProperty("weatherType")) + object.weatherType = options.enums === String ? $types[1][message.weatherType] : message.weatherType; + if (message.elongation !== undefined && message.elongation !== null && message.hasOwnProperty("elongation")) + object.elongation = message.elongation; + if (message.opacity !== undefined && message.opacity !== null && message.hasOwnProperty("opacity")) + object.opacity = message.opacity; + if (message.fogDensity !== undefined && message.fogDensity !== null && message.hasOwnProperty("fogDensity")) + object.fogDensity = message.fogDensity; + if (message.speed0 !== undefined && message.speed0 !== null && message.hasOwnProperty("speed0")) + object.speed0 = message.speed0; + if (message.speed1 !== undefined && message.speed1 !== null && message.hasOwnProperty("speed1")) + object.speed1 = message.speed1; + if (message.speed2 !== undefined && message.speed2 !== null && message.hasOwnProperty("speed2")) + object.speed2 = message.speed2; + if (message.speed3 !== undefined && message.speed3 !== null && message.hasOwnProperty("speed3")) + object.speed3 = message.speed3; + return object; + }; - LogServerProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.LogServerProto) - return object; - var message = new $root.keyhole.dbroot.LogServerProto(); - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.LogServerProto.url: object expected"); - message.url = $types[0].fromObject(object.url); - } - if (object.enable !== undefined && object.enable !== null) - message.enable = Boolean(object.enable); - if (object.throttlingFactor !== undefined && object.throttlingFactor !== null) - message.throttlingFactor = object.throttlingFactor | 0; - return message; - }; + WeatherMapping.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - LogServerProto.from = LogServerProto.fromObject; + WeatherMapping.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - LogServerProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.url = null; - object.enable = false; - object.throttlingFactor = 1; + WeatherMapping.WeatherType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["NO_PRECIPITATION"] = 0; + values["RAIN"] = 1; + values["SNOW"] = 2; + return values; + })(); + + return WeatherMapping; + })(); + + return PrecipitationsOptions; + })(); + + ClientOptionsProto.CaptureOptions = (function() { + + function CaptureOptions(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; } - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[0].toObject(message.url, options); - if (message.enable !== undefined && message.enable !== null && message.hasOwnProperty("enable")) - object.enable = message.enable; - if (message.throttlingFactor !== undefined && message.throttlingFactor !== null && message.hasOwnProperty("throttlingFactor")) - object.throttlingFactor = message.throttlingFactor; - return object; - }; - LogServerProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + CaptureOptions.prototype.allowSaveAsImage = true; + CaptureOptions.prototype.maxFreeCaptureRes = 2400; + CaptureOptions.prototype.maxPremiumCaptureRes = 4800; - LogServerProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + CaptureOptions.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.ClientOptionsProto.CaptureOptions(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.allowSaveAsImage = reader.bool(); + break; + case 2: + message.maxFreeCaptureRes = reader.int32(); + break; + case 3: + message.maxPremiumCaptureRes = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - return LogServerProto; - })(); + CaptureOptions.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.allowSaveAsImage !== undefined) + if (typeof message.allowSaveAsImage !== "boolean") + return "allowSaveAsImage: boolean expected"; + if (message.maxFreeCaptureRes !== undefined) + if (!$util.isInteger(message.maxFreeCaptureRes)) + return "maxFreeCaptureRes: integer expected"; + if (message.maxPremiumCaptureRes !== undefined) + if (!$util.isInteger(message.maxPremiumCaptureRes)) + return "maxPremiumCaptureRes: integer expected"; + return null; + }; - dbroot.EndSnippetProto = (function() { + CaptureOptions.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.CaptureOptions) + return object; + var message = new $root.keyhole.dbroot.ClientOptionsProto.CaptureOptions(); + if (object.allowSaveAsImage !== undefined && object.allowSaveAsImage !== null) + message.allowSaveAsImage = Boolean(object.allowSaveAsImage); + if (object.maxFreeCaptureRes !== undefined && object.maxFreeCaptureRes !== null) + message.maxFreeCaptureRes = object.maxFreeCaptureRes | 0; + if (object.maxPremiumCaptureRes !== undefined && object.maxPremiumCaptureRes !== null) + message.maxPremiumCaptureRes = object.maxPremiumCaptureRes | 0; + return message; + }; - function EndSnippetProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + CaptureOptions.from = CaptureOptions.fromObject; - EndSnippetProto.prototype.model = null; - EndSnippetProto.prototype.authServerUrl = null; - EndSnippetProto.prototype.disableAuthentication = false; - EndSnippetProto.prototype.mfeDomains = $util.emptyArray; - EndSnippetProto.prototype.mfeLangParam = "hl=$5Bhl5D"; - EndSnippetProto.prototype.adsUrlPatterns = ""; - EndSnippetProto.prototype.reverseGeocoderUrl = null; - EndSnippetProto.prototype.reverseGeocoderProtocolVersion = 3; - EndSnippetProto.prototype.skyDatabaseIsAvailable = true; - EndSnippetProto.prototype.skyDatabaseUrl = null; - EndSnippetProto.prototype.defaultWebPageIntlUrl = null; - EndSnippetProto.prototype.numStartUpTips = 17; - EndSnippetProto.prototype.startUpTipsUrl = null; - EndSnippetProto.prototype.numProStartUpTips = 0; - EndSnippetProto.prototype.proStartUpTipsUrl = null; - EndSnippetProto.prototype.startupTipsIntlUrl = null; - EndSnippetProto.prototype.userGuideIntlUrl = null; - EndSnippetProto.prototype.supportCenterIntlUrl = null; - EndSnippetProto.prototype.businessListingIntlUrl = null; - EndSnippetProto.prototype.supportAnswerIntlUrl = null; - EndSnippetProto.prototype.supportTopicIntlUrl = null; - EndSnippetProto.prototype.supportRequestIntlUrl = null; - EndSnippetProto.prototype.earthIntlUrl = null; - EndSnippetProto.prototype.addContentUrl = null; - EndSnippetProto.prototype.sketchupNotInstalledUrl = null; - EndSnippetProto.prototype.sketchupErrorUrl = null; - EndSnippetProto.prototype.freeLicenseUrl = null; - EndSnippetProto.prototype.proLicenseUrl = null; - EndSnippetProto.prototype.tutorialUrl = null; - EndSnippetProto.prototype.keyboardShortcutsUrl = null; - EndSnippetProto.prototype.releaseNotesUrl = null; - EndSnippetProto.prototype.hideUserData = false; - EndSnippetProto.prototype.useGeLogo = true; - EndSnippetProto.prototype.dioramaDescriptionUrlBase = null; - EndSnippetProto.prototype.dioramaDefaultColor = 4291281607; - EndSnippetProto.prototype.dioramaBlacklistUrl = null; - EndSnippetProto.prototype.clientOptions = null; - EndSnippetProto.prototype.fetchingOptions = null; - EndSnippetProto.prototype.timeMachineOptions = null; - EndSnippetProto.prototype.csiOptions = null; - EndSnippetProto.prototype.searchTab = $util.emptyArray; - EndSnippetProto.prototype.cobrandInfo = $util.emptyArray; - EndSnippetProto.prototype.validDatabase = $util.emptyArray; - EndSnippetProto.prototype.configScript = $util.emptyArray; - EndSnippetProto.prototype.deauthServerUrl = null; - EndSnippetProto.prototype.swoopParameters = null; - EndSnippetProto.prototype.bbsServerInfo = null; - EndSnippetProto.prototype.dataErrorServerInfo = null; - EndSnippetProto.prototype.planetaryDatabase = $util.emptyArray; - EndSnippetProto.prototype.logServer = null; - EndSnippetProto.prototype.autopiaOptions = null; - EndSnippetProto.prototype.searchConfig = null; - EndSnippetProto.prototype.searchInfo = null; - EndSnippetProto.prototype.elevationServiceBaseUrl = "http://maps.google.com/maps/api/elevation/"; - EndSnippetProto.prototype.elevationProfileQueryDelay = 500; - EndSnippetProto.prototype.proUpgradeUrl = null; - EndSnippetProto.prototype.earthCommunityUrl = null; - EndSnippetProto.prototype.googleMapsUrl = null; - EndSnippetProto.prototype.sharingUrl = null; - EndSnippetProto.prototype.privacyPolicyUrl = null; - EndSnippetProto.prototype.doGplusUserCheck = false; - EndSnippetProto.prototype.rocktreeDataProto = null; - EndSnippetProto.prototype.filmstripConfig = $util.emptyArray; - EndSnippetProto.prototype.showSigninButton = false; - EndSnippetProto.prototype.proMeasureUpsellUrl = null; - EndSnippetProto.prototype.proPrintUpsellUrl = null; - EndSnippetProto.prototype.starDataProto = null; - EndSnippetProto.prototype.feedbackUrl = null; - EndSnippetProto.prototype.oauth2LoginUrl = null; + CaptureOptions.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.allowSaveAsImage = true; + object.maxFreeCaptureRes = 2400; + object.maxPremiumCaptureRes = 4800; + } + if (message.allowSaveAsImage !== undefined && message.allowSaveAsImage !== null && message.hasOwnProperty("allowSaveAsImage")) + object.allowSaveAsImage = message.allowSaveAsImage; + if (message.maxFreeCaptureRes !== undefined && message.maxFreeCaptureRes !== null && message.hasOwnProperty("maxFreeCaptureRes")) + object.maxFreeCaptureRes = message.maxFreeCaptureRes; + if (message.maxPremiumCaptureRes !== undefined && message.maxPremiumCaptureRes !== null && message.hasOwnProperty("maxPremiumCaptureRes")) + object.maxPremiumCaptureRes = message.maxPremiumCaptureRes; + return object; + }; - var $types = { - 0 : "keyhole.dbroot.PlanetModelProto", - 1 : "keyhole.dbroot.StringIdOrValueProto", - 3 : "keyhole.dbroot.MfeDomainFeaturesProto", - 6 : "keyhole.dbroot.StringIdOrValueProto", - 9 : "keyhole.dbroot.StringIdOrValueProto", - 10 : "keyhole.dbroot.StringIdOrValueProto", - 12 : "keyhole.dbroot.StringIdOrValueProto", - 14 : "keyhole.dbroot.StringIdOrValueProto", - 15 : "keyhole.dbroot.StringIdOrValueProto", - 16 : "keyhole.dbroot.StringIdOrValueProto", - 17 : "keyhole.dbroot.StringIdOrValueProto", - 18 : "keyhole.dbroot.StringIdOrValueProto", - 19 : "keyhole.dbroot.StringIdOrValueProto", - 20 : "keyhole.dbroot.StringIdOrValueProto", - 21 : "keyhole.dbroot.StringIdOrValueProto", - 22 : "keyhole.dbroot.StringIdOrValueProto", - 23 : "keyhole.dbroot.StringIdOrValueProto", - 24 : "keyhole.dbroot.StringIdOrValueProto", - 25 : "keyhole.dbroot.StringIdOrValueProto", - 26 : "keyhole.dbroot.StringIdOrValueProto", - 27 : "keyhole.dbroot.StringIdOrValueProto", - 28 : "keyhole.dbroot.StringIdOrValueProto", - 29 : "keyhole.dbroot.StringIdOrValueProto", - 30 : "keyhole.dbroot.StringIdOrValueProto", - 33 : "keyhole.dbroot.StringIdOrValueProto", - 35 : "keyhole.dbroot.StringIdOrValueProto", - 36 : "keyhole.dbroot.ClientOptionsProto", - 37 : "keyhole.dbroot.FetchingOptionsProto", - 38 : "keyhole.dbroot.TimeMachineOptionsProto", - 39 : "keyhole.dbroot.CSIOptionsProto", - 40 : "keyhole.dbroot.SearchTabProto", - 41 : "keyhole.dbroot.CobrandProto", - 42 : "keyhole.dbroot.DatabaseDescriptionProto", - 43 : "keyhole.dbroot.ConfigScriptProto", - 44 : "keyhole.dbroot.StringIdOrValueProto", - 45 : "keyhole.dbroot.SwoopParamsProto", - 46 : "keyhole.dbroot.PostingServerProto", - 47 : "keyhole.dbroot.PostingServerProto", - 48 : "keyhole.dbroot.PlanetaryDatabaseProto", - 49 : "keyhole.dbroot.LogServerProto", - 50 : "keyhole.dbroot.AutopiaOptionsProto", - 51 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto", - 52 : "keyhole.dbroot.EndSnippetProto.SearchInfoProto", - 55 : "keyhole.dbroot.StringIdOrValueProto", - 56 : "keyhole.dbroot.StringIdOrValueProto", - 57 : "keyhole.dbroot.StringIdOrValueProto", - 58 : "keyhole.dbroot.StringIdOrValueProto", - 59 : "keyhole.dbroot.StringIdOrValueProto", - 61 : "keyhole.dbroot.EndSnippetProto.RockTreeDataProto", - 62 : "keyhole.dbroot.EndSnippetProto.FilmstripConfigProto", - 64 : "keyhole.dbroot.StringIdOrValueProto", - 65 : "keyhole.dbroot.StringIdOrValueProto", - 66 : "keyhole.dbroot.EndSnippetProto.StarDataProto", - 67 : "keyhole.dbroot.StringIdOrValueProto", - 68 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); + CaptureOptions.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - EndSnippetProto.decode = function decode(reader, length) { + CaptureOptions.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return CaptureOptions; + })(); + + ClientOptionsProto.MapsOptions = (function() { + + function MapsOptions(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + MapsOptions.prototype.enableMaps = false; + MapsOptions.prototype.docsAutoDownloadEnabled = false; + MapsOptions.prototype.docsAutoDownloadInterval = 0; + MapsOptions.prototype.docsAutoUploadEnabled = false; + MapsOptions.prototype.docsAutoUploadDelay = 0; + + MapsOptions.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.ClientOptionsProto.MapsOptions(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.enableMaps = reader.bool(); + break; + case 2: + message.docsAutoDownloadEnabled = reader.bool(); + break; + case 3: + message.docsAutoDownloadInterval = reader.int32(); + break; + case 4: + message.docsAutoUploadEnabled = reader.bool(); + break; + case 5: + message.docsAutoUploadDelay = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + MapsOptions.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.enableMaps !== undefined) + if (typeof message.enableMaps !== "boolean") + return "enableMaps: boolean expected"; + if (message.docsAutoDownloadEnabled !== undefined) + if (typeof message.docsAutoDownloadEnabled !== "boolean") + return "docsAutoDownloadEnabled: boolean expected"; + if (message.docsAutoDownloadInterval !== undefined) + if (!$util.isInteger(message.docsAutoDownloadInterval)) + return "docsAutoDownloadInterval: integer expected"; + if (message.docsAutoUploadEnabled !== undefined) + if (typeof message.docsAutoUploadEnabled !== "boolean") + return "docsAutoUploadEnabled: boolean expected"; + if (message.docsAutoUploadDelay !== undefined) + if (!$util.isInteger(message.docsAutoUploadDelay)) + return "docsAutoUploadDelay: integer expected"; + return null; + }; + + MapsOptions.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ClientOptionsProto.MapsOptions) + return object; + var message = new $root.keyhole.dbroot.ClientOptionsProto.MapsOptions(); + if (object.enableMaps !== undefined && object.enableMaps !== null) + message.enableMaps = Boolean(object.enableMaps); + if (object.docsAutoDownloadEnabled !== undefined && object.docsAutoDownloadEnabled !== null) + message.docsAutoDownloadEnabled = Boolean(object.docsAutoDownloadEnabled); + if (object.docsAutoDownloadInterval !== undefined && object.docsAutoDownloadInterval !== null) + message.docsAutoDownloadInterval = object.docsAutoDownloadInterval | 0; + if (object.docsAutoUploadEnabled !== undefined && object.docsAutoUploadEnabled !== null) + message.docsAutoUploadEnabled = Boolean(object.docsAutoUploadEnabled); + if (object.docsAutoUploadDelay !== undefined && object.docsAutoUploadDelay !== null) + message.docsAutoUploadDelay = object.docsAutoUploadDelay | 0; + return message; + }; + + MapsOptions.from = MapsOptions.fromObject; + + MapsOptions.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.enableMaps = false; + object.docsAutoDownloadEnabled = false; + object.docsAutoDownloadInterval = 0; + object.docsAutoUploadEnabled = false; + object.docsAutoUploadDelay = 0; + } + if (message.enableMaps !== undefined && message.enableMaps !== null && message.hasOwnProperty("enableMaps")) + object.enableMaps = message.enableMaps; + if (message.docsAutoDownloadEnabled !== undefined && message.docsAutoDownloadEnabled !== null && message.hasOwnProperty("docsAutoDownloadEnabled")) + object.docsAutoDownloadEnabled = message.docsAutoDownloadEnabled; + if (message.docsAutoDownloadInterval !== undefined && message.docsAutoDownloadInterval !== null && message.hasOwnProperty("docsAutoDownloadInterval")) + object.docsAutoDownloadInterval = message.docsAutoDownloadInterval; + if (message.docsAutoUploadEnabled !== undefined && message.docsAutoUploadEnabled !== null && message.hasOwnProperty("docsAutoUploadEnabled")) + object.docsAutoUploadEnabled = message.docsAutoUploadEnabled; + if (message.docsAutoUploadDelay !== undefined && message.docsAutoUploadDelay !== null && message.hasOwnProperty("docsAutoUploadDelay")) + object.docsAutoUploadDelay = message.docsAutoUploadDelay; + return object; + }; + + MapsOptions.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + MapsOptions.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return MapsOptions; + })(); + + return ClientOptionsProto; + })(); + + dbroot.FetchingOptionsProto = (function() { + + function FetchingOptionsProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + FetchingOptionsProto.prototype.maxRequestsPerQuery = 1; + FetchingOptionsProto.prototype.forceMaxRequestsPerQuery = false; + FetchingOptionsProto.prototype.sortBatches = false; + FetchingOptionsProto.prototype.maxDrawable = 2; + FetchingOptionsProto.prototype.maxImagery = 2; + FetchingOptionsProto.prototype.maxTerrain = 5; + FetchingOptionsProto.prototype.maxQuadtree = 5; + FetchingOptionsProto.prototype.maxDioramaMetadata = 1; + FetchingOptionsProto.prototype.maxDioramaData = 0; + FetchingOptionsProto.prototype.maxConsumerFetchRatio = 1; + FetchingOptionsProto.prototype.maxProEcFetchRatio = 0; + FetchingOptionsProto.prototype.safeOverallQps = 0; + FetchingOptionsProto.prototype.safeImageryQps = 0; + FetchingOptionsProto.prototype.domainsForHttps = "google.com gstatic.com"; + FetchingOptionsProto.prototype.hostsForHttp = ""; + + FetchingOptionsProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto(); + message = new $root.keyhole.dbroot.FetchingOptionsProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.model = $types[0].decode(reader, reader.uint32()); + message.maxRequestsPerQuery = reader.int32(); + break; + case 12: + message.forceMaxRequestsPerQuery = reader.bool(); + break; + case 13: + message.sortBatches = reader.bool(); break; case 2: - message.authServerUrl = $types[1].decode(reader, reader.uint32()); + message.maxDrawable = reader.int32(); break; case 3: - message.disableAuthentication = reader.bool(); + message.maxImagery = reader.int32(); break; case 4: - if (!(message.mfeDomains && message.mfeDomains.length)) - message.mfeDomains = []; - message.mfeDomains.push($types[3].decode(reader, reader.uint32())); + message.maxTerrain = reader.int32(); break; case 5: - message.mfeLangParam = reader.string(); + message.maxQuadtree = reader.int32(); break; case 6: - message.adsUrlPatterns = reader.string(); + message.maxDioramaMetadata = reader.int32(); break; case 7: - message.reverseGeocoderUrl = $types[6].decode(reader, reader.uint32()); + message.maxDioramaData = reader.int32(); break; case 8: - message.reverseGeocoderProtocolVersion = reader.int32(); + message.maxConsumerFetchRatio = reader.float(); break; case 9: - message.skyDatabaseIsAvailable = reader.bool(); + message.maxProEcFetchRatio = reader.float(); break; case 10: - message.skyDatabaseUrl = $types[9].decode(reader, reader.uint32()); + message.safeOverallQps = reader.float(); break; case 11: - message.defaultWebPageIntlUrl = $types[10].decode(reader, reader.uint32()); - break; - case 12: - message.numStartUpTips = reader.int32(); - break; - case 13: - message.startUpTipsUrl = $types[12].decode(reader, reader.uint32()); - break; - case 51: - message.numProStartUpTips = reader.int32(); - break; - case 52: - message.proStartUpTipsUrl = $types[14].decode(reader, reader.uint32()); - break; - case 64: - message.startupTipsIntlUrl = $types[15].decode(reader, reader.uint32()); + message.safeImageryQps = reader.float(); break; case 14: - message.userGuideIntlUrl = $types[16].decode(reader, reader.uint32()); + message.domainsForHttps = reader.string(); break; case 15: - message.supportCenterIntlUrl = $types[17].decode(reader, reader.uint32()); - break; - case 16: - message.businessListingIntlUrl = $types[18].decode(reader, reader.uint32()); - break; - case 17: - message.supportAnswerIntlUrl = $types[19].decode(reader, reader.uint32()); - break; - case 18: - message.supportTopicIntlUrl = $types[20].decode(reader, reader.uint32()); - break; - case 19: - message.supportRequestIntlUrl = $types[21].decode(reader, reader.uint32()); - break; - case 20: - message.earthIntlUrl = $types[22].decode(reader, reader.uint32()); - break; - case 21: - message.addContentUrl = $types[23].decode(reader, reader.uint32()); - break; - case 22: - message.sketchupNotInstalledUrl = $types[24].decode(reader, reader.uint32()); - break; - case 23: - message.sketchupErrorUrl = $types[25].decode(reader, reader.uint32()); - break; - case 24: - message.freeLicenseUrl = $types[26].decode(reader, reader.uint32()); - break; - case 25: - message.proLicenseUrl = $types[27].decode(reader, reader.uint32()); - break; - case 48: - message.tutorialUrl = $types[28].decode(reader, reader.uint32()); - break; - case 49: - message.keyboardShortcutsUrl = $types[29].decode(reader, reader.uint32()); - break; - case 50: - message.releaseNotesUrl = $types[30].decode(reader, reader.uint32()); - break; - case 26: - message.hideUserData = reader.bool(); - break; - case 27: - message.useGeLogo = reader.bool(); - break; - case 28: - message.dioramaDescriptionUrlBase = $types[33].decode(reader, reader.uint32()); - break; - case 29: - message.dioramaDefaultColor = reader.uint32(); - break; - case 53: - message.dioramaBlacklistUrl = $types[35].decode(reader, reader.uint32()); - break; - case 30: - message.clientOptions = $types[36].decode(reader, reader.uint32()); - break; - case 31: - message.fetchingOptions = $types[37].decode(reader, reader.uint32()); - break; - case 32: - message.timeMachineOptions = $types[38].decode(reader, reader.uint32()); - break; - case 33: - message.csiOptions = $types[39].decode(reader, reader.uint32()); - break; - case 34: - if (!(message.searchTab && message.searchTab.length)) - message.searchTab = []; - message.searchTab.push($types[40].decode(reader, reader.uint32())); - break; - case 35: - if (!(message.cobrandInfo && message.cobrandInfo.length)) - message.cobrandInfo = []; - message.cobrandInfo.push($types[41].decode(reader, reader.uint32())); - break; - case 36: - if (!(message.validDatabase && message.validDatabase.length)) - message.validDatabase = []; - message.validDatabase.push($types[42].decode(reader, reader.uint32())); - break; - case 37: - if (!(message.configScript && message.configScript.length)) - message.configScript = []; - message.configScript.push($types[43].decode(reader, reader.uint32())); - break; - case 38: - message.deauthServerUrl = $types[44].decode(reader, reader.uint32()); - break; - case 39: - message.swoopParameters = $types[45].decode(reader, reader.uint32()); - break; - case 40: - message.bbsServerInfo = $types[46].decode(reader, reader.uint32()); - break; - case 41: - message.dataErrorServerInfo = $types[47].decode(reader, reader.uint32()); + message.hostsForHttp = reader.string(); break; - case 42: - if (!(message.planetaryDatabase && message.planetaryDatabase.length)) - message.planetaryDatabase = []; - message.planetaryDatabase.push($types[48].decode(reader, reader.uint32())); + default: + reader.skipType(tag & 7); break; - case 43: - message.logServer = $types[49].decode(reader, reader.uint32()); + } + } + return message; + }; + + FetchingOptionsProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.maxRequestsPerQuery !== undefined) + if (!$util.isInteger(message.maxRequestsPerQuery)) + return "maxRequestsPerQuery: integer expected"; + if (message.forceMaxRequestsPerQuery !== undefined) + if (typeof message.forceMaxRequestsPerQuery !== "boolean") + return "forceMaxRequestsPerQuery: boolean expected"; + if (message.sortBatches !== undefined) + if (typeof message.sortBatches !== "boolean") + return "sortBatches: boolean expected"; + if (message.maxDrawable !== undefined) + if (!$util.isInteger(message.maxDrawable)) + return "maxDrawable: integer expected"; + if (message.maxImagery !== undefined) + if (!$util.isInteger(message.maxImagery)) + return "maxImagery: integer expected"; + if (message.maxTerrain !== undefined) + if (!$util.isInteger(message.maxTerrain)) + return "maxTerrain: integer expected"; + if (message.maxQuadtree !== undefined) + if (!$util.isInteger(message.maxQuadtree)) + return "maxQuadtree: integer expected"; + if (message.maxDioramaMetadata !== undefined) + if (!$util.isInteger(message.maxDioramaMetadata)) + return "maxDioramaMetadata: integer expected"; + if (message.maxDioramaData !== undefined) + if (!$util.isInteger(message.maxDioramaData)) + return "maxDioramaData: integer expected"; + if (message.maxConsumerFetchRatio !== undefined) + if (typeof message.maxConsumerFetchRatio !== "number") + return "maxConsumerFetchRatio: number expected"; + if (message.maxProEcFetchRatio !== undefined) + if (typeof message.maxProEcFetchRatio !== "number") + return "maxProEcFetchRatio: number expected"; + if (message.safeOverallQps !== undefined) + if (typeof message.safeOverallQps !== "number") + return "safeOverallQps: number expected"; + if (message.safeImageryQps !== undefined) + if (typeof message.safeImageryQps !== "number") + return "safeImageryQps: number expected"; + if (message.domainsForHttps !== undefined) + if (!$util.isString(message.domainsForHttps)) + return "domainsForHttps: string expected"; + if (message.hostsForHttp !== undefined) + if (!$util.isString(message.hostsForHttp)) + return "hostsForHttp: string expected"; + return null; + }; + + FetchingOptionsProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.FetchingOptionsProto) + return object; + var message = new $root.keyhole.dbroot.FetchingOptionsProto(); + if (object.maxRequestsPerQuery !== undefined && object.maxRequestsPerQuery !== null) + message.maxRequestsPerQuery = object.maxRequestsPerQuery | 0; + if (object.forceMaxRequestsPerQuery !== undefined && object.forceMaxRequestsPerQuery !== null) + message.forceMaxRequestsPerQuery = Boolean(object.forceMaxRequestsPerQuery); + if (object.sortBatches !== undefined && object.sortBatches !== null) + message.sortBatches = Boolean(object.sortBatches); + if (object.maxDrawable !== undefined && object.maxDrawable !== null) + message.maxDrawable = object.maxDrawable | 0; + if (object.maxImagery !== undefined && object.maxImagery !== null) + message.maxImagery = object.maxImagery | 0; + if (object.maxTerrain !== undefined && object.maxTerrain !== null) + message.maxTerrain = object.maxTerrain | 0; + if (object.maxQuadtree !== undefined && object.maxQuadtree !== null) + message.maxQuadtree = object.maxQuadtree | 0; + if (object.maxDioramaMetadata !== undefined && object.maxDioramaMetadata !== null) + message.maxDioramaMetadata = object.maxDioramaMetadata | 0; + if (object.maxDioramaData !== undefined && object.maxDioramaData !== null) + message.maxDioramaData = object.maxDioramaData | 0; + if (object.maxConsumerFetchRatio !== undefined && object.maxConsumerFetchRatio !== null) + message.maxConsumerFetchRatio = Number(object.maxConsumerFetchRatio); + if (object.maxProEcFetchRatio !== undefined && object.maxProEcFetchRatio !== null) + message.maxProEcFetchRatio = Number(object.maxProEcFetchRatio); + if (object.safeOverallQps !== undefined && object.safeOverallQps !== null) + message.safeOverallQps = Number(object.safeOverallQps); + if (object.safeImageryQps !== undefined && object.safeImageryQps !== null) + message.safeImageryQps = Number(object.safeImageryQps); + if (object.domainsForHttps !== undefined && object.domainsForHttps !== null) + message.domainsForHttps = String(object.domainsForHttps); + if (object.hostsForHttp !== undefined && object.hostsForHttp !== null) + message.hostsForHttp = String(object.hostsForHttp); + return message; + }; + + FetchingOptionsProto.from = FetchingOptionsProto.fromObject; + + FetchingOptionsProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.maxRequestsPerQuery = 1; + object.forceMaxRequestsPerQuery = false; + object.sortBatches = false; + object.maxDrawable = 2; + object.maxImagery = 2; + object.maxTerrain = 5; + object.maxQuadtree = 5; + object.maxDioramaMetadata = 1; + object.maxDioramaData = 0; + object.maxConsumerFetchRatio = 1; + object.maxProEcFetchRatio = 0; + object.safeOverallQps = 0; + object.safeImageryQps = 0; + object.domainsForHttps = "google.com gstatic.com"; + object.hostsForHttp = ""; + } + if (message.maxRequestsPerQuery !== undefined && message.maxRequestsPerQuery !== null && message.hasOwnProperty("maxRequestsPerQuery")) + object.maxRequestsPerQuery = message.maxRequestsPerQuery; + if (message.forceMaxRequestsPerQuery !== undefined && message.forceMaxRequestsPerQuery !== null && message.hasOwnProperty("forceMaxRequestsPerQuery")) + object.forceMaxRequestsPerQuery = message.forceMaxRequestsPerQuery; + if (message.sortBatches !== undefined && message.sortBatches !== null && message.hasOwnProperty("sortBatches")) + object.sortBatches = message.sortBatches; + if (message.maxDrawable !== undefined && message.maxDrawable !== null && message.hasOwnProperty("maxDrawable")) + object.maxDrawable = message.maxDrawable; + if (message.maxImagery !== undefined && message.maxImagery !== null && message.hasOwnProperty("maxImagery")) + object.maxImagery = message.maxImagery; + if (message.maxTerrain !== undefined && message.maxTerrain !== null && message.hasOwnProperty("maxTerrain")) + object.maxTerrain = message.maxTerrain; + if (message.maxQuadtree !== undefined && message.maxQuadtree !== null && message.hasOwnProperty("maxQuadtree")) + object.maxQuadtree = message.maxQuadtree; + if (message.maxDioramaMetadata !== undefined && message.maxDioramaMetadata !== null && message.hasOwnProperty("maxDioramaMetadata")) + object.maxDioramaMetadata = message.maxDioramaMetadata; + if (message.maxDioramaData !== undefined && message.maxDioramaData !== null && message.hasOwnProperty("maxDioramaData")) + object.maxDioramaData = message.maxDioramaData; + if (message.maxConsumerFetchRatio !== undefined && message.maxConsumerFetchRatio !== null && message.hasOwnProperty("maxConsumerFetchRatio")) + object.maxConsumerFetchRatio = message.maxConsumerFetchRatio; + if (message.maxProEcFetchRatio !== undefined && message.maxProEcFetchRatio !== null && message.hasOwnProperty("maxProEcFetchRatio")) + object.maxProEcFetchRatio = message.maxProEcFetchRatio; + if (message.safeOverallQps !== undefined && message.safeOverallQps !== null && message.hasOwnProperty("safeOverallQps")) + object.safeOverallQps = message.safeOverallQps; + if (message.safeImageryQps !== undefined && message.safeImageryQps !== null && message.hasOwnProperty("safeImageryQps")) + object.safeImageryQps = message.safeImageryQps; + if (message.domainsForHttps !== undefined && message.domainsForHttps !== null && message.hasOwnProperty("domainsForHttps")) + object.domainsForHttps = message.domainsForHttps; + if (message.hostsForHttp !== undefined && message.hostsForHttp !== null && message.hasOwnProperty("hostsForHttp")) + object.hostsForHttp = message.hostsForHttp; + return object; + }; + + FetchingOptionsProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + FetchingOptionsProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return FetchingOptionsProto; + })(); + + dbroot.TimeMachineOptionsProto = (function() { + + function TimeMachineOptionsProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + TimeMachineOptionsProto.prototype.serverUrl = ""; + TimeMachineOptionsProto.prototype.isTimemachine = false; + TimeMachineOptionsProto.prototype.dwellTimeMs = 500; + TimeMachineOptionsProto.prototype.discoverabilityAltitudeMeters = 15000; + + TimeMachineOptionsProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.TimeMachineOptionsProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.serverUrl = reader.string(); break; - case 44: - message.autopiaOptions = $types[50].decode(reader, reader.uint32()); + case 2: + message.isTimemachine = reader.bool(); break; - case 54: - message.searchConfig = $types[51].decode(reader, reader.uint32()); + case 3: + message.dwellTimeMs = reader.int32(); break; - case 45: - message.searchInfo = $types[52].decode(reader, reader.uint32()); + case 4: + message.discoverabilityAltitudeMeters = reader.int32(); break; - case 46: - message.elevationServiceBaseUrl = reader.string(); + default: + reader.skipType(tag & 7); break; - case 47: - message.elevationProfileQueryDelay = reader.int32(); + } + } + return message; + }; + + TimeMachineOptionsProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.serverUrl !== undefined) + if (!$util.isString(message.serverUrl)) + return "serverUrl: string expected"; + if (message.isTimemachine !== undefined) + if (typeof message.isTimemachine !== "boolean") + return "isTimemachine: boolean expected"; + if (message.dwellTimeMs !== undefined) + if (!$util.isInteger(message.dwellTimeMs)) + return "dwellTimeMs: integer expected"; + if (message.discoverabilityAltitudeMeters !== undefined) + if (!$util.isInteger(message.discoverabilityAltitudeMeters)) + return "discoverabilityAltitudeMeters: integer expected"; + return null; + }; + + TimeMachineOptionsProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.TimeMachineOptionsProto) + return object; + var message = new $root.keyhole.dbroot.TimeMachineOptionsProto(); + if (object.serverUrl !== undefined && object.serverUrl !== null) + message.serverUrl = String(object.serverUrl); + if (object.isTimemachine !== undefined && object.isTimemachine !== null) + message.isTimemachine = Boolean(object.isTimemachine); + if (object.dwellTimeMs !== undefined && object.dwellTimeMs !== null) + message.dwellTimeMs = object.dwellTimeMs | 0; + if (object.discoverabilityAltitudeMeters !== undefined && object.discoverabilityAltitudeMeters !== null) + message.discoverabilityAltitudeMeters = object.discoverabilityAltitudeMeters | 0; + return message; + }; + + TimeMachineOptionsProto.from = TimeMachineOptionsProto.fromObject; + + TimeMachineOptionsProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.serverUrl = ""; + object.isTimemachine = false; + object.dwellTimeMs = 500; + object.discoverabilityAltitudeMeters = 15000; + } + if (message.serverUrl !== undefined && message.serverUrl !== null && message.hasOwnProperty("serverUrl")) + object.serverUrl = message.serverUrl; + if (message.isTimemachine !== undefined && message.isTimemachine !== null && message.hasOwnProperty("isTimemachine")) + object.isTimemachine = message.isTimemachine; + if (message.dwellTimeMs !== undefined && message.dwellTimeMs !== null && message.hasOwnProperty("dwellTimeMs")) + object.dwellTimeMs = message.dwellTimeMs; + if (message.discoverabilityAltitudeMeters !== undefined && message.discoverabilityAltitudeMeters !== null && message.hasOwnProperty("discoverabilityAltitudeMeters")) + object.discoverabilityAltitudeMeters = message.discoverabilityAltitudeMeters; + return object; + }; + + TimeMachineOptionsProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + TimeMachineOptionsProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return TimeMachineOptionsProto; + })(); + + dbroot.AutopiaOptionsProto = (function() { + + function AutopiaOptionsProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + AutopiaOptionsProto.prototype.metadataServerUrl = "http://cbk0.google.com/cbk"; + AutopiaOptionsProto.prototype.depthmapServerUrl = "http://cbk0.google.com/cbk"; + AutopiaOptionsProto.prototype.coverageOverlayUrl = ""; + AutopiaOptionsProto.prototype.maxImageryQps = 0; + AutopiaOptionsProto.prototype.maxMetadataDepthmapQps = 0; + + AutopiaOptionsProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.AutopiaOptionsProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.metadataServerUrl = reader.string(); break; - case 55: - message.proUpgradeUrl = $types[55].decode(reader, reader.uint32()); + case 2: + message.depthmapServerUrl = reader.string(); break; - case 56: - message.earthCommunityUrl = $types[56].decode(reader, reader.uint32()); + case 3: + message.coverageOverlayUrl = reader.string(); break; - case 57: - message.googleMapsUrl = $types[57].decode(reader, reader.uint32()); + case 4: + message.maxImageryQps = reader.float(); break; - case 58: - message.sharingUrl = $types[58].decode(reader, reader.uint32()); + case 5: + message.maxMetadataDepthmapQps = reader.float(); break; - case 59: - message.privacyPolicyUrl = $types[59].decode(reader, reader.uint32()); + default: + reader.skipType(tag & 7); break; - case 60: - message.doGplusUserCheck = reader.bool(); + } + } + return message; + }; + + AutopiaOptionsProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.metadataServerUrl !== undefined) + if (!$util.isString(message.metadataServerUrl)) + return "metadataServerUrl: string expected"; + if (message.depthmapServerUrl !== undefined) + if (!$util.isString(message.depthmapServerUrl)) + return "depthmapServerUrl: string expected"; + if (message.coverageOverlayUrl !== undefined) + if (!$util.isString(message.coverageOverlayUrl)) + return "coverageOverlayUrl: string expected"; + if (message.maxImageryQps !== undefined) + if (typeof message.maxImageryQps !== "number") + return "maxImageryQps: number expected"; + if (message.maxMetadataDepthmapQps !== undefined) + if (typeof message.maxMetadataDepthmapQps !== "number") + return "maxMetadataDepthmapQps: number expected"; + return null; + }; + + AutopiaOptionsProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.AutopiaOptionsProto) + return object; + var message = new $root.keyhole.dbroot.AutopiaOptionsProto(); + if (object.metadataServerUrl !== undefined && object.metadataServerUrl !== null) + message.metadataServerUrl = String(object.metadataServerUrl); + if (object.depthmapServerUrl !== undefined && object.depthmapServerUrl !== null) + message.depthmapServerUrl = String(object.depthmapServerUrl); + if (object.coverageOverlayUrl !== undefined && object.coverageOverlayUrl !== null) + message.coverageOverlayUrl = String(object.coverageOverlayUrl); + if (object.maxImageryQps !== undefined && object.maxImageryQps !== null) + message.maxImageryQps = Number(object.maxImageryQps); + if (object.maxMetadataDepthmapQps !== undefined && object.maxMetadataDepthmapQps !== null) + message.maxMetadataDepthmapQps = Number(object.maxMetadataDepthmapQps); + return message; + }; + + AutopiaOptionsProto.from = AutopiaOptionsProto.fromObject; + + AutopiaOptionsProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.metadataServerUrl = "http://cbk0.google.com/cbk"; + object.depthmapServerUrl = "http://cbk0.google.com/cbk"; + object.coverageOverlayUrl = ""; + object.maxImageryQps = 0; + object.maxMetadataDepthmapQps = 0; + } + if (message.metadataServerUrl !== undefined && message.metadataServerUrl !== null && message.hasOwnProperty("metadataServerUrl")) + object.metadataServerUrl = message.metadataServerUrl; + if (message.depthmapServerUrl !== undefined && message.depthmapServerUrl !== null && message.hasOwnProperty("depthmapServerUrl")) + object.depthmapServerUrl = message.depthmapServerUrl; + if (message.coverageOverlayUrl !== undefined && message.coverageOverlayUrl !== null && message.hasOwnProperty("coverageOverlayUrl")) + object.coverageOverlayUrl = message.coverageOverlayUrl; + if (message.maxImageryQps !== undefined && message.maxImageryQps !== null && message.hasOwnProperty("maxImageryQps")) + object.maxImageryQps = message.maxImageryQps; + if (message.maxMetadataDepthmapQps !== undefined && message.maxMetadataDepthmapQps !== null && message.hasOwnProperty("maxMetadataDepthmapQps")) + object.maxMetadataDepthmapQps = message.maxMetadataDepthmapQps; + return object; + }; + + AutopiaOptionsProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + AutopiaOptionsProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return AutopiaOptionsProto; + })(); + + dbroot.CSIOptionsProto = (function() { + + function CSIOptionsProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + CSIOptionsProto.prototype.samplingPercentage = 0; + CSIOptionsProto.prototype.experimentId = ""; + + CSIOptionsProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.CSIOptionsProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.samplingPercentage = reader.int32(); break; - case 61: - message.rocktreeDataProto = $types[61].decode(reader, reader.uint32()); + case 2: + message.experimentId = reader.string(); break; - case 62: - if (!(message.filmstripConfig && message.filmstripConfig.length)) - message.filmstripConfig = []; - message.filmstripConfig.push($types[62].decode(reader, reader.uint32())); + default: + reader.skipType(tag & 7); break; - case 63: - message.showSigninButton = reader.bool(); + } + } + return message; + }; + + CSIOptionsProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.samplingPercentage !== undefined) + if (!$util.isInteger(message.samplingPercentage)) + return "samplingPercentage: integer expected"; + if (message.experimentId !== undefined) + if (!$util.isString(message.experimentId)) + return "experimentId: string expected"; + return null; + }; + + CSIOptionsProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.CSIOptionsProto) + return object; + var message = new $root.keyhole.dbroot.CSIOptionsProto(); + if (object.samplingPercentage !== undefined && object.samplingPercentage !== null) + message.samplingPercentage = object.samplingPercentage | 0; + if (object.experimentId !== undefined && object.experimentId !== null) + message.experimentId = String(object.experimentId); + return message; + }; + + CSIOptionsProto.from = CSIOptionsProto.fromObject; + + CSIOptionsProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.samplingPercentage = 0; + object.experimentId = ""; + } + if (message.samplingPercentage !== undefined && message.samplingPercentage !== null && message.hasOwnProperty("samplingPercentage")) + object.samplingPercentage = message.samplingPercentage; + if (message.experimentId !== undefined && message.experimentId !== null && message.hasOwnProperty("experimentId")) + object.experimentId = message.experimentId; + return object; + }; + + CSIOptionsProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; + + CSIOptionsProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return CSIOptionsProto; + })(); + + dbroot.SearchTabProto = (function() { + + function SearchTabProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } + + SearchTabProto.prototype.isVisible = false; + SearchTabProto.prototype.tabLabel = null; + SearchTabProto.prototype.baseUrl = ""; + SearchTabProto.prototype.viewportPrefix = ""; + SearchTabProto.prototype.inputBox = $util.emptyArray; + SearchTabProto.prototype.requirement = null; + + var $types = { + 1 : "keyhole.dbroot.StringIdOrValueProto", + 4 : "keyhole.dbroot.SearchTabProto.InputBoxInfo", + 5 : "keyhole.dbroot.RequirementProto" + }; + $lazyTypes.push($types); + + SearchTabProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.SearchTabProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.isVisible = reader.bool(); break; - case 65: - message.proMeasureUpsellUrl = $types[64].decode(reader, reader.uint32()); + case 2: + message.tabLabel = $types[1].decode(reader, reader.uint32()); break; - case 66: - message.proPrintUpsellUrl = $types[65].decode(reader, reader.uint32()); + case 3: + message.baseUrl = reader.string(); break; - case 67: - message.starDataProto = $types[66].decode(reader, reader.uint32()); + case 4: + message.viewportPrefix = reader.string(); break; - case 68: - message.feedbackUrl = $types[67].decode(reader, reader.uint32()); + case 5: + if (!(message.inputBox && message.inputBox.length)) + message.inputBox = []; + message.inputBox.push($types[4].decode(reader, reader.uint32())); break; - case 69: - message.oauth2LoginUrl = $types[68].decode(reader, reader.uint32()); + case 6: + message.requirement = $types[5].decode(reader, reader.uint32()); break; default: reader.skipType(tag & 7); @@ -54277,1005 +56106,146 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - EndSnippetProto.verify = function verify(message) { + SearchTabProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.model !== undefined && message.model !== null) { - var error = $types[0].verify(message.model); - if (error) - return "model." + error; - } - if (message.authServerUrl !== undefined && message.authServerUrl !== null) { - var error = $types[1].verify(message.authServerUrl); + if (typeof message.isVisible !== "boolean") + return "isVisible: boolean expected"; + if (message.tabLabel !== undefined && message.tabLabel !== null) { + var error = $types[1].verify(message.tabLabel); if (error) - return "authServerUrl." + error; + return "tabLabel." + error; } - if (message.disableAuthentication !== undefined) - if (typeof message.disableAuthentication !== "boolean") - return "disableAuthentication: boolean expected"; - if (message.mfeDomains !== undefined) { - if (!Array.isArray(message.mfeDomains)) - return "mfeDomains: array expected"; - for (var i = 0; i < message.mfeDomains.length; ++i) { - var error = $types[3].verify(message.mfeDomains[i]); + if (message.baseUrl !== undefined) + if (!$util.isString(message.baseUrl)) + return "baseUrl: string expected"; + if (message.viewportPrefix !== undefined) + if (!$util.isString(message.viewportPrefix)) + return "viewportPrefix: string expected"; + if (message.inputBox !== undefined) { + if (!Array.isArray(message.inputBox)) + return "inputBox: array expected"; + for (var i = 0; i < message.inputBox.length; ++i) { + var error = $types[4].verify(message.inputBox[i]); if (error) - return "mfeDomains." + error; + return "inputBox." + error; } } - if (message.mfeLangParam !== undefined) - if (!$util.isString(message.mfeLangParam)) - return "mfeLangParam: string expected"; - if (message.adsUrlPatterns !== undefined) - if (!$util.isString(message.adsUrlPatterns)) - return "adsUrlPatterns: string expected"; - if (message.reverseGeocoderUrl !== undefined && message.reverseGeocoderUrl !== null) { - var error = $types[6].verify(message.reverseGeocoderUrl); + if (message.requirement !== undefined && message.requirement !== null) { + var error = $types[5].verify(message.requirement); if (error) - return "reverseGeocoderUrl." + error; - } - if (message.reverseGeocoderProtocolVersion !== undefined) - if (!$util.isInteger(message.reverseGeocoderProtocolVersion)) - return "reverseGeocoderProtocolVersion: integer expected"; - if (message.skyDatabaseIsAvailable !== undefined) - if (typeof message.skyDatabaseIsAvailable !== "boolean") - return "skyDatabaseIsAvailable: boolean expected"; - if (message.skyDatabaseUrl !== undefined && message.skyDatabaseUrl !== null) { - var error = $types[9].verify(message.skyDatabaseUrl); - if (error) - return "skyDatabaseUrl." + error; - } - if (message.defaultWebPageIntlUrl !== undefined && message.defaultWebPageIntlUrl !== null) { - var error = $types[10].verify(message.defaultWebPageIntlUrl); - if (error) - return "defaultWebPageIntlUrl." + error; - } - if (message.numStartUpTips !== undefined) - if (!$util.isInteger(message.numStartUpTips)) - return "numStartUpTips: integer expected"; - if (message.startUpTipsUrl !== undefined && message.startUpTipsUrl !== null) { - var error = $types[12].verify(message.startUpTipsUrl); - if (error) - return "startUpTipsUrl." + error; - } - if (message.numProStartUpTips !== undefined) - if (!$util.isInteger(message.numProStartUpTips)) - return "numProStartUpTips: integer expected"; - if (message.proStartUpTipsUrl !== undefined && message.proStartUpTipsUrl !== null) { - var error = $types[14].verify(message.proStartUpTipsUrl); - if (error) - return "proStartUpTipsUrl." + error; - } - if (message.startupTipsIntlUrl !== undefined && message.startupTipsIntlUrl !== null) { - var error = $types[15].verify(message.startupTipsIntlUrl); - if (error) - return "startupTipsIntlUrl." + error; - } - if (message.userGuideIntlUrl !== undefined && message.userGuideIntlUrl !== null) { - var error = $types[16].verify(message.userGuideIntlUrl); - if (error) - return "userGuideIntlUrl." + error; - } - if (message.supportCenterIntlUrl !== undefined && message.supportCenterIntlUrl !== null) { - var error = $types[17].verify(message.supportCenterIntlUrl); - if (error) - return "supportCenterIntlUrl." + error; - } - if (message.businessListingIntlUrl !== undefined && message.businessListingIntlUrl !== null) { - var error = $types[18].verify(message.businessListingIntlUrl); - if (error) - return "businessListingIntlUrl." + error; - } - if (message.supportAnswerIntlUrl !== undefined && message.supportAnswerIntlUrl !== null) { - var error = $types[19].verify(message.supportAnswerIntlUrl); - if (error) - return "supportAnswerIntlUrl." + error; - } - if (message.supportTopicIntlUrl !== undefined && message.supportTopicIntlUrl !== null) { - var error = $types[20].verify(message.supportTopicIntlUrl); - if (error) - return "supportTopicIntlUrl." + error; - } - if (message.supportRequestIntlUrl !== undefined && message.supportRequestIntlUrl !== null) { - var error = $types[21].verify(message.supportRequestIntlUrl); - if (error) - return "supportRequestIntlUrl." + error; - } - if (message.earthIntlUrl !== undefined && message.earthIntlUrl !== null) { - var error = $types[22].verify(message.earthIntlUrl); - if (error) - return "earthIntlUrl." + error; - } - if (message.addContentUrl !== undefined && message.addContentUrl !== null) { - var error = $types[23].verify(message.addContentUrl); - if (error) - return "addContentUrl." + error; - } - if (message.sketchupNotInstalledUrl !== undefined && message.sketchupNotInstalledUrl !== null) { - var error = $types[24].verify(message.sketchupNotInstalledUrl); - if (error) - return "sketchupNotInstalledUrl." + error; - } - if (message.sketchupErrorUrl !== undefined && message.sketchupErrorUrl !== null) { - var error = $types[25].verify(message.sketchupErrorUrl); - if (error) - return "sketchupErrorUrl." + error; - } - if (message.freeLicenseUrl !== undefined && message.freeLicenseUrl !== null) { - var error = $types[26].verify(message.freeLicenseUrl); - if (error) - return "freeLicenseUrl." + error; - } - if (message.proLicenseUrl !== undefined && message.proLicenseUrl !== null) { - var error = $types[27].verify(message.proLicenseUrl); - if (error) - return "proLicenseUrl." + error; - } - if (message.tutorialUrl !== undefined && message.tutorialUrl !== null) { - var error = $types[28].verify(message.tutorialUrl); - if (error) - return "tutorialUrl." + error; - } - if (message.keyboardShortcutsUrl !== undefined && message.keyboardShortcutsUrl !== null) { - var error = $types[29].verify(message.keyboardShortcutsUrl); - if (error) - return "keyboardShortcutsUrl." + error; - } - if (message.releaseNotesUrl !== undefined && message.releaseNotesUrl !== null) { - var error = $types[30].verify(message.releaseNotesUrl); - if (error) - return "releaseNotesUrl." + error; - } - if (message.hideUserData !== undefined) - if (typeof message.hideUserData !== "boolean") - return "hideUserData: boolean expected"; - if (message.useGeLogo !== undefined) - if (typeof message.useGeLogo !== "boolean") - return "useGeLogo: boolean expected"; - if (message.dioramaDescriptionUrlBase !== undefined && message.dioramaDescriptionUrlBase !== null) { - var error = $types[33].verify(message.dioramaDescriptionUrlBase); - if (error) - return "dioramaDescriptionUrlBase." + error; - } - if (message.dioramaDefaultColor !== undefined) - if (!$util.isInteger(message.dioramaDefaultColor)) - return "dioramaDefaultColor: integer expected"; - if (message.dioramaBlacklistUrl !== undefined && message.dioramaBlacklistUrl !== null) { - var error = $types[35].verify(message.dioramaBlacklistUrl); - if (error) - return "dioramaBlacklistUrl." + error; - } - if (message.clientOptions !== undefined && message.clientOptions !== null) { - var error = $types[36].verify(message.clientOptions); - if (error) - return "clientOptions." + error; - } - if (message.fetchingOptions !== undefined && message.fetchingOptions !== null) { - var error = $types[37].verify(message.fetchingOptions); - if (error) - return "fetchingOptions." + error; - } - if (message.timeMachineOptions !== undefined && message.timeMachineOptions !== null) { - var error = $types[38].verify(message.timeMachineOptions); - if (error) - return "timeMachineOptions." + error; - } - if (message.csiOptions !== undefined && message.csiOptions !== null) { - var error = $types[39].verify(message.csiOptions); - if (error) - return "csiOptions." + error; - } - if (message.searchTab !== undefined) { - if (!Array.isArray(message.searchTab)) - return "searchTab: array expected"; - for (var i = 0; i < message.searchTab.length; ++i) { - var error = $types[40].verify(message.searchTab[i]); - if (error) - return "searchTab." + error; - } - } - if (message.cobrandInfo !== undefined) { - if (!Array.isArray(message.cobrandInfo)) - return "cobrandInfo: array expected"; - for (var i = 0; i < message.cobrandInfo.length; ++i) { - var error = $types[41].verify(message.cobrandInfo[i]); - if (error) - return "cobrandInfo." + error; - } - } - if (message.validDatabase !== undefined) { - if (!Array.isArray(message.validDatabase)) - return "validDatabase: array expected"; - for (var i = 0; i < message.validDatabase.length; ++i) { - var error = $types[42].verify(message.validDatabase[i]); - if (error) - return "validDatabase." + error; - } - } - if (message.configScript !== undefined) { - if (!Array.isArray(message.configScript)) - return "configScript: array expected"; - for (var i = 0; i < message.configScript.length; ++i) { - var error = $types[43].verify(message.configScript[i]); - if (error) - return "configScript." + error; - } - } - if (message.deauthServerUrl !== undefined && message.deauthServerUrl !== null) { - var error = $types[44].verify(message.deauthServerUrl); - if (error) - return "deauthServerUrl." + error; - } - if (message.swoopParameters !== undefined && message.swoopParameters !== null) { - var error = $types[45].verify(message.swoopParameters); - if (error) - return "swoopParameters." + error; - } - if (message.bbsServerInfo !== undefined && message.bbsServerInfo !== null) { - var error = $types[46].verify(message.bbsServerInfo); - if (error) - return "bbsServerInfo." + error; - } - if (message.dataErrorServerInfo !== undefined && message.dataErrorServerInfo !== null) { - var error = $types[47].verify(message.dataErrorServerInfo); - if (error) - return "dataErrorServerInfo." + error; - } - if (message.planetaryDatabase !== undefined) { - if (!Array.isArray(message.planetaryDatabase)) - return "planetaryDatabase: array expected"; - for (var i = 0; i < message.planetaryDatabase.length; ++i) { - var error = $types[48].verify(message.planetaryDatabase[i]); - if (error) - return "planetaryDatabase." + error; - } - } - if (message.logServer !== undefined && message.logServer !== null) { - var error = $types[49].verify(message.logServer); - if (error) - return "logServer." + error; - } - if (message.autopiaOptions !== undefined && message.autopiaOptions !== null) { - var error = $types[50].verify(message.autopiaOptions); - if (error) - return "autopiaOptions." + error; - } - if (message.searchConfig !== undefined && message.searchConfig !== null) { - var error = $types[51].verify(message.searchConfig); - if (error) - return "searchConfig." + error; - } - if (message.searchInfo !== undefined && message.searchInfo !== null) { - var error = $types[52].verify(message.searchInfo); - if (error) - return "searchInfo." + error; - } - if (message.elevationServiceBaseUrl !== undefined) - if (!$util.isString(message.elevationServiceBaseUrl)) - return "elevationServiceBaseUrl: string expected"; - if (message.elevationProfileQueryDelay !== undefined) - if (!$util.isInteger(message.elevationProfileQueryDelay)) - return "elevationProfileQueryDelay: integer expected"; - if (message.proUpgradeUrl !== undefined && message.proUpgradeUrl !== null) { - var error = $types[55].verify(message.proUpgradeUrl); - if (error) - return "proUpgradeUrl." + error; - } - if (message.earthCommunityUrl !== undefined && message.earthCommunityUrl !== null) { - var error = $types[56].verify(message.earthCommunityUrl); - if (error) - return "earthCommunityUrl." + error; - } - if (message.googleMapsUrl !== undefined && message.googleMapsUrl !== null) { - var error = $types[57].verify(message.googleMapsUrl); - if (error) - return "googleMapsUrl." + error; - } - if (message.sharingUrl !== undefined && message.sharingUrl !== null) { - var error = $types[58].verify(message.sharingUrl); - if (error) - return "sharingUrl." + error; - } - if (message.privacyPolicyUrl !== undefined && message.privacyPolicyUrl !== null) { - var error = $types[59].verify(message.privacyPolicyUrl); - if (error) - return "privacyPolicyUrl." + error; - } - if (message.doGplusUserCheck !== undefined) - if (typeof message.doGplusUserCheck !== "boolean") - return "doGplusUserCheck: boolean expected"; - if (message.rocktreeDataProto !== undefined && message.rocktreeDataProto !== null) { - var error = $types[61].verify(message.rocktreeDataProto); - if (error) - return "rocktreeDataProto." + error; - } - if (message.filmstripConfig !== undefined) { - if (!Array.isArray(message.filmstripConfig)) - return "filmstripConfig: array expected"; - for (var i = 0; i < message.filmstripConfig.length; ++i) { - var error = $types[62].verify(message.filmstripConfig[i]); - if (error) - return "filmstripConfig." + error; - } - } - if (message.showSigninButton !== undefined) - if (typeof message.showSigninButton !== "boolean") - return "showSigninButton: boolean expected"; - if (message.proMeasureUpsellUrl !== undefined && message.proMeasureUpsellUrl !== null) { - var error = $types[64].verify(message.proMeasureUpsellUrl); - if (error) - return "proMeasureUpsellUrl." + error; - } - if (message.proPrintUpsellUrl !== undefined && message.proPrintUpsellUrl !== null) { - var error = $types[65].verify(message.proPrintUpsellUrl); - if (error) - return "proPrintUpsellUrl." + error; - } - if (message.starDataProto !== undefined && message.starDataProto !== null) { - var error = $types[66].verify(message.starDataProto); - if (error) - return "starDataProto." + error; - } - if (message.feedbackUrl !== undefined && message.feedbackUrl !== null) { - var error = $types[67].verify(message.feedbackUrl); - if (error) - return "feedbackUrl." + error; - } - if (message.oauth2LoginUrl !== undefined && message.oauth2LoginUrl !== null) { - var error = $types[68].verify(message.oauth2LoginUrl); - if (error) - return "oauth2LoginUrl." + error; + return "requirement." + error; } return null; }; - EndSnippetProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto) + SearchTabProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.SearchTabProto) return object; - var message = new $root.keyhole.dbroot.EndSnippetProto(); - if (object.model !== undefined && object.model !== null) { - if (typeof object.model !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.model: object expected"); - message.model = $types[0].fromObject(object.model); - } - if (object.authServerUrl !== undefined && object.authServerUrl !== null) { - if (typeof object.authServerUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.authServerUrl: object expected"); - message.authServerUrl = $types[1].fromObject(object.authServerUrl); - } - if (object.disableAuthentication !== undefined && object.disableAuthentication !== null) - message.disableAuthentication = Boolean(object.disableAuthentication); - if (object.mfeDomains) { - if (!Array.isArray(object.mfeDomains)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.mfeDomains: array expected"); - message.mfeDomains = []; - for (var i = 0; i < object.mfeDomains.length; ++i) { - if (typeof object.mfeDomains[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.mfeDomains: object expected"); - message.mfeDomains[i] = $types[3].fromObject(object.mfeDomains[i]); - } - } - if (object.mfeLangParam !== undefined && object.mfeLangParam !== null) - message.mfeLangParam = String(object.mfeLangParam); - if (object.adsUrlPatterns !== undefined && object.adsUrlPatterns !== null) - message.adsUrlPatterns = String(object.adsUrlPatterns); - if (object.reverseGeocoderUrl !== undefined && object.reverseGeocoderUrl !== null) { - if (typeof object.reverseGeocoderUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.reverseGeocoderUrl: object expected"); - message.reverseGeocoderUrl = $types[6].fromObject(object.reverseGeocoderUrl); - } - if (object.reverseGeocoderProtocolVersion !== undefined && object.reverseGeocoderProtocolVersion !== null) - message.reverseGeocoderProtocolVersion = object.reverseGeocoderProtocolVersion | 0; - if (object.skyDatabaseIsAvailable !== undefined && object.skyDatabaseIsAvailable !== null) - message.skyDatabaseIsAvailable = Boolean(object.skyDatabaseIsAvailable); - if (object.skyDatabaseUrl !== undefined && object.skyDatabaseUrl !== null) { - if (typeof object.skyDatabaseUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.skyDatabaseUrl: object expected"); - message.skyDatabaseUrl = $types[9].fromObject(object.skyDatabaseUrl); - } - if (object.defaultWebPageIntlUrl !== undefined && object.defaultWebPageIntlUrl !== null) { - if (typeof object.defaultWebPageIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.defaultWebPageIntlUrl: object expected"); - message.defaultWebPageIntlUrl = $types[10].fromObject(object.defaultWebPageIntlUrl); - } - if (object.numStartUpTips !== undefined && object.numStartUpTips !== null) - message.numStartUpTips = object.numStartUpTips | 0; - if (object.startUpTipsUrl !== undefined && object.startUpTipsUrl !== null) { - if (typeof object.startUpTipsUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.startUpTipsUrl: object expected"); - message.startUpTipsUrl = $types[12].fromObject(object.startUpTipsUrl); - } - if (object.numProStartUpTips !== undefined && object.numProStartUpTips !== null) - message.numProStartUpTips = object.numProStartUpTips | 0; - if (object.proStartUpTipsUrl !== undefined && object.proStartUpTipsUrl !== null) { - if (typeof object.proStartUpTipsUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.proStartUpTipsUrl: object expected"); - message.proStartUpTipsUrl = $types[14].fromObject(object.proStartUpTipsUrl); - } - if (object.startupTipsIntlUrl !== undefined && object.startupTipsIntlUrl !== null) { - if (typeof object.startupTipsIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.startupTipsIntlUrl: object expected"); - message.startupTipsIntlUrl = $types[15].fromObject(object.startupTipsIntlUrl); - } - if (object.userGuideIntlUrl !== undefined && object.userGuideIntlUrl !== null) { - if (typeof object.userGuideIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.userGuideIntlUrl: object expected"); - message.userGuideIntlUrl = $types[16].fromObject(object.userGuideIntlUrl); - } - if (object.supportCenterIntlUrl !== undefined && object.supportCenterIntlUrl !== null) { - if (typeof object.supportCenterIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.supportCenterIntlUrl: object expected"); - message.supportCenterIntlUrl = $types[17].fromObject(object.supportCenterIntlUrl); - } - if (object.businessListingIntlUrl !== undefined && object.businessListingIntlUrl !== null) { - if (typeof object.businessListingIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.businessListingIntlUrl: object expected"); - message.businessListingIntlUrl = $types[18].fromObject(object.businessListingIntlUrl); - } - if (object.supportAnswerIntlUrl !== undefined && object.supportAnswerIntlUrl !== null) { - if (typeof object.supportAnswerIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.supportAnswerIntlUrl: object expected"); - message.supportAnswerIntlUrl = $types[19].fromObject(object.supportAnswerIntlUrl); - } - if (object.supportTopicIntlUrl !== undefined && object.supportTopicIntlUrl !== null) { - if (typeof object.supportTopicIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.supportTopicIntlUrl: object expected"); - message.supportTopicIntlUrl = $types[20].fromObject(object.supportTopicIntlUrl); - } - if (object.supportRequestIntlUrl !== undefined && object.supportRequestIntlUrl !== null) { - if (typeof object.supportRequestIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.supportRequestIntlUrl: object expected"); - message.supportRequestIntlUrl = $types[21].fromObject(object.supportRequestIntlUrl); - } - if (object.earthIntlUrl !== undefined && object.earthIntlUrl !== null) { - if (typeof object.earthIntlUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.earthIntlUrl: object expected"); - message.earthIntlUrl = $types[22].fromObject(object.earthIntlUrl); - } - if (object.addContentUrl !== undefined && object.addContentUrl !== null) { - if (typeof object.addContentUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.addContentUrl: object expected"); - message.addContentUrl = $types[23].fromObject(object.addContentUrl); - } - if (object.sketchupNotInstalledUrl !== undefined && object.sketchupNotInstalledUrl !== null) { - if (typeof object.sketchupNotInstalledUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.sketchupNotInstalledUrl: object expected"); - message.sketchupNotInstalledUrl = $types[24].fromObject(object.sketchupNotInstalledUrl); - } - if (object.sketchupErrorUrl !== undefined && object.sketchupErrorUrl !== null) { - if (typeof object.sketchupErrorUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.sketchupErrorUrl: object expected"); - message.sketchupErrorUrl = $types[25].fromObject(object.sketchupErrorUrl); - } - if (object.freeLicenseUrl !== undefined && object.freeLicenseUrl !== null) { - if (typeof object.freeLicenseUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.freeLicenseUrl: object expected"); - message.freeLicenseUrl = $types[26].fromObject(object.freeLicenseUrl); - } - if (object.proLicenseUrl !== undefined && object.proLicenseUrl !== null) { - if (typeof object.proLicenseUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.proLicenseUrl: object expected"); - message.proLicenseUrl = $types[27].fromObject(object.proLicenseUrl); - } - if (object.tutorialUrl !== undefined && object.tutorialUrl !== null) { - if (typeof object.tutorialUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.tutorialUrl: object expected"); - message.tutorialUrl = $types[28].fromObject(object.tutorialUrl); - } - if (object.keyboardShortcutsUrl !== undefined && object.keyboardShortcutsUrl !== null) { - if (typeof object.keyboardShortcutsUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.keyboardShortcutsUrl: object expected"); - message.keyboardShortcutsUrl = $types[29].fromObject(object.keyboardShortcutsUrl); - } - if (object.releaseNotesUrl !== undefined && object.releaseNotesUrl !== null) { - if (typeof object.releaseNotesUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.releaseNotesUrl: object expected"); - message.releaseNotesUrl = $types[30].fromObject(object.releaseNotesUrl); - } - if (object.hideUserData !== undefined && object.hideUserData !== null) - message.hideUserData = Boolean(object.hideUserData); - if (object.useGeLogo !== undefined && object.useGeLogo !== null) - message.useGeLogo = Boolean(object.useGeLogo); - if (object.dioramaDescriptionUrlBase !== undefined && object.dioramaDescriptionUrlBase !== null) { - if (typeof object.dioramaDescriptionUrlBase !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.dioramaDescriptionUrlBase: object expected"); - message.dioramaDescriptionUrlBase = $types[33].fromObject(object.dioramaDescriptionUrlBase); - } - if (object.dioramaDefaultColor !== undefined && object.dioramaDefaultColor !== null) - message.dioramaDefaultColor = object.dioramaDefaultColor >>> 0; - if (object.dioramaBlacklistUrl !== undefined && object.dioramaBlacklistUrl !== null) { - if (typeof object.dioramaBlacklistUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.dioramaBlacklistUrl: object expected"); - message.dioramaBlacklistUrl = $types[35].fromObject(object.dioramaBlacklistUrl); - } - if (object.clientOptions !== undefined && object.clientOptions !== null) { - if (typeof object.clientOptions !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.clientOptions: object expected"); - message.clientOptions = $types[36].fromObject(object.clientOptions); - } - if (object.fetchingOptions !== undefined && object.fetchingOptions !== null) { - if (typeof object.fetchingOptions !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.fetchingOptions: object expected"); - message.fetchingOptions = $types[37].fromObject(object.fetchingOptions); - } - if (object.timeMachineOptions !== undefined && object.timeMachineOptions !== null) { - if (typeof object.timeMachineOptions !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.timeMachineOptions: object expected"); - message.timeMachineOptions = $types[38].fromObject(object.timeMachineOptions); - } - if (object.csiOptions !== undefined && object.csiOptions !== null) { - if (typeof object.csiOptions !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.csiOptions: object expected"); - message.csiOptions = $types[39].fromObject(object.csiOptions); - } - if (object.searchTab) { - if (!Array.isArray(object.searchTab)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.searchTab: array expected"); - message.searchTab = []; - for (var i = 0; i < object.searchTab.length; ++i) { - if (typeof object.searchTab[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.searchTab: object expected"); - message.searchTab[i] = $types[40].fromObject(object.searchTab[i]); - } - } - if (object.cobrandInfo) { - if (!Array.isArray(object.cobrandInfo)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.cobrandInfo: array expected"); - message.cobrandInfo = []; - for (var i = 0; i < object.cobrandInfo.length; ++i) { - if (typeof object.cobrandInfo[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.cobrandInfo: object expected"); - message.cobrandInfo[i] = $types[41].fromObject(object.cobrandInfo[i]); - } - } - if (object.validDatabase) { - if (!Array.isArray(object.validDatabase)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.validDatabase: array expected"); - message.validDatabase = []; - for (var i = 0; i < object.validDatabase.length; ++i) { - if (typeof object.validDatabase[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.validDatabase: object expected"); - message.validDatabase[i] = $types[42].fromObject(object.validDatabase[i]); - } - } - if (object.configScript) { - if (!Array.isArray(object.configScript)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.configScript: array expected"); - message.configScript = []; - for (var i = 0; i < object.configScript.length; ++i) { - if (typeof object.configScript[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.configScript: object expected"); - message.configScript[i] = $types[43].fromObject(object.configScript[i]); - } - } - if (object.deauthServerUrl !== undefined && object.deauthServerUrl !== null) { - if (typeof object.deauthServerUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.deauthServerUrl: object expected"); - message.deauthServerUrl = $types[44].fromObject(object.deauthServerUrl); - } - if (object.swoopParameters !== undefined && object.swoopParameters !== null) { - if (typeof object.swoopParameters !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.swoopParameters: object expected"); - message.swoopParameters = $types[45].fromObject(object.swoopParameters); - } - if (object.bbsServerInfo !== undefined && object.bbsServerInfo !== null) { - if (typeof object.bbsServerInfo !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.bbsServerInfo: object expected"); - message.bbsServerInfo = $types[46].fromObject(object.bbsServerInfo); - } - if (object.dataErrorServerInfo !== undefined && object.dataErrorServerInfo !== null) { - if (typeof object.dataErrorServerInfo !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.dataErrorServerInfo: object expected"); - message.dataErrorServerInfo = $types[47].fromObject(object.dataErrorServerInfo); - } - if (object.planetaryDatabase) { - if (!Array.isArray(object.planetaryDatabase)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.planetaryDatabase: array expected"); - message.planetaryDatabase = []; - for (var i = 0; i < object.planetaryDatabase.length; ++i) { - if (typeof object.planetaryDatabase[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.planetaryDatabase: object expected"); - message.planetaryDatabase[i] = $types[48].fromObject(object.planetaryDatabase[i]); - } - } - if (object.logServer !== undefined && object.logServer !== null) { - if (typeof object.logServer !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.logServer: object expected"); - message.logServer = $types[49].fromObject(object.logServer); - } - if (object.autopiaOptions !== undefined && object.autopiaOptions !== null) { - if (typeof object.autopiaOptions !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.autopiaOptions: object expected"); - message.autopiaOptions = $types[50].fromObject(object.autopiaOptions); - } - if (object.searchConfig !== undefined && object.searchConfig !== null) { - if (typeof object.searchConfig !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.searchConfig: object expected"); - message.searchConfig = $types[51].fromObject(object.searchConfig); - } - if (object.searchInfo !== undefined && object.searchInfo !== null) { - if (typeof object.searchInfo !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.searchInfo: object expected"); - message.searchInfo = $types[52].fromObject(object.searchInfo); - } - if (object.elevationServiceBaseUrl !== undefined && object.elevationServiceBaseUrl !== null) - message.elevationServiceBaseUrl = String(object.elevationServiceBaseUrl); - if (object.elevationProfileQueryDelay !== undefined && object.elevationProfileQueryDelay !== null) - message.elevationProfileQueryDelay = object.elevationProfileQueryDelay | 0; - if (object.proUpgradeUrl !== undefined && object.proUpgradeUrl !== null) { - if (typeof object.proUpgradeUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.proUpgradeUrl: object expected"); - message.proUpgradeUrl = $types[55].fromObject(object.proUpgradeUrl); - } - if (object.earthCommunityUrl !== undefined && object.earthCommunityUrl !== null) { - if (typeof object.earthCommunityUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.earthCommunityUrl: object expected"); - message.earthCommunityUrl = $types[56].fromObject(object.earthCommunityUrl); - } - if (object.googleMapsUrl !== undefined && object.googleMapsUrl !== null) { - if (typeof object.googleMapsUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.googleMapsUrl: object expected"); - message.googleMapsUrl = $types[57].fromObject(object.googleMapsUrl); - } - if (object.sharingUrl !== undefined && object.sharingUrl !== null) { - if (typeof object.sharingUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.sharingUrl: object expected"); - message.sharingUrl = $types[58].fromObject(object.sharingUrl); - } - if (object.privacyPolicyUrl !== undefined && object.privacyPolicyUrl !== null) { - if (typeof object.privacyPolicyUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.privacyPolicyUrl: object expected"); - message.privacyPolicyUrl = $types[59].fromObject(object.privacyPolicyUrl); - } - if (object.doGplusUserCheck !== undefined && object.doGplusUserCheck !== null) - message.doGplusUserCheck = Boolean(object.doGplusUserCheck); - if (object.rocktreeDataProto !== undefined && object.rocktreeDataProto !== null) { - if (typeof object.rocktreeDataProto !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.rocktreeDataProto: object expected"); - message.rocktreeDataProto = $types[61].fromObject(object.rocktreeDataProto); + var message = new $root.keyhole.dbroot.SearchTabProto(); + if (object.isVisible !== undefined && object.isVisible !== null) + message.isVisible = Boolean(object.isVisible); + if (object.tabLabel !== undefined && object.tabLabel !== null) { + if (typeof object.tabLabel !== "object") + throw TypeError(".keyhole.dbroot.SearchTabProto.tabLabel: object expected"); + message.tabLabel = $types[1].fromObject(object.tabLabel); } - if (object.filmstripConfig) { - if (!Array.isArray(object.filmstripConfig)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.filmstripConfig: array expected"); - message.filmstripConfig = []; - for (var i = 0; i < object.filmstripConfig.length; ++i) { - if (typeof object.filmstripConfig[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.filmstripConfig: object expected"); - message.filmstripConfig[i] = $types[62].fromObject(object.filmstripConfig[i]); + if (object.baseUrl !== undefined && object.baseUrl !== null) + message.baseUrl = String(object.baseUrl); + if (object.viewportPrefix !== undefined && object.viewportPrefix !== null) + message.viewportPrefix = String(object.viewportPrefix); + if (object.inputBox) { + if (!Array.isArray(object.inputBox)) + throw TypeError(".keyhole.dbroot.SearchTabProto.inputBox: array expected"); + message.inputBox = []; + for (var i = 0; i < object.inputBox.length; ++i) { + if (typeof object.inputBox[i] !== "object") + throw TypeError(".keyhole.dbroot.SearchTabProto.inputBox: object expected"); + message.inputBox[i] = $types[4].fromObject(object.inputBox[i]); } } - if (object.showSigninButton !== undefined && object.showSigninButton !== null) - message.showSigninButton = Boolean(object.showSigninButton); - if (object.proMeasureUpsellUrl !== undefined && object.proMeasureUpsellUrl !== null) { - if (typeof object.proMeasureUpsellUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.proMeasureUpsellUrl: object expected"); - message.proMeasureUpsellUrl = $types[64].fromObject(object.proMeasureUpsellUrl); - } - if (object.proPrintUpsellUrl !== undefined && object.proPrintUpsellUrl !== null) { - if (typeof object.proPrintUpsellUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.proPrintUpsellUrl: object expected"); - message.proPrintUpsellUrl = $types[65].fromObject(object.proPrintUpsellUrl); - } - if (object.starDataProto !== undefined && object.starDataProto !== null) { - if (typeof object.starDataProto !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.starDataProto: object expected"); - message.starDataProto = $types[66].fromObject(object.starDataProto); - } - if (object.feedbackUrl !== undefined && object.feedbackUrl !== null) { - if (typeof object.feedbackUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.feedbackUrl: object expected"); - message.feedbackUrl = $types[67].fromObject(object.feedbackUrl); - } - if (object.oauth2LoginUrl !== undefined && object.oauth2LoginUrl !== null) { - if (typeof object.oauth2LoginUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.oauth2LoginUrl: object expected"); - message.oauth2LoginUrl = $types[68].fromObject(object.oauth2LoginUrl); + if (object.requirement !== undefined && object.requirement !== null) { + if (typeof object.requirement !== "object") + throw TypeError(".keyhole.dbroot.SearchTabProto.requirement: object expected"); + message.requirement = $types[5].fromObject(object.requirement); } return message; }; - EndSnippetProto.from = EndSnippetProto.fromObject; + SearchTabProto.from = SearchTabProto.fromObject; - EndSnippetProto.toObject = function toObject(message, options) { + SearchTabProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) { - object.mfeDomains = []; - object.searchTab = []; - object.cobrandInfo = []; - object.validDatabase = []; - object.configScript = []; - object.planetaryDatabase = []; - object.filmstripConfig = []; - } + if (options.arrays || options.defaults) + object.inputBox = []; if (options.defaults) { - object.model = null; - object.authServerUrl = null; - object.disableAuthentication = false; - object.mfeLangParam = "hl=$5Bhl5D"; - object.adsUrlPatterns = ""; - object.reverseGeocoderUrl = null; - object.reverseGeocoderProtocolVersion = 3; - object.skyDatabaseIsAvailable = true; - object.skyDatabaseUrl = null; - object.defaultWebPageIntlUrl = null; - object.numStartUpTips = 17; - object.startUpTipsUrl = null; - object.numProStartUpTips = 0; - object.proStartUpTipsUrl = null; - object.startupTipsIntlUrl = null; - object.userGuideIntlUrl = null; - object.supportCenterIntlUrl = null; - object.businessListingIntlUrl = null; - object.supportAnswerIntlUrl = null; - object.supportTopicIntlUrl = null; - object.supportRequestIntlUrl = null; - object.earthIntlUrl = null; - object.addContentUrl = null; - object.sketchupNotInstalledUrl = null; - object.sketchupErrorUrl = null; - object.freeLicenseUrl = null; - object.proLicenseUrl = null; - object.tutorialUrl = null; - object.keyboardShortcutsUrl = null; - object.releaseNotesUrl = null; - object.hideUserData = false; - object.useGeLogo = true; - object.dioramaDescriptionUrlBase = null; - object.dioramaDefaultColor = 4291281607; - object.dioramaBlacklistUrl = null; - object.clientOptions = null; - object.fetchingOptions = null; - object.timeMachineOptions = null; - object.csiOptions = null; - object.deauthServerUrl = null; - object.swoopParameters = null; - object.bbsServerInfo = null; - object.dataErrorServerInfo = null; - object.logServer = null; - object.autopiaOptions = null; - object.searchConfig = null; - object.searchInfo = null; - object.elevationServiceBaseUrl = "http://maps.google.com/maps/api/elevation/"; - object.elevationProfileQueryDelay = 500; - object.proUpgradeUrl = null; - object.earthCommunityUrl = null; - object.googleMapsUrl = null; - object.sharingUrl = null; - object.privacyPolicyUrl = null; - object.doGplusUserCheck = false; - object.rocktreeDataProto = null; - object.showSigninButton = false; - object.proMeasureUpsellUrl = null; - object.proPrintUpsellUrl = null; - object.starDataProto = null; - object.feedbackUrl = null; - object.oauth2LoginUrl = null; - } - if (message.model !== undefined && message.model !== null && message.hasOwnProperty("model")) - object.model = $types[0].toObject(message.model, options); - if (message.authServerUrl !== undefined && message.authServerUrl !== null && message.hasOwnProperty("authServerUrl")) - object.authServerUrl = $types[1].toObject(message.authServerUrl, options); - if (message.disableAuthentication !== undefined && message.disableAuthentication !== null && message.hasOwnProperty("disableAuthentication")) - object.disableAuthentication = message.disableAuthentication; - if (message.mfeDomains !== undefined && message.mfeDomains !== null && message.hasOwnProperty("mfeDomains")) { - object.mfeDomains = []; - for (var j = 0; j < message.mfeDomains.length; ++j) - object.mfeDomains[j] = $types[3].toObject(message.mfeDomains[j], options); - } - if (message.mfeLangParam !== undefined && message.mfeLangParam !== null && message.hasOwnProperty("mfeLangParam")) - object.mfeLangParam = message.mfeLangParam; - if (message.adsUrlPatterns !== undefined && message.adsUrlPatterns !== null && message.hasOwnProperty("adsUrlPatterns")) - object.adsUrlPatterns = message.adsUrlPatterns; - if (message.reverseGeocoderUrl !== undefined && message.reverseGeocoderUrl !== null && message.hasOwnProperty("reverseGeocoderUrl")) - object.reverseGeocoderUrl = $types[6].toObject(message.reverseGeocoderUrl, options); - if (message.reverseGeocoderProtocolVersion !== undefined && message.reverseGeocoderProtocolVersion !== null && message.hasOwnProperty("reverseGeocoderProtocolVersion")) - object.reverseGeocoderProtocolVersion = message.reverseGeocoderProtocolVersion; - if (message.skyDatabaseIsAvailable !== undefined && message.skyDatabaseIsAvailable !== null && message.hasOwnProperty("skyDatabaseIsAvailable")) - object.skyDatabaseIsAvailable = message.skyDatabaseIsAvailable; - if (message.skyDatabaseUrl !== undefined && message.skyDatabaseUrl !== null && message.hasOwnProperty("skyDatabaseUrl")) - object.skyDatabaseUrl = $types[9].toObject(message.skyDatabaseUrl, options); - if (message.defaultWebPageIntlUrl !== undefined && message.defaultWebPageIntlUrl !== null && message.hasOwnProperty("defaultWebPageIntlUrl")) - object.defaultWebPageIntlUrl = $types[10].toObject(message.defaultWebPageIntlUrl, options); - if (message.numStartUpTips !== undefined && message.numStartUpTips !== null && message.hasOwnProperty("numStartUpTips")) - object.numStartUpTips = message.numStartUpTips; - if (message.startUpTipsUrl !== undefined && message.startUpTipsUrl !== null && message.hasOwnProperty("startUpTipsUrl")) - object.startUpTipsUrl = $types[12].toObject(message.startUpTipsUrl, options); - if (message.numProStartUpTips !== undefined && message.numProStartUpTips !== null && message.hasOwnProperty("numProStartUpTips")) - object.numProStartUpTips = message.numProStartUpTips; - if (message.proStartUpTipsUrl !== undefined && message.proStartUpTipsUrl !== null && message.hasOwnProperty("proStartUpTipsUrl")) - object.proStartUpTipsUrl = $types[14].toObject(message.proStartUpTipsUrl, options); - if (message.startupTipsIntlUrl !== undefined && message.startupTipsIntlUrl !== null && message.hasOwnProperty("startupTipsIntlUrl")) - object.startupTipsIntlUrl = $types[15].toObject(message.startupTipsIntlUrl, options); - if (message.userGuideIntlUrl !== undefined && message.userGuideIntlUrl !== null && message.hasOwnProperty("userGuideIntlUrl")) - object.userGuideIntlUrl = $types[16].toObject(message.userGuideIntlUrl, options); - if (message.supportCenterIntlUrl !== undefined && message.supportCenterIntlUrl !== null && message.hasOwnProperty("supportCenterIntlUrl")) - object.supportCenterIntlUrl = $types[17].toObject(message.supportCenterIntlUrl, options); - if (message.businessListingIntlUrl !== undefined && message.businessListingIntlUrl !== null && message.hasOwnProperty("businessListingIntlUrl")) - object.businessListingIntlUrl = $types[18].toObject(message.businessListingIntlUrl, options); - if (message.supportAnswerIntlUrl !== undefined && message.supportAnswerIntlUrl !== null && message.hasOwnProperty("supportAnswerIntlUrl")) - object.supportAnswerIntlUrl = $types[19].toObject(message.supportAnswerIntlUrl, options); - if (message.supportTopicIntlUrl !== undefined && message.supportTopicIntlUrl !== null && message.hasOwnProperty("supportTopicIntlUrl")) - object.supportTopicIntlUrl = $types[20].toObject(message.supportTopicIntlUrl, options); - if (message.supportRequestIntlUrl !== undefined && message.supportRequestIntlUrl !== null && message.hasOwnProperty("supportRequestIntlUrl")) - object.supportRequestIntlUrl = $types[21].toObject(message.supportRequestIntlUrl, options); - if (message.earthIntlUrl !== undefined && message.earthIntlUrl !== null && message.hasOwnProperty("earthIntlUrl")) - object.earthIntlUrl = $types[22].toObject(message.earthIntlUrl, options); - if (message.addContentUrl !== undefined && message.addContentUrl !== null && message.hasOwnProperty("addContentUrl")) - object.addContentUrl = $types[23].toObject(message.addContentUrl, options); - if (message.sketchupNotInstalledUrl !== undefined && message.sketchupNotInstalledUrl !== null && message.hasOwnProperty("sketchupNotInstalledUrl")) - object.sketchupNotInstalledUrl = $types[24].toObject(message.sketchupNotInstalledUrl, options); - if (message.sketchupErrorUrl !== undefined && message.sketchupErrorUrl !== null && message.hasOwnProperty("sketchupErrorUrl")) - object.sketchupErrorUrl = $types[25].toObject(message.sketchupErrorUrl, options); - if (message.freeLicenseUrl !== undefined && message.freeLicenseUrl !== null && message.hasOwnProperty("freeLicenseUrl")) - object.freeLicenseUrl = $types[26].toObject(message.freeLicenseUrl, options); - if (message.proLicenseUrl !== undefined && message.proLicenseUrl !== null && message.hasOwnProperty("proLicenseUrl")) - object.proLicenseUrl = $types[27].toObject(message.proLicenseUrl, options); - if (message.tutorialUrl !== undefined && message.tutorialUrl !== null && message.hasOwnProperty("tutorialUrl")) - object.tutorialUrl = $types[28].toObject(message.tutorialUrl, options); - if (message.keyboardShortcutsUrl !== undefined && message.keyboardShortcutsUrl !== null && message.hasOwnProperty("keyboardShortcutsUrl")) - object.keyboardShortcutsUrl = $types[29].toObject(message.keyboardShortcutsUrl, options); - if (message.releaseNotesUrl !== undefined && message.releaseNotesUrl !== null && message.hasOwnProperty("releaseNotesUrl")) - object.releaseNotesUrl = $types[30].toObject(message.releaseNotesUrl, options); - if (message.hideUserData !== undefined && message.hideUserData !== null && message.hasOwnProperty("hideUserData")) - object.hideUserData = message.hideUserData; - if (message.useGeLogo !== undefined && message.useGeLogo !== null && message.hasOwnProperty("useGeLogo")) - object.useGeLogo = message.useGeLogo; - if (message.dioramaDescriptionUrlBase !== undefined && message.dioramaDescriptionUrlBase !== null && message.hasOwnProperty("dioramaDescriptionUrlBase")) - object.dioramaDescriptionUrlBase = $types[33].toObject(message.dioramaDescriptionUrlBase, options); - if (message.dioramaDefaultColor !== undefined && message.dioramaDefaultColor !== null && message.hasOwnProperty("dioramaDefaultColor")) - object.dioramaDefaultColor = message.dioramaDefaultColor; - if (message.dioramaBlacklistUrl !== undefined && message.dioramaBlacklistUrl !== null && message.hasOwnProperty("dioramaBlacklistUrl")) - object.dioramaBlacklistUrl = $types[35].toObject(message.dioramaBlacklistUrl, options); - if (message.clientOptions !== undefined && message.clientOptions !== null && message.hasOwnProperty("clientOptions")) - object.clientOptions = $types[36].toObject(message.clientOptions, options); - if (message.fetchingOptions !== undefined && message.fetchingOptions !== null && message.hasOwnProperty("fetchingOptions")) - object.fetchingOptions = $types[37].toObject(message.fetchingOptions, options); - if (message.timeMachineOptions !== undefined && message.timeMachineOptions !== null && message.hasOwnProperty("timeMachineOptions")) - object.timeMachineOptions = $types[38].toObject(message.timeMachineOptions, options); - if (message.csiOptions !== undefined && message.csiOptions !== null && message.hasOwnProperty("csiOptions")) - object.csiOptions = $types[39].toObject(message.csiOptions, options); - if (message.searchTab !== undefined && message.searchTab !== null && message.hasOwnProperty("searchTab")) { - object.searchTab = []; - for (var j = 0; j < message.searchTab.length; ++j) - object.searchTab[j] = $types[40].toObject(message.searchTab[j], options); - } - if (message.cobrandInfo !== undefined && message.cobrandInfo !== null && message.hasOwnProperty("cobrandInfo")) { - object.cobrandInfo = []; - for (var j = 0; j < message.cobrandInfo.length; ++j) - object.cobrandInfo[j] = $types[41].toObject(message.cobrandInfo[j], options); - } - if (message.validDatabase !== undefined && message.validDatabase !== null && message.hasOwnProperty("validDatabase")) { - object.validDatabase = []; - for (var j = 0; j < message.validDatabase.length; ++j) - object.validDatabase[j] = $types[42].toObject(message.validDatabase[j], options); - } - if (message.configScript !== undefined && message.configScript !== null && message.hasOwnProperty("configScript")) { - object.configScript = []; - for (var j = 0; j < message.configScript.length; ++j) - object.configScript[j] = $types[43].toObject(message.configScript[j], options); - } - if (message.deauthServerUrl !== undefined && message.deauthServerUrl !== null && message.hasOwnProperty("deauthServerUrl")) - object.deauthServerUrl = $types[44].toObject(message.deauthServerUrl, options); - if (message.swoopParameters !== undefined && message.swoopParameters !== null && message.hasOwnProperty("swoopParameters")) - object.swoopParameters = $types[45].toObject(message.swoopParameters, options); - if (message.bbsServerInfo !== undefined && message.bbsServerInfo !== null && message.hasOwnProperty("bbsServerInfo")) - object.bbsServerInfo = $types[46].toObject(message.bbsServerInfo, options); - if (message.dataErrorServerInfo !== undefined && message.dataErrorServerInfo !== null && message.hasOwnProperty("dataErrorServerInfo")) - object.dataErrorServerInfo = $types[47].toObject(message.dataErrorServerInfo, options); - if (message.planetaryDatabase !== undefined && message.planetaryDatabase !== null && message.hasOwnProperty("planetaryDatabase")) { - object.planetaryDatabase = []; - for (var j = 0; j < message.planetaryDatabase.length; ++j) - object.planetaryDatabase[j] = $types[48].toObject(message.planetaryDatabase[j], options); + object.isVisible = false; + object.tabLabel = null; + object.baseUrl = ""; + object.viewportPrefix = ""; + object.requirement = null; } - if (message.logServer !== undefined && message.logServer !== null && message.hasOwnProperty("logServer")) - object.logServer = $types[49].toObject(message.logServer, options); - if (message.autopiaOptions !== undefined && message.autopiaOptions !== null && message.hasOwnProperty("autopiaOptions")) - object.autopiaOptions = $types[50].toObject(message.autopiaOptions, options); - if (message.searchConfig !== undefined && message.searchConfig !== null && message.hasOwnProperty("searchConfig")) - object.searchConfig = $types[51].toObject(message.searchConfig, options); - if (message.searchInfo !== undefined && message.searchInfo !== null && message.hasOwnProperty("searchInfo")) - object.searchInfo = $types[52].toObject(message.searchInfo, options); - if (message.elevationServiceBaseUrl !== undefined && message.elevationServiceBaseUrl !== null && message.hasOwnProperty("elevationServiceBaseUrl")) - object.elevationServiceBaseUrl = message.elevationServiceBaseUrl; - if (message.elevationProfileQueryDelay !== undefined && message.elevationProfileQueryDelay !== null && message.hasOwnProperty("elevationProfileQueryDelay")) - object.elevationProfileQueryDelay = message.elevationProfileQueryDelay; - if (message.proUpgradeUrl !== undefined && message.proUpgradeUrl !== null && message.hasOwnProperty("proUpgradeUrl")) - object.proUpgradeUrl = $types[55].toObject(message.proUpgradeUrl, options); - if (message.earthCommunityUrl !== undefined && message.earthCommunityUrl !== null && message.hasOwnProperty("earthCommunityUrl")) - object.earthCommunityUrl = $types[56].toObject(message.earthCommunityUrl, options); - if (message.googleMapsUrl !== undefined && message.googleMapsUrl !== null && message.hasOwnProperty("googleMapsUrl")) - object.googleMapsUrl = $types[57].toObject(message.googleMapsUrl, options); - if (message.sharingUrl !== undefined && message.sharingUrl !== null && message.hasOwnProperty("sharingUrl")) - object.sharingUrl = $types[58].toObject(message.sharingUrl, options); - if (message.privacyPolicyUrl !== undefined && message.privacyPolicyUrl !== null && message.hasOwnProperty("privacyPolicyUrl")) - object.privacyPolicyUrl = $types[59].toObject(message.privacyPolicyUrl, options); - if (message.doGplusUserCheck !== undefined && message.doGplusUserCheck !== null && message.hasOwnProperty("doGplusUserCheck")) - object.doGplusUserCheck = message.doGplusUserCheck; - if (message.rocktreeDataProto !== undefined && message.rocktreeDataProto !== null && message.hasOwnProperty("rocktreeDataProto")) - object.rocktreeDataProto = $types[61].toObject(message.rocktreeDataProto, options); - if (message.filmstripConfig !== undefined && message.filmstripConfig !== null && message.hasOwnProperty("filmstripConfig")) { - object.filmstripConfig = []; - for (var j = 0; j < message.filmstripConfig.length; ++j) - object.filmstripConfig[j] = $types[62].toObject(message.filmstripConfig[j], options); + if (message.isVisible !== undefined && message.isVisible !== null && message.hasOwnProperty("isVisible")) + object.isVisible = message.isVisible; + if (message.tabLabel !== undefined && message.tabLabel !== null && message.hasOwnProperty("tabLabel")) + object.tabLabel = $types[1].toObject(message.tabLabel, options); + if (message.baseUrl !== undefined && message.baseUrl !== null && message.hasOwnProperty("baseUrl")) + object.baseUrl = message.baseUrl; + if (message.viewportPrefix !== undefined && message.viewportPrefix !== null && message.hasOwnProperty("viewportPrefix")) + object.viewportPrefix = message.viewportPrefix; + if (message.inputBox !== undefined && message.inputBox !== null && message.hasOwnProperty("inputBox")) { + object.inputBox = []; + for (var j = 0; j < message.inputBox.length; ++j) + object.inputBox[j] = $types[4].toObject(message.inputBox[j], options); } - if (message.showSigninButton !== undefined && message.showSigninButton !== null && message.hasOwnProperty("showSigninButton")) - object.showSigninButton = message.showSigninButton; - if (message.proMeasureUpsellUrl !== undefined && message.proMeasureUpsellUrl !== null && message.hasOwnProperty("proMeasureUpsellUrl")) - object.proMeasureUpsellUrl = $types[64].toObject(message.proMeasureUpsellUrl, options); - if (message.proPrintUpsellUrl !== undefined && message.proPrintUpsellUrl !== null && message.hasOwnProperty("proPrintUpsellUrl")) - object.proPrintUpsellUrl = $types[65].toObject(message.proPrintUpsellUrl, options); - if (message.starDataProto !== undefined && message.starDataProto !== null && message.hasOwnProperty("starDataProto")) - object.starDataProto = $types[66].toObject(message.starDataProto, options); - if (message.feedbackUrl !== undefined && message.feedbackUrl !== null && message.hasOwnProperty("feedbackUrl")) - object.feedbackUrl = $types[67].toObject(message.feedbackUrl, options); - if (message.oauth2LoginUrl !== undefined && message.oauth2LoginUrl !== null && message.hasOwnProperty("oauth2LoginUrl")) - object.oauth2LoginUrl = $types[68].toObject(message.oauth2LoginUrl, options); + if (message.requirement !== undefined && message.requirement !== null && message.hasOwnProperty("requirement")) + object.requirement = $types[5].toObject(message.requirement, options); return object; }; - EndSnippetProto.prototype.toObject = function toObject(options) { + SearchTabProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - EndSnippetProto.prototype.toJSON = function toJSON() { + SearchTabProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - EndSnippetProto.SearchConfigProto = (function() { + SearchTabProto.InputBoxInfo = (function() { - function SearchConfigProto(properties) { + function InputBoxInfo(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - SearchConfigProto.prototype.searchServer = $util.emptyArray; - SearchConfigProto.prototype.oneboxService = $util.emptyArray; - SearchConfigProto.prototype.kmlSearchUrl = null; - SearchConfigProto.prototype.kmlRenderUrl = null; - SearchConfigProto.prototype.searchHistoryUrl = null; - SearchConfigProto.prototype.errorPageUrl = null; + InputBoxInfo.prototype.label = null; + InputBoxInfo.prototype.queryVerb = ""; + InputBoxInfo.prototype.queryPrepend = ""; var $types = { - 0 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer", - 1 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto", - 2 : "keyhole.dbroot.StringIdOrValueProto", - 3 : "keyhole.dbroot.StringIdOrValueProto", - 4 : "keyhole.dbroot.StringIdOrValueProto", - 5 : "keyhole.dbroot.StringIdOrValueProto" + 0 : "keyhole.dbroot.StringIdOrValueProto" }; $lazyTypes.push($types); - SearchConfigProto.decode = function decode(reader, length) { + InputBoxInfo.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto(); + message = new $root.keyhole.dbroot.SearchTabProto.InputBoxInfo(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - if (!(message.searchServer && message.searchServer.length)) - message.searchServer = []; - message.searchServer.push($types[0].decode(reader, reader.uint32())); + message.label = $types[0].decode(reader, reader.uint32()); break; case 2: - if (!(message.oneboxService && message.oneboxService.length)) - message.oneboxService = []; - message.oneboxService.push($types[1].decode(reader, reader.uint32())); + message.queryVerb = reader.string(); break; case 3: - message.kmlSearchUrl = $types[2].decode(reader, reader.uint32()); - break; - case 4: - message.kmlRenderUrl = $types[3].decode(reader, reader.uint32()); - break; - case 6: - message.searchHistoryUrl = $types[4].decode(reader, reader.uint32()); - break; - case 5: - message.errorPageUrl = $types[5].decode(reader, reader.uint32()); + message.queryPrepend = reader.string(); break; default: reader.skipType(tag & 7); @@ -55285,788 +56255,275 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - SearchConfigProto.verify = function verify(message) { + InputBoxInfo.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.searchServer !== undefined) { - if (!Array.isArray(message.searchServer)) - return "searchServer: array expected"; - for (var i = 0; i < message.searchServer.length; ++i) { - var error = $types[0].verify(message.searchServer[i]); - if (error) - return "searchServer." + error; - } - } - if (message.oneboxService !== undefined) { - if (!Array.isArray(message.oneboxService)) - return "oneboxService: array expected"; - for (var i = 0; i < message.oneboxService.length; ++i) { - var error = $types[1].verify(message.oneboxService[i]); - if (error) - return "oneboxService." + error; - } - } - if (message.kmlSearchUrl !== undefined && message.kmlSearchUrl !== null) { - var error = $types[2].verify(message.kmlSearchUrl); - if (error) - return "kmlSearchUrl." + error; - } - if (message.kmlRenderUrl !== undefined && message.kmlRenderUrl !== null) { - var error = $types[3].verify(message.kmlRenderUrl); - if (error) - return "kmlRenderUrl." + error; - } - if (message.searchHistoryUrl !== undefined && message.searchHistoryUrl !== null) { - var error = $types[4].verify(message.searchHistoryUrl); - if (error) - return "searchHistoryUrl." + error; - } - if (message.errorPageUrl !== undefined && message.errorPageUrl !== null) { - var error = $types[5].verify(message.errorPageUrl); - if (error) - return "errorPageUrl." + error; - } + var error = $types[0].verify(message.label); + if (error) + return "label." + error; + if (!$util.isString(message.queryVerb)) + return "queryVerb: string expected"; + if (message.queryPrepend !== undefined) + if (!$util.isString(message.queryPrepend)) + return "queryPrepend: string expected"; return null; }; - SearchConfigProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto) + InputBoxInfo.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.SearchTabProto.InputBoxInfo) return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto(); - if (object.searchServer) { - if (!Array.isArray(object.searchServer)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.searchServer: array expected"); - message.searchServer = []; - for (var i = 0; i < object.searchServer.length; ++i) { - if (typeof object.searchServer[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.searchServer: object expected"); - message.searchServer[i] = $types[0].fromObject(object.searchServer[i]); - } - } - if (object.oneboxService) { - if (!Array.isArray(object.oneboxService)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.oneboxService: array expected"); - message.oneboxService = []; - for (var i = 0; i < object.oneboxService.length; ++i) { - if (typeof object.oneboxService[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.oneboxService: object expected"); - message.oneboxService[i] = $types[1].fromObject(object.oneboxService[i]); - } - } - if (object.kmlSearchUrl !== undefined && object.kmlSearchUrl !== null) { - if (typeof object.kmlSearchUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.kmlSearchUrl: object expected"); - message.kmlSearchUrl = $types[2].fromObject(object.kmlSearchUrl); - } - if (object.kmlRenderUrl !== undefined && object.kmlRenderUrl !== null) { - if (typeof object.kmlRenderUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.kmlRenderUrl: object expected"); - message.kmlRenderUrl = $types[3].fromObject(object.kmlRenderUrl); - } - if (object.searchHistoryUrl !== undefined && object.searchHistoryUrl !== null) { - if (typeof object.searchHistoryUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.searchHistoryUrl: object expected"); - message.searchHistoryUrl = $types[4].fromObject(object.searchHistoryUrl); - } - if (object.errorPageUrl !== undefined && object.errorPageUrl !== null) { - if (typeof object.errorPageUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.errorPageUrl: object expected"); - message.errorPageUrl = $types[5].fromObject(object.errorPageUrl); + var message = new $root.keyhole.dbroot.SearchTabProto.InputBoxInfo(); + if (object.label !== undefined && object.label !== null) { + if (typeof object.label !== "object") + throw TypeError(".keyhole.dbroot.SearchTabProto.InputBoxInfo.label: object expected"); + message.label = $types[0].fromObject(object.label); } + if (object.queryVerb !== undefined && object.queryVerb !== null) + message.queryVerb = String(object.queryVerb); + if (object.queryPrepend !== undefined && object.queryPrepend !== null) + message.queryPrepend = String(object.queryPrepend); return message; }; - SearchConfigProto.from = SearchConfigProto.fromObject; + InputBoxInfo.from = InputBoxInfo.fromObject; - SearchConfigProto.toObject = function toObject(message, options) { + InputBoxInfo.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) { - object.searchServer = []; - object.oneboxService = []; - } if (options.defaults) { - object.kmlSearchUrl = null; - object.kmlRenderUrl = null; - object.searchHistoryUrl = null; - object.errorPageUrl = null; - } - if (message.searchServer !== undefined && message.searchServer !== null && message.hasOwnProperty("searchServer")) { - object.searchServer = []; - for (var j = 0; j < message.searchServer.length; ++j) - object.searchServer[j] = $types[0].toObject(message.searchServer[j], options); - } - if (message.oneboxService !== undefined && message.oneboxService !== null && message.hasOwnProperty("oneboxService")) { - object.oneboxService = []; - for (var j = 0; j < message.oneboxService.length; ++j) - object.oneboxService[j] = $types[1].toObject(message.oneboxService[j], options); + object.label = null; + object.queryVerb = ""; + object.queryPrepend = ""; } - if (message.kmlSearchUrl !== undefined && message.kmlSearchUrl !== null && message.hasOwnProperty("kmlSearchUrl")) - object.kmlSearchUrl = $types[2].toObject(message.kmlSearchUrl, options); - if (message.kmlRenderUrl !== undefined && message.kmlRenderUrl !== null && message.hasOwnProperty("kmlRenderUrl")) - object.kmlRenderUrl = $types[3].toObject(message.kmlRenderUrl, options); - if (message.searchHistoryUrl !== undefined && message.searchHistoryUrl !== null && message.hasOwnProperty("searchHistoryUrl")) - object.searchHistoryUrl = $types[4].toObject(message.searchHistoryUrl, options); - if (message.errorPageUrl !== undefined && message.errorPageUrl !== null && message.hasOwnProperty("errorPageUrl")) - object.errorPageUrl = $types[5].toObject(message.errorPageUrl, options); + if (message.label !== undefined && message.label !== null && message.hasOwnProperty("label")) + object.label = $types[0].toObject(message.label, options); + if (message.queryVerb !== undefined && message.queryVerb !== null && message.hasOwnProperty("queryVerb")) + object.queryVerb = message.queryVerb; + if (message.queryPrepend !== undefined && message.queryPrepend !== null && message.hasOwnProperty("queryPrepend")) + object.queryPrepend = message.queryPrepend; return object; }; - SearchConfigProto.prototype.toObject = function toObject(options) { + InputBoxInfo.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - SearchConfigProto.prototype.toJSON = function toJSON() { + InputBoxInfo.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - SearchConfigProto.SearchServer = (function() { - - function SearchServer(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - SearchServer.prototype.name = null; - SearchServer.prototype.url = null; - SearchServer.prototype.type = 0; - SearchServer.prototype.htmlTransformUrl = null; - SearchServer.prototype.kmlTransformUrl = null; - SearchServer.prototype.supplementalUi = null; - SearchServer.prototype.suggestion = $util.emptyArray; - SearchServer.prototype.searchlet = $util.emptyArray; - SearchServer.prototype.requirements = null; - SearchServer.prototype.suggestServer = null; - - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 1 : "keyhole.dbroot.StringIdOrValueProto", - 2 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.ResultType", - 3 : "keyhole.dbroot.StringIdOrValueProto", - 4 : "keyhole.dbroot.StringIdOrValueProto", - 5 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi", - 6 : "keyhole.dbroot.StringIdOrValueProto", - 7 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto", - 8 : "keyhole.dbroot.RequirementProto", - 9 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); - - SearchServer.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.name = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.url = $types[1].decode(reader, reader.uint32()); - break; - case 3: - message.type = reader.uint32(); - break; - case 4: - message.htmlTransformUrl = $types[3].decode(reader, reader.uint32()); - break; - case 5: - message.kmlTransformUrl = $types[4].decode(reader, reader.uint32()); - break; - case 6: - message.supplementalUi = $types[5].decode(reader, reader.uint32()); - break; - case 9: - if (!(message.suggestion && message.suggestion.length)) - message.suggestion = []; - message.suggestion.push($types[6].decode(reader, reader.uint32())); - break; - case 7: - if (!(message.searchlet && message.searchlet.length)) - message.searchlet = []; - message.searchlet.push($types[7].decode(reader, reader.uint32())); - break; - case 8: - message.requirements = $types[8].decode(reader, reader.uint32()); - break; - case 10: - message.suggestServer = $types[9].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - SearchServer.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.name !== undefined && message.name !== null) { - var error = $types[0].verify(message.name); - if (error) - return "name." + error; - } - if (message.url !== undefined && message.url !== null) { - var error = $types[1].verify(message.url); - if (error) - return "url." + error; - } - if (message.type !== undefined) - switch (message.type) { - default: - return "type: enum value expected"; - case 0: - case 1: - break; - } - if (message.htmlTransformUrl !== undefined && message.htmlTransformUrl !== null) { - var error = $types[3].verify(message.htmlTransformUrl); - if (error) - return "htmlTransformUrl." + error; - } - if (message.kmlTransformUrl !== undefined && message.kmlTransformUrl !== null) { - var error = $types[4].verify(message.kmlTransformUrl); - if (error) - return "kmlTransformUrl." + error; - } - if (message.supplementalUi !== undefined && message.supplementalUi !== null) { - var error = $types[5].verify(message.supplementalUi); - if (error) - return "supplementalUi." + error; - } - if (message.suggestion !== undefined) { - if (!Array.isArray(message.suggestion)) - return "suggestion: array expected"; - for (var i = 0; i < message.suggestion.length; ++i) { - var error = $types[6].verify(message.suggestion[i]); - if (error) - return "suggestion." + error; - } - } - if (message.searchlet !== undefined) { - if (!Array.isArray(message.searchlet)) - return "searchlet: array expected"; - for (var i = 0; i < message.searchlet.length; ++i) { - var error = $types[7].verify(message.searchlet[i]); - if (error) - return "searchlet." + error; - } - } - if (message.requirements !== undefined && message.requirements !== null) { - var error = $types[8].verify(message.requirements); - if (error) - return "requirements." + error; - } - if (message.suggestServer !== undefined && message.suggestServer !== null) { - var error = $types[9].verify(message.suggestServer); - if (error) - return "suggestServer." + error; - } - return null; - }; - - SearchServer.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer(); - if (object.name !== undefined && object.name !== null) { - if (typeof object.name !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.name: object expected"); - message.name = $types[0].fromObject(object.name); - } - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.url: object expected"); - message.url = $types[1].fromObject(object.url); - } - switch (object.type) { - case "RESULT_TYPE_KML": - case 0: - message.type = 0; - break; - case "RESULT_TYPE_XML": - case 1: - message.type = 1; - break; - } - if (object.htmlTransformUrl !== undefined && object.htmlTransformUrl !== null) { - if (typeof object.htmlTransformUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.htmlTransformUrl: object expected"); - message.htmlTransformUrl = $types[3].fromObject(object.htmlTransformUrl); - } - if (object.kmlTransformUrl !== undefined && object.kmlTransformUrl !== null) { - if (typeof object.kmlTransformUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.kmlTransformUrl: object expected"); - message.kmlTransformUrl = $types[4].fromObject(object.kmlTransformUrl); - } - if (object.supplementalUi !== undefined && object.supplementalUi !== null) { - if (typeof object.supplementalUi !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.supplementalUi: object expected"); - message.supplementalUi = $types[5].fromObject(object.supplementalUi); - } - if (object.suggestion) { - if (!Array.isArray(object.suggestion)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.suggestion: array expected"); - message.suggestion = []; - for (var i = 0; i < object.suggestion.length; ++i) { - if (typeof object.suggestion[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.suggestion: object expected"); - message.suggestion[i] = $types[6].fromObject(object.suggestion[i]); - } - } - if (object.searchlet) { - if (!Array.isArray(object.searchlet)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.searchlet: array expected"); - message.searchlet = []; - for (var i = 0; i < object.searchlet.length; ++i) { - if (typeof object.searchlet[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.searchlet: object expected"); - message.searchlet[i] = $types[7].fromObject(object.searchlet[i]); - } - } - if (object.requirements !== undefined && object.requirements !== null) { - if (typeof object.requirements !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.requirements: object expected"); - message.requirements = $types[8].fromObject(object.requirements); - } - if (object.suggestServer !== undefined && object.suggestServer !== null) { - if (typeof object.suggestServer !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.suggestServer: object expected"); - message.suggestServer = $types[9].fromObject(object.suggestServer); - } - return message; - }; - - SearchServer.from = SearchServer.fromObject; - - SearchServer.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) { - object.suggestion = []; - object.searchlet = []; - } - if (options.defaults) { - object.name = null; - object.url = null; - object.type = options.enums === String ? "RESULT_TYPE_KML" : 0; - object.htmlTransformUrl = null; - object.kmlTransformUrl = null; - object.supplementalUi = null; - object.requirements = null; - object.suggestServer = null; - } - if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) - object.name = $types[0].toObject(message.name, options); - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[1].toObject(message.url, options); - if (message.type !== undefined && message.type !== null && message.hasOwnProperty("type")) - object.type = options.enums === String ? $types[2][message.type] : message.type; - if (message.htmlTransformUrl !== undefined && message.htmlTransformUrl !== null && message.hasOwnProperty("htmlTransformUrl")) - object.htmlTransformUrl = $types[3].toObject(message.htmlTransformUrl, options); - if (message.kmlTransformUrl !== undefined && message.kmlTransformUrl !== null && message.hasOwnProperty("kmlTransformUrl")) - object.kmlTransformUrl = $types[4].toObject(message.kmlTransformUrl, options); - if (message.supplementalUi !== undefined && message.supplementalUi !== null && message.hasOwnProperty("supplementalUi")) - object.supplementalUi = $types[5].toObject(message.supplementalUi, options); - if (message.suggestion !== undefined && message.suggestion !== null && message.hasOwnProperty("suggestion")) { - object.suggestion = []; - for (var j = 0; j < message.suggestion.length; ++j) - object.suggestion[j] = $types[6].toObject(message.suggestion[j], options); - } - if (message.searchlet !== undefined && message.searchlet !== null && message.hasOwnProperty("searchlet")) { - object.searchlet = []; - for (var j = 0; j < message.searchlet.length; ++j) - object.searchlet[j] = $types[7].toObject(message.searchlet[j], options); - } - if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) - object.requirements = $types[8].toObject(message.requirements, options); - if (message.suggestServer !== undefined && message.suggestServer !== null && message.hasOwnProperty("suggestServer")) - object.suggestServer = $types[9].toObject(message.suggestServer, options); - return object; - }; - - SearchServer.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - SearchServer.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - SearchServer.ResultType = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["RESULT_TYPE_KML"] = 0; - values["RESULT_TYPE_XML"] = 1; - return values; - })(); - - SearchServer.SupplementalUi = (function() { - - function SupplementalUi(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - SupplementalUi.prototype.url = null; - SupplementalUi.prototype.label = null; - SupplementalUi.prototype.height = 160; - - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 1 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); - - SupplementalUi.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.url = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.label = $types[1].decode(reader, reader.uint32()); - break; - case 3: - message.height = reader.int32(); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - SupplementalUi.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.url !== undefined && message.url !== null) { - var error = $types[0].verify(message.url); - if (error) - return "url." + error; - } - if (message.label !== undefined && message.label !== null) { - var error = $types[1].verify(message.label); - if (error) - return "label." + error; - } - if (message.height !== undefined) - if (!$util.isInteger(message.height)) - return "height: integer expected"; - return null; - }; - - SupplementalUi.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi(); - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi.url: object expected"); - message.url = $types[0].fromObject(object.url); - } - if (object.label !== undefined && object.label !== null) { - if (typeof object.label !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi.label: object expected"); - message.label = $types[1].fromObject(object.label); - } - if (object.height !== undefined && object.height !== null) - message.height = object.height | 0; - return message; - }; - - SupplementalUi.from = SupplementalUi.fromObject; - - SupplementalUi.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.url = null; - object.label = null; - object.height = 160; - } - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[0].toObject(message.url, options); - if (message.label !== undefined && message.label !== null && message.hasOwnProperty("label")) - object.label = $types[1].toObject(message.label, options); - if (message.height !== undefined && message.height !== null && message.hasOwnProperty("height")) - object.height = message.height; - return object; - }; - - SupplementalUi.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - SupplementalUi.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return SupplementalUi; - })(); - - SearchServer.SearchletProto = (function() { - - function SearchletProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } - - SearchletProto.prototype.url = null; - SearchletProto.prototype.name = null; - SearchletProto.prototype.requirements = null; - - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 1 : "keyhole.dbroot.StringIdOrValueProto", - 2 : "keyhole.dbroot.RequirementProto" - }; - $lazyTypes.push($types); - - SearchletProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.url = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.name = $types[1].decode(reader, reader.uint32()); - break; - case 3: - message.requirements = $types[2].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - SearchletProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.url !== undefined && message.url !== null) { - var error = $types[0].verify(message.url); - if (error) - return "url." + error; - } - if (message.name !== undefined && message.name !== null) { - var error = $types[1].verify(message.name); - if (error) - return "name." + error; - } - if (message.requirements !== undefined && message.requirements !== null) { - var error = $types[2].verify(message.requirements); - if (error) - return "requirements." + error; - } - return null; - }; - - SearchletProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto(); - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto.url: object expected"); - message.url = $types[0].fromObject(object.url); - } - if (object.name !== undefined && object.name !== null) { - if (typeof object.name !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto.name: object expected"); - message.name = $types[1].fromObject(object.name); - } - if (object.requirements !== undefined && object.requirements !== null) { - if (typeof object.requirements !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto.requirements: object expected"); - message.requirements = $types[2].fromObject(object.requirements); - } - return message; - }; - - SearchletProto.from = SearchletProto.fromObject; - - SearchletProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.url = null; - object.name = null; - object.requirements = null; - } - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[0].toObject(message.url, options); - if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) - object.name = $types[1].toObject(message.name, options); - if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) - object.requirements = $types[2].toObject(message.requirements, options); - return object; - }; + return InputBoxInfo; + })(); - SearchletProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + return SearchTabProto; + })(); - SearchletProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + dbroot.CobrandProto = (function() { - return SearchletProto; - })(); + function CobrandProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - return SearchServer; - })(); + CobrandProto.prototype.logoUrl = ""; + CobrandProto.prototype.xCoord = null; + CobrandProto.prototype.yCoord = null; + CobrandProto.prototype.tiePoint = 6; + CobrandProto.prototype.screenSize = 0; - SearchConfigProto.OneboxServiceProto = (function() { + var $types = { + 1 : "keyhole.dbroot.CobrandProto.Coord", + 2 : "keyhole.dbroot.CobrandProto.Coord", + 3 : "keyhole.dbroot.CobrandProto.TiePoint" + }; + $lazyTypes.push($types); - function OneboxServiceProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; + CobrandProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.CobrandProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.logoUrl = reader.string(); + break; + case 2: + message.xCoord = $types[1].decode(reader, reader.uint32()); + break; + case 3: + message.yCoord = $types[2].decode(reader, reader.uint32()); + break; + case 4: + message.tiePoint = reader.uint32(); + break; + case 5: + message.screenSize = reader.double(); + break; + default: + reader.skipType(tag & 7); + break; } + } + return message; + }; - OneboxServiceProto.prototype.serviceUrl = null; - OneboxServiceProto.prototype.requirements = null; - - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 1 : "keyhole.dbroot.RequirementProto" - }; - $lazyTypes.push($types); - - OneboxServiceProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.serviceUrl = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.requirements = $types[1].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - OneboxServiceProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.serviceUrl !== undefined && message.serviceUrl !== null) { - var error = $types[0].verify(message.serviceUrl); - if (error) - return "serviceUrl." + error; - } - if (message.requirements !== undefined && message.requirements !== null) { - var error = $types[1].verify(message.requirements); - if (error) - return "requirements." + error; - } - return null; - }; - - OneboxServiceProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto(); - if (object.serviceUrl !== undefined && object.serviceUrl !== null) { - if (typeof object.serviceUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto.serviceUrl: object expected"); - message.serviceUrl = $types[0].fromObject(object.serviceUrl); - } - if (object.requirements !== undefined && object.requirements !== null) { - if (typeof object.requirements !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto.requirements: object expected"); - message.requirements = $types[1].fromObject(object.requirements); - } - return message; - }; - - OneboxServiceProto.from = OneboxServiceProto.fromObject; + CobrandProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isString(message.logoUrl)) + return "logoUrl: string expected"; + if (message.xCoord !== undefined && message.xCoord !== null) { + var error = $types[1].verify(message.xCoord); + if (error) + return "xCoord." + error; + } + if (message.yCoord !== undefined && message.yCoord !== null) { + var error = $types[2].verify(message.yCoord); + if (error) + return "yCoord." + error; + } + if (message.tiePoint !== undefined) + switch (message.tiePoint) { + default: + return "tiePoint: enum value expected"; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + break; + } + if (message.screenSize !== undefined) + if (typeof message.screenSize !== "number") + return "screenSize: number expected"; + return null; + }; - OneboxServiceProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.serviceUrl = null; - object.requirements = null; - } - if (message.serviceUrl !== undefined && message.serviceUrl !== null && message.hasOwnProperty("serviceUrl")) - object.serviceUrl = $types[0].toObject(message.serviceUrl, options); - if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) - object.requirements = $types[1].toObject(message.requirements, options); - return object; - }; + CobrandProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.CobrandProto) + return object; + var message = new $root.keyhole.dbroot.CobrandProto(); + if (object.logoUrl !== undefined && object.logoUrl !== null) + message.logoUrl = String(object.logoUrl); + if (object.xCoord !== undefined && object.xCoord !== null) { + if (typeof object.xCoord !== "object") + throw TypeError(".keyhole.dbroot.CobrandProto.xCoord: object expected"); + message.xCoord = $types[1].fromObject(object.xCoord); + } + if (object.yCoord !== undefined && object.yCoord !== null) { + if (typeof object.yCoord !== "object") + throw TypeError(".keyhole.dbroot.CobrandProto.yCoord: object expected"); + message.yCoord = $types[2].fromObject(object.yCoord); + } + switch (object.tiePoint) { + case "TOP_LEFT": + case 0: + message.tiePoint = 0; + break; + case "TOP_CENTER": + case 1: + message.tiePoint = 1; + break; + case "TOP_RIGHT": + case 2: + message.tiePoint = 2; + break; + case "MID_LEFT": + case 3: + message.tiePoint = 3; + break; + case "MID_CENTER": + case 4: + message.tiePoint = 4; + break; + case "MID_RIGHT": + case 5: + message.tiePoint = 5; + break; + case "BOTTOM_LEFT": + case 6: + message.tiePoint = 6; + break; + case "BOTTOM_CENTER": + case 7: + message.tiePoint = 7; + break; + case "BOTTOM_RIGHT": + case 8: + message.tiePoint = 8; + break; + } + if (object.screenSize !== undefined && object.screenSize !== null) + message.screenSize = Number(object.screenSize); + return message; + }; - OneboxServiceProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + CobrandProto.from = CobrandProto.fromObject; - OneboxServiceProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + CobrandProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.logoUrl = ""; + object.xCoord = null; + object.yCoord = null; + object.tiePoint = options.enums === String ? "BOTTOM_LEFT" : 6; + object.screenSize = 0; + } + if (message.logoUrl !== undefined && message.logoUrl !== null && message.hasOwnProperty("logoUrl")) + object.logoUrl = message.logoUrl; + if (message.xCoord !== undefined && message.xCoord !== null && message.hasOwnProperty("xCoord")) + object.xCoord = $types[1].toObject(message.xCoord, options); + if (message.yCoord !== undefined && message.yCoord !== null && message.hasOwnProperty("yCoord")) + object.yCoord = $types[2].toObject(message.yCoord, options); + if (message.tiePoint !== undefined && message.tiePoint !== null && message.hasOwnProperty("tiePoint")) + object.tiePoint = options.enums === String ? $types[3][message.tiePoint] : message.tiePoint; + if (message.screenSize !== undefined && message.screenSize !== null && message.hasOwnProperty("screenSize")) + object.screenSize = message.screenSize; + return object; + }; - return OneboxServiceProto; - })(); + CobrandProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - return SearchConfigProto; - })(); + CobrandProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - EndSnippetProto.SearchInfoProto = (function() { + CobrandProto.Coord = (function() { - function SearchInfoProto(properties) { + function Coord(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - SearchInfoProto.prototype.defaultUrl = "http://maps.google.com/maps"; - SearchInfoProto.prototype.geocodeParam = "q"; + Coord.prototype.value = 0; + Coord.prototype.isRelative = false; - SearchInfoProto.decode = function decode(reader, length) { + Coord.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.SearchInfoProto(); + message = new $root.keyhole.dbroot.CobrandProto.Coord(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.defaultUrl = reader.string(); + message.value = reader.double(); break; case 2: - message.geocodeParam = reader.string(); + message.isRelative = reader.bool(); break; default: reader.skipType(tag & 7); @@ -56076,644 +56533,359 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - SearchInfoProto.verify = function verify(message) { + Coord.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.defaultUrl !== undefined) - if (!$util.isString(message.defaultUrl)) - return "defaultUrl: string expected"; - if (message.geocodeParam !== undefined) - if (!$util.isString(message.geocodeParam)) - return "geocodeParam: string expected"; + if (typeof message.value !== "number") + return "value: number expected"; + if (message.isRelative !== undefined) + if (typeof message.isRelative !== "boolean") + return "isRelative: boolean expected"; return null; }; - SearchInfoProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchInfoProto) + Coord.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.CobrandProto.Coord) return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.SearchInfoProto(); - if (object.defaultUrl !== undefined && object.defaultUrl !== null) - message.defaultUrl = String(object.defaultUrl); - if (object.geocodeParam !== undefined && object.geocodeParam !== null) - message.geocodeParam = String(object.geocodeParam); + var message = new $root.keyhole.dbroot.CobrandProto.Coord(); + if (object.value !== undefined && object.value !== null) + message.value = Number(object.value); + if (object.isRelative !== undefined && object.isRelative !== null) + message.isRelative = Boolean(object.isRelative); return message; }; - SearchInfoProto.from = SearchInfoProto.fromObject; + Coord.from = Coord.fromObject; - SearchInfoProto.toObject = function toObject(message, options) { + Coord.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { - object.defaultUrl = "http://maps.google.com/maps"; - object.geocodeParam = "q"; + object.value = 0; + object.isRelative = false; } - if (message.defaultUrl !== undefined && message.defaultUrl !== null && message.hasOwnProperty("defaultUrl")) - object.defaultUrl = message.defaultUrl; - if (message.geocodeParam !== undefined && message.geocodeParam !== null && message.hasOwnProperty("geocodeParam")) - object.geocodeParam = message.geocodeParam; + if (message.value !== undefined && message.value !== null && message.hasOwnProperty("value")) + object.value = message.value; + if (message.isRelative !== undefined && message.isRelative !== null && message.hasOwnProperty("isRelative")) + object.isRelative = message.isRelative; return object; }; - SearchInfoProto.prototype.toObject = function toObject(options) { + Coord.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - SearchInfoProto.prototype.toJSON = function toJSON() { + Coord.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return SearchInfoProto; + return Coord; })(); - EndSnippetProto.RockTreeDataProto = (function() { + CobrandProto.TiePoint = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["TOP_LEFT"] = 0; + values["TOP_CENTER"] = 1; + values["TOP_RIGHT"] = 2; + values["MID_LEFT"] = 3; + values["MID_CENTER"] = 4; + values["MID_RIGHT"] = 5; + values["BOTTOM_LEFT"] = 6; + values["BOTTOM_CENTER"] = 7; + values["BOTTOM_RIGHT"] = 8; + return values; + })(); - function RockTreeDataProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + return CobrandProto; + })(); - RockTreeDataProto.prototype.url = null; + dbroot.DatabaseDescriptionProto = (function() { - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); + function DatabaseDescriptionProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - RockTreeDataProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.RockTreeDataProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.url = $types[0].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + DatabaseDescriptionProto.prototype.databaseName = null; + DatabaseDescriptionProto.prototype.databaseUrl = ""; - RockTreeDataProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.url !== undefined && message.url !== null) { - var error = $types[0].verify(message.url); - if (error) - return "url." + error; - } - return null; - }; + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - RockTreeDataProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.RockTreeDataProto) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.RockTreeDataProto(); - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.RockTreeDataProto.url: object expected"); - message.url = $types[0].fromObject(object.url); + DatabaseDescriptionProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.DatabaseDescriptionProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.databaseName = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.databaseUrl = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; } - return message; - }; + } + return message; + }; - RockTreeDataProto.from = RockTreeDataProto.fromObject; + DatabaseDescriptionProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.databaseName !== undefined && message.databaseName !== null) { + var error = $types[0].verify(message.databaseName); + if (error) + return "databaseName." + error; + } + if (!$util.isString(message.databaseUrl)) + return "databaseUrl: string expected"; + return null; + }; - RockTreeDataProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) - object.url = null; - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[0].toObject(message.url, options); + DatabaseDescriptionProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.DatabaseDescriptionProto) return object; - }; - - RockTreeDataProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - RockTreeDataProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return RockTreeDataProto; - })(); - - EndSnippetProto.FilmstripConfigProto = (function() { - - function FilmstripConfigProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; + var message = new $root.keyhole.dbroot.DatabaseDescriptionProto(); + if (object.databaseName !== undefined && object.databaseName !== null) { + if (typeof object.databaseName !== "object") + throw TypeError(".keyhole.dbroot.DatabaseDescriptionProto.databaseName: object expected"); + message.databaseName = $types[0].fromObject(object.databaseName); } + if (object.databaseUrl !== undefined && object.databaseUrl !== null) + message.databaseUrl = String(object.databaseUrl); + return message; + }; - FilmstripConfigProto.prototype.requirements = null; - FilmstripConfigProto.prototype.alleycatUrlTemplate = null; - FilmstripConfigProto.prototype.fallbackAlleycatUrlTemplate = null; - FilmstripConfigProto.prototype.metadataUrlTemplate = null; - FilmstripConfigProto.prototype.thumbnailUrlTemplate = null; - FilmstripConfigProto.prototype.kmlUrlTemplate = null; - FilmstripConfigProto.prototype.featuredToursUrl = null; - FilmstripConfigProto.prototype.enableViewportFallback = false; - FilmstripConfigProto.prototype.viewportFallbackDistance = 0; - FilmstripConfigProto.prototype.imageryType = $util.emptyArray; - - var $types = { - 0 : "keyhole.dbroot.RequirementProto", - 1 : "keyhole.dbroot.StringIdOrValueProto", - 2 : "keyhole.dbroot.StringIdOrValueProto", - 3 : "keyhole.dbroot.StringIdOrValueProto", - 4 : "keyhole.dbroot.StringIdOrValueProto", - 5 : "keyhole.dbroot.StringIdOrValueProto", - 6 : "keyhole.dbroot.StringIdOrValueProto", - 9 : "keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto" - }; - $lazyTypes.push($types); - - FilmstripConfigProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.requirements = $types[0].decode(reader, reader.uint32()); - break; - case 2: - message.alleycatUrlTemplate = $types[1].decode(reader, reader.uint32()); - break; - case 9: - message.fallbackAlleycatUrlTemplate = $types[2].decode(reader, reader.uint32()); - break; - case 3: - message.metadataUrlTemplate = $types[3].decode(reader, reader.uint32()); - break; - case 4: - message.thumbnailUrlTemplate = $types[4].decode(reader, reader.uint32()); - break; - case 5: - message.kmlUrlTemplate = $types[5].decode(reader, reader.uint32()); - break; - case 6: - message.featuredToursUrl = $types[6].decode(reader, reader.uint32()); - break; - case 7: - message.enableViewportFallback = reader.bool(); - break; - case 8: - message.viewportFallbackDistance = reader.uint32(); - break; - case 10: - if (!(message.imageryType && message.imageryType.length)) - message.imageryType = []; - message.imageryType.push($types[9].decode(reader, reader.uint32())); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + DatabaseDescriptionProto.from = DatabaseDescriptionProto.fromObject; - FilmstripConfigProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.requirements !== undefined && message.requirements !== null) { - var error = $types[0].verify(message.requirements); - if (error) - return "requirements." + error; - } - if (message.alleycatUrlTemplate !== undefined && message.alleycatUrlTemplate !== null) { - var error = $types[1].verify(message.alleycatUrlTemplate); - if (error) - return "alleycatUrlTemplate." + error; - } - if (message.fallbackAlleycatUrlTemplate !== undefined && message.fallbackAlleycatUrlTemplate !== null) { - var error = $types[2].verify(message.fallbackAlleycatUrlTemplate); - if (error) - return "fallbackAlleycatUrlTemplate." + error; - } - if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null) { - var error = $types[3].verify(message.metadataUrlTemplate); - if (error) - return "metadataUrlTemplate." + error; - } - if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null) { - var error = $types[4].verify(message.thumbnailUrlTemplate); - if (error) - return "thumbnailUrlTemplate." + error; - } - if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null) { - var error = $types[5].verify(message.kmlUrlTemplate); - if (error) - return "kmlUrlTemplate." + error; - } - if (message.featuredToursUrl !== undefined && message.featuredToursUrl !== null) { - var error = $types[6].verify(message.featuredToursUrl); - if (error) - return "featuredToursUrl." + error; - } - if (message.enableViewportFallback !== undefined) - if (typeof message.enableViewportFallback !== "boolean") - return "enableViewportFallback: boolean expected"; - if (message.viewportFallbackDistance !== undefined) - if (!$util.isInteger(message.viewportFallbackDistance)) - return "viewportFallbackDistance: integer expected"; - if (message.imageryType !== undefined) { - if (!Array.isArray(message.imageryType)) - return "imageryType: array expected"; - for (var i = 0; i < message.imageryType.length; ++i) { - var error = $types[9].verify(message.imageryType[i]); - if (error) - return "imageryType." + error; - } - } - return null; - }; + DatabaseDescriptionProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.databaseName = null; + object.databaseUrl = ""; + } + if (message.databaseName !== undefined && message.databaseName !== null && message.hasOwnProperty("databaseName")) + object.databaseName = $types[0].toObject(message.databaseName, options); + if (message.databaseUrl !== undefined && message.databaseUrl !== null && message.hasOwnProperty("databaseUrl")) + object.databaseUrl = message.databaseUrl; + return object; + }; - FilmstripConfigProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto(); - if (object.requirements !== undefined && object.requirements !== null) { - if (typeof object.requirements !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.requirements: object expected"); - message.requirements = $types[0].fromObject(object.requirements); - } - if (object.alleycatUrlTemplate !== undefined && object.alleycatUrlTemplate !== null) { - if (typeof object.alleycatUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.alleycatUrlTemplate: object expected"); - message.alleycatUrlTemplate = $types[1].fromObject(object.alleycatUrlTemplate); - } - if (object.fallbackAlleycatUrlTemplate !== undefined && object.fallbackAlleycatUrlTemplate !== null) { - if (typeof object.fallbackAlleycatUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.fallbackAlleycatUrlTemplate: object expected"); - message.fallbackAlleycatUrlTemplate = $types[2].fromObject(object.fallbackAlleycatUrlTemplate); - } - if (object.metadataUrlTemplate !== undefined && object.metadataUrlTemplate !== null) { - if (typeof object.metadataUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.metadataUrlTemplate: object expected"); - message.metadataUrlTemplate = $types[3].fromObject(object.metadataUrlTemplate); - } - if (object.thumbnailUrlTemplate !== undefined && object.thumbnailUrlTemplate !== null) { - if (typeof object.thumbnailUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.thumbnailUrlTemplate: object expected"); - message.thumbnailUrlTemplate = $types[4].fromObject(object.thumbnailUrlTemplate); - } - if (object.kmlUrlTemplate !== undefined && object.kmlUrlTemplate !== null) { - if (typeof object.kmlUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.kmlUrlTemplate: object expected"); - message.kmlUrlTemplate = $types[5].fromObject(object.kmlUrlTemplate); - } - if (object.featuredToursUrl !== undefined && object.featuredToursUrl !== null) { - if (typeof object.featuredToursUrl !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.featuredToursUrl: object expected"); - message.featuredToursUrl = $types[6].fromObject(object.featuredToursUrl); - } - if (object.enableViewportFallback !== undefined && object.enableViewportFallback !== null) - message.enableViewportFallback = Boolean(object.enableViewportFallback); - if (object.viewportFallbackDistance !== undefined && object.viewportFallbackDistance !== null) - message.viewportFallbackDistance = object.viewportFallbackDistance >>> 0; - if (object.imageryType) { - if (!Array.isArray(object.imageryType)) - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.imageryType: array expected"); - message.imageryType = []; - for (var i = 0; i < object.imageryType.length; ++i) { - if (typeof object.imageryType[i] !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.imageryType: object expected"); - message.imageryType[i] = $types[9].fromObject(object.imageryType[i]); - } - } - return message; - }; + DatabaseDescriptionProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - FilmstripConfigProto.from = FilmstripConfigProto.fromObject; + DatabaseDescriptionProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - FilmstripConfigProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) - object.imageryType = []; - if (options.defaults) { - object.requirements = null; - object.alleycatUrlTemplate = null; - object.fallbackAlleycatUrlTemplate = null; - object.metadataUrlTemplate = null; - object.thumbnailUrlTemplate = null; - object.kmlUrlTemplate = null; - object.featuredToursUrl = null; - object.enableViewportFallback = false; - object.viewportFallbackDistance = 0; - } - if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) - object.requirements = $types[0].toObject(message.requirements, options); - if (message.alleycatUrlTemplate !== undefined && message.alleycatUrlTemplate !== null && message.hasOwnProperty("alleycatUrlTemplate")) - object.alleycatUrlTemplate = $types[1].toObject(message.alleycatUrlTemplate, options); - if (message.fallbackAlleycatUrlTemplate !== undefined && message.fallbackAlleycatUrlTemplate !== null && message.hasOwnProperty("fallbackAlleycatUrlTemplate")) - object.fallbackAlleycatUrlTemplate = $types[2].toObject(message.fallbackAlleycatUrlTemplate, options); - if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null && message.hasOwnProperty("metadataUrlTemplate")) - object.metadataUrlTemplate = $types[3].toObject(message.metadataUrlTemplate, options); - if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null && message.hasOwnProperty("thumbnailUrlTemplate")) - object.thumbnailUrlTemplate = $types[4].toObject(message.thumbnailUrlTemplate, options); - if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null && message.hasOwnProperty("kmlUrlTemplate")) - object.kmlUrlTemplate = $types[5].toObject(message.kmlUrlTemplate, options); - if (message.featuredToursUrl !== undefined && message.featuredToursUrl !== null && message.hasOwnProperty("featuredToursUrl")) - object.featuredToursUrl = $types[6].toObject(message.featuredToursUrl, options); - if (message.enableViewportFallback !== undefined && message.enableViewportFallback !== null && message.hasOwnProperty("enableViewportFallback")) - object.enableViewportFallback = message.enableViewportFallback; - if (message.viewportFallbackDistance !== undefined && message.viewportFallbackDistance !== null && message.hasOwnProperty("viewportFallbackDistance")) - object.viewportFallbackDistance = message.viewportFallbackDistance; - if (message.imageryType !== undefined && message.imageryType !== null && message.hasOwnProperty("imageryType")) { - object.imageryType = []; - for (var j = 0; j < message.imageryType.length; ++j) - object.imageryType[j] = $types[9].toObject(message.imageryType[j], options); - } - return object; - }; + return DatabaseDescriptionProto; + })(); - FilmstripConfigProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + dbroot.ConfigScriptProto = (function() { - FilmstripConfigProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + function ConfigScriptProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - FilmstripConfigProto.AlleycatImageryTypeProto = (function() { + ConfigScriptProto.prototype.scriptName = ""; + ConfigScriptProto.prototype.scriptData = ""; - function AlleycatImageryTypeProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; + ConfigScriptProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.ConfigScriptProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.scriptName = reader.string(); + break; + case 2: + message.scriptData = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; } + } + return message; + }; - AlleycatImageryTypeProto.prototype.imageryTypeId = 0; - AlleycatImageryTypeProto.prototype.imageryTypeLabel = ""; - AlleycatImageryTypeProto.prototype.metadataUrlTemplate = null; - AlleycatImageryTypeProto.prototype.thumbnailUrlTemplate = null; - AlleycatImageryTypeProto.prototype.kmlUrlTemplate = null; - - var $types = { - 2 : "keyhole.dbroot.StringIdOrValueProto", - 3 : "keyhole.dbroot.StringIdOrValueProto", - 4 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); - - AlleycatImageryTypeProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.imageryTypeId = reader.int32(); - break; - case 2: - message.imageryTypeLabel = reader.string(); - break; - case 3: - message.metadataUrlTemplate = $types[2].decode(reader, reader.uint32()); - break; - case 4: - message.thumbnailUrlTemplate = $types[3].decode(reader, reader.uint32()); - break; - case 5: - message.kmlUrlTemplate = $types[4].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; - - AlleycatImageryTypeProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.imageryTypeId !== undefined) - if (!$util.isInteger(message.imageryTypeId)) - return "imageryTypeId: integer expected"; - if (message.imageryTypeLabel !== undefined) - if (!$util.isString(message.imageryTypeLabel)) - return "imageryTypeLabel: string expected"; - if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null) { - var error = $types[2].verify(message.metadataUrlTemplate); - if (error) - return "metadataUrlTemplate." + error; - } - if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null) { - var error = $types[3].verify(message.thumbnailUrlTemplate); - if (error) - return "thumbnailUrlTemplate." + error; - } - if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null) { - var error = $types[4].verify(message.kmlUrlTemplate); - if (error) - return "kmlUrlTemplate." + error; - } - return null; - }; - - AlleycatImageryTypeProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto(); - if (object.imageryTypeId !== undefined && object.imageryTypeId !== null) - message.imageryTypeId = object.imageryTypeId | 0; - if (object.imageryTypeLabel !== undefined && object.imageryTypeLabel !== null) - message.imageryTypeLabel = String(object.imageryTypeLabel); - if (object.metadataUrlTemplate !== undefined && object.metadataUrlTemplate !== null) { - if (typeof object.metadataUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto.metadataUrlTemplate: object expected"); - message.metadataUrlTemplate = $types[2].fromObject(object.metadataUrlTemplate); - } - if (object.thumbnailUrlTemplate !== undefined && object.thumbnailUrlTemplate !== null) { - if (typeof object.thumbnailUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto.thumbnailUrlTemplate: object expected"); - message.thumbnailUrlTemplate = $types[3].fromObject(object.thumbnailUrlTemplate); - } - if (object.kmlUrlTemplate !== undefined && object.kmlUrlTemplate !== null) { - if (typeof object.kmlUrlTemplate !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto.kmlUrlTemplate: object expected"); - message.kmlUrlTemplate = $types[4].fromObject(object.kmlUrlTemplate); - } - return message; - }; - - AlleycatImageryTypeProto.from = AlleycatImageryTypeProto.fromObject; - - AlleycatImageryTypeProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.imageryTypeId = 0; - object.imageryTypeLabel = ""; - object.metadataUrlTemplate = null; - object.thumbnailUrlTemplate = null; - object.kmlUrlTemplate = null; - } - if (message.imageryTypeId !== undefined && message.imageryTypeId !== null && message.hasOwnProperty("imageryTypeId")) - object.imageryTypeId = message.imageryTypeId; - if (message.imageryTypeLabel !== undefined && message.imageryTypeLabel !== null && message.hasOwnProperty("imageryTypeLabel")) - object.imageryTypeLabel = message.imageryTypeLabel; - if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null && message.hasOwnProperty("metadataUrlTemplate")) - object.metadataUrlTemplate = $types[2].toObject(message.metadataUrlTemplate, options); - if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null && message.hasOwnProperty("thumbnailUrlTemplate")) - object.thumbnailUrlTemplate = $types[3].toObject(message.thumbnailUrlTemplate, options); - if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null && message.hasOwnProperty("kmlUrlTemplate")) - object.kmlUrlTemplate = $types[4].toObject(message.kmlUrlTemplate, options); - return object; - }; - - AlleycatImageryTypeProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + ConfigScriptProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isString(message.scriptName)) + return "scriptName: string expected"; + if (!$util.isString(message.scriptData)) + return "scriptData: string expected"; + return null; + }; - AlleycatImageryTypeProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + ConfigScriptProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.ConfigScriptProto) + return object; + var message = new $root.keyhole.dbroot.ConfigScriptProto(); + if (object.scriptName !== undefined && object.scriptName !== null) + message.scriptName = String(object.scriptName); + if (object.scriptData !== undefined && object.scriptData !== null) + message.scriptData = String(object.scriptData); + return message; + }; - return AlleycatImageryTypeProto; - })(); + ConfigScriptProto.from = ConfigScriptProto.fromObject; - return FilmstripConfigProto; - })(); + ConfigScriptProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.scriptName = ""; + object.scriptData = ""; + } + if (message.scriptName !== undefined && message.scriptName !== null && message.hasOwnProperty("scriptName")) + object.scriptName = message.scriptName; + if (message.scriptData !== undefined && message.scriptData !== null && message.hasOwnProperty("scriptData")) + object.scriptData = message.scriptData; + return object; + }; - EndSnippetProto.StarDataProto = (function() { + ConfigScriptProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - function StarDataProto(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - this[keys[i]] = properties[keys[i]]; - } + ConfigScriptProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - StarDataProto.prototype.url = null; + return ConfigScriptProto; + })(); - var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto" - }; - $lazyTypes.push($types); + dbroot.SwoopParamsProto = (function() { - StarDataProto.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EndSnippetProto.StarDataProto(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.url = $types[0].decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag & 7); - break; - } - } - return message; - }; + function SwoopParamsProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - StarDataProto.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.url !== undefined && message.url !== null) { - var error = $types[0].verify(message.url); - if (error) - return "url." + error; - } - return null; - }; + SwoopParamsProto.prototype.startDistInMeters = 0; - StarDataProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EndSnippetProto.StarDataProto) - return object; - var message = new $root.keyhole.dbroot.EndSnippetProto.StarDataProto(); - if (object.url !== undefined && object.url !== null) { - if (typeof object.url !== "object") - throw TypeError(".keyhole.dbroot.EndSnippetProto.StarDataProto.url: object expected"); - message.url = $types[0].fromObject(object.url); + SwoopParamsProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.SwoopParamsProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.startDistInMeters = reader.double(); + break; + default: + reader.skipType(tag & 7); + break; } - return message; - }; + } + return message; + }; - StarDataProto.from = StarDataProto.fromObject; + SwoopParamsProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.startDistInMeters !== undefined) + if (typeof message.startDistInMeters !== "number") + return "startDistInMeters: number expected"; + return null; + }; - StarDataProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) - object.url = null; - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = $types[0].toObject(message.url, options); + SwoopParamsProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.SwoopParamsProto) return object; - }; + var message = new $root.keyhole.dbroot.SwoopParamsProto(); + if (object.startDistInMeters !== undefined && object.startDistInMeters !== null) + message.startDistInMeters = Number(object.startDistInMeters); + return message; + }; - StarDataProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; + SwoopParamsProto.from = SwoopParamsProto.fromObject; - StarDataProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; + SwoopParamsProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.startDistInMeters = 0; + if (message.startDistInMeters !== undefined && message.startDistInMeters !== null && message.hasOwnProperty("startDistInMeters")) + object.startDistInMeters = message.startDistInMeters; + return object; + }; - return StarDataProto; - })(); + SwoopParamsProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - return EndSnippetProto; + SwoopParamsProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return SwoopParamsProto; })(); - dbroot.DbRootRefProto = (function() { + dbroot.PostingServerProto = (function() { - function DbRootRefProto(properties) { + function PostingServerProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - DbRootRefProto.prototype.url = ""; - DbRootRefProto.prototype.isCritical = false; - DbRootRefProto.prototype.requirements = null; + PostingServerProto.prototype.name = null; + PostingServerProto.prototype.baseUrl = null; + PostingServerProto.prototype.postWizardPath = null; + PostingServerProto.prototype.fileSubmitPath = null; var $types = { - 2 : "keyhole.dbroot.RequirementProto" + 0 : "keyhole.dbroot.StringIdOrValueProto", + 1 : "keyhole.dbroot.StringIdOrValueProto", + 2 : "keyhole.dbroot.StringIdOrValueProto", + 3 : "keyhole.dbroot.StringIdOrValueProto" }; $lazyTypes.push($types); - DbRootRefProto.decode = function decode(reader, length) { + PostingServerProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.DbRootRefProto(); + message = new $root.keyhole.dbroot.PostingServerProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { - case 2: - message.url = reader.string(); - break; case 1: - message.isCritical = reader.bool(); + message.name = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.baseUrl = $types[1].decode(reader, reader.uint32()); break; case 3: - message.requirements = $types[2].decode(reader, reader.uint32()); + message.postWizardPath = $types[2].decode(reader, reader.uint32()); + break; + case 4: + message.fileSubmitPath = $types[3].decode(reader, reader.uint32()); break; default: reader.skipType(tag & 7); @@ -56723,89 +56895,123 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - DbRootRefProto.verify = function verify(message) { + PostingServerProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (!$util.isString(message.url)) - return "url: string expected"; - if (message.isCritical !== undefined) - if (typeof message.isCritical !== "boolean") - return "isCritical: boolean expected"; - if (message.requirements !== undefined && message.requirements !== null) { - var error = $types[2].verify(message.requirements); + if (message.name !== undefined && message.name !== null) { + var error = $types[0].verify(message.name); if (error) - return "requirements." + error; + return "name." + error; + } + if (message.baseUrl !== undefined && message.baseUrl !== null) { + var error = $types[1].verify(message.baseUrl); + if (error) + return "baseUrl." + error; + } + if (message.postWizardPath !== undefined && message.postWizardPath !== null) { + var error = $types[2].verify(message.postWizardPath); + if (error) + return "postWizardPath." + error; + } + if (message.fileSubmitPath !== undefined && message.fileSubmitPath !== null) { + var error = $types[3].verify(message.fileSubmitPath); + if (error) + return "fileSubmitPath." + error; } return null; }; - DbRootRefProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.DbRootRefProto) + PostingServerProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.PostingServerProto) return object; - var message = new $root.keyhole.dbroot.DbRootRefProto(); - if (object.url !== undefined && object.url !== null) - message.url = String(object.url); - if (object.isCritical !== undefined && object.isCritical !== null) - message.isCritical = Boolean(object.isCritical); - if (object.requirements !== undefined && object.requirements !== null) { - if (typeof object.requirements !== "object") - throw TypeError(".keyhole.dbroot.DbRootRefProto.requirements: object expected"); - message.requirements = $types[2].fromObject(object.requirements); + var message = new $root.keyhole.dbroot.PostingServerProto(); + if (object.name !== undefined && object.name !== null) { + if (typeof object.name !== "object") + throw TypeError(".keyhole.dbroot.PostingServerProto.name: object expected"); + message.name = $types[0].fromObject(object.name); + } + if (object.baseUrl !== undefined && object.baseUrl !== null) { + if (typeof object.baseUrl !== "object") + throw TypeError(".keyhole.dbroot.PostingServerProto.baseUrl: object expected"); + message.baseUrl = $types[1].fromObject(object.baseUrl); + } + if (object.postWizardPath !== undefined && object.postWizardPath !== null) { + if (typeof object.postWizardPath !== "object") + throw TypeError(".keyhole.dbroot.PostingServerProto.postWizardPath: object expected"); + message.postWizardPath = $types[2].fromObject(object.postWizardPath); + } + if (object.fileSubmitPath !== undefined && object.fileSubmitPath !== null) { + if (typeof object.fileSubmitPath !== "object") + throw TypeError(".keyhole.dbroot.PostingServerProto.fileSubmitPath: object expected"); + message.fileSubmitPath = $types[3].fromObject(object.fileSubmitPath); } return message; }; - DbRootRefProto.from = DbRootRefProto.fromObject; + PostingServerProto.from = PostingServerProto.fromObject; - DbRootRefProto.toObject = function toObject(message, options) { + PostingServerProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; if (options.defaults) { - object.url = ""; - object.isCritical = false; - object.requirements = null; + object.name = null; + object.baseUrl = null; + object.postWizardPath = null; + object.fileSubmitPath = null; } - if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) - object.url = message.url; - if (message.isCritical !== undefined && message.isCritical !== null && message.hasOwnProperty("isCritical")) - object.isCritical = message.isCritical; - if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) - object.requirements = $types[2].toObject(message.requirements, options); + if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) + object.name = $types[0].toObject(message.name, options); + if (message.baseUrl !== undefined && message.baseUrl !== null && message.hasOwnProperty("baseUrl")) + object.baseUrl = $types[1].toObject(message.baseUrl, options); + if (message.postWizardPath !== undefined && message.postWizardPath !== null && message.hasOwnProperty("postWizardPath")) + object.postWizardPath = $types[2].toObject(message.postWizardPath, options); + if (message.fileSubmitPath !== undefined && message.fileSubmitPath !== null && message.hasOwnProperty("fileSubmitPath")) + object.fileSubmitPath = $types[3].toObject(message.fileSubmitPath, options); return object; }; - DbRootRefProto.prototype.toObject = function toObject(options) { + PostingServerProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - DbRootRefProto.prototype.toJSON = function toJSON() { + PostingServerProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return DbRootRefProto; + return PostingServerProto; })(); - dbroot.DatabaseVersionProto = (function() { + dbroot.PlanetaryDatabaseProto = (function() { - function DatabaseVersionProto(properties) { + function PlanetaryDatabaseProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - DatabaseVersionProto.prototype.quadtreeVersion = 0; + PlanetaryDatabaseProto.prototype.url = null; + PlanetaryDatabaseProto.prototype.name = null; - DatabaseVersionProto.decode = function decode(reader, length) { + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto", + 1 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); + + PlanetaryDatabaseProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.DatabaseVersionProto(); + message = new $root.keyhole.dbroot.PlanetaryDatabaseProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.quadtreeVersion = reader.uint32(); + message.url = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.name = $types[1].decode(reader, reader.uint32()); break; default: reader.skipType(tag & 7); @@ -56815,148 +57021,96 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - DatabaseVersionProto.verify = function verify(message) { + PlanetaryDatabaseProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (!$util.isInteger(message.quadtreeVersion)) - return "quadtreeVersion: integer expected"; + var error = $types[0].verify(message.url); + if (error) + return "url." + error; + var error = $types[1].verify(message.name); + if (error) + return "name." + error; return null; }; - DatabaseVersionProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.DatabaseVersionProto) + PlanetaryDatabaseProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.PlanetaryDatabaseProto) return object; - var message = new $root.keyhole.dbroot.DatabaseVersionProto(); - if (object.quadtreeVersion !== undefined && object.quadtreeVersion !== null) - message.quadtreeVersion = object.quadtreeVersion >>> 0; + var message = new $root.keyhole.dbroot.PlanetaryDatabaseProto(); + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.PlanetaryDatabaseProto.url: object expected"); + message.url = $types[0].fromObject(object.url); + } + if (object.name !== undefined && object.name !== null) { + if (typeof object.name !== "object") + throw TypeError(".keyhole.dbroot.PlanetaryDatabaseProto.name: object expected"); + message.name = $types[1].fromObject(object.name); + } return message; }; - DatabaseVersionProto.from = DatabaseVersionProto.fromObject; + PlanetaryDatabaseProto.from = PlanetaryDatabaseProto.fromObject; - DatabaseVersionProto.toObject = function toObject(message, options) { + PlanetaryDatabaseProto.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) - object.quadtreeVersion = 0; - if (message.quadtreeVersion !== undefined && message.quadtreeVersion !== null && message.hasOwnProperty("quadtreeVersion")) - object.quadtreeVersion = message.quadtreeVersion; + if (options.defaults) { + object.url = null; + object.name = null; + } + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[0].toObject(message.url, options); + if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) + object.name = $types[1].toObject(message.name, options); return object; }; - DatabaseVersionProto.prototype.toObject = function toObject(options) { + PlanetaryDatabaseProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - DatabaseVersionProto.prototype.toJSON = function toJSON() { + PlanetaryDatabaseProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return DatabaseVersionProto; + return PlanetaryDatabaseProto; })(); - dbroot.DbRootProto = (function() { + dbroot.LogServerProto = (function() { - function DbRootProto(properties) { + function LogServerProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - DbRootProto.prototype.databaseName = null; - DbRootProto.prototype.imageryPresent = true; - DbRootProto.prototype.protoImagery = false; - DbRootProto.prototype.terrainPresent = false; - DbRootProto.prototype.providerInfo = $util.emptyArray; - DbRootProto.prototype.nestedFeature = $util.emptyArray; - DbRootProto.prototype.styleAttribute = $util.emptyArray; - DbRootProto.prototype.styleMap = $util.emptyArray; - DbRootProto.prototype.endSnippet = null; - DbRootProto.prototype.translationEntry = $util.emptyArray; - DbRootProto.prototype.language = "en"; - DbRootProto.prototype.version = 5; - DbRootProto.prototype.dbrootReference = $util.emptyArray; - DbRootProto.prototype.databaseVersion = null; - DbRootProto.prototype.refreshTimeout = 0; + LogServerProto.prototype.url = null; + LogServerProto.prototype.enable = false; + LogServerProto.prototype.throttlingFactor = 1; var $types = { - 0 : "keyhole.dbroot.StringIdOrValueProto", - 4 : "keyhole.dbroot.ProviderInfoProto", - 5 : "keyhole.dbroot.NestedFeatureProto", - 6 : "keyhole.dbroot.StyleAttributeProto", - 7 : "keyhole.dbroot.StyleMapProto", - 8 : "keyhole.dbroot.EndSnippetProto", - 9 : "keyhole.dbroot.StringEntryProto", - 12 : "keyhole.dbroot.DbRootRefProto", - 13 : "keyhole.dbroot.DatabaseVersionProto" + 0 : "keyhole.dbroot.StringIdOrValueProto" }; $lazyTypes.push($types); - DbRootProto.decode = function decode(reader, length) { + LogServerProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.DbRootProto(); + message = new $root.keyhole.dbroot.LogServerProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { - case 15: - message.databaseName = $types[0].decode(reader, reader.uint32()); - break; case 1: - message.imageryPresent = reader.bool(); - break; - case 14: - message.protoImagery = reader.bool(); + message.url = $types[0].decode(reader, reader.uint32()); break; case 2: - message.terrainPresent = reader.bool(); + message.enable = reader.bool(); break; case 3: - if (!(message.providerInfo && message.providerInfo.length)) - message.providerInfo = []; - message.providerInfo.push($types[4].decode(reader, reader.uint32())); - break; - case 4: - if (!(message.nestedFeature && message.nestedFeature.length)) - message.nestedFeature = []; - message.nestedFeature.push($types[5].decode(reader, reader.uint32())); - break; - case 5: - if (!(message.styleAttribute && message.styleAttribute.length)) - message.styleAttribute = []; - message.styleAttribute.push($types[6].decode(reader, reader.uint32())); - break; - case 6: - if (!(message.styleMap && message.styleMap.length)) - message.styleMap = []; - message.styleMap.push($types[7].decode(reader, reader.uint32())); - break; - case 7: - message.endSnippet = $types[8].decode(reader, reader.uint32()); - break; - case 8: - if (!(message.translationEntry && message.translationEntry.length)) - message.translationEntry = []; - message.translationEntry.push($types[9].decode(reader, reader.uint32())); - break; - case 9: - message.language = reader.string(); - break; - case 10: - message.version = reader.int32(); - break; - case 11: - if (!(message.dbrootReference && message.dbrootReference.length)) - message.dbrootReference = []; - message.dbrootReference.push($types[12].decode(reader, reader.uint32())); - break; - case 13: - message.databaseVersion = $types[13].decode(reader, reader.uint32()); - break; - case 16: - message.refreshTimeout = reader.int32(); + message.throttlingFactor = reader.int32(); break; default: reader.skipType(tag & 7); @@ -56966,313 +57120,435 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - DbRootProto.verify = function verify(message) { + LogServerProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.databaseName !== undefined && message.databaseName !== null) { - var error = $types[0].verify(message.databaseName); + if (message.url !== undefined && message.url !== null) { + var error = $types[0].verify(message.url); if (error) - return "databaseName." + error; - } - if (message.imageryPresent !== undefined) - if (typeof message.imageryPresent !== "boolean") - return "imageryPresent: boolean expected"; - if (message.protoImagery !== undefined) - if (typeof message.protoImagery !== "boolean") - return "protoImagery: boolean expected"; - if (message.terrainPresent !== undefined) - if (typeof message.terrainPresent !== "boolean") - return "terrainPresent: boolean expected"; - if (message.providerInfo !== undefined) { - if (!Array.isArray(message.providerInfo)) - return "providerInfo: array expected"; - for (var i = 0; i < message.providerInfo.length; ++i) { - var error = $types[4].verify(message.providerInfo[i]); - if (error) - return "providerInfo." + error; - } - } - if (message.nestedFeature !== undefined) { - if (!Array.isArray(message.nestedFeature)) - return "nestedFeature: array expected"; - for (var i = 0; i < message.nestedFeature.length; ++i) { - var error = $types[5].verify(message.nestedFeature[i]); - if (error) - return "nestedFeature." + error; - } - } - if (message.styleAttribute !== undefined) { - if (!Array.isArray(message.styleAttribute)) - return "styleAttribute: array expected"; - for (var i = 0; i < message.styleAttribute.length; ++i) { - var error = $types[6].verify(message.styleAttribute[i]); - if (error) - return "styleAttribute." + error; - } + return "url." + error; } - if (message.styleMap !== undefined) { - if (!Array.isArray(message.styleMap)) - return "styleMap: array expected"; - for (var i = 0; i < message.styleMap.length; ++i) { - var error = $types[7].verify(message.styleMap[i]); - if (error) - return "styleMap." + error; - } + if (message.enable !== undefined) + if (typeof message.enable !== "boolean") + return "enable: boolean expected"; + if (message.throttlingFactor !== undefined) + if (!$util.isInteger(message.throttlingFactor)) + return "throttlingFactor: integer expected"; + return null; + }; + + LogServerProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.LogServerProto) + return object; + var message = new $root.keyhole.dbroot.LogServerProto(); + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.LogServerProto.url: object expected"); + message.url = $types[0].fromObject(object.url); } - if (message.endSnippet !== undefined && message.endSnippet !== null) { - var error = $types[8].verify(message.endSnippet); - if (error) - return "endSnippet." + error; + if (object.enable !== undefined && object.enable !== null) + message.enable = Boolean(object.enable); + if (object.throttlingFactor !== undefined && object.throttlingFactor !== null) + message.throttlingFactor = object.throttlingFactor | 0; + return message; + }; + + LogServerProto.from = LogServerProto.fromObject; + + LogServerProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.url = null; + object.enable = false; + object.throttlingFactor = 1; } - if (message.translationEntry !== undefined) { - if (!Array.isArray(message.translationEntry)) - return "translationEntry: array expected"; - for (var i = 0; i < message.translationEntry.length; ++i) { - var error = $types[9].verify(message.translationEntry[i]); - if (error) - return "translationEntry." + error; - } - } - if (message.language !== undefined) - if (!$util.isString(message.language)) - return "language: string expected"; - if (message.version !== undefined) - if (!$util.isInteger(message.version)) - return "version: integer expected"; - if (message.dbrootReference !== undefined) { - if (!Array.isArray(message.dbrootReference)) - return "dbrootReference: array expected"; - for (var i = 0; i < message.dbrootReference.length; ++i) { - var error = $types[12].verify(message.dbrootReference[i]); - if (error) - return "dbrootReference." + error; - } - } - if (message.databaseVersion !== undefined && message.databaseVersion !== null) { - var error = $types[13].verify(message.databaseVersion); - if (error) - return "databaseVersion." + error; - } - if (message.refreshTimeout !== undefined) - if (!$util.isInteger(message.refreshTimeout)) - return "refreshTimeout: integer expected"; - return null; - }; - - DbRootProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.DbRootProto) - return object; - var message = new $root.keyhole.dbroot.DbRootProto(); - if (object.databaseName !== undefined && object.databaseName !== null) { - if (typeof object.databaseName !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.databaseName: object expected"); - message.databaseName = $types[0].fromObject(object.databaseName); - } - if (object.imageryPresent !== undefined && object.imageryPresent !== null) - message.imageryPresent = Boolean(object.imageryPresent); - if (object.protoImagery !== undefined && object.protoImagery !== null) - message.protoImagery = Boolean(object.protoImagery); - if (object.terrainPresent !== undefined && object.terrainPresent !== null) - message.terrainPresent = Boolean(object.terrainPresent); - if (object.providerInfo) { - if (!Array.isArray(object.providerInfo)) - throw TypeError(".keyhole.dbroot.DbRootProto.providerInfo: array expected"); - message.providerInfo = []; - for (var i = 0; i < object.providerInfo.length; ++i) { - if (typeof object.providerInfo[i] !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.providerInfo: object expected"); - message.providerInfo[i] = $types[4].fromObject(object.providerInfo[i]); - } - } - if (object.nestedFeature) { - if (!Array.isArray(object.nestedFeature)) - throw TypeError(".keyhole.dbroot.DbRootProto.nestedFeature: array expected"); - message.nestedFeature = []; - for (var i = 0; i < object.nestedFeature.length; ++i) { - if (typeof object.nestedFeature[i] !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.nestedFeature: object expected"); - message.nestedFeature[i] = $types[5].fromObject(object.nestedFeature[i]); - } - } - if (object.styleAttribute) { - if (!Array.isArray(object.styleAttribute)) - throw TypeError(".keyhole.dbroot.DbRootProto.styleAttribute: array expected"); - message.styleAttribute = []; - for (var i = 0; i < object.styleAttribute.length; ++i) { - if (typeof object.styleAttribute[i] !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.styleAttribute: object expected"); - message.styleAttribute[i] = $types[6].fromObject(object.styleAttribute[i]); - } - } - if (object.styleMap) { - if (!Array.isArray(object.styleMap)) - throw TypeError(".keyhole.dbroot.DbRootProto.styleMap: array expected"); - message.styleMap = []; - for (var i = 0; i < object.styleMap.length; ++i) { - if (typeof object.styleMap[i] !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.styleMap: object expected"); - message.styleMap[i] = $types[7].fromObject(object.styleMap[i]); - } - } - if (object.endSnippet !== undefined && object.endSnippet !== null) { - if (typeof object.endSnippet !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.endSnippet: object expected"); - message.endSnippet = $types[8].fromObject(object.endSnippet); - } - if (object.translationEntry) { - if (!Array.isArray(object.translationEntry)) - throw TypeError(".keyhole.dbroot.DbRootProto.translationEntry: array expected"); - message.translationEntry = []; - for (var i = 0; i < object.translationEntry.length; ++i) { - if (typeof object.translationEntry[i] !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.translationEntry: object expected"); - message.translationEntry[i] = $types[9].fromObject(object.translationEntry[i]); - } - } - if (object.language !== undefined && object.language !== null) - message.language = String(object.language); - if (object.version !== undefined && object.version !== null) - message.version = object.version | 0; - if (object.dbrootReference) { - if (!Array.isArray(object.dbrootReference)) - throw TypeError(".keyhole.dbroot.DbRootProto.dbrootReference: array expected"); - message.dbrootReference = []; - for (var i = 0; i < object.dbrootReference.length; ++i) { - if (typeof object.dbrootReference[i] !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.dbrootReference: object expected"); - message.dbrootReference[i] = $types[12].fromObject(object.dbrootReference[i]); - } - } - if (object.databaseVersion !== undefined && object.databaseVersion !== null) { - if (typeof object.databaseVersion !== "object") - throw TypeError(".keyhole.dbroot.DbRootProto.databaseVersion: object expected"); - message.databaseVersion = $types[13].fromObject(object.databaseVersion); - } - if (object.refreshTimeout !== undefined && object.refreshTimeout !== null) - message.refreshTimeout = object.refreshTimeout | 0; - return message; - }; - - DbRootProto.from = DbRootProto.fromObject; - - DbRootProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.arrays || options.defaults) { - object.providerInfo = []; - object.nestedFeature = []; - object.styleAttribute = []; - object.styleMap = []; - object.translationEntry = []; - object.dbrootReference = []; - } - if (options.defaults) { - object.databaseName = null; - object.imageryPresent = true; - object.protoImagery = false; - object.terrainPresent = false; - object.endSnippet = null; - object.language = "en"; - object.version = 5; - object.databaseVersion = null; - object.refreshTimeout = 0; - } - if (message.databaseName !== undefined && message.databaseName !== null && message.hasOwnProperty("databaseName")) - object.databaseName = $types[0].toObject(message.databaseName, options); - if (message.imageryPresent !== undefined && message.imageryPresent !== null && message.hasOwnProperty("imageryPresent")) - object.imageryPresent = message.imageryPresent; - if (message.protoImagery !== undefined && message.protoImagery !== null && message.hasOwnProperty("protoImagery")) - object.protoImagery = message.protoImagery; - if (message.terrainPresent !== undefined && message.terrainPresent !== null && message.hasOwnProperty("terrainPresent")) - object.terrainPresent = message.terrainPresent; - if (message.providerInfo !== undefined && message.providerInfo !== null && message.hasOwnProperty("providerInfo")) { - object.providerInfo = []; - for (var j = 0; j < message.providerInfo.length; ++j) - object.providerInfo[j] = $types[4].toObject(message.providerInfo[j], options); - } - if (message.nestedFeature !== undefined && message.nestedFeature !== null && message.hasOwnProperty("nestedFeature")) { - object.nestedFeature = []; - for (var j = 0; j < message.nestedFeature.length; ++j) - object.nestedFeature[j] = $types[5].toObject(message.nestedFeature[j], options); - } - if (message.styleAttribute !== undefined && message.styleAttribute !== null && message.hasOwnProperty("styleAttribute")) { - object.styleAttribute = []; - for (var j = 0; j < message.styleAttribute.length; ++j) - object.styleAttribute[j] = $types[6].toObject(message.styleAttribute[j], options); - } - if (message.styleMap !== undefined && message.styleMap !== null && message.hasOwnProperty("styleMap")) { - object.styleMap = []; - for (var j = 0; j < message.styleMap.length; ++j) - object.styleMap[j] = $types[7].toObject(message.styleMap[j], options); - } - if (message.endSnippet !== undefined && message.endSnippet !== null && message.hasOwnProperty("endSnippet")) - object.endSnippet = $types[8].toObject(message.endSnippet, options); - if (message.translationEntry !== undefined && message.translationEntry !== null && message.hasOwnProperty("translationEntry")) { - object.translationEntry = []; - for (var j = 0; j < message.translationEntry.length; ++j) - object.translationEntry[j] = $types[9].toObject(message.translationEntry[j], options); - } - if (message.language !== undefined && message.language !== null && message.hasOwnProperty("language")) - object.language = message.language; - if (message.version !== undefined && message.version !== null && message.hasOwnProperty("version")) - object.version = message.version; - if (message.dbrootReference !== undefined && message.dbrootReference !== null && message.hasOwnProperty("dbrootReference")) { - object.dbrootReference = []; - for (var j = 0; j < message.dbrootReference.length; ++j) - object.dbrootReference[j] = $types[12].toObject(message.dbrootReference[j], options); - } - if (message.databaseVersion !== undefined && message.databaseVersion !== null && message.hasOwnProperty("databaseVersion")) - object.databaseVersion = $types[13].toObject(message.databaseVersion, options); - if (message.refreshTimeout !== undefined && message.refreshTimeout !== null && message.hasOwnProperty("refreshTimeout")) - object.refreshTimeout = message.refreshTimeout; + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[0].toObject(message.url, options); + if (message.enable !== undefined && message.enable !== null && message.hasOwnProperty("enable")) + object.enable = message.enable; + if (message.throttlingFactor !== undefined && message.throttlingFactor !== null && message.hasOwnProperty("throttlingFactor")) + object.throttlingFactor = message.throttlingFactor; return object; }; - DbRootProto.prototype.toObject = function toObject(options) { + LogServerProto.prototype.toObject = function toObject(options) { return this.constructor.toObject(this, options); }; - DbRootProto.prototype.toJSON = function toJSON() { + LogServerProto.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; - return DbRootProto; + return LogServerProto; })(); - dbroot.EncryptedDbRootProto = (function() { + dbroot.EndSnippetProto = (function() { - function EncryptedDbRootProto(properties) { + function EndSnippetProto(properties) { if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) this[keys[i]] = properties[keys[i]]; } - EncryptedDbRootProto.prototype.encryptionType = 0; - EncryptedDbRootProto.prototype.encryptionData = $util.newBuffer([]); - EncryptedDbRootProto.prototype.dbrootData = $util.newBuffer([]); + EndSnippetProto.prototype.model = null; + EndSnippetProto.prototype.authServerUrl = null; + EndSnippetProto.prototype.disableAuthentication = false; + EndSnippetProto.prototype.mfeDomains = $util.emptyArray; + EndSnippetProto.prototype.mfeLangParam = "hl=$5Bhl5D"; + EndSnippetProto.prototype.adsUrlPatterns = ""; + EndSnippetProto.prototype.reverseGeocoderUrl = null; + EndSnippetProto.prototype.reverseGeocoderProtocolVersion = 3; + EndSnippetProto.prototype.skyDatabaseIsAvailable = true; + EndSnippetProto.prototype.skyDatabaseUrl = null; + EndSnippetProto.prototype.defaultWebPageIntlUrl = null; + EndSnippetProto.prototype.numStartUpTips = 17; + EndSnippetProto.prototype.startUpTipsUrl = null; + EndSnippetProto.prototype.numProStartUpTips = 0; + EndSnippetProto.prototype.proStartUpTipsUrl = null; + EndSnippetProto.prototype.startupTipsIntlUrl = null; + EndSnippetProto.prototype.userGuideIntlUrl = null; + EndSnippetProto.prototype.supportCenterIntlUrl = null; + EndSnippetProto.prototype.businessListingIntlUrl = null; + EndSnippetProto.prototype.supportAnswerIntlUrl = null; + EndSnippetProto.prototype.supportTopicIntlUrl = null; + EndSnippetProto.prototype.supportRequestIntlUrl = null; + EndSnippetProto.prototype.earthIntlUrl = null; + EndSnippetProto.prototype.addContentUrl = null; + EndSnippetProto.prototype.sketchupNotInstalledUrl = null; + EndSnippetProto.prototype.sketchupErrorUrl = null; + EndSnippetProto.prototype.freeLicenseUrl = null; + EndSnippetProto.prototype.proLicenseUrl = null; + EndSnippetProto.prototype.tutorialUrl = null; + EndSnippetProto.prototype.keyboardShortcutsUrl = null; + EndSnippetProto.prototype.releaseNotesUrl = null; + EndSnippetProto.prototype.hideUserData = false; + EndSnippetProto.prototype.useGeLogo = true; + EndSnippetProto.prototype.dioramaDescriptionUrlBase = null; + EndSnippetProto.prototype.dioramaDefaultColor = 4291281607; + EndSnippetProto.prototype.dioramaBlacklistUrl = null; + EndSnippetProto.prototype.clientOptions = null; + EndSnippetProto.prototype.fetchingOptions = null; + EndSnippetProto.prototype.timeMachineOptions = null; + EndSnippetProto.prototype.csiOptions = null; + EndSnippetProto.prototype.searchTab = $util.emptyArray; + EndSnippetProto.prototype.cobrandInfo = $util.emptyArray; + EndSnippetProto.prototype.validDatabase = $util.emptyArray; + EndSnippetProto.prototype.configScript = $util.emptyArray; + EndSnippetProto.prototype.deauthServerUrl = null; + EndSnippetProto.prototype.swoopParameters = null; + EndSnippetProto.prototype.bbsServerInfo = null; + EndSnippetProto.prototype.dataErrorServerInfo = null; + EndSnippetProto.prototype.planetaryDatabase = $util.emptyArray; + EndSnippetProto.prototype.logServer = null; + EndSnippetProto.prototype.autopiaOptions = null; + EndSnippetProto.prototype.searchConfig = null; + EndSnippetProto.prototype.searchInfo = null; + EndSnippetProto.prototype.elevationServiceBaseUrl = "http://maps.google.com/maps/api/elevation/"; + EndSnippetProto.prototype.elevationProfileQueryDelay = 500; + EndSnippetProto.prototype.proUpgradeUrl = null; + EndSnippetProto.prototype.earthCommunityUrl = null; + EndSnippetProto.prototype.googleMapsUrl = null; + EndSnippetProto.prototype.sharingUrl = null; + EndSnippetProto.prototype.privacyPolicyUrl = null; + EndSnippetProto.prototype.doGplusUserCheck = false; + EndSnippetProto.prototype.rocktreeDataProto = null; + EndSnippetProto.prototype.filmstripConfig = $util.emptyArray; + EndSnippetProto.prototype.showSigninButton = false; + EndSnippetProto.prototype.proMeasureUpsellUrl = null; + EndSnippetProto.prototype.proPrintUpsellUrl = null; + EndSnippetProto.prototype.starDataProto = null; + EndSnippetProto.prototype.feedbackUrl = null; + EndSnippetProto.prototype.oauth2LoginUrl = null; var $types = { - 0 : "keyhole.dbroot.EncryptedDbRootProto.EncryptionType" + 0 : "keyhole.dbroot.PlanetModelProto", + 1 : "keyhole.dbroot.StringIdOrValueProto", + 3 : "keyhole.dbroot.MfeDomainFeaturesProto", + 6 : "keyhole.dbroot.StringIdOrValueProto", + 9 : "keyhole.dbroot.StringIdOrValueProto", + 10 : "keyhole.dbroot.StringIdOrValueProto", + 12 : "keyhole.dbroot.StringIdOrValueProto", + 14 : "keyhole.dbroot.StringIdOrValueProto", + 15 : "keyhole.dbroot.StringIdOrValueProto", + 16 : "keyhole.dbroot.StringIdOrValueProto", + 17 : "keyhole.dbroot.StringIdOrValueProto", + 18 : "keyhole.dbroot.StringIdOrValueProto", + 19 : "keyhole.dbroot.StringIdOrValueProto", + 20 : "keyhole.dbroot.StringIdOrValueProto", + 21 : "keyhole.dbroot.StringIdOrValueProto", + 22 : "keyhole.dbroot.StringIdOrValueProto", + 23 : "keyhole.dbroot.StringIdOrValueProto", + 24 : "keyhole.dbroot.StringIdOrValueProto", + 25 : "keyhole.dbroot.StringIdOrValueProto", + 26 : "keyhole.dbroot.StringIdOrValueProto", + 27 : "keyhole.dbroot.StringIdOrValueProto", + 28 : "keyhole.dbroot.StringIdOrValueProto", + 29 : "keyhole.dbroot.StringIdOrValueProto", + 30 : "keyhole.dbroot.StringIdOrValueProto", + 33 : "keyhole.dbroot.StringIdOrValueProto", + 35 : "keyhole.dbroot.StringIdOrValueProto", + 36 : "keyhole.dbroot.ClientOptionsProto", + 37 : "keyhole.dbroot.FetchingOptionsProto", + 38 : "keyhole.dbroot.TimeMachineOptionsProto", + 39 : "keyhole.dbroot.CSIOptionsProto", + 40 : "keyhole.dbroot.SearchTabProto", + 41 : "keyhole.dbroot.CobrandProto", + 42 : "keyhole.dbroot.DatabaseDescriptionProto", + 43 : "keyhole.dbroot.ConfigScriptProto", + 44 : "keyhole.dbroot.StringIdOrValueProto", + 45 : "keyhole.dbroot.SwoopParamsProto", + 46 : "keyhole.dbroot.PostingServerProto", + 47 : "keyhole.dbroot.PostingServerProto", + 48 : "keyhole.dbroot.PlanetaryDatabaseProto", + 49 : "keyhole.dbroot.LogServerProto", + 50 : "keyhole.dbroot.AutopiaOptionsProto", + 51 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto", + 52 : "keyhole.dbroot.EndSnippetProto.SearchInfoProto", + 55 : "keyhole.dbroot.StringIdOrValueProto", + 56 : "keyhole.dbroot.StringIdOrValueProto", + 57 : "keyhole.dbroot.StringIdOrValueProto", + 58 : "keyhole.dbroot.StringIdOrValueProto", + 59 : "keyhole.dbroot.StringIdOrValueProto", + 61 : "keyhole.dbroot.EndSnippetProto.RockTreeDataProto", + 62 : "keyhole.dbroot.EndSnippetProto.FilmstripConfigProto", + 64 : "keyhole.dbroot.StringIdOrValueProto", + 65 : "keyhole.dbroot.StringIdOrValueProto", + 66 : "keyhole.dbroot.EndSnippetProto.StarDataProto", + 67 : "keyhole.dbroot.StringIdOrValueProto", + 68 : "keyhole.dbroot.StringIdOrValueProto" }; $lazyTypes.push($types); - EncryptedDbRootProto.decode = function decode(reader, length) { + EndSnippetProto.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, - message = new $root.keyhole.dbroot.EncryptedDbRootProto(); + message = new $root.keyhole.dbroot.EndSnippetProto(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { case 1: - message.encryptionType = reader.uint32(); + message.model = $types[0].decode(reader, reader.uint32()); break; case 2: - message.encryptionData = reader.bytes(); + message.authServerUrl = $types[1].decode(reader, reader.uint32()); break; case 3: - message.dbrootData = reader.bytes(); + message.disableAuthentication = reader.bool(); + break; + case 4: + if (!(message.mfeDomains && message.mfeDomains.length)) + message.mfeDomains = []; + message.mfeDomains.push($types[3].decode(reader, reader.uint32())); + break; + case 5: + message.mfeLangParam = reader.string(); + break; + case 6: + message.adsUrlPatterns = reader.string(); + break; + case 7: + message.reverseGeocoderUrl = $types[6].decode(reader, reader.uint32()); + break; + case 8: + message.reverseGeocoderProtocolVersion = reader.int32(); + break; + case 9: + message.skyDatabaseIsAvailable = reader.bool(); + break; + case 10: + message.skyDatabaseUrl = $types[9].decode(reader, reader.uint32()); + break; + case 11: + message.defaultWebPageIntlUrl = $types[10].decode(reader, reader.uint32()); + break; + case 12: + message.numStartUpTips = reader.int32(); + break; + case 13: + message.startUpTipsUrl = $types[12].decode(reader, reader.uint32()); + break; + case 51: + message.numProStartUpTips = reader.int32(); + break; + case 52: + message.proStartUpTipsUrl = $types[14].decode(reader, reader.uint32()); + break; + case 64: + message.startupTipsIntlUrl = $types[15].decode(reader, reader.uint32()); + break; + case 14: + message.userGuideIntlUrl = $types[16].decode(reader, reader.uint32()); + break; + case 15: + message.supportCenterIntlUrl = $types[17].decode(reader, reader.uint32()); + break; + case 16: + message.businessListingIntlUrl = $types[18].decode(reader, reader.uint32()); + break; + case 17: + message.supportAnswerIntlUrl = $types[19].decode(reader, reader.uint32()); + break; + case 18: + message.supportTopicIntlUrl = $types[20].decode(reader, reader.uint32()); + break; + case 19: + message.supportRequestIntlUrl = $types[21].decode(reader, reader.uint32()); + break; + case 20: + message.earthIntlUrl = $types[22].decode(reader, reader.uint32()); + break; + case 21: + message.addContentUrl = $types[23].decode(reader, reader.uint32()); + break; + case 22: + message.sketchupNotInstalledUrl = $types[24].decode(reader, reader.uint32()); + break; + case 23: + message.sketchupErrorUrl = $types[25].decode(reader, reader.uint32()); + break; + case 24: + message.freeLicenseUrl = $types[26].decode(reader, reader.uint32()); + break; + case 25: + message.proLicenseUrl = $types[27].decode(reader, reader.uint32()); + break; + case 48: + message.tutorialUrl = $types[28].decode(reader, reader.uint32()); + break; + case 49: + message.keyboardShortcutsUrl = $types[29].decode(reader, reader.uint32()); + break; + case 50: + message.releaseNotesUrl = $types[30].decode(reader, reader.uint32()); + break; + case 26: + message.hideUserData = reader.bool(); + break; + case 27: + message.useGeLogo = reader.bool(); + break; + case 28: + message.dioramaDescriptionUrlBase = $types[33].decode(reader, reader.uint32()); + break; + case 29: + message.dioramaDefaultColor = reader.uint32(); + break; + case 53: + message.dioramaBlacklistUrl = $types[35].decode(reader, reader.uint32()); + break; + case 30: + message.clientOptions = $types[36].decode(reader, reader.uint32()); + break; + case 31: + message.fetchingOptions = $types[37].decode(reader, reader.uint32()); + break; + case 32: + message.timeMachineOptions = $types[38].decode(reader, reader.uint32()); + break; + case 33: + message.csiOptions = $types[39].decode(reader, reader.uint32()); + break; + case 34: + if (!(message.searchTab && message.searchTab.length)) + message.searchTab = []; + message.searchTab.push($types[40].decode(reader, reader.uint32())); + break; + case 35: + if (!(message.cobrandInfo && message.cobrandInfo.length)) + message.cobrandInfo = []; + message.cobrandInfo.push($types[41].decode(reader, reader.uint32())); + break; + case 36: + if (!(message.validDatabase && message.validDatabase.length)) + message.validDatabase = []; + message.validDatabase.push($types[42].decode(reader, reader.uint32())); + break; + case 37: + if (!(message.configScript && message.configScript.length)) + message.configScript = []; + message.configScript.push($types[43].decode(reader, reader.uint32())); + break; + case 38: + message.deauthServerUrl = $types[44].decode(reader, reader.uint32()); + break; + case 39: + message.swoopParameters = $types[45].decode(reader, reader.uint32()); + break; + case 40: + message.bbsServerInfo = $types[46].decode(reader, reader.uint32()); + break; + case 41: + message.dataErrorServerInfo = $types[47].decode(reader, reader.uint32()); + break; + case 42: + if (!(message.planetaryDatabase && message.planetaryDatabase.length)) + message.planetaryDatabase = []; + message.planetaryDatabase.push($types[48].decode(reader, reader.uint32())); + break; + case 43: + message.logServer = $types[49].decode(reader, reader.uint32()); + break; + case 44: + message.autopiaOptions = $types[50].decode(reader, reader.uint32()); + break; + case 54: + message.searchConfig = $types[51].decode(reader, reader.uint32()); + break; + case 45: + message.searchInfo = $types[52].decode(reader, reader.uint32()); + break; + case 46: + message.elevationServiceBaseUrl = reader.string(); + break; + case 47: + message.elevationProfileQueryDelay = reader.int32(); + break; + case 55: + message.proUpgradeUrl = $types[55].decode(reader, reader.uint32()); + break; + case 56: + message.earthCommunityUrl = $types[56].decode(reader, reader.uint32()); + break; + case 57: + message.googleMapsUrl = $types[57].decode(reader, reader.uint32()); + break; + case 58: + message.sharingUrl = $types[58].decode(reader, reader.uint32()); + break; + case 59: + message.privacyPolicyUrl = $types[59].decode(reader, reader.uint32()); + break; + case 60: + message.doGplusUserCheck = reader.bool(); + break; + case 61: + message.rocktreeDataProto = $types[61].decode(reader, reader.uint32()); + break; + case 62: + if (!(message.filmstripConfig && message.filmstripConfig.length)) + message.filmstripConfig = []; + message.filmstripConfig.push($types[62].decode(reader, reader.uint32())); + break; + case 63: + message.showSigninButton = reader.bool(); + break; + case 65: + message.proMeasureUpsellUrl = $types[64].decode(reader, reader.uint32()); + break; + case 66: + message.proPrintUpsellUrl = $types[65].decode(reader, reader.uint32()); + break; + case 67: + message.starDataProto = $types[66].decode(reader, reader.uint32()); + break; + case 68: + message.feedbackUrl = $types[67].decode(reader, reader.uint32()); + break; + case 69: + message.oauth2LoginUrl = $types[68].decode(reader, reader.uint32()); break; default: reader.skipType(tag & 7); @@ -57282,9392 +57558,10937 @@ define('ThirdParty/google-earth-dbroot-parser',[ return message; }; - EncryptedDbRootProto.verify = function verify(message) { + EndSnippetProto.verify = function verify(message) { if (typeof message !== "object" || message === null) return "object expected"; - if (message.encryptionType !== undefined) - switch (message.encryptionType) { - default: - return "encryptionType: enum value expected"; - case 0: - break; + if (message.model !== undefined && message.model !== null) { + var error = $types[0].verify(message.model); + if (error) + return "model." + error; + } + if (message.authServerUrl !== undefined && message.authServerUrl !== null) { + var error = $types[1].verify(message.authServerUrl); + if (error) + return "authServerUrl." + error; + } + if (message.disableAuthentication !== undefined) + if (typeof message.disableAuthentication !== "boolean") + return "disableAuthentication: boolean expected"; + if (message.mfeDomains !== undefined) { + if (!Array.isArray(message.mfeDomains)) + return "mfeDomains: array expected"; + for (var i = 0; i < message.mfeDomains.length; ++i) { + var error = $types[3].verify(message.mfeDomains[i]); + if (error) + return "mfeDomains." + error; } - if (message.encryptionData !== undefined) - if (!(message.encryptionData && typeof message.encryptionData.length === "number" || $util.isString(message.encryptionData))) - return "encryptionData: buffer expected"; - if (message.dbrootData !== undefined) - if (!(message.dbrootData && typeof message.dbrootData.length === "number" || $util.isString(message.dbrootData))) - return "dbrootData: buffer expected"; + } + if (message.mfeLangParam !== undefined) + if (!$util.isString(message.mfeLangParam)) + return "mfeLangParam: string expected"; + if (message.adsUrlPatterns !== undefined) + if (!$util.isString(message.adsUrlPatterns)) + return "adsUrlPatterns: string expected"; + if (message.reverseGeocoderUrl !== undefined && message.reverseGeocoderUrl !== null) { + var error = $types[6].verify(message.reverseGeocoderUrl); + if (error) + return "reverseGeocoderUrl." + error; + } + if (message.reverseGeocoderProtocolVersion !== undefined) + if (!$util.isInteger(message.reverseGeocoderProtocolVersion)) + return "reverseGeocoderProtocolVersion: integer expected"; + if (message.skyDatabaseIsAvailable !== undefined) + if (typeof message.skyDatabaseIsAvailable !== "boolean") + return "skyDatabaseIsAvailable: boolean expected"; + if (message.skyDatabaseUrl !== undefined && message.skyDatabaseUrl !== null) { + var error = $types[9].verify(message.skyDatabaseUrl); + if (error) + return "skyDatabaseUrl." + error; + } + if (message.defaultWebPageIntlUrl !== undefined && message.defaultWebPageIntlUrl !== null) { + var error = $types[10].verify(message.defaultWebPageIntlUrl); + if (error) + return "defaultWebPageIntlUrl." + error; + } + if (message.numStartUpTips !== undefined) + if (!$util.isInteger(message.numStartUpTips)) + return "numStartUpTips: integer expected"; + if (message.startUpTipsUrl !== undefined && message.startUpTipsUrl !== null) { + var error = $types[12].verify(message.startUpTipsUrl); + if (error) + return "startUpTipsUrl." + error; + } + if (message.numProStartUpTips !== undefined) + if (!$util.isInteger(message.numProStartUpTips)) + return "numProStartUpTips: integer expected"; + if (message.proStartUpTipsUrl !== undefined && message.proStartUpTipsUrl !== null) { + var error = $types[14].verify(message.proStartUpTipsUrl); + if (error) + return "proStartUpTipsUrl." + error; + } + if (message.startupTipsIntlUrl !== undefined && message.startupTipsIntlUrl !== null) { + var error = $types[15].verify(message.startupTipsIntlUrl); + if (error) + return "startupTipsIntlUrl." + error; + } + if (message.userGuideIntlUrl !== undefined && message.userGuideIntlUrl !== null) { + var error = $types[16].verify(message.userGuideIntlUrl); + if (error) + return "userGuideIntlUrl." + error; + } + if (message.supportCenterIntlUrl !== undefined && message.supportCenterIntlUrl !== null) { + var error = $types[17].verify(message.supportCenterIntlUrl); + if (error) + return "supportCenterIntlUrl." + error; + } + if (message.businessListingIntlUrl !== undefined && message.businessListingIntlUrl !== null) { + var error = $types[18].verify(message.businessListingIntlUrl); + if (error) + return "businessListingIntlUrl." + error; + } + if (message.supportAnswerIntlUrl !== undefined && message.supportAnswerIntlUrl !== null) { + var error = $types[19].verify(message.supportAnswerIntlUrl); + if (error) + return "supportAnswerIntlUrl." + error; + } + if (message.supportTopicIntlUrl !== undefined && message.supportTopicIntlUrl !== null) { + var error = $types[20].verify(message.supportTopicIntlUrl); + if (error) + return "supportTopicIntlUrl." + error; + } + if (message.supportRequestIntlUrl !== undefined && message.supportRequestIntlUrl !== null) { + var error = $types[21].verify(message.supportRequestIntlUrl); + if (error) + return "supportRequestIntlUrl." + error; + } + if (message.earthIntlUrl !== undefined && message.earthIntlUrl !== null) { + var error = $types[22].verify(message.earthIntlUrl); + if (error) + return "earthIntlUrl." + error; + } + if (message.addContentUrl !== undefined && message.addContentUrl !== null) { + var error = $types[23].verify(message.addContentUrl); + if (error) + return "addContentUrl." + error; + } + if (message.sketchupNotInstalledUrl !== undefined && message.sketchupNotInstalledUrl !== null) { + var error = $types[24].verify(message.sketchupNotInstalledUrl); + if (error) + return "sketchupNotInstalledUrl." + error; + } + if (message.sketchupErrorUrl !== undefined && message.sketchupErrorUrl !== null) { + var error = $types[25].verify(message.sketchupErrorUrl); + if (error) + return "sketchupErrorUrl." + error; + } + if (message.freeLicenseUrl !== undefined && message.freeLicenseUrl !== null) { + var error = $types[26].verify(message.freeLicenseUrl); + if (error) + return "freeLicenseUrl." + error; + } + if (message.proLicenseUrl !== undefined && message.proLicenseUrl !== null) { + var error = $types[27].verify(message.proLicenseUrl); + if (error) + return "proLicenseUrl." + error; + } + if (message.tutorialUrl !== undefined && message.tutorialUrl !== null) { + var error = $types[28].verify(message.tutorialUrl); + if (error) + return "tutorialUrl." + error; + } + if (message.keyboardShortcutsUrl !== undefined && message.keyboardShortcutsUrl !== null) { + var error = $types[29].verify(message.keyboardShortcutsUrl); + if (error) + return "keyboardShortcutsUrl." + error; + } + if (message.releaseNotesUrl !== undefined && message.releaseNotesUrl !== null) { + var error = $types[30].verify(message.releaseNotesUrl); + if (error) + return "releaseNotesUrl." + error; + } + if (message.hideUserData !== undefined) + if (typeof message.hideUserData !== "boolean") + return "hideUserData: boolean expected"; + if (message.useGeLogo !== undefined) + if (typeof message.useGeLogo !== "boolean") + return "useGeLogo: boolean expected"; + if (message.dioramaDescriptionUrlBase !== undefined && message.dioramaDescriptionUrlBase !== null) { + var error = $types[33].verify(message.dioramaDescriptionUrlBase); + if (error) + return "dioramaDescriptionUrlBase." + error; + } + if (message.dioramaDefaultColor !== undefined) + if (!$util.isInteger(message.dioramaDefaultColor)) + return "dioramaDefaultColor: integer expected"; + if (message.dioramaBlacklistUrl !== undefined && message.dioramaBlacklistUrl !== null) { + var error = $types[35].verify(message.dioramaBlacklistUrl); + if (error) + return "dioramaBlacklistUrl." + error; + } + if (message.clientOptions !== undefined && message.clientOptions !== null) { + var error = $types[36].verify(message.clientOptions); + if (error) + return "clientOptions." + error; + } + if (message.fetchingOptions !== undefined && message.fetchingOptions !== null) { + var error = $types[37].verify(message.fetchingOptions); + if (error) + return "fetchingOptions." + error; + } + if (message.timeMachineOptions !== undefined && message.timeMachineOptions !== null) { + var error = $types[38].verify(message.timeMachineOptions); + if (error) + return "timeMachineOptions." + error; + } + if (message.csiOptions !== undefined && message.csiOptions !== null) { + var error = $types[39].verify(message.csiOptions); + if (error) + return "csiOptions." + error; + } + if (message.searchTab !== undefined) { + if (!Array.isArray(message.searchTab)) + return "searchTab: array expected"; + for (var i = 0; i < message.searchTab.length; ++i) { + var error = $types[40].verify(message.searchTab[i]); + if (error) + return "searchTab." + error; + } + } + if (message.cobrandInfo !== undefined) { + if (!Array.isArray(message.cobrandInfo)) + return "cobrandInfo: array expected"; + for (var i = 0; i < message.cobrandInfo.length; ++i) { + var error = $types[41].verify(message.cobrandInfo[i]); + if (error) + return "cobrandInfo." + error; + } + } + if (message.validDatabase !== undefined) { + if (!Array.isArray(message.validDatabase)) + return "validDatabase: array expected"; + for (var i = 0; i < message.validDatabase.length; ++i) { + var error = $types[42].verify(message.validDatabase[i]); + if (error) + return "validDatabase." + error; + } + } + if (message.configScript !== undefined) { + if (!Array.isArray(message.configScript)) + return "configScript: array expected"; + for (var i = 0; i < message.configScript.length; ++i) { + var error = $types[43].verify(message.configScript[i]); + if (error) + return "configScript." + error; + } + } + if (message.deauthServerUrl !== undefined && message.deauthServerUrl !== null) { + var error = $types[44].verify(message.deauthServerUrl); + if (error) + return "deauthServerUrl." + error; + } + if (message.swoopParameters !== undefined && message.swoopParameters !== null) { + var error = $types[45].verify(message.swoopParameters); + if (error) + return "swoopParameters." + error; + } + if (message.bbsServerInfo !== undefined && message.bbsServerInfo !== null) { + var error = $types[46].verify(message.bbsServerInfo); + if (error) + return "bbsServerInfo." + error; + } + if (message.dataErrorServerInfo !== undefined && message.dataErrorServerInfo !== null) { + var error = $types[47].verify(message.dataErrorServerInfo); + if (error) + return "dataErrorServerInfo." + error; + } + if (message.planetaryDatabase !== undefined) { + if (!Array.isArray(message.planetaryDatabase)) + return "planetaryDatabase: array expected"; + for (var i = 0; i < message.planetaryDatabase.length; ++i) { + var error = $types[48].verify(message.planetaryDatabase[i]); + if (error) + return "planetaryDatabase." + error; + } + } + if (message.logServer !== undefined && message.logServer !== null) { + var error = $types[49].verify(message.logServer); + if (error) + return "logServer." + error; + } + if (message.autopiaOptions !== undefined && message.autopiaOptions !== null) { + var error = $types[50].verify(message.autopiaOptions); + if (error) + return "autopiaOptions." + error; + } + if (message.searchConfig !== undefined && message.searchConfig !== null) { + var error = $types[51].verify(message.searchConfig); + if (error) + return "searchConfig." + error; + } + if (message.searchInfo !== undefined && message.searchInfo !== null) { + var error = $types[52].verify(message.searchInfo); + if (error) + return "searchInfo." + error; + } + if (message.elevationServiceBaseUrl !== undefined) + if (!$util.isString(message.elevationServiceBaseUrl)) + return "elevationServiceBaseUrl: string expected"; + if (message.elevationProfileQueryDelay !== undefined) + if (!$util.isInteger(message.elevationProfileQueryDelay)) + return "elevationProfileQueryDelay: integer expected"; + if (message.proUpgradeUrl !== undefined && message.proUpgradeUrl !== null) { + var error = $types[55].verify(message.proUpgradeUrl); + if (error) + return "proUpgradeUrl." + error; + } + if (message.earthCommunityUrl !== undefined && message.earthCommunityUrl !== null) { + var error = $types[56].verify(message.earthCommunityUrl); + if (error) + return "earthCommunityUrl." + error; + } + if (message.googleMapsUrl !== undefined && message.googleMapsUrl !== null) { + var error = $types[57].verify(message.googleMapsUrl); + if (error) + return "googleMapsUrl." + error; + } + if (message.sharingUrl !== undefined && message.sharingUrl !== null) { + var error = $types[58].verify(message.sharingUrl); + if (error) + return "sharingUrl." + error; + } + if (message.privacyPolicyUrl !== undefined && message.privacyPolicyUrl !== null) { + var error = $types[59].verify(message.privacyPolicyUrl); + if (error) + return "privacyPolicyUrl." + error; + } + if (message.doGplusUserCheck !== undefined) + if (typeof message.doGplusUserCheck !== "boolean") + return "doGplusUserCheck: boolean expected"; + if (message.rocktreeDataProto !== undefined && message.rocktreeDataProto !== null) { + var error = $types[61].verify(message.rocktreeDataProto); + if (error) + return "rocktreeDataProto." + error; + } + if (message.filmstripConfig !== undefined) { + if (!Array.isArray(message.filmstripConfig)) + return "filmstripConfig: array expected"; + for (var i = 0; i < message.filmstripConfig.length; ++i) { + var error = $types[62].verify(message.filmstripConfig[i]); + if (error) + return "filmstripConfig." + error; + } + } + if (message.showSigninButton !== undefined) + if (typeof message.showSigninButton !== "boolean") + return "showSigninButton: boolean expected"; + if (message.proMeasureUpsellUrl !== undefined && message.proMeasureUpsellUrl !== null) { + var error = $types[64].verify(message.proMeasureUpsellUrl); + if (error) + return "proMeasureUpsellUrl." + error; + } + if (message.proPrintUpsellUrl !== undefined && message.proPrintUpsellUrl !== null) { + var error = $types[65].verify(message.proPrintUpsellUrl); + if (error) + return "proPrintUpsellUrl." + error; + } + if (message.starDataProto !== undefined && message.starDataProto !== null) { + var error = $types[66].verify(message.starDataProto); + if (error) + return "starDataProto." + error; + } + if (message.feedbackUrl !== undefined && message.feedbackUrl !== null) { + var error = $types[67].verify(message.feedbackUrl); + if (error) + return "feedbackUrl." + error; + } + if (message.oauth2LoginUrl !== undefined && message.oauth2LoginUrl !== null) { + var error = $types[68].verify(message.oauth2LoginUrl); + if (error) + return "oauth2LoginUrl." + error; + } return null; }; - EncryptedDbRootProto.fromObject = function fromObject(object) { - if (object instanceof $root.keyhole.dbroot.EncryptedDbRootProto) + EndSnippetProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto) return object; - var message = new $root.keyhole.dbroot.EncryptedDbRootProto(); - switch (object.encryptionType) { - case "ENCRYPTION_XOR": - case 0: - message.encryptionType = 0; - break; + var message = new $root.keyhole.dbroot.EndSnippetProto(); + if (object.model !== undefined && object.model !== null) { + if (typeof object.model !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.model: object expected"); + message.model = $types[0].fromObject(object.model); } - if (object.encryptionData !== undefined && object.encryptionData !== null) - if (typeof object.encryptionData === "string") - $util.base64.decode(object.encryptionData, message.encryptionData = $util.newBuffer($util.base64.length(object.encryptionData)), 0); - else if (object.encryptionData.length) - message.encryptionData = object.encryptionData; - if (object.dbrootData !== undefined && object.dbrootData !== null) - if (typeof object.dbrootData === "string") - $util.base64.decode(object.dbrootData, message.dbrootData = $util.newBuffer($util.base64.length(object.dbrootData)), 0); - else if (object.dbrootData.length) - message.dbrootData = object.dbrootData; - return message; - }; - - EncryptedDbRootProto.from = EncryptedDbRootProto.fromObject; - - EncryptedDbRootProto.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - object.encryptionType = options.enums === String ? "ENCRYPTION_XOR" : 0; - object.encryptionData = options.bytes === String ? "" : []; - object.dbrootData = options.bytes === String ? "" : []; + if (object.authServerUrl !== undefined && object.authServerUrl !== null) { + if (typeof object.authServerUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.authServerUrl: object expected"); + message.authServerUrl = $types[1].fromObject(object.authServerUrl); } - if (message.encryptionType !== undefined && message.encryptionType !== null && message.hasOwnProperty("encryptionType")) - object.encryptionType = options.enums === String ? $types[0][message.encryptionType] : message.encryptionType; - if (message.encryptionData !== undefined && message.encryptionData !== null && message.hasOwnProperty("encryptionData")) - object.encryptionData = options.bytes === String ? $util.base64.encode(message.encryptionData, 0, message.encryptionData.length) : options.bytes === Array ? Array.prototype.slice.call(message.encryptionData) : message.encryptionData; - if (message.dbrootData !== undefined && message.dbrootData !== null && message.hasOwnProperty("dbrootData")) - object.dbrootData = options.bytes === String ? $util.base64.encode(message.dbrootData, 0, message.dbrootData.length) : options.bytes === Array ? Array.prototype.slice.call(message.dbrootData) : message.dbrootData; - return object; - }; - - EncryptedDbRootProto.prototype.toObject = function toObject(options) { - return this.constructor.toObject(this, options); - }; - - EncryptedDbRootProto.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - EncryptedDbRootProto.EncryptionType = (function() { - var valuesById = {}, values = Object.create(valuesById); - values["ENCRYPTION_XOR"] = 0; - return values; - })(); - - return EncryptedDbRootProto; - })(); - - return dbroot; - })(); - - return keyhole; - })(); - - $util.lazyResolve($root, $lazyTypes); - - // End generated code - - return $root.keyhole.dbroot; -}); - -/*global define*/ -define('Core/isBitSet',[], function() { - 'use strict'; - - /** - * @private - */ - function isBitSet(bits, mask) { - return ((bits & mask) !== 0); - } - - return isBitSet; -}); - -/*global define*/ -define('Core/GoogleEarthEnterpriseTileInformation',[ - './defined', - './isBitSet' -], function( - defined, - isBitSet) { - 'use strict'; - - // Bitmask for checking tile properties - var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; - var anyChildBitmask = 0x0F; - var cacheFlagBitmask = 0x10; // True if there is a child subtree - var imageBitmask = 0x40; - var terrainBitmask = 0x80; - - /** - * Contains information about each tile from a Google Earth Enterprise server - * - * @param {Number} bits Bitmask that contains the type of data and available children for each tile. - * @param {Number} cnodeVersion Version of the request for subtree metadata. - * @param {Number} imageryVersion Version of the request for imagery tile. - * @param {Number} terrainVersion Version of the request for terrain tile. - * @param {Number} imageryProvider Id of imagery provider. - * @param {Number} terrainProvider Id of terrain provider. - * - * @private - */ - function GoogleEarthEnterpriseTileInformation(bits, cnodeVersion, imageryVersion, terrainVersion, imageryProvider, terrainProvider) { - this._bits = bits; - this.cnodeVersion = cnodeVersion; - this.imageryVersion = imageryVersion; - this.terrainVersion = terrainVersion; - this.imageryProvider = imageryProvider; - this.terrainProvider = terrainProvider; - this.ancestorHasTerrain = false; // Set it later once we find its parent - this.terrainState = undefined; - } - - /** - * Creates GoogleEarthEnterpriseTileInformation from an object - * - * @param {Object} info Object to be cloned - * @param {GoogleEarthEnterpriseTileInformation} [result] The object onto which to store the result. - * @returns {GoogleEarthEnterpriseTileInformation} The modified result parameter or a new GoogleEarthEnterpriseTileInformation instance if none was provided. - */ - GoogleEarthEnterpriseTileInformation.clone = function(info, result) { - if (!defined(result)) { - result = new GoogleEarthEnterpriseTileInformation(info._bits, info.cnodeVersion, info.imageryVersion, info.terrainVersion, - info.imageryProvider, info.terrainProvider); - } else { - result._bits = info._bits; - result.cnodeVersion = info.cnodeVersion; - result.imageryVersion = info.imageryVersion; - result.terrainVersion = info.terrainVersion; - result.imageryProvider = info.imageryProvider; - result.terrainProvider = info.terrainProvider; - } - result.ancestorHasTerrain = info.ancestorHasTerrain; - result.terrainState = info.terrainState; + if (object.disableAuthentication !== undefined && object.disableAuthentication !== null) + message.disableAuthentication = Boolean(object.disableAuthentication); + if (object.mfeDomains) { + if (!Array.isArray(object.mfeDomains)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.mfeDomains: array expected"); + message.mfeDomains = []; + for (var i = 0; i < object.mfeDomains.length; ++i) { + if (typeof object.mfeDomains[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.mfeDomains: object expected"); + message.mfeDomains[i] = $types[3].fromObject(object.mfeDomains[i]); + } + } + if (object.mfeLangParam !== undefined && object.mfeLangParam !== null) + message.mfeLangParam = String(object.mfeLangParam); + if (object.adsUrlPatterns !== undefined && object.adsUrlPatterns !== null) + message.adsUrlPatterns = String(object.adsUrlPatterns); + if (object.reverseGeocoderUrl !== undefined && object.reverseGeocoderUrl !== null) { + if (typeof object.reverseGeocoderUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.reverseGeocoderUrl: object expected"); + message.reverseGeocoderUrl = $types[6].fromObject(object.reverseGeocoderUrl); + } + if (object.reverseGeocoderProtocolVersion !== undefined && object.reverseGeocoderProtocolVersion !== null) + message.reverseGeocoderProtocolVersion = object.reverseGeocoderProtocolVersion | 0; + if (object.skyDatabaseIsAvailable !== undefined && object.skyDatabaseIsAvailable !== null) + message.skyDatabaseIsAvailable = Boolean(object.skyDatabaseIsAvailable); + if (object.skyDatabaseUrl !== undefined && object.skyDatabaseUrl !== null) { + if (typeof object.skyDatabaseUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.skyDatabaseUrl: object expected"); + message.skyDatabaseUrl = $types[9].fromObject(object.skyDatabaseUrl); + } + if (object.defaultWebPageIntlUrl !== undefined && object.defaultWebPageIntlUrl !== null) { + if (typeof object.defaultWebPageIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.defaultWebPageIntlUrl: object expected"); + message.defaultWebPageIntlUrl = $types[10].fromObject(object.defaultWebPageIntlUrl); + } + if (object.numStartUpTips !== undefined && object.numStartUpTips !== null) + message.numStartUpTips = object.numStartUpTips | 0; + if (object.startUpTipsUrl !== undefined && object.startUpTipsUrl !== null) { + if (typeof object.startUpTipsUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.startUpTipsUrl: object expected"); + message.startUpTipsUrl = $types[12].fromObject(object.startUpTipsUrl); + } + if (object.numProStartUpTips !== undefined && object.numProStartUpTips !== null) + message.numProStartUpTips = object.numProStartUpTips | 0; + if (object.proStartUpTipsUrl !== undefined && object.proStartUpTipsUrl !== null) { + if (typeof object.proStartUpTipsUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.proStartUpTipsUrl: object expected"); + message.proStartUpTipsUrl = $types[14].fromObject(object.proStartUpTipsUrl); + } + if (object.startupTipsIntlUrl !== undefined && object.startupTipsIntlUrl !== null) { + if (typeof object.startupTipsIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.startupTipsIntlUrl: object expected"); + message.startupTipsIntlUrl = $types[15].fromObject(object.startupTipsIntlUrl); + } + if (object.userGuideIntlUrl !== undefined && object.userGuideIntlUrl !== null) { + if (typeof object.userGuideIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.userGuideIntlUrl: object expected"); + message.userGuideIntlUrl = $types[16].fromObject(object.userGuideIntlUrl); + } + if (object.supportCenterIntlUrl !== undefined && object.supportCenterIntlUrl !== null) { + if (typeof object.supportCenterIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.supportCenterIntlUrl: object expected"); + message.supportCenterIntlUrl = $types[17].fromObject(object.supportCenterIntlUrl); + } + if (object.businessListingIntlUrl !== undefined && object.businessListingIntlUrl !== null) { + if (typeof object.businessListingIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.businessListingIntlUrl: object expected"); + message.businessListingIntlUrl = $types[18].fromObject(object.businessListingIntlUrl); + } + if (object.supportAnswerIntlUrl !== undefined && object.supportAnswerIntlUrl !== null) { + if (typeof object.supportAnswerIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.supportAnswerIntlUrl: object expected"); + message.supportAnswerIntlUrl = $types[19].fromObject(object.supportAnswerIntlUrl); + } + if (object.supportTopicIntlUrl !== undefined && object.supportTopicIntlUrl !== null) { + if (typeof object.supportTopicIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.supportTopicIntlUrl: object expected"); + message.supportTopicIntlUrl = $types[20].fromObject(object.supportTopicIntlUrl); + } + if (object.supportRequestIntlUrl !== undefined && object.supportRequestIntlUrl !== null) { + if (typeof object.supportRequestIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.supportRequestIntlUrl: object expected"); + message.supportRequestIntlUrl = $types[21].fromObject(object.supportRequestIntlUrl); + } + if (object.earthIntlUrl !== undefined && object.earthIntlUrl !== null) { + if (typeof object.earthIntlUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.earthIntlUrl: object expected"); + message.earthIntlUrl = $types[22].fromObject(object.earthIntlUrl); + } + if (object.addContentUrl !== undefined && object.addContentUrl !== null) { + if (typeof object.addContentUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.addContentUrl: object expected"); + message.addContentUrl = $types[23].fromObject(object.addContentUrl); + } + if (object.sketchupNotInstalledUrl !== undefined && object.sketchupNotInstalledUrl !== null) { + if (typeof object.sketchupNotInstalledUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.sketchupNotInstalledUrl: object expected"); + message.sketchupNotInstalledUrl = $types[24].fromObject(object.sketchupNotInstalledUrl); + } + if (object.sketchupErrorUrl !== undefined && object.sketchupErrorUrl !== null) { + if (typeof object.sketchupErrorUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.sketchupErrorUrl: object expected"); + message.sketchupErrorUrl = $types[25].fromObject(object.sketchupErrorUrl); + } + if (object.freeLicenseUrl !== undefined && object.freeLicenseUrl !== null) { + if (typeof object.freeLicenseUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.freeLicenseUrl: object expected"); + message.freeLicenseUrl = $types[26].fromObject(object.freeLicenseUrl); + } + if (object.proLicenseUrl !== undefined && object.proLicenseUrl !== null) { + if (typeof object.proLicenseUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.proLicenseUrl: object expected"); + message.proLicenseUrl = $types[27].fromObject(object.proLicenseUrl); + } + if (object.tutorialUrl !== undefined && object.tutorialUrl !== null) { + if (typeof object.tutorialUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.tutorialUrl: object expected"); + message.tutorialUrl = $types[28].fromObject(object.tutorialUrl); + } + if (object.keyboardShortcutsUrl !== undefined && object.keyboardShortcutsUrl !== null) { + if (typeof object.keyboardShortcutsUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.keyboardShortcutsUrl: object expected"); + message.keyboardShortcutsUrl = $types[29].fromObject(object.keyboardShortcutsUrl); + } + if (object.releaseNotesUrl !== undefined && object.releaseNotesUrl !== null) { + if (typeof object.releaseNotesUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.releaseNotesUrl: object expected"); + message.releaseNotesUrl = $types[30].fromObject(object.releaseNotesUrl); + } + if (object.hideUserData !== undefined && object.hideUserData !== null) + message.hideUserData = Boolean(object.hideUserData); + if (object.useGeLogo !== undefined && object.useGeLogo !== null) + message.useGeLogo = Boolean(object.useGeLogo); + if (object.dioramaDescriptionUrlBase !== undefined && object.dioramaDescriptionUrlBase !== null) { + if (typeof object.dioramaDescriptionUrlBase !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.dioramaDescriptionUrlBase: object expected"); + message.dioramaDescriptionUrlBase = $types[33].fromObject(object.dioramaDescriptionUrlBase); + } + if (object.dioramaDefaultColor !== undefined && object.dioramaDefaultColor !== null) + message.dioramaDefaultColor = object.dioramaDefaultColor >>> 0; + if (object.dioramaBlacklistUrl !== undefined && object.dioramaBlacklistUrl !== null) { + if (typeof object.dioramaBlacklistUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.dioramaBlacklistUrl: object expected"); + message.dioramaBlacklistUrl = $types[35].fromObject(object.dioramaBlacklistUrl); + } + if (object.clientOptions !== undefined && object.clientOptions !== null) { + if (typeof object.clientOptions !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.clientOptions: object expected"); + message.clientOptions = $types[36].fromObject(object.clientOptions); + } + if (object.fetchingOptions !== undefined && object.fetchingOptions !== null) { + if (typeof object.fetchingOptions !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.fetchingOptions: object expected"); + message.fetchingOptions = $types[37].fromObject(object.fetchingOptions); + } + if (object.timeMachineOptions !== undefined && object.timeMachineOptions !== null) { + if (typeof object.timeMachineOptions !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.timeMachineOptions: object expected"); + message.timeMachineOptions = $types[38].fromObject(object.timeMachineOptions); + } + if (object.csiOptions !== undefined && object.csiOptions !== null) { + if (typeof object.csiOptions !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.csiOptions: object expected"); + message.csiOptions = $types[39].fromObject(object.csiOptions); + } + if (object.searchTab) { + if (!Array.isArray(object.searchTab)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.searchTab: array expected"); + message.searchTab = []; + for (var i = 0; i < object.searchTab.length; ++i) { + if (typeof object.searchTab[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.searchTab: object expected"); + message.searchTab[i] = $types[40].fromObject(object.searchTab[i]); + } + } + if (object.cobrandInfo) { + if (!Array.isArray(object.cobrandInfo)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.cobrandInfo: array expected"); + message.cobrandInfo = []; + for (var i = 0; i < object.cobrandInfo.length; ++i) { + if (typeof object.cobrandInfo[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.cobrandInfo: object expected"); + message.cobrandInfo[i] = $types[41].fromObject(object.cobrandInfo[i]); + } + } + if (object.validDatabase) { + if (!Array.isArray(object.validDatabase)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.validDatabase: array expected"); + message.validDatabase = []; + for (var i = 0; i < object.validDatabase.length; ++i) { + if (typeof object.validDatabase[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.validDatabase: object expected"); + message.validDatabase[i] = $types[42].fromObject(object.validDatabase[i]); + } + } + if (object.configScript) { + if (!Array.isArray(object.configScript)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.configScript: array expected"); + message.configScript = []; + for (var i = 0; i < object.configScript.length; ++i) { + if (typeof object.configScript[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.configScript: object expected"); + message.configScript[i] = $types[43].fromObject(object.configScript[i]); + } + } + if (object.deauthServerUrl !== undefined && object.deauthServerUrl !== null) { + if (typeof object.deauthServerUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.deauthServerUrl: object expected"); + message.deauthServerUrl = $types[44].fromObject(object.deauthServerUrl); + } + if (object.swoopParameters !== undefined && object.swoopParameters !== null) { + if (typeof object.swoopParameters !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.swoopParameters: object expected"); + message.swoopParameters = $types[45].fromObject(object.swoopParameters); + } + if (object.bbsServerInfo !== undefined && object.bbsServerInfo !== null) { + if (typeof object.bbsServerInfo !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.bbsServerInfo: object expected"); + message.bbsServerInfo = $types[46].fromObject(object.bbsServerInfo); + } + if (object.dataErrorServerInfo !== undefined && object.dataErrorServerInfo !== null) { + if (typeof object.dataErrorServerInfo !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.dataErrorServerInfo: object expected"); + message.dataErrorServerInfo = $types[47].fromObject(object.dataErrorServerInfo); + } + if (object.planetaryDatabase) { + if (!Array.isArray(object.planetaryDatabase)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.planetaryDatabase: array expected"); + message.planetaryDatabase = []; + for (var i = 0; i < object.planetaryDatabase.length; ++i) { + if (typeof object.planetaryDatabase[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.planetaryDatabase: object expected"); + message.planetaryDatabase[i] = $types[48].fromObject(object.planetaryDatabase[i]); + } + } + if (object.logServer !== undefined && object.logServer !== null) { + if (typeof object.logServer !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.logServer: object expected"); + message.logServer = $types[49].fromObject(object.logServer); + } + if (object.autopiaOptions !== undefined && object.autopiaOptions !== null) { + if (typeof object.autopiaOptions !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.autopiaOptions: object expected"); + message.autopiaOptions = $types[50].fromObject(object.autopiaOptions); + } + if (object.searchConfig !== undefined && object.searchConfig !== null) { + if (typeof object.searchConfig !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.searchConfig: object expected"); + message.searchConfig = $types[51].fromObject(object.searchConfig); + } + if (object.searchInfo !== undefined && object.searchInfo !== null) { + if (typeof object.searchInfo !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.searchInfo: object expected"); + message.searchInfo = $types[52].fromObject(object.searchInfo); + } + if (object.elevationServiceBaseUrl !== undefined && object.elevationServiceBaseUrl !== null) + message.elevationServiceBaseUrl = String(object.elevationServiceBaseUrl); + if (object.elevationProfileQueryDelay !== undefined && object.elevationProfileQueryDelay !== null) + message.elevationProfileQueryDelay = object.elevationProfileQueryDelay | 0; + if (object.proUpgradeUrl !== undefined && object.proUpgradeUrl !== null) { + if (typeof object.proUpgradeUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.proUpgradeUrl: object expected"); + message.proUpgradeUrl = $types[55].fromObject(object.proUpgradeUrl); + } + if (object.earthCommunityUrl !== undefined && object.earthCommunityUrl !== null) { + if (typeof object.earthCommunityUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.earthCommunityUrl: object expected"); + message.earthCommunityUrl = $types[56].fromObject(object.earthCommunityUrl); + } + if (object.googleMapsUrl !== undefined && object.googleMapsUrl !== null) { + if (typeof object.googleMapsUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.googleMapsUrl: object expected"); + message.googleMapsUrl = $types[57].fromObject(object.googleMapsUrl); + } + if (object.sharingUrl !== undefined && object.sharingUrl !== null) { + if (typeof object.sharingUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.sharingUrl: object expected"); + message.sharingUrl = $types[58].fromObject(object.sharingUrl); + } + if (object.privacyPolicyUrl !== undefined && object.privacyPolicyUrl !== null) { + if (typeof object.privacyPolicyUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.privacyPolicyUrl: object expected"); + message.privacyPolicyUrl = $types[59].fromObject(object.privacyPolicyUrl); + } + if (object.doGplusUserCheck !== undefined && object.doGplusUserCheck !== null) + message.doGplusUserCheck = Boolean(object.doGplusUserCheck); + if (object.rocktreeDataProto !== undefined && object.rocktreeDataProto !== null) { + if (typeof object.rocktreeDataProto !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.rocktreeDataProto: object expected"); + message.rocktreeDataProto = $types[61].fromObject(object.rocktreeDataProto); + } + if (object.filmstripConfig) { + if (!Array.isArray(object.filmstripConfig)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.filmstripConfig: array expected"); + message.filmstripConfig = []; + for (var i = 0; i < object.filmstripConfig.length; ++i) { + if (typeof object.filmstripConfig[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.filmstripConfig: object expected"); + message.filmstripConfig[i] = $types[62].fromObject(object.filmstripConfig[i]); + } + } + if (object.showSigninButton !== undefined && object.showSigninButton !== null) + message.showSigninButton = Boolean(object.showSigninButton); + if (object.proMeasureUpsellUrl !== undefined && object.proMeasureUpsellUrl !== null) { + if (typeof object.proMeasureUpsellUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.proMeasureUpsellUrl: object expected"); + message.proMeasureUpsellUrl = $types[64].fromObject(object.proMeasureUpsellUrl); + } + if (object.proPrintUpsellUrl !== undefined && object.proPrintUpsellUrl !== null) { + if (typeof object.proPrintUpsellUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.proPrintUpsellUrl: object expected"); + message.proPrintUpsellUrl = $types[65].fromObject(object.proPrintUpsellUrl); + } + if (object.starDataProto !== undefined && object.starDataProto !== null) { + if (typeof object.starDataProto !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.starDataProto: object expected"); + message.starDataProto = $types[66].fromObject(object.starDataProto); + } + if (object.feedbackUrl !== undefined && object.feedbackUrl !== null) { + if (typeof object.feedbackUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.feedbackUrl: object expected"); + message.feedbackUrl = $types[67].fromObject(object.feedbackUrl); + } + if (object.oauth2LoginUrl !== undefined && object.oauth2LoginUrl !== null) { + if (typeof object.oauth2LoginUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.oauth2LoginUrl: object expected"); + message.oauth2LoginUrl = $types[68].fromObject(object.oauth2LoginUrl); + } + return message; + }; - return result; - }; + EndSnippetProto.from = EndSnippetProto.fromObject; - /** - * Sets the parent for the tile - * - * @param {GoogleEarthEnterpriseTileInformation} parent Parent tile - */ - GoogleEarthEnterpriseTileInformation.prototype.setParent = function(parent) { - this.ancestorHasTerrain = parent.ancestorHasTerrain || this.hasTerrain(); - }; + EndSnippetProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.mfeDomains = []; + object.searchTab = []; + object.cobrandInfo = []; + object.validDatabase = []; + object.configScript = []; + object.planetaryDatabase = []; + object.filmstripConfig = []; + } + if (options.defaults) { + object.model = null; + object.authServerUrl = null; + object.disableAuthentication = false; + object.mfeLangParam = "hl=$5Bhl5D"; + object.adsUrlPatterns = ""; + object.reverseGeocoderUrl = null; + object.reverseGeocoderProtocolVersion = 3; + object.skyDatabaseIsAvailable = true; + object.skyDatabaseUrl = null; + object.defaultWebPageIntlUrl = null; + object.numStartUpTips = 17; + object.startUpTipsUrl = null; + object.numProStartUpTips = 0; + object.proStartUpTipsUrl = null; + object.startupTipsIntlUrl = null; + object.userGuideIntlUrl = null; + object.supportCenterIntlUrl = null; + object.businessListingIntlUrl = null; + object.supportAnswerIntlUrl = null; + object.supportTopicIntlUrl = null; + object.supportRequestIntlUrl = null; + object.earthIntlUrl = null; + object.addContentUrl = null; + object.sketchupNotInstalledUrl = null; + object.sketchupErrorUrl = null; + object.freeLicenseUrl = null; + object.proLicenseUrl = null; + object.tutorialUrl = null; + object.keyboardShortcutsUrl = null; + object.releaseNotesUrl = null; + object.hideUserData = false; + object.useGeLogo = true; + object.dioramaDescriptionUrlBase = null; + object.dioramaDefaultColor = 4291281607; + object.dioramaBlacklistUrl = null; + object.clientOptions = null; + object.fetchingOptions = null; + object.timeMachineOptions = null; + object.csiOptions = null; + object.deauthServerUrl = null; + object.swoopParameters = null; + object.bbsServerInfo = null; + object.dataErrorServerInfo = null; + object.logServer = null; + object.autopiaOptions = null; + object.searchConfig = null; + object.searchInfo = null; + object.elevationServiceBaseUrl = "http://maps.google.com/maps/api/elevation/"; + object.elevationProfileQueryDelay = 500; + object.proUpgradeUrl = null; + object.earthCommunityUrl = null; + object.googleMapsUrl = null; + object.sharingUrl = null; + object.privacyPolicyUrl = null; + object.doGplusUserCheck = false; + object.rocktreeDataProto = null; + object.showSigninButton = false; + object.proMeasureUpsellUrl = null; + object.proPrintUpsellUrl = null; + object.starDataProto = null; + object.feedbackUrl = null; + object.oauth2LoginUrl = null; + } + if (message.model !== undefined && message.model !== null && message.hasOwnProperty("model")) + object.model = $types[0].toObject(message.model, options); + if (message.authServerUrl !== undefined && message.authServerUrl !== null && message.hasOwnProperty("authServerUrl")) + object.authServerUrl = $types[1].toObject(message.authServerUrl, options); + if (message.disableAuthentication !== undefined && message.disableAuthentication !== null && message.hasOwnProperty("disableAuthentication")) + object.disableAuthentication = message.disableAuthentication; + if (message.mfeDomains !== undefined && message.mfeDomains !== null && message.hasOwnProperty("mfeDomains")) { + object.mfeDomains = []; + for (var j = 0; j < message.mfeDomains.length; ++j) + object.mfeDomains[j] = $types[3].toObject(message.mfeDomains[j], options); + } + if (message.mfeLangParam !== undefined && message.mfeLangParam !== null && message.hasOwnProperty("mfeLangParam")) + object.mfeLangParam = message.mfeLangParam; + if (message.adsUrlPatterns !== undefined && message.adsUrlPatterns !== null && message.hasOwnProperty("adsUrlPatterns")) + object.adsUrlPatterns = message.adsUrlPatterns; + if (message.reverseGeocoderUrl !== undefined && message.reverseGeocoderUrl !== null && message.hasOwnProperty("reverseGeocoderUrl")) + object.reverseGeocoderUrl = $types[6].toObject(message.reverseGeocoderUrl, options); + if (message.reverseGeocoderProtocolVersion !== undefined && message.reverseGeocoderProtocolVersion !== null && message.hasOwnProperty("reverseGeocoderProtocolVersion")) + object.reverseGeocoderProtocolVersion = message.reverseGeocoderProtocolVersion; + if (message.skyDatabaseIsAvailable !== undefined && message.skyDatabaseIsAvailable !== null && message.hasOwnProperty("skyDatabaseIsAvailable")) + object.skyDatabaseIsAvailable = message.skyDatabaseIsAvailable; + if (message.skyDatabaseUrl !== undefined && message.skyDatabaseUrl !== null && message.hasOwnProperty("skyDatabaseUrl")) + object.skyDatabaseUrl = $types[9].toObject(message.skyDatabaseUrl, options); + if (message.defaultWebPageIntlUrl !== undefined && message.defaultWebPageIntlUrl !== null && message.hasOwnProperty("defaultWebPageIntlUrl")) + object.defaultWebPageIntlUrl = $types[10].toObject(message.defaultWebPageIntlUrl, options); + if (message.numStartUpTips !== undefined && message.numStartUpTips !== null && message.hasOwnProperty("numStartUpTips")) + object.numStartUpTips = message.numStartUpTips; + if (message.startUpTipsUrl !== undefined && message.startUpTipsUrl !== null && message.hasOwnProperty("startUpTipsUrl")) + object.startUpTipsUrl = $types[12].toObject(message.startUpTipsUrl, options); + if (message.numProStartUpTips !== undefined && message.numProStartUpTips !== null && message.hasOwnProperty("numProStartUpTips")) + object.numProStartUpTips = message.numProStartUpTips; + if (message.proStartUpTipsUrl !== undefined && message.proStartUpTipsUrl !== null && message.hasOwnProperty("proStartUpTipsUrl")) + object.proStartUpTipsUrl = $types[14].toObject(message.proStartUpTipsUrl, options); + if (message.startupTipsIntlUrl !== undefined && message.startupTipsIntlUrl !== null && message.hasOwnProperty("startupTipsIntlUrl")) + object.startupTipsIntlUrl = $types[15].toObject(message.startupTipsIntlUrl, options); + if (message.userGuideIntlUrl !== undefined && message.userGuideIntlUrl !== null && message.hasOwnProperty("userGuideIntlUrl")) + object.userGuideIntlUrl = $types[16].toObject(message.userGuideIntlUrl, options); + if (message.supportCenterIntlUrl !== undefined && message.supportCenterIntlUrl !== null && message.hasOwnProperty("supportCenterIntlUrl")) + object.supportCenterIntlUrl = $types[17].toObject(message.supportCenterIntlUrl, options); + if (message.businessListingIntlUrl !== undefined && message.businessListingIntlUrl !== null && message.hasOwnProperty("businessListingIntlUrl")) + object.businessListingIntlUrl = $types[18].toObject(message.businessListingIntlUrl, options); + if (message.supportAnswerIntlUrl !== undefined && message.supportAnswerIntlUrl !== null && message.hasOwnProperty("supportAnswerIntlUrl")) + object.supportAnswerIntlUrl = $types[19].toObject(message.supportAnswerIntlUrl, options); + if (message.supportTopicIntlUrl !== undefined && message.supportTopicIntlUrl !== null && message.hasOwnProperty("supportTopicIntlUrl")) + object.supportTopicIntlUrl = $types[20].toObject(message.supportTopicIntlUrl, options); + if (message.supportRequestIntlUrl !== undefined && message.supportRequestIntlUrl !== null && message.hasOwnProperty("supportRequestIntlUrl")) + object.supportRequestIntlUrl = $types[21].toObject(message.supportRequestIntlUrl, options); + if (message.earthIntlUrl !== undefined && message.earthIntlUrl !== null && message.hasOwnProperty("earthIntlUrl")) + object.earthIntlUrl = $types[22].toObject(message.earthIntlUrl, options); + if (message.addContentUrl !== undefined && message.addContentUrl !== null && message.hasOwnProperty("addContentUrl")) + object.addContentUrl = $types[23].toObject(message.addContentUrl, options); + if (message.sketchupNotInstalledUrl !== undefined && message.sketchupNotInstalledUrl !== null && message.hasOwnProperty("sketchupNotInstalledUrl")) + object.sketchupNotInstalledUrl = $types[24].toObject(message.sketchupNotInstalledUrl, options); + if (message.sketchupErrorUrl !== undefined && message.sketchupErrorUrl !== null && message.hasOwnProperty("sketchupErrorUrl")) + object.sketchupErrorUrl = $types[25].toObject(message.sketchupErrorUrl, options); + if (message.freeLicenseUrl !== undefined && message.freeLicenseUrl !== null && message.hasOwnProperty("freeLicenseUrl")) + object.freeLicenseUrl = $types[26].toObject(message.freeLicenseUrl, options); + if (message.proLicenseUrl !== undefined && message.proLicenseUrl !== null && message.hasOwnProperty("proLicenseUrl")) + object.proLicenseUrl = $types[27].toObject(message.proLicenseUrl, options); + if (message.tutorialUrl !== undefined && message.tutorialUrl !== null && message.hasOwnProperty("tutorialUrl")) + object.tutorialUrl = $types[28].toObject(message.tutorialUrl, options); + if (message.keyboardShortcutsUrl !== undefined && message.keyboardShortcutsUrl !== null && message.hasOwnProperty("keyboardShortcutsUrl")) + object.keyboardShortcutsUrl = $types[29].toObject(message.keyboardShortcutsUrl, options); + if (message.releaseNotesUrl !== undefined && message.releaseNotesUrl !== null && message.hasOwnProperty("releaseNotesUrl")) + object.releaseNotesUrl = $types[30].toObject(message.releaseNotesUrl, options); + if (message.hideUserData !== undefined && message.hideUserData !== null && message.hasOwnProperty("hideUserData")) + object.hideUserData = message.hideUserData; + if (message.useGeLogo !== undefined && message.useGeLogo !== null && message.hasOwnProperty("useGeLogo")) + object.useGeLogo = message.useGeLogo; + if (message.dioramaDescriptionUrlBase !== undefined && message.dioramaDescriptionUrlBase !== null && message.hasOwnProperty("dioramaDescriptionUrlBase")) + object.dioramaDescriptionUrlBase = $types[33].toObject(message.dioramaDescriptionUrlBase, options); + if (message.dioramaDefaultColor !== undefined && message.dioramaDefaultColor !== null && message.hasOwnProperty("dioramaDefaultColor")) + object.dioramaDefaultColor = message.dioramaDefaultColor; + if (message.dioramaBlacklistUrl !== undefined && message.dioramaBlacklistUrl !== null && message.hasOwnProperty("dioramaBlacklistUrl")) + object.dioramaBlacklistUrl = $types[35].toObject(message.dioramaBlacklistUrl, options); + if (message.clientOptions !== undefined && message.clientOptions !== null && message.hasOwnProperty("clientOptions")) + object.clientOptions = $types[36].toObject(message.clientOptions, options); + if (message.fetchingOptions !== undefined && message.fetchingOptions !== null && message.hasOwnProperty("fetchingOptions")) + object.fetchingOptions = $types[37].toObject(message.fetchingOptions, options); + if (message.timeMachineOptions !== undefined && message.timeMachineOptions !== null && message.hasOwnProperty("timeMachineOptions")) + object.timeMachineOptions = $types[38].toObject(message.timeMachineOptions, options); + if (message.csiOptions !== undefined && message.csiOptions !== null && message.hasOwnProperty("csiOptions")) + object.csiOptions = $types[39].toObject(message.csiOptions, options); + if (message.searchTab !== undefined && message.searchTab !== null && message.hasOwnProperty("searchTab")) { + object.searchTab = []; + for (var j = 0; j < message.searchTab.length; ++j) + object.searchTab[j] = $types[40].toObject(message.searchTab[j], options); + } + if (message.cobrandInfo !== undefined && message.cobrandInfo !== null && message.hasOwnProperty("cobrandInfo")) { + object.cobrandInfo = []; + for (var j = 0; j < message.cobrandInfo.length; ++j) + object.cobrandInfo[j] = $types[41].toObject(message.cobrandInfo[j], options); + } + if (message.validDatabase !== undefined && message.validDatabase !== null && message.hasOwnProperty("validDatabase")) { + object.validDatabase = []; + for (var j = 0; j < message.validDatabase.length; ++j) + object.validDatabase[j] = $types[42].toObject(message.validDatabase[j], options); + } + if (message.configScript !== undefined && message.configScript !== null && message.hasOwnProperty("configScript")) { + object.configScript = []; + for (var j = 0; j < message.configScript.length; ++j) + object.configScript[j] = $types[43].toObject(message.configScript[j], options); + } + if (message.deauthServerUrl !== undefined && message.deauthServerUrl !== null && message.hasOwnProperty("deauthServerUrl")) + object.deauthServerUrl = $types[44].toObject(message.deauthServerUrl, options); + if (message.swoopParameters !== undefined && message.swoopParameters !== null && message.hasOwnProperty("swoopParameters")) + object.swoopParameters = $types[45].toObject(message.swoopParameters, options); + if (message.bbsServerInfo !== undefined && message.bbsServerInfo !== null && message.hasOwnProperty("bbsServerInfo")) + object.bbsServerInfo = $types[46].toObject(message.bbsServerInfo, options); + if (message.dataErrorServerInfo !== undefined && message.dataErrorServerInfo !== null && message.hasOwnProperty("dataErrorServerInfo")) + object.dataErrorServerInfo = $types[47].toObject(message.dataErrorServerInfo, options); + if (message.planetaryDatabase !== undefined && message.planetaryDatabase !== null && message.hasOwnProperty("planetaryDatabase")) { + object.planetaryDatabase = []; + for (var j = 0; j < message.planetaryDatabase.length; ++j) + object.planetaryDatabase[j] = $types[48].toObject(message.planetaryDatabase[j], options); + } + if (message.logServer !== undefined && message.logServer !== null && message.hasOwnProperty("logServer")) + object.logServer = $types[49].toObject(message.logServer, options); + if (message.autopiaOptions !== undefined && message.autopiaOptions !== null && message.hasOwnProperty("autopiaOptions")) + object.autopiaOptions = $types[50].toObject(message.autopiaOptions, options); + if (message.searchConfig !== undefined && message.searchConfig !== null && message.hasOwnProperty("searchConfig")) + object.searchConfig = $types[51].toObject(message.searchConfig, options); + if (message.searchInfo !== undefined && message.searchInfo !== null && message.hasOwnProperty("searchInfo")) + object.searchInfo = $types[52].toObject(message.searchInfo, options); + if (message.elevationServiceBaseUrl !== undefined && message.elevationServiceBaseUrl !== null && message.hasOwnProperty("elevationServiceBaseUrl")) + object.elevationServiceBaseUrl = message.elevationServiceBaseUrl; + if (message.elevationProfileQueryDelay !== undefined && message.elevationProfileQueryDelay !== null && message.hasOwnProperty("elevationProfileQueryDelay")) + object.elevationProfileQueryDelay = message.elevationProfileQueryDelay; + if (message.proUpgradeUrl !== undefined && message.proUpgradeUrl !== null && message.hasOwnProperty("proUpgradeUrl")) + object.proUpgradeUrl = $types[55].toObject(message.proUpgradeUrl, options); + if (message.earthCommunityUrl !== undefined && message.earthCommunityUrl !== null && message.hasOwnProperty("earthCommunityUrl")) + object.earthCommunityUrl = $types[56].toObject(message.earthCommunityUrl, options); + if (message.googleMapsUrl !== undefined && message.googleMapsUrl !== null && message.hasOwnProperty("googleMapsUrl")) + object.googleMapsUrl = $types[57].toObject(message.googleMapsUrl, options); + if (message.sharingUrl !== undefined && message.sharingUrl !== null && message.hasOwnProperty("sharingUrl")) + object.sharingUrl = $types[58].toObject(message.sharingUrl, options); + if (message.privacyPolicyUrl !== undefined && message.privacyPolicyUrl !== null && message.hasOwnProperty("privacyPolicyUrl")) + object.privacyPolicyUrl = $types[59].toObject(message.privacyPolicyUrl, options); + if (message.doGplusUserCheck !== undefined && message.doGplusUserCheck !== null && message.hasOwnProperty("doGplusUserCheck")) + object.doGplusUserCheck = message.doGplusUserCheck; + if (message.rocktreeDataProto !== undefined && message.rocktreeDataProto !== null && message.hasOwnProperty("rocktreeDataProto")) + object.rocktreeDataProto = $types[61].toObject(message.rocktreeDataProto, options); + if (message.filmstripConfig !== undefined && message.filmstripConfig !== null && message.hasOwnProperty("filmstripConfig")) { + object.filmstripConfig = []; + for (var j = 0; j < message.filmstripConfig.length; ++j) + object.filmstripConfig[j] = $types[62].toObject(message.filmstripConfig[j], options); + } + if (message.showSigninButton !== undefined && message.showSigninButton !== null && message.hasOwnProperty("showSigninButton")) + object.showSigninButton = message.showSigninButton; + if (message.proMeasureUpsellUrl !== undefined && message.proMeasureUpsellUrl !== null && message.hasOwnProperty("proMeasureUpsellUrl")) + object.proMeasureUpsellUrl = $types[64].toObject(message.proMeasureUpsellUrl, options); + if (message.proPrintUpsellUrl !== undefined && message.proPrintUpsellUrl !== null && message.hasOwnProperty("proPrintUpsellUrl")) + object.proPrintUpsellUrl = $types[65].toObject(message.proPrintUpsellUrl, options); + if (message.starDataProto !== undefined && message.starDataProto !== null && message.hasOwnProperty("starDataProto")) + object.starDataProto = $types[66].toObject(message.starDataProto, options); + if (message.feedbackUrl !== undefined && message.feedbackUrl !== null && message.hasOwnProperty("feedbackUrl")) + object.feedbackUrl = $types[67].toObject(message.feedbackUrl, options); + if (message.oauth2LoginUrl !== undefined && message.oauth2LoginUrl !== null && message.hasOwnProperty("oauth2LoginUrl")) + object.oauth2LoginUrl = $types[68].toObject(message.oauth2LoginUrl, options); + return object; + }; - /** - * Gets whether a subtree is available - * - * @returns {Boolean} true if subtree is available, false otherwise. - */ - GoogleEarthEnterpriseTileInformation.prototype.hasSubtree = function() { - return isBitSet(this._bits, cacheFlagBitmask); - }; + EndSnippetProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - /** - * Gets whether imagery is available - * - * @returns {Boolean} true if imagery is available, false otherwise. - */ - GoogleEarthEnterpriseTileInformation.prototype.hasImagery = function() { - return isBitSet(this._bits, imageBitmask); - }; + EndSnippetProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * Gets whether terrain is available - * - * @returns {Boolean} true if terrain is available, false otherwise. - */ - GoogleEarthEnterpriseTileInformation.prototype.hasTerrain = function() { - return isBitSet(this._bits, terrainBitmask); - }; + EndSnippetProto.SearchConfigProto = (function() { - /** - * Gets whether any children are present - * - * @returns {Boolean} true if any children are available, false otherwise. - */ - GoogleEarthEnterpriseTileInformation.prototype.hasChildren = function() { - return isBitSet(this._bits, anyChildBitmask); - }; + function SearchConfigProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - /** - * Gets whether a specified child is available - * - * @param {Number} index Index of child tile - * - * @returns {Boolean} true if child is available, false otherwise - */ - GoogleEarthEnterpriseTileInformation.prototype.hasChild = function(index) { - return isBitSet(this._bits, childrenBitmasks[index]); - }; + SearchConfigProto.prototype.searchServer = $util.emptyArray; + SearchConfigProto.prototype.oneboxService = $util.emptyArray; + SearchConfigProto.prototype.kmlSearchUrl = null; + SearchConfigProto.prototype.kmlRenderUrl = null; + SearchConfigProto.prototype.searchHistoryUrl = null; + SearchConfigProto.prototype.errorPageUrl = null; - /** - * Gets bitmask containing children - * - * @returns {Number} Children bitmask - */ - GoogleEarthEnterpriseTileInformation.prototype.getChildBitmask = function() { - return this._bits & anyChildBitmask; - }; + var $types = { + 0 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer", + 1 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto", + 2 : "keyhole.dbroot.StringIdOrValueProto", + 3 : "keyhole.dbroot.StringIdOrValueProto", + 4 : "keyhole.dbroot.StringIdOrValueProto", + 5 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - return GoogleEarthEnterpriseTileInformation; -}); + SearchConfigProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (!(message.searchServer && message.searchServer.length)) + message.searchServer = []; + message.searchServer.push($types[0].decode(reader, reader.uint32())); + break; + case 2: + if (!(message.oneboxService && message.oneboxService.length)) + message.oneboxService = []; + message.oneboxService.push($types[1].decode(reader, reader.uint32())); + break; + case 3: + message.kmlSearchUrl = $types[2].decode(reader, reader.uint32()); + break; + case 4: + message.kmlRenderUrl = $types[3].decode(reader, reader.uint32()); + break; + case 6: + message.searchHistoryUrl = $types[4].decode(reader, reader.uint32()); + break; + case 5: + message.errorPageUrl = $types[5].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; -/*global define*/ -define('Core/GoogleEarthEnterpriseMetadata',[ - '../ThirdParty/google-earth-dbroot-parser', - '../ThirdParty/when', - './appendForwardSlash', - './Check', - './Credit', - './defaultValue', - './defined', - './defineProperties', - './GoogleEarthEnterpriseTileInformation', - './isBitSet', - './joinUrls', - './loadArrayBuffer', - './Math', - './RuntimeError', - './TaskProcessor', - './throttleRequestByServer' -], function( - dbrootParser, - when, - appendForwardSlash, - Check, - Credit, - defaultValue, - defined, - defineProperties, - GoogleEarthEnterpriseTileInformation, - isBitSet, - joinUrls, - loadArrayBuffer, - CesiumMath, - RuntimeError, - TaskProcessor, - throttleRequestByServer) { - 'use strict'; + SearchConfigProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.searchServer !== undefined) { + if (!Array.isArray(message.searchServer)) + return "searchServer: array expected"; + for (var i = 0; i < message.searchServer.length; ++i) { + var error = $types[0].verify(message.searchServer[i]); + if (error) + return "searchServer." + error; + } + } + if (message.oneboxService !== undefined) { + if (!Array.isArray(message.oneboxService)) + return "oneboxService: array expected"; + for (var i = 0; i < message.oneboxService.length; ++i) { + var error = $types[1].verify(message.oneboxService[i]); + if (error) + return "oneboxService." + error; + } + } + if (message.kmlSearchUrl !== undefined && message.kmlSearchUrl !== null) { + var error = $types[2].verify(message.kmlSearchUrl); + if (error) + return "kmlSearchUrl." + error; + } + if (message.kmlRenderUrl !== undefined && message.kmlRenderUrl !== null) { + var error = $types[3].verify(message.kmlRenderUrl); + if (error) + return "kmlRenderUrl." + error; + } + if (message.searchHistoryUrl !== undefined && message.searchHistoryUrl !== null) { + var error = $types[4].verify(message.searchHistoryUrl); + if (error) + return "searchHistoryUrl." + error; + } + if (message.errorPageUrl !== undefined && message.errorPageUrl !== null) { + var error = $types[5].verify(message.errorPageUrl); + if (error) + return "errorPageUrl." + error; + } + return null; + }; - function stringToBuffer(str) { - var len = str.length; - var buffer = new ArrayBuffer(len); - var ui8 = new Uint8Array(buffer); - for (var i = 0; i < len; ++i) { - ui8[i] = str.charCodeAt(i); - } + SearchConfigProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto(); + if (object.searchServer) { + if (!Array.isArray(object.searchServer)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.searchServer: array expected"); + message.searchServer = []; + for (var i = 0; i < object.searchServer.length; ++i) { + if (typeof object.searchServer[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.searchServer: object expected"); + message.searchServer[i] = $types[0].fromObject(object.searchServer[i]); + } + } + if (object.oneboxService) { + if (!Array.isArray(object.oneboxService)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.oneboxService: array expected"); + message.oneboxService = []; + for (var i = 0; i < object.oneboxService.length; ++i) { + if (typeof object.oneboxService[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.oneboxService: object expected"); + message.oneboxService[i] = $types[1].fromObject(object.oneboxService[i]); + } + } + if (object.kmlSearchUrl !== undefined && object.kmlSearchUrl !== null) { + if (typeof object.kmlSearchUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.kmlSearchUrl: object expected"); + message.kmlSearchUrl = $types[2].fromObject(object.kmlSearchUrl); + } + if (object.kmlRenderUrl !== undefined && object.kmlRenderUrl !== null) { + if (typeof object.kmlRenderUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.kmlRenderUrl: object expected"); + message.kmlRenderUrl = $types[3].fromObject(object.kmlRenderUrl); + } + if (object.searchHistoryUrl !== undefined && object.searchHistoryUrl !== null) { + if (typeof object.searchHistoryUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.searchHistoryUrl: object expected"); + message.searchHistoryUrl = $types[4].fromObject(object.searchHistoryUrl); + } + if (object.errorPageUrl !== undefined && object.errorPageUrl !== null) { + if (typeof object.errorPageUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.errorPageUrl: object expected"); + message.errorPageUrl = $types[5].fromObject(object.errorPageUrl); + } + return message; + }; - return buffer; - } + SearchConfigProto.from = SearchConfigProto.fromObject; - // Decodes packet with a key that has been around since the beginning of Google Earth Enterprise - var defaultKey = stringToBuffer('\x45\xf4\xbd\x0b\x79\xe2\x6a\x45\x22\x05\x92\x2c\x17\xcd\x06\x71\xf8\x49\x10\x46\x67\x51\x00\x42\x25\xc6\xe8\x61\x2c\x66\x29\x08\xc6\x34\xdc\x6a\x62\x25\x79\x0a\x77\x1d\x6d\x69\xd6\xf0\x9c\x6b\x93\xa1\xbd\x4e\x75\xe0\x41\x04\x5b\xdf\x40\x56\x0c\xd9\xbb\x72\x9b\x81\x7c\x10\x33\x53\xee\x4f\x6c\xd4\x71\x05\xb0\x7b\xc0\x7f\x45\x03\x56\x5a\xad\x77\x55\x65\x0b\x33\x92\x2a\xac\x19\x6c\x35\x14\xc5\x1d\x30\x73\xf8\x33\x3e\x6d\x46\x38\x4a\xb4\xdd\xf0\x2e\xdd\x17\x75\x16\xda\x8c\x44\x74\x22\x06\xfa\x61\x22\x0c\x33\x22\x53\x6f\xaf\x39\x44\x0b\x8c\x0e\x39\xd9\x39\x13\x4c\xb9\xbf\x7f\xab\x5c\x8c\x50\x5f\x9f\x22\x75\x78\x1f\xe9\x07\x71\x91\x68\x3b\xc1\xc4\x9b\x7f\xf0\x3c\x56\x71\x48\x82\x05\x27\x55\x66\x59\x4e\x65\x1d\x98\x75\xa3\x61\x46\x7d\x61\x3f\x15\x41\x00\x9f\x14\x06\xd7\xb4\x34\x4d\xce\x13\x87\x46\xb0\x1a\xd5\x05\x1c\xb8\x8a\x27\x7b\x8b\xdc\x2b\xbb\x4d\x67\x30\xc8\xd1\xf6\x5c\x8f\x50\xfa\x5b\x2f\x46\x9b\x6e\x35\x18\x2f\x27\x43\x2e\xeb\x0a\x0c\x5e\x10\x05\x10\xa5\x73\x1b\x65\x34\xe5\x6c\x2e\x6a\x43\x27\x63\x14\x23\x55\xa9\x3f\x71\x7b\x67\x43\x7d\x3a\xaf\xcd\xe2\x54\x55\x9c\xfd\x4b\xc6\xe2\x9f\x2f\x28\xed\xcb\x5c\xc6\x2d\x66\x07\x88\xa7\x3b\x2f\x18\x2a\x22\x4e\x0e\xb0\x6b\x2e\xdd\x0d\x95\x7d\x7d\x47\xba\x43\xb2\x11\xb2\x2b\x3e\x4d\xaa\x3e\x7d\xe6\xce\x49\x89\xc6\xe6\x78\x0c\x61\x31\x05\x2d\x01\xa4\x4f\xa5\x7e\x71\x20\x88\xec\x0d\x31\xe8\x4e\x0b\x00\x6e\x50\x68\x7d\x17\x3d\x08\x0d\x17\x95\xa6\x6e\xa3\x68\x97\x24\x5b\x6b\xf3\x17\x23\xf3\xb6\x73\xb3\x0d\x0b\x40\xc0\x9f\xd8\x04\x51\x5d\xfa\x1a\x17\x22\x2e\x15\x6a\xdf\x49\x00\xb9\xa0\x77\x55\xc6\xef\x10\x6a\xbf\x7b\x47\x4c\x7f\x83\x17\x05\xee\xdc\xdc\x46\x85\xa9\xad\x53\x07\x2b\x53\x34\x06\x07\xff\x14\x94\x59\x19\x02\xe4\x38\xe8\x31\x83\x4e\xb9\x58\x46\x6b\xcb\x2d\x23\x86\x92\x70\x00\x35\x88\x22\xcf\x31\xb2\x26\x2f\xe7\xc3\x75\x2d\x36\x2c\x72\x74\xb0\x23\x47\xb7\xd3\xd1\x26\x16\x85\x37\x72\xe2\x00\x8c\x44\xcf\x10\xda\x33\x2d\x1a\xde\x60\x86\x69\x23\x69\x2a\x7c\xcd\x4b\x51\x0d\x95\x54\x39\x77\x2e\x29\xea\x1b\xa6\x50\xa2\x6a\x8f\x6f\x50\x99\x5c\x3e\x54\xfb\xef\x50\x5b\x0b\x07\x45\x17\x89\x6d\x28\x13\x77\x37\x1d\xdb\x8e\x1e\x4a\x05\x66\x4a\x6f\x99\x20\xe5\x70\xe2\xb9\x71\x7e\x0c\x6d\x49\x04\x2d\x7a\xfe\x72\xc7\xf2\x59\x30\x8f\xbb\x02\x5d\x73\xe5\xc9\x20\xea\x78\xec\x20\x90\xf0\x8a\x7f\x42\x17\x7c\x47\x19\x60\xb0\x16\xbd\x26\xb7\x71\xb6\xc7\x9f\x0e\xd1\x33\x82\x3d\xd3\xab\xee\x63\x99\xc8\x2b\x53\xa0\x44\x5c\x71\x01\xc6\xcc\x44\x1f\x32\x4f\x3c\xca\xc0\x29\x3d\x52\xd3\x61\x19\x58\xa9\x7d\x65\xb4\xdc\xcf\x0d\xf4\x3d\xf1\x08\xa9\x42\xda\x23\x09\xd8\xbf\x5e\x50\x49\xf8\x4d\xc0\xcb\x47\x4c\x1c\x4f\xf7\x7b\x2b\xd8\x16\x18\xc5\x31\x92\x3b\xb5\x6f\xdc\x6c\x0d\x92\x88\x16\xd1\x9e\xdb\x3f\xe2\xe9\xda\x5f\xd4\x84\xe2\x46\x61\x5a\xde\x1c\x55\xcf\xa4\x00\xbe\xfd\xce\x67\xf1\x4a\x69\x1c\x97\xe6\x20\x48\xd8\x5d\x7f\x7e\xae\x71\x20\x0e\x4e\xae\xc0\x56\xa9\x91\x01\x3c\x82\x1d\x0f\x72\xe7\x76\xec\x29\x49\xd6\x5d\x2d\x83\xe3\xdb\x36\x06\xa9\x3b\x66\x13\x97\x87\x6a\xd5\xb6\x3d\x50\x5e\x52\xb9\x4b\xc7\x73\x57\x78\xc9\xf4\x2e\x59\x07\x95\x93\x6f\xd0\x4b\x17\x57\x19\x3e\x27\x27\xc7\x60\xdb\x3b\xed\x9a\x0e\x53\x44\x16\x3e\x3f\x8d\x92\x6d\x77\xa2\x0a\xeb\x3f\x52\xa8\xc6\x55\x5e\x31\x49\x37\x85\xf4\xc5\x1f\x26\x2d\xa9\x1c\xbf\x8b\x27\x54\xda\xc3\x6a\x20\xe5\x2a\x78\x04\xb0\xd6\x90\x70\x72\xaa\x8b\x68\xbd\x88\xf7\x02\x5f\x48\xb1\x7e\xc0\x58\x4c\x3f\x66\x1a\xf9\x3e\xe1\x65\xc0\x70\xa7\xcf\x38\x69\xaf\xf0\x56\x6c\x64\x49\x9c\x27\xad\x78\x74\x4f\xc2\x87\xde\x56\x39\x00\xda\x77\x0b\xcb\x2d\x1b\x89\xfb\x35\x4f\x02\xf5\x08\x51\x13\x60\xc1\x0a\x5a\x47\x4d\x26\x1c\x33\x30\x78\xda\xc0\x9c\x46\x47\xe2\x5b\x79\x60\x49\x6e\x37\x67\x53\x0a\x3e\xe9\xec\x46\x39\xb2\xf1\x34\x0d\xc6\x84\x53\x75\x6e\xe1\x0c\x59\xd9\x1e\xde\x29\x85\x10\x7b\x49\x49\xa5\x77\x79\xbe\x49\x56\x2e\x36\xe7\x0b\x3a\xbb\x4f\x03\x62\x7b\xd2\x4d\x31\x95\x2f\xbd\x38\x7b\xa8\x4f\x21\xe1\xec\x46\x70\x76\x95\x7d\x29\x22\x78\x88\x0a\x90\xdd\x9d\x5c\xda\xde\x19\x51\xcf\xf0\xfc\x59\x52\x65\x7c\x33\x13\xdf\xf3\x48\xda\xbb\x2a\x75\xdb\x60\xb2\x02\x15\xd4\xfc\x19\xed\x1b\xec\x7f\x35\xa8\xff\x28\x31\x07\x2d\x12\xc8\xdc\x88\x46\x7c\x8a\x5b\x22'); + SearchConfigProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.searchServer = []; + object.oneboxService = []; + } + if (options.defaults) { + object.kmlSearchUrl = null; + object.kmlRenderUrl = null; + object.searchHistoryUrl = null; + object.errorPageUrl = null; + } + if (message.searchServer !== undefined && message.searchServer !== null && message.hasOwnProperty("searchServer")) { + object.searchServer = []; + for (var j = 0; j < message.searchServer.length; ++j) + object.searchServer[j] = $types[0].toObject(message.searchServer[j], options); + } + if (message.oneboxService !== undefined && message.oneboxService !== null && message.hasOwnProperty("oneboxService")) { + object.oneboxService = []; + for (var j = 0; j < message.oneboxService.length; ++j) + object.oneboxService[j] = $types[1].toObject(message.oneboxService[j], options); + } + if (message.kmlSearchUrl !== undefined && message.kmlSearchUrl !== null && message.hasOwnProperty("kmlSearchUrl")) + object.kmlSearchUrl = $types[2].toObject(message.kmlSearchUrl, options); + if (message.kmlRenderUrl !== undefined && message.kmlRenderUrl !== null && message.hasOwnProperty("kmlRenderUrl")) + object.kmlRenderUrl = $types[3].toObject(message.kmlRenderUrl, options); + if (message.searchHistoryUrl !== undefined && message.searchHistoryUrl !== null && message.hasOwnProperty("searchHistoryUrl")) + object.searchHistoryUrl = $types[4].toObject(message.searchHistoryUrl, options); + if (message.errorPageUrl !== undefined && message.errorPageUrl !== null && message.hasOwnProperty("errorPageUrl")) + object.errorPageUrl = $types[5].toObject(message.errorPageUrl, options); + return object; + }; - /** - * Provides metadata using the Google Earth Enterprise REST API. This is used by the GoogleEarthEnterpriseImageryProvider - * and GoogleEarthEnterpriseTerrainProvider to share metadata requests. - * - * @alias GoogleEarthEnterpriseMetadata - * @constructor - * - * @param {Object} options Object with the following properties: - * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. - * @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 GoogleEarthEnterpriseImageryProvider - * @see GoogleEarthEnterpriseTerrainProvider - * - */ - function GoogleEarthEnterpriseMetadata(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - /** - * True if imagery is available. - * @type {Boolean} - * @default true - */ - this.imageryPresent = true; + SearchConfigProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - /** - * True if imagery is sent as a protocol buffer, false if sent as plain images. If undefined we will try both. - * @type {Boolean} - * @default undefined - */ - this.protoImagery = undefined; + SearchConfigProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * True if terrain is available. - * @type {Boolean} - * @default true - */ - this.terrainPresent = true; + SearchConfigProto.SearchServer = (function() { - /** - * Exponent used to compute constant to calculate negative height values. - * @type {Number} - * @default 32 - */ - this.negativeAltitudeExponentBias = 32; + function SearchServer(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - /** - * Threshold where any numbers smaller are actually negative values. They are multiplied by -2^negativeAltitudeExponentBias. - * @type {Number} - * @default EPSILON12 - */ - this.negativeAltitudeThreshold = CesiumMath.EPSILON12; + SearchServer.prototype.name = null; + SearchServer.prototype.url = null; + SearchServer.prototype.type = 0; + SearchServer.prototype.htmlTransformUrl = null; + SearchServer.prototype.kmlTransformUrl = null; + SearchServer.prototype.supplementalUi = null; + SearchServer.prototype.suggestion = $util.emptyArray; + SearchServer.prototype.searchlet = $util.emptyArray; + SearchServer.prototype.requirements = null; + SearchServer.prototype.suggestServer = null; - /** - * Dictionary of provider id to copyright strings. - * @type {Object} - * @default {} - */ - this.providers = {}; + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto", + 1 : "keyhole.dbroot.StringIdOrValueProto", + 2 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.ResultType", + 3 : "keyhole.dbroot.StringIdOrValueProto", + 4 : "keyhole.dbroot.StringIdOrValueProto", + 5 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi", + 6 : "keyhole.dbroot.StringIdOrValueProto", + 7 : "keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto", + 8 : "keyhole.dbroot.RequirementProto", + 9 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - /** - * Key used to decode packets - * @type {ArrayBuffer} - */ - this.key = undefined; + SearchServer.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.name = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.url = $types[1].decode(reader, reader.uint32()); + break; + case 3: + message.type = reader.uint32(); + break; + case 4: + message.htmlTransformUrl = $types[3].decode(reader, reader.uint32()); + break; + case 5: + message.kmlTransformUrl = $types[4].decode(reader, reader.uint32()); + break; + case 6: + message.supplementalUi = $types[5].decode(reader, reader.uint32()); + break; + case 9: + if (!(message.suggestion && message.suggestion.length)) + message.suggestion = []; + message.suggestion.push($types[6].decode(reader, reader.uint32())); + break; + case 7: + if (!(message.searchlet && message.searchlet.length)) + message.searchlet = []; + message.searchlet.push($types[7].decode(reader, reader.uint32())); + break; + case 8: + message.requirements = $types[8].decode(reader, reader.uint32()); + break; + case 10: + message.suggestServer = $types[9].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - this._quadPacketVersion = 1; + SearchServer.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.name !== undefined && message.name !== null) { + var error = $types[0].verify(message.name); + if (error) + return "name." + error; + } + if (message.url !== undefined && message.url !== null) { + var error = $types[1].verify(message.url); + if (error) + return "url." + error; + } + if (message.type !== undefined) + switch (message.type) { + default: + return "type: enum value expected"; + case 0: + case 1: + break; + } + if (message.htmlTransformUrl !== undefined && message.htmlTransformUrl !== null) { + var error = $types[3].verify(message.htmlTransformUrl); + if (error) + return "htmlTransformUrl." + error; + } + if (message.kmlTransformUrl !== undefined && message.kmlTransformUrl !== null) { + var error = $types[4].verify(message.kmlTransformUrl); + if (error) + return "kmlTransformUrl." + error; + } + if (message.supplementalUi !== undefined && message.supplementalUi !== null) { + var error = $types[5].verify(message.supplementalUi); + if (error) + return "supplementalUi." + error; + } + if (message.suggestion !== undefined) { + if (!Array.isArray(message.suggestion)) + return "suggestion: array expected"; + for (var i = 0; i < message.suggestion.length; ++i) { + var error = $types[6].verify(message.suggestion[i]); + if (error) + return "suggestion." + error; + } + } + if (message.searchlet !== undefined) { + if (!Array.isArray(message.searchlet)) + return "searchlet: array expected"; + for (var i = 0; i < message.searchlet.length; ++i) { + var error = $types[7].verify(message.searchlet[i]); + if (error) + return "searchlet." + error; + } + } + if (message.requirements !== undefined && message.requirements !== null) { + var error = $types[8].verify(message.requirements); + if (error) + return "requirements." + error; + } + if (message.suggestServer !== undefined && message.suggestServer !== null) { + var error = $types[9].verify(message.suggestServer); + if (error) + return "suggestServer." + error; + } + return null; + }; - this._url = appendForwardSlash(options.url); - this._proxy = options.proxy; + SearchServer.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer(); + if (object.name !== undefined && object.name !== null) { + if (typeof object.name !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.name: object expected"); + message.name = $types[0].fromObject(object.name); + } + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.url: object expected"); + message.url = $types[1].fromObject(object.url); + } + switch (object.type) { + case "RESULT_TYPE_KML": + case 0: + message.type = 0; + break; + case "RESULT_TYPE_XML": + case 1: + message.type = 1; + break; + } + if (object.htmlTransformUrl !== undefined && object.htmlTransformUrl !== null) { + if (typeof object.htmlTransformUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.htmlTransformUrl: object expected"); + message.htmlTransformUrl = $types[3].fromObject(object.htmlTransformUrl); + } + if (object.kmlTransformUrl !== undefined && object.kmlTransformUrl !== null) { + if (typeof object.kmlTransformUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.kmlTransformUrl: object expected"); + message.kmlTransformUrl = $types[4].fromObject(object.kmlTransformUrl); + } + if (object.supplementalUi !== undefined && object.supplementalUi !== null) { + if (typeof object.supplementalUi !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.supplementalUi: object expected"); + message.supplementalUi = $types[5].fromObject(object.supplementalUi); + } + if (object.suggestion) { + if (!Array.isArray(object.suggestion)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.suggestion: array expected"); + message.suggestion = []; + for (var i = 0; i < object.suggestion.length; ++i) { + if (typeof object.suggestion[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.suggestion: object expected"); + message.suggestion[i] = $types[6].fromObject(object.suggestion[i]); + } + } + if (object.searchlet) { + if (!Array.isArray(object.searchlet)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.searchlet: array expected"); + message.searchlet = []; + for (var i = 0; i < object.searchlet.length; ++i) { + if (typeof object.searchlet[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.searchlet: object expected"); + message.searchlet[i] = $types[7].fromObject(object.searchlet[i]); + } + } + if (object.requirements !== undefined && object.requirements !== null) { + if (typeof object.requirements !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.requirements: object expected"); + message.requirements = $types[8].fromObject(object.requirements); + } + if (object.suggestServer !== undefined && object.suggestServer !== null) { + if (typeof object.suggestServer !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.suggestServer: object expected"); + message.suggestServer = $types[9].fromObject(object.suggestServer); + } + return message; + }; - this._tileInfo = {}; - this._subtreePromises = {}; + SearchServer.from = SearchServer.fromObject; - var that = this; - this._readyPromise = requestDbRoot(this) - .then(function() { - return that.getQuadTreePacket('', that._quadPacketVersion, false); - }) - .then(function() { - return true; - }) - .otherwise(function(e) { - var message = 'An error occurred while accessing ' + getMetadataUrl(that, '', 1) + '.'; - return when.reject(new RuntimeError(message)); - }); - } + SearchServer.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.suggestion = []; + object.searchlet = []; + } + if (options.defaults) { + object.name = null; + object.url = null; + object.type = options.enums === String ? "RESULT_TYPE_KML" : 0; + object.htmlTransformUrl = null; + object.kmlTransformUrl = null; + object.supplementalUi = null; + object.requirements = null; + object.suggestServer = null; + } + if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) + object.name = $types[0].toObject(message.name, options); + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[1].toObject(message.url, options); + if (message.type !== undefined && message.type !== null && message.hasOwnProperty("type")) + object.type = options.enums === String ? $types[2][message.type] : message.type; + if (message.htmlTransformUrl !== undefined && message.htmlTransformUrl !== null && message.hasOwnProperty("htmlTransformUrl")) + object.htmlTransformUrl = $types[3].toObject(message.htmlTransformUrl, options); + if (message.kmlTransformUrl !== undefined && message.kmlTransformUrl !== null && message.hasOwnProperty("kmlTransformUrl")) + object.kmlTransformUrl = $types[4].toObject(message.kmlTransformUrl, options); + if (message.supplementalUi !== undefined && message.supplementalUi !== null && message.hasOwnProperty("supplementalUi")) + object.supplementalUi = $types[5].toObject(message.supplementalUi, options); + if (message.suggestion !== undefined && message.suggestion !== null && message.hasOwnProperty("suggestion")) { + object.suggestion = []; + for (var j = 0; j < message.suggestion.length; ++j) + object.suggestion[j] = $types[6].toObject(message.suggestion[j], options); + } + if (message.searchlet !== undefined && message.searchlet !== null && message.hasOwnProperty("searchlet")) { + object.searchlet = []; + for (var j = 0; j < message.searchlet.length; ++j) + object.searchlet[j] = $types[7].toObject(message.searchlet[j], options); + } + if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) + object.requirements = $types[8].toObject(message.requirements, options); + if (message.suggestServer !== undefined && message.suggestServer !== null && message.hasOwnProperty("suggestServer")) + object.suggestServer = $types[9].toObject(message.suggestServer, options); + return object; + }; - defineProperties(GoogleEarthEnterpriseMetadata.prototype, { - /** - * Gets the name of the Google Earth Enterprise server. - * @memberof GoogleEarthEnterpriseMetadata.prototype - * @type {String} - * @readonly - */ - url : { - get : function() { - return this._url; - } - }, + SearchServer.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - /** - * Gets the proxy used for metadata requests. - * @memberof GoogleEarthEnterpriseImageryProvider.prototype - * @type {Proxy} - * @readonly - */ - proxy : { - get : function() { - return this._proxy; - } - }, + SearchServer.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * Gets a promise that resolves to true when the metadata is ready for use. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Promise.} - * @readonly - */ - readyPromise : { - get : function() { - return this._readyPromise; - } - } - }); + SearchServer.ResultType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["RESULT_TYPE_KML"] = 0; + values["RESULT_TYPE_XML"] = 1; + return values; + })(); - /** - * Converts a tiles (x, y, level) position into a quadkey used to request an image - * from a Google Earth Enterprise 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 GoogleEarthEnterpriseMetadata#quadKeyToTileXY - */ - GoogleEarthEnterpriseMetadata.tileXYToQuadKey = function(x, y, level) { - var quadkey = ''; - for (var i = level; i >= 0; --i) { - var bitmask = 1 << i; - var digit = 0; + SearchServer.SupplementalUi = (function() { - // Tile Layout - // ___ ___ - //| | | - //| 3 | 2 | - //|-------| - //| 0 | 1 | - //|___|___| - // + function SupplementalUi(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - if (!isBitSet(y, bitmask)) { // Top Row - digit |= 2; - if (!isBitSet(x, bitmask)) { // Right to left - digit |= 1; - } - } else { - if (isBitSet(x, bitmask)) { // Left to right - digit |= 1; - } - } + SupplementalUi.prototype.url = null; + SupplementalUi.prototype.label = null; + SupplementalUi.prototype.height = 160; - quadkey += digit; - } - return quadkey; - }; + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto", + 1 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - /** - * Converts a tile's quadkey used to request an image from a Google Earth Enterprise server into the - * (x, y, level) position. - * - * @param {String} quadkey The tile's quad key - * - * @see GoogleEarthEnterpriseMetadata#tileXYToQuadKey - */ - GoogleEarthEnterpriseMetadata.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]; + SupplementalUi.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.url = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.label = $types[1].decode(reader, reader.uint32()); + break; + case 3: + message.height = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - if (isBitSet(digit, 2)) { // Top Row - if (!isBitSet(digit, 1)) { // // Right to left - x |= bitmask; - } - } else { - y |= bitmask; - if (isBitSet(digit, 1)) { // Left to right - x |= bitmask; - } - } - } - return { - x : x, - y : y, - level : level - }; - }; + SupplementalUi.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.url !== undefined && message.url !== null) { + var error = $types[0].verify(message.url); + if (error) + return "url." + error; + } + if (message.label !== undefined && message.label !== null) { + var error = $types[1].verify(message.label); + if (error) + return "label." + error; + } + if (message.height !== undefined) + if (!$util.isInteger(message.height)) + return "height: integer expected"; + return null; + }; - GoogleEarthEnterpriseMetadata.prototype.isValid = function(quadKey) { - var info = this.getTileInformationFromQuadKey(quadKey); - if (defined(info)) { - return info !== null; - } + SupplementalUi.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi(); + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi.url: object expected"); + message.url = $types[0].fromObject(object.url); + } + if (object.label !== undefined && object.label !== null) { + if (typeof object.label !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SupplementalUi.label: object expected"); + message.label = $types[1].fromObject(object.label); + } + if (object.height !== undefined && object.height !== null) + message.height = object.height | 0; + return message; + }; - var valid = true; - var q = quadKey; - var last; - while (q.length > 1) { - last = q.substring(q.length - 1); - q = q.substring(0, q.length - 1); - info = this.getTileInformationFromQuadKey(q); - if (defined(info)) { - if (!info.hasSubtree() && - !info.hasChild(parseInt(last))) { - // We have no subtree or child available at some point in this node's ancestry - valid = false; - } + SupplementalUi.from = SupplementalUi.fromObject; - break; - } else if (info === null) { - // Some node in the ancestry was loaded and said there wasn't a subtree - valid = false; - break; - } - } + SupplementalUi.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.url = null; + object.label = null; + object.height = 160; + } + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[0].toObject(message.url, options); + if (message.label !== undefined && message.label !== null && message.hasOwnProperty("label")) + object.label = $types[1].toObject(message.label, options); + if (message.height !== undefined && message.height !== null && message.hasOwnProperty("height")) + object.height = message.height; + return object; + }; - return valid; - }; + SupplementalUi.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); + SupplementalUi.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * Retrieves a Google Earth Enterprise quadtree packet. - * - * @param {String} [quadKey=''] The quadkey to retrieve the packet for. - * @param {Number} [version=1] The cnode version to be used in the request. - * @param {Boolean} [throttle=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. - * - * @private - */ - GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version, throttle) { - version = defaultValue(version, 1); - quadKey = defaultValue(quadKey, ''); - throttle = defaultValue(throttle, true); - var url = getMetadataUrl(this, quadKey, version); - var proxy = this._proxy; - if (defined(proxy)) { - url = proxy.getURL(url); - } + return SupplementalUi; + })(); - var promise; - if (throttle) { - promise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(promise)) { - return undefined; - } - } else { - promise = loadArrayBuffer(url); - } + SearchServer.SearchletProto = (function() { - var tileInfo = this._tileInfo; - var key = this.key; - return promise - .then(function(metadata) { - var decodePromise = taskProcessor.scheduleTask({ - buffer : metadata, - quadKey : quadKey, - type : 'Metadata', - key: key - }, [metadata]); + function SearchletProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - return decodePromise - .then(function(result) { - var root; - var topLevelKeyLength = -1; - if (quadKey !== '') { - // Root tile has no data except children bits, so put them into the tile info - topLevelKeyLength = quadKey.length + 1; - var top = result[quadKey]; - root = tileInfo[quadKey]; - root._bits |= top._bits; + SearchletProto.prototype.url = null; + SearchletProto.prototype.name = null; + SearchletProto.prototype.requirements = null; - delete result[quadKey]; - } + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto", + 1 : "keyhole.dbroot.StringIdOrValueProto", + 2 : "keyhole.dbroot.RequirementProto" + }; + $lazyTypes.push($types); - // Copy the resulting objects into tileInfo - // Make sure we start with shorter quadkeys first, so we know the parents have - // already been processed. Otherwise we can lose ancestorHasTerrain along the way. - var keys = Object.keys(result); - keys.sort(function(a, b) { - return a.length - b.length; - }); - var keysLength = keys.length; - for (var i = 0; i < keysLength; ++i) { - var key = keys[i]; - var r = result[key]; - if (r !== null) { - var info = GoogleEarthEnterpriseTileInformation.clone(result[key]); - var keyLength = key.length; - if (keyLength === topLevelKeyLength) { - info.setParent(root); - } else if(keyLength > 1){ - var parent = tileInfo[key.substring(0, key.length - 1)]; - info.setParent(parent); + SearchletProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.url = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.name = $types[1].decode(reader, reader.uint32()); + break; + case 3: + message.requirements = $types[2].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } } - tileInfo[key] = info; - } else { - tileInfo[key] = null; - } - } - }); - }); - }; - - /** - * Populates the metadata subtree down to the specified tile. - * - * @param {Number} x The tile X coordinate. - * @param {Number} y The tile Y coordinate. - * @param {Number} level The tile level. - * @param {Boolean} [throttle=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} A promise that resolves to the tile info for the requested quad key - * - * @private - */ - GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level, throttle) { - throttle = defaultValue(throttle, true); - var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - return populateSubtree(this, quadkey, throttle); - }; + return message; + }; - function populateSubtree(that, quadKey, throttle) { - var tileInfo = that._tileInfo; - var q = quadKey; - var t = tileInfo[q]; - // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded - if (defined(t) && (!t.hasSubtree() || t.hasChildren())) { - return t; - } + SearchletProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.url !== undefined && message.url !== null) { + var error = $types[0].verify(message.url); + if (error) + return "url." + error; + } + if (message.name !== undefined && message.name !== null) { + var error = $types[1].verify(message.name); + if (error) + return "name." + error; + } + if (message.requirements !== undefined && message.requirements !== null) { + var error = $types[2].verify(message.requirements); + if (error) + return "requirements." + error; + } + return null; + }; - while ((t === undefined) && q.length > 1) { - q = q.substring(0, q.length - 1); - t = tileInfo[q]; - } + SearchletProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto(); + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto.url: object expected"); + message.url = $types[0].fromObject(object.url); + } + if (object.name !== undefined && object.name !== null) { + if (typeof object.name !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto.name: object expected"); + message.name = $types[1].fromObject(object.name); + } + if (object.requirements !== undefined && object.requirements !== null) { + if (typeof object.requirements !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.SearchServer.SearchletProto.requirements: object expected"); + message.requirements = $types[2].fromObject(object.requirements); + } + return message; + }; - var subtreePromises = that._subtreePromises; - var promise = subtreePromises[q]; - if (defined(promise)) { - return promise - .then(function() { - // Recursively call this incase we need multiple subtree requests - return populateSubtree(that, quadKey, throttle); - }); - } + SearchletProto.from = SearchletProto.fromObject; - // t is either - // null so one of its parents was a leaf node, so this tile doesn't exist - // exists but doesn't have a subtree to request - // undefined so no parent exists - this shouldn't ever happen once the provider is ready - if (!defined(t) || !t.hasSubtree()) { - return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); - } + SearchletProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.url = null; + object.name = null; + object.requirements = null; + } + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[0].toObject(message.url, options); + if (message.name !== undefined && message.name !== null && message.hasOwnProperty("name")) + object.name = $types[1].toObject(message.name, options); + if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) + object.requirements = $types[2].toObject(message.requirements, options); + return object; + }; - // We need to split up the promise here because when will execute syncronously if getQuadTreePacket - // is already resolved (like in the tests), so subtreePromises will never get cleared out. - // Only the initial request will also remove the promise from subtreePromises. - promise = that.getQuadTreePacket(q, t.cnodeVersion, throttle); - if (!defined(promise)) { - return undefined; - } - subtreePromises[q] = promise; + SearchletProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - return promise - .then(function() { - // Recursively call this incase we need multiple subtree requests - return populateSubtree(that, quadKey, throttle); - }) - .always(function() { - delete subtreePromises[q]; - }); - } + SearchletProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * Gets information about a tile - * - * @param {Number} x The tile X coordinate. - * @param {Number} y The tile Y coordinate. - * @param {Number} level The tile level. - * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. - * - * @private - */ - GoogleEarthEnterpriseMetadata.prototype.getTileInformation = function(x, y, level) { - var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - return this._tileInfo[quadkey]; - }; + return SearchletProto; + })(); - /** - * Gets information about a tile from a quadKey - * - * @param {String} quadkey The quadkey for the tile - * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. - * - * @private - */ - GoogleEarthEnterpriseMetadata.prototype.getTileInformationFromQuadKey = function(quadkey) { - return this._tileInfo[quadkey]; - }; + return SearchServer; + })(); - function getMetadataUrl(that, quadKey, version) { - return joinUrls(that._url, 'flatfile?q2-0' + quadKey + '-q.' + version.toString()); - } + SearchConfigProto.OneboxServiceProto = (function() { - function requestDbRoot(that) { - var url = joinUrls(that._url, 'dbRoot.v5?output=proto'); - var proxy = that._proxy; - if (defined(proxy)) { - url = proxy.getURL(url); - } + function OneboxServiceProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - var promise = loadArrayBuffer(url) - .then(function(buf) { - var encryptedDbRootProto = dbrootParser.EncryptedDbRootProto.decode(new Uint8Array(buf)); + OneboxServiceProto.prototype.serviceUrl = null; + OneboxServiceProto.prototype.requirements = null; - var byteArray = encryptedDbRootProto.encryptionData; - var offset = byteArray.byteOffset; - var end = offset + byteArray.byteLength; - var key = that.key = byteArray.buffer.slice(offset, end); + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto", + 1 : "keyhole.dbroot.RequirementProto" + }; + $lazyTypes.push($types); - byteArray = encryptedDbRootProto.dbrootData; - offset = byteArray.byteOffset; - end = offset + byteArray.byteLength; - var dbRootCompressed = byteArray.buffer.slice(offset, end); - return taskProcessor.scheduleTask({ - buffer : dbRootCompressed, - type : 'DbRoot', - key: key - }, [dbRootCompressed]); - }) - .then(function(result) { - var dbRoot = dbrootParser.DbRootProto.decode(new Uint8Array(result.buffer)); - that.imageryPresent = defaultValue(dbRoot.imageryPresent, that.imageryPresent); - that.protoImagery = dbRoot.protoImagery; - that.terrainPresent = defaultValue(dbRoot.terrainPresent, that.terrainPresent); - if (defined(dbRoot.endSnippet) && defined(dbRoot.endSnippet.model)) { - var model = dbRoot.endSnippet.model; - that.negativeAltitudeExponentBias = defaultValue(model.negativeAltitudeExponentBias, that.negativeAltitudeExponentBias); - that.negativeAltitudeThreshold = defaultValue(model.compressedNegativeAltitudeThreshold, that.negativeAltitudeThreshold); - } - if (defined(dbRoot.databaseVersion)) { - that._quadPacketVersion = defaultValue(dbRoot.databaseVersion.quadtreeVersion, that._quadPacketVersion); - } - var providers = that.providers; - var providerInfo = defaultValue(dbRoot.providerInfo, []); - var count = providerInfo.length; - for(var i=0;i>> 3) { + case 1: + message.serviceUrl = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.requirements = $types[1].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + OneboxServiceProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.serviceUrl !== undefined && message.serviceUrl !== null) { + var error = $types[0].verify(message.serviceUrl); + if (error) + return "serviceUrl." + error; + } + if (message.requirements !== undefined && message.requirements !== null) { + var error = $types[1].verify(message.requirements); + if (error) + return "requirements." + error; + } + return null; + }; - return promise; - } + OneboxServiceProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto(); + if (object.serviceUrl !== undefined && object.serviceUrl !== null) { + if (typeof object.serviceUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto.serviceUrl: object expected"); + message.serviceUrl = $types[0].fromObject(object.serviceUrl); + } + if (object.requirements !== undefined && object.requirements !== null) { + if (typeof object.requirements !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.SearchConfigProto.OneboxServiceProto.requirements: object expected"); + message.requirements = $types[1].fromObject(object.requirements); + } + return message; + }; - return GoogleEarthEnterpriseMetadata; -}); + OneboxServiceProto.from = OneboxServiceProto.fromObject; -/*global define*/ -define('Core/GoogleEarthEnterpriseTerrainData',[ - './BoundingSphere', - './Cartesian2', - './Cartesian3', - './Check', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './IndexDatatype', - './Intersections2D', - './Math', - './OrientedBoundingBox', - './QuantizedMeshTerrainData', - './Rectangle', - './TaskProcessor', - './TerrainEncoding', - './TerrainMesh' -], function( - BoundingSphere, - Cartesian2, - Cartesian3, - Check, - defaultValue, - defined, - defineProperties, - DeveloperError, - IndexDatatype, - Intersections2D, - CesiumMath, - OrientedBoundingBox, - QuantizedMeshTerrainData, - Rectangle, - TaskProcessor, - TerrainEncoding, - TerrainMesh) { - 'use strict'; + OneboxServiceProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.serviceUrl = null; + object.requirements = null; + } + if (message.serviceUrl !== undefined && message.serviceUrl !== null && message.hasOwnProperty("serviceUrl")) + object.serviceUrl = $types[0].toObject(message.serviceUrl, options); + if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) + object.requirements = $types[1].toObject(message.requirements, options); + return object; + }; - /** - * Terrain data for a single tile from a Google Earth Enterprise server. - * - * @alias GoogleEarthEnterpriseTerrainData - * @constructor - * - * @param {Object} options Object with the following properties: - * @param {ArrayBuffer} options.buffer The buffer containing terrain data. - * @param {Number} options.negativeAltitudeExponentBias Multiplier for negative terrain heights that are encoded as very small positive values. - * @param {Number} options.negativeElevationThreshold Threshold for negative values - * @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 PositionBit ValueChild Tile
    01Southwest
    12Southeast
    24Northeast
    38Northwest
    - * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance; - * otherwise, false. - * @param {Credit[]} [options.credits] Array of credits for this tile. - * - * - * @example - * var buffer = ... - * var childTileMask = ... - * var terrainData = new Cesium.GoogleEarthEnterpriseTerrainData({ - * buffer : heightBuffer, - * childTileMask : childTileMask - * }); - * - * @see TerrainData - * @see HeightTerrainData - * @see QuantizedMeshTerrainData - */ - function GoogleEarthEnterpriseTerrainData(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - this._buffer = options.buffer; - this._credits = options.credits; - this._negativeAltitudeExponentBias = options.negativeAltitudeExponentBias; - this._negativeElevationThreshold = options.negativeElevationThreshold; + OneboxServiceProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - // Convert from google layout to layout of other providers - // 3 2 -> 2 3 - // 0 1 -> 0 1 - var googleChildTileMask = defaultValue(options.childTileMask, 15); - var childTileMask = googleChildTileMask & 3; // Bottom row is identical - childTileMask |= (googleChildTileMask & 4) ? 8 : 0; // NE - childTileMask |= (googleChildTileMask & 8) ? 4 : 0; // NW + OneboxServiceProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - this._childTileMask = childTileMask; + return OneboxServiceProto; + })(); - this._createdByUpsampling = defaultValue(options.createdByUpsampling, false); + return SearchConfigProto; + })(); - this._skirtHeight = undefined; - this._bufferType = this._buffer.constructor; - this._mesh = undefined; - this._minimumHeight = undefined; - this._maximumHeight = undefined; - this._vertexCountWithoutSkirts = undefined; - this._skirtIndex = undefined; - } + EndSnippetProto.SearchInfoProto = (function() { - defineProperties(GoogleEarthEnterpriseTerrainData.prototype, { - /** - * An array of credits for this tile - * @memberof GoogleEarthEnterpriseTerrainData.prototype - * @type {Credit[]} - */ - credits : { - get : function() { - return this._credits; - } - }, - /** - * 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 GoogleEarthEnterpriseTerrainData.prototype - * @type {Uint8Array|Image|Canvas} - */ - waterMask : { - get : function() { - return undefined; - } - } - }); + function SearchInfoProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - var taskProcessor = new TaskProcessor('createVerticesFromGoogleEarthEnterpriseBuffer'); + SearchInfoProto.prototype.defaultUrl = "http://maps.google.com/maps"; + SearchInfoProto.prototype.geocodeParam = "q"; - var nativeRectangleScratch = new Rectangle(); - var rectangleScratch = new Rectangle(); + SearchInfoProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.SearchInfoProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.defaultUrl = reader.string(); + break; + case 2: + message.geocodeParam = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - /** - * 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. - */ - GoogleEarthEnterpriseTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) { - - var ellipsoid = tilingScheme.ellipsoid; - tilingScheme.tileXYToNativeRectangle(x, y, level, nativeRectangleScratch); - tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch); - exaggeration = defaultValue(exaggeration, 1.0); + SearchInfoProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.defaultUrl !== undefined) + if (!$util.isString(message.defaultUrl)) + return "defaultUrl: string expected"; + if (message.geocodeParam !== undefined) + if (!$util.isString(message.geocodeParam)) + return "geocodeParam: string expected"; + return null; + }; - // Compute the center of the tile for RTC rendering. - var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangleScratch)); + SearchInfoProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.SearchInfoProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.SearchInfoProto(); + if (object.defaultUrl !== undefined && object.defaultUrl !== null) + message.defaultUrl = String(object.defaultUrl); + if (object.geocodeParam !== undefined && object.geocodeParam !== null) + message.geocodeParam = String(object.geocodeParam); + return message; + }; - var levelZeroMaxError = 40075.16; // From Google's Doc - var thisLevelMaxError = levelZeroMaxError / (1 << level); - this._skirtHeight = Math.min(thisLevelMaxError * 8.0, 1000.0); + SearchInfoProto.from = SearchInfoProto.fromObject; - var verticesPromise = taskProcessor.scheduleTask({ - buffer : this._buffer, - nativeRectangle : nativeRectangleScratch, - rectangle : rectangleScratch, - relativeToCenter : center, - ellipsoid : ellipsoid, - skirtHeight : this._skirtHeight, - exaggeration : exaggeration, - includeWebMercatorT : true, - negativeAltitudeExponentBias: this._negativeAltitudeExponentBias, - negativeElevationThreshold: this._negativeElevationThreshold - }); + SearchInfoProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.defaultUrl = "http://maps.google.com/maps"; + object.geocodeParam = "q"; + } + if (message.defaultUrl !== undefined && message.defaultUrl !== null && message.hasOwnProperty("defaultUrl")) + object.defaultUrl = message.defaultUrl; + if (message.geocodeParam !== undefined && message.geocodeParam !== null && message.hasOwnProperty("geocodeParam")) + object.geocodeParam = message.geocodeParam; + return object; + }; - if (!defined(verticesPromise)) { - // Postponed - return undefined; - } + SearchInfoProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - var that = this; - return verticesPromise - .then(function(result) { - that._mesh = new TerrainMesh( - center, - new Float32Array(result.vertices), - new Uint16Array(result.indices), - result.minimumHeight, - result.maximumHeight, - result.boundingSphere3D, - result.occludeePointInScaledSpace, - result.numberOfAttributes, - result.orientedBoundingBox, - TerrainEncoding.clone(result.encoding), - exaggeration); + SearchInfoProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts; - that._skirtIndex = result.skirtIndex; - that._minimumHeight = result.minimumHeight; - that._maximumHeight = result.maximumHeight; + return SearchInfoProto; + })(); - // Free memory received from server after mesh is created. - that._buffer = undefined; - return that._mesh; - }); - }; + EndSnippetProto.RockTreeDataProto = (function() { - /** - * 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. - */ - GoogleEarthEnterpriseTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) { - var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0); - var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0); + function RockTreeDataProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - if (!defined(this._mesh)) { - return interpolateHeight(this, u, v, rectangle); - } + RockTreeDataProto.prototype.url = null; - return interpolateMeshHeight(this, u, v); - }; + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh'); + RockTreeDataProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.RockTreeDataProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.url = $types[0].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - /** - * 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. - */ - GoogleEarthEnterpriseTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) { - - var mesh = this._mesh; - if (!defined(this._mesh)) { - return undefined; - } + RockTreeDataProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.url !== undefined && message.url !== null) { + var error = $types[0].verify(message.url); + if (error) + return "url." + error; + } + return null; + }; - var isEastChild = thisX * 2 !== descendantX; - var isNorthChild = thisY * 2 === descendantY; + RockTreeDataProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.RockTreeDataProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.RockTreeDataProto(); + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.RockTreeDataProto.url: object expected"); + message.url = $types[0].fromObject(object.url); + } + return message; + }; - var ellipsoid = tilingScheme.ellipsoid; - var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel); + RockTreeDataProto.from = RockTreeDataProto.fromObject; - 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 - }); + RockTreeDataProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.url = null; + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[0].toObject(message.url, options); + return object; + }; - if (!defined(upsamplePromise)) { - // Postponed - return undefined; - } + RockTreeDataProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - var that = this; - return upsamplePromise - .then(function(result) { - var quantizedVertices = new Uint16Array(result.vertices); - var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); + RockTreeDataProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - var skirtHeight = that._skirtHeight; + return RockTreeDataProto; + })(); - // Use QuantizedMeshTerrainData since we have what we need already parsed - return new QuantizedMeshTerrainData({ - quantizedVertices : quantizedVertices, - indices : indicesTypedArray, - 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 : skirtHeight, - southSkirtHeight : skirtHeight, - eastSkirtHeight : skirtHeight, - northSkirtHeight : skirtHeight, - childTileMask : 0, - createdByUpsampling : true, - credits : that._credits - }); - }); - }; + EndSnippetProto.FilmstripConfigProto = (function() { - /** - * 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. - */ - GoogleEarthEnterpriseTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) { - - var bitNumber = 2; // northwest child - if (childX !== thisX * 2) { - ++bitNumber; // east child - } - if (childY !== thisY * 2) { - bitNumber -= 2; // south child - } + function FilmstripConfigProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - return (this._childTileMask & (1 << bitNumber)) !== 0; - }; + FilmstripConfigProto.prototype.requirements = null; + FilmstripConfigProto.prototype.alleycatUrlTemplate = null; + FilmstripConfigProto.prototype.fallbackAlleycatUrlTemplate = null; + FilmstripConfigProto.prototype.metadataUrlTemplate = null; + FilmstripConfigProto.prototype.thumbnailUrlTemplate = null; + FilmstripConfigProto.prototype.kmlUrlTemplate = null; + FilmstripConfigProto.prototype.featuredToursUrl = null; + FilmstripConfigProto.prototype.enableViewportFallback = false; + FilmstripConfigProto.prototype.viewportFallbackDistance = 0; + FilmstripConfigProto.prototype.imageryType = $util.emptyArray; - /** - * 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. - */ - GoogleEarthEnterpriseTerrainData.prototype.wasCreatedByUpsampling = function() { - return this._createdByUpsampling; - }; + var $types = { + 0 : "keyhole.dbroot.RequirementProto", + 1 : "keyhole.dbroot.StringIdOrValueProto", + 2 : "keyhole.dbroot.StringIdOrValueProto", + 3 : "keyhole.dbroot.StringIdOrValueProto", + 4 : "keyhole.dbroot.StringIdOrValueProto", + 5 : "keyhole.dbroot.StringIdOrValueProto", + 6 : "keyhole.dbroot.StringIdOrValueProto", + 9 : "keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto" + }; + $lazyTypes.push($types); - var texCoordScratch0 = new Cartesian2(); - var texCoordScratch1 = new Cartesian2(); - var texCoordScratch2 = new Cartesian2(); - var barycentricCoordinateScratch = new Cartesian3(); + FilmstripConfigProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.requirements = $types[0].decode(reader, reader.uint32()); + break; + case 2: + message.alleycatUrlTemplate = $types[1].decode(reader, reader.uint32()); + break; + case 9: + message.fallbackAlleycatUrlTemplate = $types[2].decode(reader, reader.uint32()); + break; + case 3: + message.metadataUrlTemplate = $types[3].decode(reader, reader.uint32()); + break; + case 4: + message.thumbnailUrlTemplate = $types[4].decode(reader, reader.uint32()); + break; + case 5: + message.kmlUrlTemplate = $types[5].decode(reader, reader.uint32()); + break; + case 6: + message.featuredToursUrl = $types[6].decode(reader, reader.uint32()); + break; + case 7: + message.enableViewportFallback = reader.bool(); + break; + case 8: + message.viewportFallbackDistance = reader.uint32(); + break; + case 10: + if (!(message.imageryType && message.imageryType.length)) + message.imageryType = []; + message.imageryType.push($types[9].decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - function interpolateMeshHeight(terrainData, u, v) { - var mesh = terrainData._mesh; - var vertices = mesh.vertices; - var encoding = mesh.encoding; - var indices = mesh.indices; + FilmstripConfigProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.requirements !== undefined && message.requirements !== null) { + var error = $types[0].verify(message.requirements); + if (error) + return "requirements." + error; + } + if (message.alleycatUrlTemplate !== undefined && message.alleycatUrlTemplate !== null) { + var error = $types[1].verify(message.alleycatUrlTemplate); + if (error) + return "alleycatUrlTemplate." + error; + } + if (message.fallbackAlleycatUrlTemplate !== undefined && message.fallbackAlleycatUrlTemplate !== null) { + var error = $types[2].verify(message.fallbackAlleycatUrlTemplate); + if (error) + return "fallbackAlleycatUrlTemplate." + error; + } + if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null) { + var error = $types[3].verify(message.metadataUrlTemplate); + if (error) + return "metadataUrlTemplate." + error; + } + if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null) { + var error = $types[4].verify(message.thumbnailUrlTemplate); + if (error) + return "thumbnailUrlTemplate." + error; + } + if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null) { + var error = $types[5].verify(message.kmlUrlTemplate); + if (error) + return "kmlUrlTemplate." + error; + } + if (message.featuredToursUrl !== undefined && message.featuredToursUrl !== null) { + var error = $types[6].verify(message.featuredToursUrl); + if (error) + return "featuredToursUrl." + error; + } + if (message.enableViewportFallback !== undefined) + if (typeof message.enableViewportFallback !== "boolean") + return "enableViewportFallback: boolean expected"; + if (message.viewportFallbackDistance !== undefined) + if (!$util.isInteger(message.viewportFallbackDistance)) + return "viewportFallbackDistance: integer expected"; + if (message.imageryType !== undefined) { + if (!Array.isArray(message.imageryType)) + return "imageryType: array expected"; + for (var i = 0; i < message.imageryType.length; ++i) { + var error = $types[9].verify(message.imageryType[i]); + if (error) + return "imageryType." + error; + } + } + return null; + }; - 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]; + FilmstripConfigProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto(); + if (object.requirements !== undefined && object.requirements !== null) { + if (typeof object.requirements !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.requirements: object expected"); + message.requirements = $types[0].fromObject(object.requirements); + } + if (object.alleycatUrlTemplate !== undefined && object.alleycatUrlTemplate !== null) { + if (typeof object.alleycatUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.alleycatUrlTemplate: object expected"); + message.alleycatUrlTemplate = $types[1].fromObject(object.alleycatUrlTemplate); + } + if (object.fallbackAlleycatUrlTemplate !== undefined && object.fallbackAlleycatUrlTemplate !== null) { + if (typeof object.fallbackAlleycatUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.fallbackAlleycatUrlTemplate: object expected"); + message.fallbackAlleycatUrlTemplate = $types[2].fromObject(object.fallbackAlleycatUrlTemplate); + } + if (object.metadataUrlTemplate !== undefined && object.metadataUrlTemplate !== null) { + if (typeof object.metadataUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.metadataUrlTemplate: object expected"); + message.metadataUrlTemplate = $types[3].fromObject(object.metadataUrlTemplate); + } + if (object.thumbnailUrlTemplate !== undefined && object.thumbnailUrlTemplate !== null) { + if (typeof object.thumbnailUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.thumbnailUrlTemplate: object expected"); + message.thumbnailUrlTemplate = $types[4].fromObject(object.thumbnailUrlTemplate); + } + if (object.kmlUrlTemplate !== undefined && object.kmlUrlTemplate !== null) { + if (typeof object.kmlUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.kmlUrlTemplate: object expected"); + message.kmlUrlTemplate = $types[5].fromObject(object.kmlUrlTemplate); + } + if (object.featuredToursUrl !== undefined && object.featuredToursUrl !== null) { + if (typeof object.featuredToursUrl !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.featuredToursUrl: object expected"); + message.featuredToursUrl = $types[6].fromObject(object.featuredToursUrl); + } + if (object.enableViewportFallback !== undefined && object.enableViewportFallback !== null) + message.enableViewportFallback = Boolean(object.enableViewportFallback); + if (object.viewportFallbackDistance !== undefined && object.viewportFallbackDistance !== null) + message.viewportFallbackDistance = object.viewportFallbackDistance >>> 0; + if (object.imageryType) { + if (!Array.isArray(object.imageryType)) + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.imageryType: array expected"); + message.imageryType = []; + for (var i = 0; i < object.imageryType.length; ++i) { + if (typeof object.imageryType[i] !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.imageryType: object expected"); + message.imageryType[i] = $types[9].fromObject(object.imageryType[i]); + } + } + return message; + }; - var uv0 = encoding.decodeTextureCoordinates(vertices, i0, texCoordScratch0); - var uv1 = encoding.decodeTextureCoordinates(vertices, i1, texCoordScratch1); - var uv2 = encoding.decodeTextureCoordinates(vertices, i2, texCoordScratch2); + FilmstripConfigProto.from = FilmstripConfigProto.fromObject; - 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; - } - } + FilmstripConfigProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.imageryType = []; + if (options.defaults) { + object.requirements = null; + object.alleycatUrlTemplate = null; + object.fallbackAlleycatUrlTemplate = null; + object.metadataUrlTemplate = null; + object.thumbnailUrlTemplate = null; + object.kmlUrlTemplate = null; + object.featuredToursUrl = null; + object.enableViewportFallback = false; + object.viewportFallbackDistance = 0; + } + if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) + object.requirements = $types[0].toObject(message.requirements, options); + if (message.alleycatUrlTemplate !== undefined && message.alleycatUrlTemplate !== null && message.hasOwnProperty("alleycatUrlTemplate")) + object.alleycatUrlTemplate = $types[1].toObject(message.alleycatUrlTemplate, options); + if (message.fallbackAlleycatUrlTemplate !== undefined && message.fallbackAlleycatUrlTemplate !== null && message.hasOwnProperty("fallbackAlleycatUrlTemplate")) + object.fallbackAlleycatUrlTemplate = $types[2].toObject(message.fallbackAlleycatUrlTemplate, options); + if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null && message.hasOwnProperty("metadataUrlTemplate")) + object.metadataUrlTemplate = $types[3].toObject(message.metadataUrlTemplate, options); + if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null && message.hasOwnProperty("thumbnailUrlTemplate")) + object.thumbnailUrlTemplate = $types[4].toObject(message.thumbnailUrlTemplate, options); + if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null && message.hasOwnProperty("kmlUrlTemplate")) + object.kmlUrlTemplate = $types[5].toObject(message.kmlUrlTemplate, options); + if (message.featuredToursUrl !== undefined && message.featuredToursUrl !== null && message.hasOwnProperty("featuredToursUrl")) + object.featuredToursUrl = $types[6].toObject(message.featuredToursUrl, options); + if (message.enableViewportFallback !== undefined && message.enableViewportFallback !== null && message.hasOwnProperty("enableViewportFallback")) + object.enableViewportFallback = message.enableViewportFallback; + if (message.viewportFallbackDistance !== undefined && message.viewportFallbackDistance !== null && message.hasOwnProperty("viewportFallbackDistance")) + object.viewportFallbackDistance = message.viewportFallbackDistance; + if (message.imageryType !== undefined && message.imageryType !== null && message.hasOwnProperty("imageryType")) { + object.imageryType = []; + for (var j = 0; j < message.imageryType.length; ++j) + object.imageryType[j] = $types[9].toObject(message.imageryType[j], options); + } + return object; + }; - // Position does not lie in any triangle in this mesh. - return undefined; - } + FilmstripConfigProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; - var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; - var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; + FilmstripConfigProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - function interpolateHeight(terrainData, u, v, rectangle) { - var buffer = terrainData._buffer; - var quad = 0; // SW - var uStart = 0.0; - var vStart = 0.0; - if (v > 0.5) { // Upper row - if (u > 0.5) { // NE - quad = 2; - uStart = 0.5; - } else { // NW - quad = 3; - } - vStart = 0.5; - } else if (u > 0.5) { // SE - quad = 1; - uStart = 0.5; - } + FilmstripConfigProto.AlleycatImageryTypeProto = (function() { - var dv = new DataView(buffer); - var offset = 0; - for (var q = 0; q < quad; ++q) { - offset += dv.getUint32(offset, true); - offset += sizeOfUint32; - } - offset += sizeOfUint32; // Skip length of quad - offset += 2 * sizeOfDouble; // Skip origin + function AlleycatImageryTypeProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - // Read sizes - var xSize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); - offset += sizeOfDouble; - var ySize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); - offset += sizeOfDouble; + AlleycatImageryTypeProto.prototype.imageryTypeId = 0; + AlleycatImageryTypeProto.prototype.imageryTypeLabel = ""; + AlleycatImageryTypeProto.prototype.metadataUrlTemplate = null; + AlleycatImageryTypeProto.prototype.thumbnailUrlTemplate = null; + AlleycatImageryTypeProto.prototype.kmlUrlTemplate = null; - // Samples per quad - var xScale = rectangle.width / xSize / 2; - var yScale = rectangle.height / ySize / 2; + var $types = { + 2 : "keyhole.dbroot.StringIdOrValueProto", + 3 : "keyhole.dbroot.StringIdOrValueProto", + 4 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - // Number of points - var numPoints = dv.getInt32(offset, true); - offset += sizeOfInt32; + AlleycatImageryTypeProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.imageryTypeId = reader.int32(); + break; + case 2: + message.imageryTypeLabel = reader.string(); + break; + case 3: + message.metadataUrlTemplate = $types[2].decode(reader, reader.uint32()); + break; + case 4: + message.thumbnailUrlTemplate = $types[3].decode(reader, reader.uint32()); + break; + case 5: + message.kmlUrlTemplate = $types[4].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - // Number of faces - var numIndices = dv.getInt32(offset, true) * 3; - offset += sizeOfInt32; + AlleycatImageryTypeProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.imageryTypeId !== undefined) + if (!$util.isInteger(message.imageryTypeId)) + return "imageryTypeId: integer expected"; + if (message.imageryTypeLabel !== undefined) + if (!$util.isString(message.imageryTypeLabel)) + return "imageryTypeLabel: string expected"; + if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null) { + var error = $types[2].verify(message.metadataUrlTemplate); + if (error) + return "metadataUrlTemplate." + error; + } + if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null) { + var error = $types[3].verify(message.thumbnailUrlTemplate); + if (error) + return "thumbnailUrlTemplate." + error; + } + if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null) { + var error = $types[4].verify(message.kmlUrlTemplate); + if (error) + return "kmlUrlTemplate." + error; + } + return null; + }; - offset += sizeOfInt32; // Skip Level + AlleycatImageryTypeProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto(); + if (object.imageryTypeId !== undefined && object.imageryTypeId !== null) + message.imageryTypeId = object.imageryTypeId | 0; + if (object.imageryTypeLabel !== undefined && object.imageryTypeLabel !== null) + message.imageryTypeLabel = String(object.imageryTypeLabel); + if (object.metadataUrlTemplate !== undefined && object.metadataUrlTemplate !== null) { + if (typeof object.metadataUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto.metadataUrlTemplate: object expected"); + message.metadataUrlTemplate = $types[2].fromObject(object.metadataUrlTemplate); + } + if (object.thumbnailUrlTemplate !== undefined && object.thumbnailUrlTemplate !== null) { + if (typeof object.thumbnailUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto.thumbnailUrlTemplate: object expected"); + message.thumbnailUrlTemplate = $types[3].fromObject(object.thumbnailUrlTemplate); + } + if (object.kmlUrlTemplate !== undefined && object.kmlUrlTemplate !== null) { + if (typeof object.kmlUrlTemplate !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.FilmstripConfigProto.AlleycatImageryTypeProto.kmlUrlTemplate: object expected"); + message.kmlUrlTemplate = $types[4].fromObject(object.kmlUrlTemplate); + } + return message; + }; - var uBuffer = new Array(numPoints); - var vBuffer = new Array(numPoints); - var heights = new Array(numPoints); - for (var i = 0; i < numPoints; ++i) { - uBuffer[i] = uStart + (dv.getUint8(offset++) * xScale); - vBuffer[i] = vStart + (dv.getUint8(offset++) * yScale); + AlleycatImageryTypeProto.from = AlleycatImageryTypeProto.fromObject; - // Height is stored in units of (1/EarthRadius) or (1/6371010.0) - heights[i] = (dv.getFloat32(offset, true) * 6371010.0); - offset += sizeOfFloat; - } + AlleycatImageryTypeProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.imageryTypeId = 0; + object.imageryTypeLabel = ""; + object.metadataUrlTemplate = null; + object.thumbnailUrlTemplate = null; + object.kmlUrlTemplate = null; + } + if (message.imageryTypeId !== undefined && message.imageryTypeId !== null && message.hasOwnProperty("imageryTypeId")) + object.imageryTypeId = message.imageryTypeId; + if (message.imageryTypeLabel !== undefined && message.imageryTypeLabel !== null && message.hasOwnProperty("imageryTypeLabel")) + object.imageryTypeLabel = message.imageryTypeLabel; + if (message.metadataUrlTemplate !== undefined && message.metadataUrlTemplate !== null && message.hasOwnProperty("metadataUrlTemplate")) + object.metadataUrlTemplate = $types[2].toObject(message.metadataUrlTemplate, options); + if (message.thumbnailUrlTemplate !== undefined && message.thumbnailUrlTemplate !== null && message.hasOwnProperty("thumbnailUrlTemplate")) + object.thumbnailUrlTemplate = $types[3].toObject(message.thumbnailUrlTemplate, options); + if (message.kmlUrlTemplate !== undefined && message.kmlUrlTemplate !== null && message.hasOwnProperty("kmlUrlTemplate")) + object.kmlUrlTemplate = $types[4].toObject(message.kmlUrlTemplate, options); + return object; + }; - var indices = new Array(numIndices); - for (i = 0; i < numIndices; ++i) { - indices[i] = dv.getUint16(offset, true); - offset += sizeOfUint16; - } + AlleycatImageryTypeProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - for (i = 0; i < numIndices; i += 3) { - var i0 = indices[i]; - var i1 = indices[i + 1]; - var i2 = indices[i + 2]; + AlleycatImageryTypeProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - var u0 = uBuffer[i0]; - var u1 = uBuffer[i1]; - var u2 = uBuffer[i2]; + return AlleycatImageryTypeProto; + })(); - var v0 = vBuffer[i0]; - var v1 = vBuffer[i1]; - var v2 = vBuffer[i2]; + return FilmstripConfigProto; + })(); - 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) { - return barycentric.x * heights[i0] + - barycentric.y * heights[i1] + - barycentric.z * heights[i2]; - } - } + EndSnippetProto.StarDataProto = (function() { - // Position does not lie in any triangle in this mesh. - return undefined; - } + function StarDataProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - return GoogleEarthEnterpriseTerrainData; -}); + StarDataProto.prototype.url = null; -/*global define*/ -define('Core/GoogleEarthEnterpriseTerrainProvider',[ - '../ThirdParty/when', - './Credit', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './Event', - './GeographicTilingScheme', - './GoogleEarthEnterpriseMetadata', - './GoogleEarthEnterpriseTerrainData', - './HeightmapTerrainData', - './JulianDate', - './loadArrayBuffer', - './Math', - './Rectangle', - './RuntimeError', - './TaskProcessor', - './throttleRequestByServer', - './TileProviderError' -], function( - when, - Credit, - defaultValue, - defined, - defineProperties, - DeveloperError, - Event, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - GoogleEarthEnterpriseTerrainData, - HeightmapTerrainData, - JulianDate, - loadArrayBuffer, - CesiumMath, - Rectangle, - RuntimeError, - TaskProcessor, - throttleRequestByServer, - TileProviderError) { - 'use strict'; + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto" + }; + $lazyTypes.push($types); - var TerrainState = { - UNKNOWN : 0, - NONE : 1, - SELF : 2, - PARENT : 3 - }; + StarDataProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EndSnippetProto.StarDataProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.url = $types[0].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - var julianDateScratch = new JulianDate(); + StarDataProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.url !== undefined && message.url !== null) { + var error = $types[0].verify(message.url); + if (error) + return "url." + error; + } + return null; + }; - function TerrainCache() { - this._terrainCache = {}; - this._lastTidy = JulianDate.now(); - } + StarDataProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EndSnippetProto.StarDataProto) + return object; + var message = new $root.keyhole.dbroot.EndSnippetProto.StarDataProto(); + if (object.url !== undefined && object.url !== null) { + if (typeof object.url !== "object") + throw TypeError(".keyhole.dbroot.EndSnippetProto.StarDataProto.url: object expected"); + message.url = $types[0].fromObject(object.url); + } + return message; + }; - TerrainCache.prototype.add = function(quadKey, buffer) { - this._terrainCache[quadKey] = { - buffer : buffer, - timestamp : JulianDate.now() - }; - }; + StarDataProto.from = StarDataProto.fromObject; - TerrainCache.prototype.get = function(quadKey) { - var terrainCache = this._terrainCache; - var result = terrainCache[quadKey]; - if (defined(result)) { - delete this._terrainCache[quadKey]; - return result.buffer; - } - }; + StarDataProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.url = null; + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = $types[0].toObject(message.url, options); + return object; + }; - TerrainCache.prototype.tidy = function() { - JulianDate.now(julianDateScratch); - if (JulianDate.secondsDifference(julianDateScratch, this._lastTidy) > 10) { - var terrainCache = this._terrainCache; - var keys = Object.keys(terrainCache); - var count = keys.length; - for (var i = 0; i < count; ++i) { - var k = keys[i]; - var e = terrainCache[k]; - if (JulianDate.secondsDifference(julianDateScratch, e.timestamp) > 10) { - delete terrainCache[k]; - } - } + StarDataProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - JulianDate.clone(julianDateScratch, this._lastTidy); - } - }; + StarDataProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * Provides tiled terrain using the Google Earth Enterprise REST API. - * - * @alias GoogleEarthEnterpriseTerrainProvider - * @constructor - * - * @param {Object} options Object with the following properties: - * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. - * @param {GoogleEarthEnterpriseMetadata} options.metadata A metadata object that can be used to share metadata requests with a GoogleEarthEnterpriseImageryProvider. - * @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 {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. - * - * @see GoogleEarthEnterpriseImageryProvider - * @see CesiumTerrainProvider - * - * @example - * var geeMetadata = new GoogleEarthEnterpriseMetadata('http://www.earthenterprise.org/3d'); - * var gee = new Cesium.GoogleEarthEnterpriseTerrainProvider({ - * metadata : geeMetadata - * }); - * - * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} - */ - function GoogleEarthEnterpriseTerrainProvider(options) { - options = defaultValue(options, {}); + return StarDataProto; + })(); - - var metadata; - if (defined(options.metadata)) { - metadata = this._metadata = options.metadata; - } else { - metadata = this._metadata = new GoogleEarthEnterpriseMetadata({ - url : options.url, - proxy : options.proxy - }); - } - this._proxy = defaultValue(options.proxy, this._metadata.proxy); + return EndSnippetProto; + })(); - this._tilingScheme = new GeographicTilingScheme({ - numberOfLevelZeroTilesX : 2, - numberOfLevelZeroTilesY : 2, - rectangle : new Rectangle(-CesiumMath.PI, -CesiumMath.PI, CesiumMath.PI, CesiumMath.PI), - ellipsoid : options.ellipsoid - }); + dbroot.DbRootRefProto = (function() { - var credit = options.credit; - if (typeof credit === 'string') { - credit = new Credit(credit); - } - this._credit = credit; + function DbRootRefProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - // Pulled from Google's documentation - this._levelZeroMaximumGeometricError = 40075.16; + DbRootRefProto.prototype.url = ""; + DbRootRefProto.prototype.isCritical = false; + DbRootRefProto.prototype.requirements = null; - this._terrainCache = new TerrainCache(); - this._terrainPromises = {}; + var $types = { + 2 : "keyhole.dbroot.RequirementProto" + }; + $lazyTypes.push($types); - this._errorEvent = new Event(); + DbRootRefProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.DbRootRefProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 2: + message.url = reader.string(); + break; + case 1: + message.isCritical = reader.bool(); + break; + case 3: + message.requirements = $types[2].decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - this._ready = false; - var that = this; - var metadataError; - this._readyPromise = metadata.readyPromise - .then(function(result) { - if (!metadata.terrainPresent) { - var e = new RuntimeError('The server ' + metadata.url + ' doesn\'t have terrain'); - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, e.message, undefined, undefined, undefined, e); - return when.reject(e); - } + DbRootRefProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isString(message.url)) + return "url: string expected"; + if (message.isCritical !== undefined) + if (typeof message.isCritical !== "boolean") + return "isCritical: boolean expected"; + if (message.requirements !== undefined && message.requirements !== null) { + var error = $types[2].verify(message.requirements); + if (error) + return "requirements." + error; + } + return null; + }; - TileProviderError.handleSuccess(metadataError); - that._ready = result; - return result; - }) - .otherwise(function(e) { - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, e.message, undefined, undefined, undefined, e); - return when.reject(e); - }); - } + DbRootRefProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.DbRootRefProto) + return object; + var message = new $root.keyhole.dbroot.DbRootRefProto(); + if (object.url !== undefined && object.url !== null) + message.url = String(object.url); + if (object.isCritical !== undefined && object.isCritical !== null) + message.isCritical = Boolean(object.isCritical); + if (object.requirements !== undefined && object.requirements !== null) { + if (typeof object.requirements !== "object") + throw TypeError(".keyhole.dbroot.DbRootRefProto.requirements: object expected"); + message.requirements = $types[2].fromObject(object.requirements); + } + return message; + }; - defineProperties(GoogleEarthEnterpriseTerrainProvider.prototype, { - /** - * Gets the name of the Google Earth Enterprise server url hosting the imagery. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {String} - * @readonly - */ - url : { - get : function() { - return this._metadata.url; - } - }, + DbRootRefProto.from = DbRootRefProto.fromObject; - /** - * Gets the proxy used by this provider. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Proxy} - * @readonly - */ - proxy : { - get : function() { - return this._proxy; - } - }, + DbRootRefProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.url = ""; + object.isCritical = false; + object.requirements = null; + } + if (message.url !== undefined && message.url !== null && message.hasOwnProperty("url")) + object.url = message.url; + if (message.isCritical !== undefined && message.isCritical !== null && message.hasOwnProperty("isCritical")) + object.isCritical = message.isCritical; + if (message.requirements !== undefined && message.requirements !== null && message.hasOwnProperty("requirements")) + object.requirements = $types[2].toObject(message.requirements, options); + return object; + }; - /** - * Gets the tiling scheme used by this provider. This function should - * not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {TilingScheme} - * @readonly - */ - tilingScheme : { - get : function() { - - return this._tilingScheme; - } - }, + DbRootRefProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - /** - * 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 GoogleEarthEnterpriseProvider.prototype - * @type {Event} - * @readonly - */ - errorEvent : { - get : function() { - return this._errorEvent; - } - }, + DbRootRefProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - /** - * Gets a value indicating whether or not the provider is ready for use. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Boolean} - * @readonly - */ - ready : { - get : function() { - return this._ready; - } - }, + return DbRootRefProto; + })(); - /** - * Gets a promise that resolves to true when the provider is ready for use. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Promise.} - * @readonly - */ - readyPromise : { - get : function() { - return this._readyPromise; - } - }, + dbroot.DatabaseVersionProto = (function() { - /** - * 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 GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Credit} - * @readonly - */ - credit : { - get : function() { - return this._credit; - } - }, + function DatabaseVersionProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - /** - * 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 GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Boolean} - */ - hasWaterMask : { - get : function() { - return false; - } - }, + DatabaseVersionProto.prototype.quadtreeVersion = 0; - /** - * Gets a value indicating whether or not the requested tiles include vertex normals. - * This function should not be called before {@link GoogleEarthEnterpriseProvider#ready} returns true. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {Boolean} - */ - hasVertexNormals : { - get : function() { - return false; - } - }, + DatabaseVersionProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.DatabaseVersionProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.quadtreeVersion = reader.uint32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - /** - * Gets an object that can be used to determine availability of terrain from this provider, such as - * at points and in rectangles. This function should not be called before - * {@link GoogleEarthEnterpriseProvider#ready} returns true. This property may be undefined if availability - * information is not available. - * @memberof GoogleEarthEnterpriseProvider.prototype - * @type {TileAvailability} - */ - availability : { - get : function() { - return undefined; - } - } - }); + DatabaseVersionProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (!$util.isInteger(message.quadtreeVersion)) + return "quadtreeVersion: integer expected"; + return null; + }; - var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); + DatabaseVersionProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.DatabaseVersionProto) + return object; + var message = new $root.keyhole.dbroot.DatabaseVersionProto(); + if (object.quadtreeVersion !== undefined && object.quadtreeVersion !== null) + message.quadtreeVersion = object.quadtreeVersion >>> 0; + return message; + }; - // If the tile has its own terrain, then you can just use its child bitmask. If it was requested using it's parent - // then you need to check all of its children to see if they have terrain. - function computeChildMask(quadKey, info, metadata) { - var childMask = info.getChildBitmask(); - if (info.terrainState === TerrainState.PARENT) { - childMask = 0; - for (var i = 0; i < 4; ++i) { - var child = metadata.getTileInformationFromQuadKey(quadKey + i.toString()); - if (defined(child) && child.hasTerrain()) { - childMask |= (1 << i); - } - } - } + DatabaseVersionProto.from = DatabaseVersionProto.fromObject; - return childMask; - } + DatabaseVersionProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.quadtreeVersion = 0; + if (message.quadtreeVersion !== undefined && message.quadtreeVersion !== null && message.hasOwnProperty("quadtreeVersion")) + object.quadtreeVersion = message.quadtreeVersion; + return object; + }; - /** - * Requests the geometry for a given tile. This function should not be called before - * {@link GoogleEarthEnterpriseProvider#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 GoogleEarthEnterpriseProvider#ready} - * returns true. - */ - GoogleEarthEnterpriseTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { - - var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - var terrainCache = this._terrainCache; - var metadata = this._metadata; - var info = metadata.getTileInformationFromQuadKey(quadKey); + DatabaseVersionProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - // Check if this tile is even possibly available - if (!defined(info)) { - return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); - } + DatabaseVersionProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - var terrainState = info.terrainState; - if (!defined(terrainState)) { - // First time we have tried to load this tile, so set terrain state to UNKNOWN - terrainState = info.terrainState = TerrainState.UNKNOWN; - } + return DatabaseVersionProto; + })(); - // If its in the cache, return it - var buffer = terrainCache.get(quadKey); - if (defined(buffer)) { - var credit = metadata.providers[info.terrainProvider]; - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : computeChildMask(quadKey, info, metadata), - credits : defined(credit) ? [credit] : undefined, - negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias, - negativeElevationThreshold: metadata.negativeAltitudeThreshold - }); - } + dbroot.DbRootProto = (function() { - // Clean up the cache - terrainCache.tidy(); + function DbRootProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; + } - // We have a tile, check to see if no ancestors have terrain or that we know for sure it doesn't - if (!info.ancestorHasTerrain) { - // We haven't reached a level with terrain, so return the ellipsoid - return new HeightmapTerrainData({ - buffer : new Uint8Array(16 * 16), - width : 16, - height : 16 - }); - } else if (terrainState === TerrainState.NONE) { - // Already have info and there isn't any terrain here - return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); - } + DbRootProto.prototype.databaseName = null; + DbRootProto.prototype.imageryPresent = true; + DbRootProto.prototype.protoImagery = false; + DbRootProto.prototype.terrainPresent = false; + DbRootProto.prototype.providerInfo = $util.emptyArray; + DbRootProto.prototype.nestedFeature = $util.emptyArray; + DbRootProto.prototype.styleAttribute = $util.emptyArray; + DbRootProto.prototype.styleMap = $util.emptyArray; + DbRootProto.prototype.endSnippet = null; + DbRootProto.prototype.translationEntry = $util.emptyArray; + DbRootProto.prototype.language = "en"; + DbRootProto.prototype.version = 5; + DbRootProto.prototype.dbrootReference = $util.emptyArray; + DbRootProto.prototype.databaseVersion = null; + DbRootProto.prototype.refreshTimeout = 0; - // Figure out where we are getting the terrain and what version - var parentInfo; - var q = quadKey; - var terrainVersion = -1; - switch (terrainState) { - case TerrainState.SELF: // We have terrain and have retrieved it before - terrainVersion = info.terrainVersion; - break; - case TerrainState.PARENT: // We have terrain in our parent - q = q.substring(0, q.length - 1); - parentInfo = metadata.getTileInformationFromQuadKey(q); - terrainVersion = parentInfo.terrainVersion; - break; - case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet - if (info.hasTerrain()) { - terrainVersion = info.terrainVersion; // We should have terrain - } else { - q = q.substring(0, q.length - 1); - parentInfo = metadata.getTileInformationFromQuadKey(q); - if (defined(parentInfo) && parentInfo.hasTerrain()) { - terrainVersion = parentInfo.terrainVersion; // Try checking in the parent + var $types = { + 0 : "keyhole.dbroot.StringIdOrValueProto", + 4 : "keyhole.dbroot.ProviderInfoProto", + 5 : "keyhole.dbroot.NestedFeatureProto", + 6 : "keyhole.dbroot.StyleAttributeProto", + 7 : "keyhole.dbroot.StyleMapProto", + 8 : "keyhole.dbroot.EndSnippetProto", + 9 : "keyhole.dbroot.StringEntryProto", + 12 : "keyhole.dbroot.DbRootRefProto", + 13 : "keyhole.dbroot.DatabaseVersionProto" + }; + $lazyTypes.push($types); + + DbRootProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.DbRootProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 15: + message.databaseName = $types[0].decode(reader, reader.uint32()); + break; + case 1: + message.imageryPresent = reader.bool(); + break; + case 14: + message.protoImagery = reader.bool(); + break; + case 2: + message.terrainPresent = reader.bool(); + break; + case 3: + if (!(message.providerInfo && message.providerInfo.length)) + message.providerInfo = []; + message.providerInfo.push($types[4].decode(reader, reader.uint32())); + break; + case 4: + if (!(message.nestedFeature && message.nestedFeature.length)) + message.nestedFeature = []; + message.nestedFeature.push($types[5].decode(reader, reader.uint32())); + break; + case 5: + if (!(message.styleAttribute && message.styleAttribute.length)) + message.styleAttribute = []; + message.styleAttribute.push($types[6].decode(reader, reader.uint32())); + break; + case 6: + if (!(message.styleMap && message.styleMap.length)) + message.styleMap = []; + message.styleMap.push($types[7].decode(reader, reader.uint32())); + break; + case 7: + message.endSnippet = $types[8].decode(reader, reader.uint32()); + break; + case 8: + if (!(message.translationEntry && message.translationEntry.length)) + message.translationEntry = []; + message.translationEntry.push($types[9].decode(reader, reader.uint32())); + break; + case 9: + message.language = reader.string(); + break; + case 10: + message.version = reader.int32(); + break; + case 11: + if (!(message.dbrootReference && message.dbrootReference.length)) + message.dbrootReference = []; + message.dbrootReference.push($types[12].decode(reader, reader.uint32())); + break; + case 13: + message.databaseVersion = $types[13].decode(reader, reader.uint32()); + break; + case 16: + message.refreshTimeout = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } } - } - break; - } + return message; + }; - // We can't figure out where to get the terrain - if (terrainVersion < 0) { - return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); - } + DbRootProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.databaseName !== undefined && message.databaseName !== null) { + var error = $types[0].verify(message.databaseName); + if (error) + return "databaseName." + error; + } + if (message.imageryPresent !== undefined) + if (typeof message.imageryPresent !== "boolean") + return "imageryPresent: boolean expected"; + if (message.protoImagery !== undefined) + if (typeof message.protoImagery !== "boolean") + return "protoImagery: boolean expected"; + if (message.terrainPresent !== undefined) + if (typeof message.terrainPresent !== "boolean") + return "terrainPresent: boolean expected"; + if (message.providerInfo !== undefined) { + if (!Array.isArray(message.providerInfo)) + return "providerInfo: array expected"; + for (var i = 0; i < message.providerInfo.length; ++i) { + var error = $types[4].verify(message.providerInfo[i]); + if (error) + return "providerInfo." + error; + } + } + if (message.nestedFeature !== undefined) { + if (!Array.isArray(message.nestedFeature)) + return "nestedFeature: array expected"; + for (var i = 0; i < message.nestedFeature.length; ++i) { + var error = $types[5].verify(message.nestedFeature[i]); + if (error) + return "nestedFeature." + error; + } + } + if (message.styleAttribute !== undefined) { + if (!Array.isArray(message.styleAttribute)) + return "styleAttribute: array expected"; + for (var i = 0; i < message.styleAttribute.length; ++i) { + var error = $types[6].verify(message.styleAttribute[i]); + if (error) + return "styleAttribute." + error; + } + } + if (message.styleMap !== undefined) { + if (!Array.isArray(message.styleMap)) + return "styleMap: array expected"; + for (var i = 0; i < message.styleMap.length; ++i) { + var error = $types[7].verify(message.styleMap[i]); + if (error) + return "styleMap." + error; + } + } + if (message.endSnippet !== undefined && message.endSnippet !== null) { + var error = $types[8].verify(message.endSnippet); + if (error) + return "endSnippet." + error; + } + if (message.translationEntry !== undefined) { + if (!Array.isArray(message.translationEntry)) + return "translationEntry: array expected"; + for (var i = 0; i < message.translationEntry.length; ++i) { + var error = $types[9].verify(message.translationEntry[i]); + if (error) + return "translationEntry." + error; + } + } + if (message.language !== undefined) + if (!$util.isString(message.language)) + return "language: string expected"; + if (message.version !== undefined) + if (!$util.isInteger(message.version)) + return "version: integer expected"; + if (message.dbrootReference !== undefined) { + if (!Array.isArray(message.dbrootReference)) + return "dbrootReference: array expected"; + for (var i = 0; i < message.dbrootReference.length; ++i) { + var error = $types[12].verify(message.dbrootReference[i]); + if (error) + return "dbrootReference." + error; + } + } + if (message.databaseVersion !== undefined && message.databaseVersion !== null) { + var error = $types[13].verify(message.databaseVersion); + if (error) + return "databaseVersion." + error; + } + if (message.refreshTimeout !== undefined) + if (!$util.isInteger(message.refreshTimeout)) + return "refreshTimeout: integer expected"; + return null; + }; - // Load that terrain - var terrainPromises = this._terrainPromises; - var url = buildTerrainUrl(this, q, terrainVersion); - var promise; - if (defined(terrainPromises[q])) { // Already being loaded possibly from another child, so return existing promise - promise = terrainPromises[q]; - } else { // Create new request for terrain - var requestPromise; - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - requestPromise = throttleRequestByServer(url, loadArrayBuffer); - if (!defined(requestPromise)) { - return undefined; // Throttled - } - } else { - requestPromise = loadArrayBuffer(url); - } + DbRootProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.DbRootProto) + return object; + var message = new $root.keyhole.dbroot.DbRootProto(); + if (object.databaseName !== undefined && object.databaseName !== null) { + if (typeof object.databaseName !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.databaseName: object expected"); + message.databaseName = $types[0].fromObject(object.databaseName); + } + if (object.imageryPresent !== undefined && object.imageryPresent !== null) + message.imageryPresent = Boolean(object.imageryPresent); + if (object.protoImagery !== undefined && object.protoImagery !== null) + message.protoImagery = Boolean(object.protoImagery); + if (object.terrainPresent !== undefined && object.terrainPresent !== null) + message.terrainPresent = Boolean(object.terrainPresent); + if (object.providerInfo) { + if (!Array.isArray(object.providerInfo)) + throw TypeError(".keyhole.dbroot.DbRootProto.providerInfo: array expected"); + message.providerInfo = []; + for (var i = 0; i < object.providerInfo.length; ++i) { + if (typeof object.providerInfo[i] !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.providerInfo: object expected"); + message.providerInfo[i] = $types[4].fromObject(object.providerInfo[i]); + } + } + if (object.nestedFeature) { + if (!Array.isArray(object.nestedFeature)) + throw TypeError(".keyhole.dbroot.DbRootProto.nestedFeature: array expected"); + message.nestedFeature = []; + for (var i = 0; i < object.nestedFeature.length; ++i) { + if (typeof object.nestedFeature[i] !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.nestedFeature: object expected"); + message.nestedFeature[i] = $types[5].fromObject(object.nestedFeature[i]); + } + } + if (object.styleAttribute) { + if (!Array.isArray(object.styleAttribute)) + throw TypeError(".keyhole.dbroot.DbRootProto.styleAttribute: array expected"); + message.styleAttribute = []; + for (var i = 0; i < object.styleAttribute.length; ++i) { + if (typeof object.styleAttribute[i] !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.styleAttribute: object expected"); + message.styleAttribute[i] = $types[6].fromObject(object.styleAttribute[i]); + } + } + if (object.styleMap) { + if (!Array.isArray(object.styleMap)) + throw TypeError(".keyhole.dbroot.DbRootProto.styleMap: array expected"); + message.styleMap = []; + for (var i = 0; i < object.styleMap.length; ++i) { + if (typeof object.styleMap[i] !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.styleMap: object expected"); + message.styleMap[i] = $types[7].fromObject(object.styleMap[i]); + } + } + if (object.endSnippet !== undefined && object.endSnippet !== null) { + if (typeof object.endSnippet !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.endSnippet: object expected"); + message.endSnippet = $types[8].fromObject(object.endSnippet); + } + if (object.translationEntry) { + if (!Array.isArray(object.translationEntry)) + throw TypeError(".keyhole.dbroot.DbRootProto.translationEntry: array expected"); + message.translationEntry = []; + for (var i = 0; i < object.translationEntry.length; ++i) { + if (typeof object.translationEntry[i] !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.translationEntry: object expected"); + message.translationEntry[i] = $types[9].fromObject(object.translationEntry[i]); + } + } + if (object.language !== undefined && object.language !== null) + message.language = String(object.language); + if (object.version !== undefined && object.version !== null) + message.version = object.version | 0; + if (object.dbrootReference) { + if (!Array.isArray(object.dbrootReference)) + throw TypeError(".keyhole.dbroot.DbRootProto.dbrootReference: array expected"); + message.dbrootReference = []; + for (var i = 0; i < object.dbrootReference.length; ++i) { + if (typeof object.dbrootReference[i] !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.dbrootReference: object expected"); + message.dbrootReference[i] = $types[12].fromObject(object.dbrootReference[i]); + } + } + if (object.databaseVersion !== undefined && object.databaseVersion !== null) { + if (typeof object.databaseVersion !== "object") + throw TypeError(".keyhole.dbroot.DbRootProto.databaseVersion: object expected"); + message.databaseVersion = $types[13].fromObject(object.databaseVersion); + } + if (object.refreshTimeout !== undefined && object.refreshTimeout !== null) + message.refreshTimeout = object.refreshTimeout | 0; + return message; + }; - promise = requestPromise - .then(function(terrain) { - if (defined(terrain)) { - return taskProcessor.scheduleTask({ - buffer : terrain, - type : 'Terrain', - key : metadata.key - }, [terrain]) - .then(function(terrainTiles) { - // Add requested tile and mark it as SELF - var requestedInfo = metadata.getTileInformationFromQuadKey(q); - requestedInfo.terrainState = TerrainState.SELF; - terrainCache.add(q, terrainTiles[0]); - var provider = requestedInfo.terrainProvider; + DbRootProto.from = DbRootProto.fromObject; - // Add children to cache - var count = terrainTiles.length - 1; - for (var j = 0; j < count; ++j) { - var childKey = q + j.toString(); - var child = metadata.getTileInformationFromQuadKey(childKey); - if (defined(child)) { - terrainCache.add(childKey, terrainTiles[j + 1]); - child.terrainState = TerrainState.PARENT; - if (child.terrainProvider === 0) { - child.terrainProvider = provider; - } - } - } - }); + DbRootProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.providerInfo = []; + object.nestedFeature = []; + object.styleAttribute = []; + object.styleMap = []; + object.translationEntry = []; + object.dbrootReference = []; } + if (options.defaults) { + object.databaseName = null; + object.imageryPresent = true; + object.protoImagery = false; + object.terrainPresent = false; + object.endSnippet = null; + object.language = "en"; + object.version = 5; + object.databaseVersion = null; + object.refreshTimeout = 0; + } + if (message.databaseName !== undefined && message.databaseName !== null && message.hasOwnProperty("databaseName")) + object.databaseName = $types[0].toObject(message.databaseName, options); + if (message.imageryPresent !== undefined && message.imageryPresent !== null && message.hasOwnProperty("imageryPresent")) + object.imageryPresent = message.imageryPresent; + if (message.protoImagery !== undefined && message.protoImagery !== null && message.hasOwnProperty("protoImagery")) + object.protoImagery = message.protoImagery; + if (message.terrainPresent !== undefined && message.terrainPresent !== null && message.hasOwnProperty("terrainPresent")) + object.terrainPresent = message.terrainPresent; + if (message.providerInfo !== undefined && message.providerInfo !== null && message.hasOwnProperty("providerInfo")) { + object.providerInfo = []; + for (var j = 0; j < message.providerInfo.length; ++j) + object.providerInfo[j] = $types[4].toObject(message.providerInfo[j], options); + } + if (message.nestedFeature !== undefined && message.nestedFeature !== null && message.hasOwnProperty("nestedFeature")) { + object.nestedFeature = []; + for (var j = 0; j < message.nestedFeature.length; ++j) + object.nestedFeature[j] = $types[5].toObject(message.nestedFeature[j], options); + } + if (message.styleAttribute !== undefined && message.styleAttribute !== null && message.hasOwnProperty("styleAttribute")) { + object.styleAttribute = []; + for (var j = 0; j < message.styleAttribute.length; ++j) + object.styleAttribute[j] = $types[6].toObject(message.styleAttribute[j], options); + } + if (message.styleMap !== undefined && message.styleMap !== null && message.hasOwnProperty("styleMap")) { + object.styleMap = []; + for (var j = 0; j < message.styleMap.length; ++j) + object.styleMap[j] = $types[7].toObject(message.styleMap[j], options); + } + if (message.endSnippet !== undefined && message.endSnippet !== null && message.hasOwnProperty("endSnippet")) + object.endSnippet = $types[8].toObject(message.endSnippet, options); + if (message.translationEntry !== undefined && message.translationEntry !== null && message.hasOwnProperty("translationEntry")) { + object.translationEntry = []; + for (var j = 0; j < message.translationEntry.length; ++j) + object.translationEntry[j] = $types[9].toObject(message.translationEntry[j], options); + } + if (message.language !== undefined && message.language !== null && message.hasOwnProperty("language")) + object.language = message.language; + if (message.version !== undefined && message.version !== null && message.hasOwnProperty("version")) + object.version = message.version; + if (message.dbrootReference !== undefined && message.dbrootReference !== null && message.hasOwnProperty("dbrootReference")) { + object.dbrootReference = []; + for (var j = 0; j < message.dbrootReference.length; ++j) + object.dbrootReference[j] = $types[12].toObject(message.dbrootReference[j], options); + } + if (message.databaseVersion !== undefined && message.databaseVersion !== null && message.hasOwnProperty("databaseVersion")) + object.databaseVersion = $types[13].toObject(message.databaseVersion, options); + if (message.refreshTimeout !== undefined && message.refreshTimeout !== null && message.hasOwnProperty("refreshTimeout")) + object.refreshTimeout = message.refreshTimeout; + return object; + }; - return when.reject(new RuntimeError('Failed to load terrain.')); - }) - .otherwise(function(error) { - info.terrainState = TerrainState.NONE; - return when.reject(error); - }); + DbRootProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - terrainPromises[q] = promise; // Store promise without delete from terrainPromises + DbRootProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - // Set promise so we remove from terrainPromises just one time - promise = promise - .always(function() { - delete terrainPromises[q]; - }); - } + return DbRootProto; + })(); - return promise - .then(function() { - var buffer = terrainCache.get(quadKey); - if (defined(buffer)) { - var credit = metadata.providers[info.terrainProvider]; - return new GoogleEarthEnterpriseTerrainData({ - buffer : buffer, - childTileMask : computeChildMask(quadKey, info, metadata), - credits : defined(credit) ? [credit] : undefined, - negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias, - negativeElevationThreshold: metadata.negativeAltitudeThreshold - }); - } else { - info.terrainState = TerrainState.NONE; - return when.reject(new RuntimeError('Failed to load terrain.')); + dbroot.EncryptedDbRootProto = (function() { + + function EncryptedDbRootProto(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + this[keys[i]] = properties[keys[i]]; } - }); - }; - /** - * 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. - */ - GoogleEarthEnterpriseTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) { - return this._levelZeroMaximumGeometricError / (1 << level); - }; + EncryptedDbRootProto.prototype.encryptionType = 0; + EncryptedDbRootProto.prototype.encryptionData = $util.newBuffer([]); + EncryptedDbRootProto.prototype.dbrootData = $util.newBuffer([]); - /** - * 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. - */ - GoogleEarthEnterpriseTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { - var metadata = this._metadata; - var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var $types = { + 0 : "keyhole.dbroot.EncryptedDbRootProto.EncryptionType" + }; + $lazyTypes.push($types); - var info = metadata.getTileInformation(x, y, level); - if (info === null) { - return false; - } + EncryptedDbRootProto.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.keyhole.dbroot.EncryptedDbRootProto(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.encryptionType = reader.uint32(); + break; + case 2: + message.encryptionData = reader.bytes(); + break; + case 3: + message.dbrootData = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; - if (defined(info)) { - if (!info.ancestorHasTerrain) { - return true; // We'll just return the ellipsoid - } + EncryptedDbRootProto.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.encryptionType !== undefined) + switch (message.encryptionType) { + default: + return "encryptionType: enum value expected"; + case 0: + break; + } + if (message.encryptionData !== undefined) + if (!(message.encryptionData && typeof message.encryptionData.length === "number" || $util.isString(message.encryptionData))) + return "encryptionData: buffer expected"; + if (message.dbrootData !== undefined) + if (!(message.dbrootData && typeof message.dbrootData.length === "number" || $util.isString(message.dbrootData))) + return "dbrootData: buffer expected"; + return null; + }; - var terrainState = info.terrainState; - if (terrainState === TerrainState.NONE) { - return false; // Terrain is not available - } + EncryptedDbRootProto.fromObject = function fromObject(object) { + if (object instanceof $root.keyhole.dbroot.EncryptedDbRootProto) + return object; + var message = new $root.keyhole.dbroot.EncryptedDbRootProto(); + switch (object.encryptionType) { + case "ENCRYPTION_XOR": + case 0: + message.encryptionType = 0; + break; + } + if (object.encryptionData !== undefined && object.encryptionData !== null) + if (typeof object.encryptionData === "string") + $util.base64.decode(object.encryptionData, message.encryptionData = $util.newBuffer($util.base64.length(object.encryptionData)), 0); + else if (object.encryptionData.length) + message.encryptionData = object.encryptionData; + if (object.dbrootData !== undefined && object.dbrootData !== null) + if (typeof object.dbrootData === "string") + $util.base64.decode(object.dbrootData, message.dbrootData = $util.newBuffer($util.base64.length(object.dbrootData)), 0); + else if (object.dbrootData.length) + message.dbrootData = object.dbrootData; + return message; + }; - if (!defined(terrainState) || (terrainState === TerrainState.UNKNOWN)) { - info.terrainState = TerrainState.UNKNOWN; - if (!info.hasTerrain()) { - quadKey = quadKey.substring(0, quadKey.length - 1); - var parentInfo = metadata.getTileInformationFromQuadKey(quadKey); - if (!defined(parentInfo) || !parentInfo.hasTerrain()) { - return false; + EncryptedDbRootProto.from = EncryptedDbRootProto.fromObject; + + EncryptedDbRootProto.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.encryptionType = options.enums === String ? "ENCRYPTION_XOR" : 0; + object.encryptionData = options.bytes === String ? "" : []; + object.dbrootData = options.bytes === String ? "" : []; } - } - } + if (message.encryptionType !== undefined && message.encryptionType !== null && message.hasOwnProperty("encryptionType")) + object.encryptionType = options.enums === String ? $types[0][message.encryptionType] : message.encryptionType; + if (message.encryptionData !== undefined && message.encryptionData !== null && message.hasOwnProperty("encryptionData")) + object.encryptionData = options.bytes === String ? $util.base64.encode(message.encryptionData, 0, message.encryptionData.length) : options.bytes === Array ? Array.prototype.slice.call(message.encryptionData) : message.encryptionData; + if (message.dbrootData !== undefined && message.dbrootData !== null && message.hasOwnProperty("dbrootData")) + object.dbrootData = options.bytes === String ? $util.base64.encode(message.dbrootData, 0, message.dbrootData.length) : options.bytes === Array ? Array.prototype.slice.call(message.dbrootData) : message.dbrootData; + return object; + }; - return true; - } + EncryptedDbRootProto.prototype.toObject = function toObject(options) { + return this.constructor.toObject(this, options); + }; - if (metadata.isValid(quadKey)) { - // We will need this tile, so request metadata and return false for now - metadata.populateSubtree(x, y, level); - } - return false; - }; + EncryptedDbRootProto.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; - // - // Functions to handle imagery packets - // - function buildTerrainUrl(terrainProvider, quadKey, version) { - version = (defined(version) && version > 0) ? version : 1; - var terrainUrl = terrainProvider.url + 'flatfile?f1c-0' + quadKey + '-t.' + version.toString(); + EncryptedDbRootProto.EncryptionType = (function() { + var valuesById = {}, values = Object.create(valuesById); + values["ENCRYPTION_XOR"] = 0; + return values; + })(); - var proxy = terrainProvider._proxy; - if (defined(proxy)) { - terrainUrl = proxy.getURL(terrainUrl); - } + return EncryptedDbRootProto; + })(); - return terrainUrl; - } + return dbroot; + })(); - return GoogleEarthEnterpriseTerrainProvider; -}); + return keyhole; + })(); -/*global define*/ -define('Core/HeadingPitchRange',[ - './defaultValue', - './defined' - ], function( - defaultValue, - defined) { - 'use strict'; + $util.lazyResolve($root, $lazyTypes); - /** - * 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); + // End generated code - /** - * 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); + return $root.keyhole.dbroot; +}); - /** - * Range is the distance from the center of the local frame. - * @type {Number} - */ - this.range = defaultValue(range, 0.0); - } +define('Core/isBitSet',[], function() { + 'use strict'; /** - * 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) + * @private */ - 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; - }; + function isBitSet(bits, mask) { + return ((bits & mask) !== 0); + } - return HeadingPitchRange; + return isBitSet; }); -/*global define*/ -define('Core/HermitePolynomialApproximation',[ - './defaultValue', +define('Core/GoogleEarthEnterpriseTileInformation',[ './defined', - './DeveloperError', - './Math' + './isBitSet' ], function( - defaultValue, defined, - DeveloperError, - CesiumMath) { + isBitSet) { '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; - } + // Bitmask for checking tile properties + var childrenBitmasks = [0x01, 0x02, 0x04, 0x08]; + var anyChildBitmask = 0x0F; + var cacheFlagBitmask = 0x10; // True if there is a child subtree + var imageBitmask = 0x40; + var terrainBitmask = 0x80; - result = 1; - for (i = 0; i < termOrder; i++) { - reserved = false; - for (j = 0; j < reservedIndices.length && !reserved; j++) { - if (i === reservedIndices[j]) { - reserved = true; - } - } + /** + * Contains information about each tile from a Google Earth Enterprise server + * + * @param {Number} bits Bitmask that contains the type of data and available children for each tile. + * @param {Number} cnodeVersion Version of the request for subtree metadata. + * @param {Number} imageryVersion Version of the request for imagery tile. + * @param {Number} terrainVersion Version of the request for terrain tile. + * @param {Number} imageryProvider Id of imagery provider. + * @param {Number} terrainProvider Id of terrain provider. + * + * @private + */ + function GoogleEarthEnterpriseTileInformation(bits, cnodeVersion, imageryVersion, terrainVersion, imageryProvider, terrainProvider) { + this._bits = bits; + this.cnodeVersion = cnodeVersion; + this.imageryVersion = imageryVersion; + this.terrainVersion = terrainVersion; + this.imageryProvider = imageryProvider; + this.terrainProvider = terrainProvider; + this.ancestorHasTerrain = false; // Set it later once we find its parent + this.terrainState = undefined; + } - if (!reserved) { - result *= x - xTable[zIndices[i]]; - } + /** + * Creates GoogleEarthEnterpriseTileInformation from an object + * + * @param {Object} info Object to be cloned + * @param {GoogleEarthEnterpriseTileInformation} [result] The object onto which to store the result. + * @returns {GoogleEarthEnterpriseTileInformation} The modified result parameter or a new GoogleEarthEnterpriseTileInformation instance if none was provided. + */ + GoogleEarthEnterpriseTileInformation.clone = function(info, result) { + if (!defined(result)) { + result = new GoogleEarthEnterpriseTileInformation(info._bits, info.cnodeVersion, info.imageryVersion, info.terrainVersion, + info.imageryProvider, info.terrainProvider); + } else { + result._bits = info._bits; + result.cnodeVersion = info.cnodeVersion; + result.imageryVersion = info.imageryVersion; + result.terrainVersion = info.terrainVersion; + result.imageryProvider = info.imageryProvider; + result.terrainProvider = info.terrainProvider; } + result.ancestorHasTerrain = info.ancestorHasTerrain; + result.terrainState = info.terrainState; return result; - } + }; /** - * An {@link InterpolationAlgorithm} for performing Hermite interpolation. + * Sets the parent for the tile * - * @exports HermitePolynomialApproximation + * @param {GoogleEarthEnterpriseTileInformation} parent Parent tile */ - var HermitePolynomialApproximation = { - type : 'Hermite' + GoogleEarthEnterpriseTileInformation.prototype.setParent = function(parent) { + this.ancestorHasTerrain = parent.ancestorHasTerrain || this.hasTerrain(); }; /** - * Given the desired degree, returns the number of data points required for interpolation. + * Gets whether a subtree is available * - * @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. + * @returns {Boolean} true if subtree is available, false otherwise. */ - HermitePolynomialApproximation.getRequiredDataPoints = function(degree, inputOrder) { - inputOrder = defaultValue(inputOrder, 0); - - - return Math.max(Math.floor((degree + 1) / (inputOrder + 1)), 2); + GoogleEarthEnterpriseTileInformation.prototype.hasSubtree = function() { + return isBitSet(this._bits, cacheFlagBitmask); }; /** - * Interpolates values using Hermite Polynomial Approximation. + * Gets whether imagery is available * - * @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. + * @returns {Boolean} true if imagery is available, false otherwise. */ - 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; - } - } - } + GoogleEarthEnterpriseTileInformation.prototype.hasImagery = function() { + return isBitSet(this._bits, imageBitmask); + }; - return result; + /** + * Gets whether terrain is available + * + * @returns {Boolean} true if terrain is available, false otherwise. + */ + GoogleEarthEnterpriseTileInformation.prototype.hasTerrain = function() { + return isBitSet(this._bits, terrainBitmask); }; - var arrayScratch = []; + /** + * Gets whether any children are present + * + * @returns {Boolean} true if any children are available, false otherwise. + */ + GoogleEarthEnterpriseTileInformation.prototype.hasChildren = function() { + return isBitSet(this._bits, anyChildBitmask); + }; /** - * Interpolates values using Hermite Polynomial Approximation. + * Gets whether a specified child is available * - * @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. + * @param {Number} index Index of child tile * - * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided. + * @returns {Boolean} true if child is available, false otherwise */ - 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; + GoogleEarthEnterpriseTileInformation.prototype.hasChild = function(index) { + return isBitSet(this._bits, childrenBitmasks[index]); }; - 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; + /** + * Gets bitmask containing children + * + * @returns {Number} Children bitmask + */ + GoogleEarthEnterpriseTileInformation.prototype.getChildBitmask = function() { + return this._bits & anyChildBitmask; + }; - for (j = 0; j < zIndiceslength - i; j++) { - var zj = xTable[zIndices[j]]; - var zn = xTable[zIndices[j + i]]; + return GoogleEarthEnterpriseTileInformation; +}); - 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); - } +define('Core/GoogleEarthEnterpriseMetadata',[ + '../ThirdParty/google-earth-dbroot-parser', + '../ThirdParty/when', + './appendForwardSlash', + './Check', + './Credit', + './defaultValue', + './defined', + './defineProperties', + './GoogleEarthEnterpriseTileInformation', + './isBitSet', + './joinUrls', + './loadArrayBuffer', + './Math', + './Request', + './RequestType', + './RuntimeError', + './TaskProcessor' + ], function( + dbrootParser, + when, + appendForwardSlash, + Check, + Credit, + defaultValue, + defined, + defineProperties, + GoogleEarthEnterpriseTileInformation, + isBitSet, + joinUrls, + loadArrayBuffer, + CesiumMath, + Request, + RequestType, + RuntimeError, + TaskProcessor) { + 'use strict'; - if (nonZeroCoefficients) { - highestNonZero = Math.max(highestNonZero, i); - } - } + function stringToBuffer(str) { + var len = str.length; + var buffer = new ArrayBuffer(len); + var ui8 = new Uint8Array(buffer); + for (var i = 0; i < len; ++i) { + ui8[i] = str.charCodeAt(i); } - return highestNonZero; + return buffer; } - return HermitePolynomialApproximation; -}); - -/*global define*/ -define('Core/IauOrientationParameters',[],function() { - 'use strict'; + // Decodes packet with a key that has been around since the beginning of Google Earth Enterprise + var defaultKey = stringToBuffer('\x45\xf4\xbd\x0b\x79\xe2\x6a\x45\x22\x05\x92\x2c\x17\xcd\x06\x71\xf8\x49\x10\x46\x67\x51\x00\x42\x25\xc6\xe8\x61\x2c\x66\x29\x08\xc6\x34\xdc\x6a\x62\x25\x79\x0a\x77\x1d\x6d\x69\xd6\xf0\x9c\x6b\x93\xa1\xbd\x4e\x75\xe0\x41\x04\x5b\xdf\x40\x56\x0c\xd9\xbb\x72\x9b\x81\x7c\x10\x33\x53\xee\x4f\x6c\xd4\x71\x05\xb0\x7b\xc0\x7f\x45\x03\x56\x5a\xad\x77\x55\x65\x0b\x33\x92\x2a\xac\x19\x6c\x35\x14\xc5\x1d\x30\x73\xf8\x33\x3e\x6d\x46\x38\x4a\xb4\xdd\xf0\x2e\xdd\x17\x75\x16\xda\x8c\x44\x74\x22\x06\xfa\x61\x22\x0c\x33\x22\x53\x6f\xaf\x39\x44\x0b\x8c\x0e\x39\xd9\x39\x13\x4c\xb9\xbf\x7f\xab\x5c\x8c\x50\x5f\x9f\x22\x75\x78\x1f\xe9\x07\x71\x91\x68\x3b\xc1\xc4\x9b\x7f\xf0\x3c\x56\x71\x48\x82\x05\x27\x55\x66\x59\x4e\x65\x1d\x98\x75\xa3\x61\x46\x7d\x61\x3f\x15\x41\x00\x9f\x14\x06\xd7\xb4\x34\x4d\xce\x13\x87\x46\xb0\x1a\xd5\x05\x1c\xb8\x8a\x27\x7b\x8b\xdc\x2b\xbb\x4d\x67\x30\xc8\xd1\xf6\x5c\x8f\x50\xfa\x5b\x2f\x46\x9b\x6e\x35\x18\x2f\x27\x43\x2e\xeb\x0a\x0c\x5e\x10\x05\x10\xa5\x73\x1b\x65\x34\xe5\x6c\x2e\x6a\x43\x27\x63\x14\x23\x55\xa9\x3f\x71\x7b\x67\x43\x7d\x3a\xaf\xcd\xe2\x54\x55\x9c\xfd\x4b\xc6\xe2\x9f\x2f\x28\xed\xcb\x5c\xc6\x2d\x66\x07\x88\xa7\x3b\x2f\x18\x2a\x22\x4e\x0e\xb0\x6b\x2e\xdd\x0d\x95\x7d\x7d\x47\xba\x43\xb2\x11\xb2\x2b\x3e\x4d\xaa\x3e\x7d\xe6\xce\x49\x89\xc6\xe6\x78\x0c\x61\x31\x05\x2d\x01\xa4\x4f\xa5\x7e\x71\x20\x88\xec\x0d\x31\xe8\x4e\x0b\x00\x6e\x50\x68\x7d\x17\x3d\x08\x0d\x17\x95\xa6\x6e\xa3\x68\x97\x24\x5b\x6b\xf3\x17\x23\xf3\xb6\x73\xb3\x0d\x0b\x40\xc0\x9f\xd8\x04\x51\x5d\xfa\x1a\x17\x22\x2e\x15\x6a\xdf\x49\x00\xb9\xa0\x77\x55\xc6\xef\x10\x6a\xbf\x7b\x47\x4c\x7f\x83\x17\x05\xee\xdc\xdc\x46\x85\xa9\xad\x53\x07\x2b\x53\x34\x06\x07\xff\x14\x94\x59\x19\x02\xe4\x38\xe8\x31\x83\x4e\xb9\x58\x46\x6b\xcb\x2d\x23\x86\x92\x70\x00\x35\x88\x22\xcf\x31\xb2\x26\x2f\xe7\xc3\x75\x2d\x36\x2c\x72\x74\xb0\x23\x47\xb7\xd3\xd1\x26\x16\x85\x37\x72\xe2\x00\x8c\x44\xcf\x10\xda\x33\x2d\x1a\xde\x60\x86\x69\x23\x69\x2a\x7c\xcd\x4b\x51\x0d\x95\x54\x39\x77\x2e\x29\xea\x1b\xa6\x50\xa2\x6a\x8f\x6f\x50\x99\x5c\x3e\x54\xfb\xef\x50\x5b\x0b\x07\x45\x17\x89\x6d\x28\x13\x77\x37\x1d\xdb\x8e\x1e\x4a\x05\x66\x4a\x6f\x99\x20\xe5\x70\xe2\xb9\x71\x7e\x0c\x6d\x49\x04\x2d\x7a\xfe\x72\xc7\xf2\x59\x30\x8f\xbb\x02\x5d\x73\xe5\xc9\x20\xea\x78\xec\x20\x90\xf0\x8a\x7f\x42\x17\x7c\x47\x19\x60\xb0\x16\xbd\x26\xb7\x71\xb6\xc7\x9f\x0e\xd1\x33\x82\x3d\xd3\xab\xee\x63\x99\xc8\x2b\x53\xa0\x44\x5c\x71\x01\xc6\xcc\x44\x1f\x32\x4f\x3c\xca\xc0\x29\x3d\x52\xd3\x61\x19\x58\xa9\x7d\x65\xb4\xdc\xcf\x0d\xf4\x3d\xf1\x08\xa9\x42\xda\x23\x09\xd8\xbf\x5e\x50\x49\xf8\x4d\xc0\xcb\x47\x4c\x1c\x4f\xf7\x7b\x2b\xd8\x16\x18\xc5\x31\x92\x3b\xb5\x6f\xdc\x6c\x0d\x92\x88\x16\xd1\x9e\xdb\x3f\xe2\xe9\xda\x5f\xd4\x84\xe2\x46\x61\x5a\xde\x1c\x55\xcf\xa4\x00\xbe\xfd\xce\x67\xf1\x4a\x69\x1c\x97\xe6\x20\x48\xd8\x5d\x7f\x7e\xae\x71\x20\x0e\x4e\xae\xc0\x56\xa9\x91\x01\x3c\x82\x1d\x0f\x72\xe7\x76\xec\x29\x49\xd6\x5d\x2d\x83\xe3\xdb\x36\x06\xa9\x3b\x66\x13\x97\x87\x6a\xd5\xb6\x3d\x50\x5e\x52\xb9\x4b\xc7\x73\x57\x78\xc9\xf4\x2e\x59\x07\x95\x93\x6f\xd0\x4b\x17\x57\x19\x3e\x27\x27\xc7\x60\xdb\x3b\xed\x9a\x0e\x53\x44\x16\x3e\x3f\x8d\x92\x6d\x77\xa2\x0a\xeb\x3f\x52\xa8\xc6\x55\x5e\x31\x49\x37\x85\xf4\xc5\x1f\x26\x2d\xa9\x1c\xbf\x8b\x27\x54\xda\xc3\x6a\x20\xe5\x2a\x78\x04\xb0\xd6\x90\x70\x72\xaa\x8b\x68\xbd\x88\xf7\x02\x5f\x48\xb1\x7e\xc0\x58\x4c\x3f\x66\x1a\xf9\x3e\xe1\x65\xc0\x70\xa7\xcf\x38\x69\xaf\xf0\x56\x6c\x64\x49\x9c\x27\xad\x78\x74\x4f\xc2\x87\xde\x56\x39\x00\xda\x77\x0b\xcb\x2d\x1b\x89\xfb\x35\x4f\x02\xf5\x08\x51\x13\x60\xc1\x0a\x5a\x47\x4d\x26\x1c\x33\x30\x78\xda\xc0\x9c\x46\x47\xe2\x5b\x79\x60\x49\x6e\x37\x67\x53\x0a\x3e\xe9\xec\x46\x39\xb2\xf1\x34\x0d\xc6\x84\x53\x75\x6e\xe1\x0c\x59\xd9\x1e\xde\x29\x85\x10\x7b\x49\x49\xa5\x77\x79\xbe\x49\x56\x2e\x36\xe7\x0b\x3a\xbb\x4f\x03\x62\x7b\xd2\x4d\x31\x95\x2f\xbd\x38\x7b\xa8\x4f\x21\xe1\xec\x46\x70\x76\x95\x7d\x29\x22\x78\x88\x0a\x90\xdd\x9d\x5c\xda\xde\x19\x51\xcf\xf0\xfc\x59\x52\x65\x7c\x33\x13\xdf\xf3\x48\xda\xbb\x2a\x75\xdb\x60\xb2\x02\x15\xd4\xfc\x19\xed\x1b\xec\x7f\x35\xa8\xff\x28\x31\x07\x2d\x12\xc8\xdc\x88\x46\x7c\x8a\x5b\x22'); /** - * 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. - *

    + * Provides metadata using the Google Earth Enterprise REST API. This is used by the GoogleEarthEnterpriseImageryProvider + * and GoogleEarthEnterpriseTerrainProvider to share metadata requests. * - * @exports IauOrientationParameters + * @alias GoogleEarthEnterpriseMetadata + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @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 GoogleEarthEnterpriseImageryProvider + * @see GoogleEarthEnterpriseTerrainProvider * - * @private */ - function IauOrientationParameters(rightAscension, declination, rotation, rotationRate) { + function GoogleEarthEnterpriseMetadata(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + /** - * The right ascension of the north pole of the body with respect to - * the International Celestial Reference Frame, in radians. - * @type {Number} - * - * @private + * True if imagery is available. + * @type {Boolean} + * @default true */ - this.rightAscension = rightAscension; + this.imageryPresent = true; /** - * The declination of the north pole of the body with respect to - * the International Celestial Reference Frame, in radians. - * @type {Number} - * - * @private + * True if imagery is sent as a protocol buffer, false if sent as plain images. If undefined we will try both. + * @type {Boolean} + * @default undefined */ - this.declination = declination; + this.protoImagery = undefined; /** - * The rotation about the north pole used to align a set of axes with - * the meridian defined by the IAU report, in radians. + * True if terrain is available. + * @type {Boolean} + * @default true + */ + this.terrainPresent = true; + + /** + * Exponent used to compute constant to calculate negative height values. * @type {Number} - * - * @private + * @default 32 */ - this.rotation = rotation; + this.negativeAltitudeExponentBias = 32; /** - * The instantaneous rotation rate about the north pole, in radians per second. + * Threshold where any numbers smaller are actually negative values. They are multiplied by -2^negativeAltitudeExponentBias. * @type {Number} - * - * @private + * @default EPSILON12 */ - this.rotationRate = rotationRate; + this.negativeAltitudeThreshold = CesiumMath.EPSILON12; + + /** + * Dictionary of provider id to copyright strings. + * @type {Object} + * @default {} + */ + this.providers = {}; + + /** + * Key used to decode packets + * @type {ArrayBuffer} + */ + this.key = undefined; + + this._quadPacketVersion = 1; + + this._url = appendForwardSlash(options.url); + this._proxy = options.proxy; + + this._tileInfo = {}; + this._subtreePromises = {}; + + var that = this; + this._readyPromise = requestDbRoot(this) + .then(function() { + return that.getQuadTreePacket('', that._quadPacketVersion); + }) + .then(function() { + return true; + }) + .otherwise(function(e) { + var message = 'An error occurred while accessing ' + getMetadataUrl(that, '', 1) + '.'; + return when.reject(new RuntimeError(message)); + }); } - return IauOrientationParameters; -}); + defineProperties(GoogleEarthEnterpriseMetadata.prototype, { + /** + * Gets the name of the Google Earth Enterprise server. + * @memberof GoogleEarthEnterpriseMetadata.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._url; + } + }, -/*global define*/ -define('Core/Iau2000Orientation',[ - './defined', - './IauOrientationParameters', - './JulianDate', - './Math', - './TimeConstants' - ], function( - defined, - IauOrientationParameters, - JulianDate, - CesiumMath, - TimeConstants) { - 'use strict'; + /** + * Gets the proxy used for metadata requests. + * @memberof GoogleEarthEnterpriseMetadata.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._proxy; + } + }, + + /** + * Gets a promise that resolves to true when the metadata is ready for use. + * @memberof GoogleEarthEnterpriseMetadata.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise; + } + } + }); /** - * 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. + * Converts a tiles (x, y, level) position into a quadkey used to request an image + * from a Google Earth Enterprise server. * - * @exports Iau2000Orientation + * @param {Number} x The tile's x coordinate. + * @param {Number} y The tile's y coordinate. + * @param {Number} level The tile's zoom level. * - * @private + * @see GoogleEarthEnterpriseMetadata#quadKeyToTileXY */ - var Iau2000Orientation = {}; + GoogleEarthEnterpriseMetadata.tileXYToQuadKey = function(x, y, level) { + var quadkey = ''; + for (var i = level; i >= 0; --i) { + var bitmask = 1 << i; + var digit = 0; - var TdtMinusTai = 32.184; - var J2000d = 2451545.0; + // Tile Layout + // ___ ___ + //| | | + //| 3 | 2 | + //|-------| + //| 0 | 1 | + //|___|___| + // - 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(); + if (!isBitSet(y, bitmask)) { // Top Row + digit |= 2; + if (!isBitSet(x, bitmask)) { // Right to left + digit |= 1; + } + } else if (isBitSet(x, bitmask)) { // Left to right + digit |= 1; + } + + quadkey += digit; + } + return quadkey; + }; /** - * Compute the orientation parameters for the Moon. + * Converts a tile's quadkey used to request an image from a Google Earth Enterprise server into the + * (x, y, level) position. * - * @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. + * @param {String} quadkey The tile's quad key + * + * @see GoogleEarthEnterpriseMetadata#tileXYToQuadKey */ - 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); + GoogleEarthEnterpriseMetadata.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]; - 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); + if (isBitSet(digit, 2)) { // Top Row + if (!isBitSet(digit, 1)) { // // Right to left + x |= bitmask; + } + } else { + y |= bitmask; + if (isBitSet(digit, 1)) { // Left to right + x |= bitmask; + } + } + } + return { + x : x, + y : y, + level : level + }; + }; - 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; + GoogleEarthEnterpriseMetadata.prototype.isValid = function(quadKey) { + var info = this.getTileInformationFromQuadKey(quadKey); + if (defined(info)) { + return info !== null; + } - 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; + var valid = true; + var q = quadKey; + var last; + while (q.length > 1) { + last = q.substring(q.length - 1); + q = q.substring(0, q.length - 1); + info = this.getTileInformationFromQuadKey(q); + if (defined(info)) { + if (!info.hasSubtree() && + !info.hasChild(parseInt(last))) { + // We have no subtree or child available at some point in this node's ancestry + valid = false; + } - if (!defined(result)) { - result = new IauOrientationParameters(); + break; + } else if (info === null) { + // Some node in the ancestry was loaded and said there wasn't a subtree + valid = false; + break; + } } - result.rightAscension = rightAscension; - result.declination = declination; - result.rotation = rotation; - result.rotationRate = rotationRate; - - return result; + return valid; }; - return Iau2000Orientation; -}); - -/*global define*/ -define('Core/IauOrientationAxes',[ - './Cartesian3', - './defined', - './Iau2000Orientation', - './JulianDate', - './Math', - './Matrix3', - './Quaternion' - ], function( - Cartesian3, - defined, - Iau2000Orientation, - JulianDate, - CesiumMath, - Matrix3, - Quaternion) { - 'use strict'; + var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); /** - * 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}. + * Retrieves a Google Earth Enterprise quadtree packet. * - * @see Iau2000Orientation + * @param {String} [quadKey=''] The quadkey to retrieve the packet for. + * @param {Number} [version=1] The cnode version to be used in the request. + * @param {Request} [request] The request object. Intended for internal use only. * * @private */ - function IauOrientationAxes(computeFunction) { - if (!defined(computeFunction) || typeof computeFunction !== 'function') { - computeFunction = Iau2000Orientation.ComputeMoon; + GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version, request) { + version = defaultValue(version, 1); + quadKey = defaultValue(quadKey, ''); + var url = getMetadataUrl(this, quadKey, version); + var proxy = this._proxy; + if (defined(proxy)) { + url = proxy.getURL(url); } - 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); + var promise = loadArrayBuffer(url, undefined, request); - if (!defined(result)) { - result = new Matrix3(); + if (!defined(promise)) { + return undefined; // Throttled } - 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; + var tileInfo = this._tileInfo; + var key = this.key; + return promise + .then(function(metadata) { + var decodePromise = taskProcessor.scheduleTask({ + buffer : metadata, + quadKey : quadKey, + type : 'Metadata', + key: key + }, [metadata]); - return result; - } + return decodePromise + .then(function(result) { + var root; + var topLevelKeyLength = -1; + if (quadKey !== '') { + // Root tile has no data except children bits, so put them into the tile info + topLevelKeyLength = quadKey.length + 1; + var top = result[quadKey]; + root = tileInfo[quadKey]; + root._bits |= top._bits; - var rotMtxScratch = new Matrix3(); - var quatScratch = new Quaternion(); + delete result[quadKey]; + } + + // Copy the resulting objects into tileInfo + // Make sure we start with shorter quadkeys first, so we know the parents have + // already been processed. Otherwise we can lose ancestorHasTerrain along the way. + var keys = Object.keys(result); + keys.sort(function(a, b) { + return a.length - b.length; + }); + var keysLength = keys.length; + for (var i = 0; i < keysLength; ++i) { + var key = keys[i]; + var r = result[key]; + if (r !== null) { + var info = GoogleEarthEnterpriseTileInformation.clone(result[key]); + var keyLength = key.length; + if (keyLength === topLevelKeyLength) { + info.setParent(root); + } else if(keyLength > 1){ + var parent = tileInfo[key.substring(0, key.length - 1)]; + info.setParent(parent); + } + tileInfo[key] = info; + } else { + tileInfo[key] = null; + } + } + }); + }); + }; /** - * Computes a rotation from ICRF to a Globe's Fixed axes. + * Populates the metadata subtree down to the specified tile. * - * @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. + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. + * + * @returns {Promise} A promise that resolves to the tile info for the requested quad key + * + * @private */ - IauOrientationAxes.prototype.evaluate = function(date, result) { - if (!defined(date)) { - date = JulianDate.now(); - } + GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level, request) { + var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + return populateSubtree(this, quadkey, request); + }; - var alphaDeltaW = this._computeFunction(date); - var precMtx = computeRotationMatrix(alphaDeltaW.rightAscension, alphaDeltaW.declination, result); + function populateSubtree(that, quadKey, request) { + var tileInfo = that._tileInfo; + var q = quadKey; + var t = tileInfo[q]; + // If we have tileInfo make sure sure it is not a node with a subtree that's not loaded + if (defined(t) && (!t.hasSubtree() || t.hasChildren())) { + return t; + } - var rot = CesiumMath.zeroToTwoPi(alphaDeltaW.rotation); - var quat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, rot, quatScratch); - var rotMtx = Matrix3.fromQuaternion(Quaternion.conjugate(quat, quat), rotMtxScratch); + while ((t === undefined) && q.length > 1) { + q = q.substring(0, q.length - 1); + t = tileInfo[q]; + } - var cbi2cbf = Matrix3.multiply(rotMtx, precMtx, precMtx); - return cbi2cbf; - }; + var subtreeRequest; + var subtreePromises = that._subtreePromises; + var promise = subtreePromises[q]; + if (defined(promise)) { + return promise + .then(function() { + // Recursively call this in case we need multiple subtree requests + subtreeRequest = new Request({ + throttle : request.throttle, + throttleByServer : request.throttleByServer, + type : request.type, + priorityFunction : request.priorityFunction + }); + return populateSubtree(that, quadKey, subtreeRequest); + }); + } - /** - * 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. - */ + // t is either + // null so one of its parents was a leaf node, so this tile doesn't exist + // exists but doesn't have a subtree to request + // undefined so no parent exists - this shouldn't ever happen once the provider is ready + if (!defined(t) || !t.hasSubtree()) { + return when.reject(new RuntimeError('Couldn\'t load metadata for tile ' + quadKey)); + } - return IauOrientationAxes; -}); + // We need to split up the promise here because when will execute syncronously if getQuadTreePacket + // is already resolved (like in the tests), so subtreePromises will never get cleared out. + // Only the initial request will also remove the promise from subtreePromises. + promise = that.getQuadTreePacket(q, t.cnodeVersion, request); + if (!defined(promise)) { + return undefined; + } + subtreePromises[q] = promise; -/*global define*/ -define('Core/InterpolationAlgorithm',[ - './DeveloperError' - ], function( - DeveloperError) { - 'use strict'; + return promise + .then(function() { + // Recursively call this in case we need multiple subtree requests + subtreeRequest = new Request({ + throttle : request.throttle, + throttleByServer : request.throttleByServer, + type : request.type, + priorityFunction : request.priorityFunction + }); + return populateSubtree(that, quadKey, subtreeRequest); + }) + .always(function() { + delete subtreePromises[q]; + }); + } /** - * The interface for interpolation algorithms. - * - * @exports InterpolationAlgorithm + * Gets information about a tile * - * @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} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. * - * @param {Number} degree The desired degree of interpolation. - * @returns {Number} The number of required data points needed for the desired degree of interpolation. + * @private */ - InterpolationAlgorithm.getRequiredDataPoints = DeveloperError.throwInstantiationError; + GoogleEarthEnterpriseMetadata.prototype.getTileInformation = function(x, y, level) { + var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + return this._tileInfo[quadkey]; + }; /** - * Performs zero order interpolation. - * @function + * Gets information about a tile from a quadKey * - * @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. + * @param {String} quadkey The quadkey for the tile + * @returns {GoogleEarthEnterpriseTileInformation|undefined} Information about the tile or undefined if it isn't loaded. * - * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided. + * @private */ - InterpolationAlgorithm.interpolateOrderZero = DeveloperError.throwInstantiationError; + GoogleEarthEnterpriseMetadata.prototype.getTileInformationFromQuadKey = function(quadkey) { + return this._tileInfo[quadkey]; + }; - /** - * 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; + function getMetadataUrl(that, quadKey, version) { + return joinUrls(that._url, 'flatfile?q2-0' + quadKey + '-q.' + version.toString()); + } - return InterpolationAlgorithm; -}); + function requestDbRoot(that) { + var url = joinUrls(that._url, 'dbRoot.v5?output=proto'); + var proxy = that._proxy; + if (defined(proxy)) { + url = proxy.getURL(url); + } -/*global define*/ -define('Core/isDataUri',[ - './defined' - ], function( - defined) { - 'use strict'; + var promise = loadArrayBuffer(url) + .then(function(buf) { + var encryptedDbRootProto = dbrootParser.EncryptedDbRootProto.decode(new Uint8Array(buf)); - var dataUriRegex = /^data:/i; + var byteArray = encryptedDbRootProto.encryptionData; + var offset = byteArray.byteOffset; + var end = offset + byteArray.byteLength; + var key = that.key = byteArray.buffer.slice(offset, end); - /** - * Determines if the specified uri is a data uri. - * - * @exports isDataUri - * - * @param {String} uri The uri to test. - * @returns {Boolean} true when the uri is a data uri; otherwise, false. - * - * @private - */ - function isDataUri(uri) { - if (defined(uri)) { - return dataUriRegex.test(uri); - } + byteArray = encryptedDbRootProto.dbrootData; + offset = byteArray.byteOffset; + end = offset + byteArray.byteLength; + var dbRootCompressed = byteArray.buffer.slice(offset, end); + return taskProcessor.scheduleTask({ + buffer : dbRootCompressed, + type : 'DbRoot', + key: key + }, [dbRootCompressed]); + }) + .then(function(result) { + var dbRoot = dbrootParser.DbRootProto.decode(new Uint8Array(result.buffer)); + that.imageryPresent = defaultValue(dbRoot.imageryPresent, that.imageryPresent); + that.protoImagery = dbRoot.protoImagery; + that.terrainPresent = defaultValue(dbRoot.terrainPresent, that.terrainPresent); + if (defined(dbRoot.endSnippet) && defined(dbRoot.endSnippet.model)) { + var model = dbRoot.endSnippet.model; + that.negativeAltitudeExponentBias = defaultValue(model.negativeAltitudeExponentBias, that.negativeAltitudeExponentBias); + that.negativeAltitudeThreshold = defaultValue(model.compressedNegativeAltitudeThreshold, that.negativeAltitudeThreshold); + } + if (defined(dbRoot.databaseVersion)) { + that._quadPacketVersion = defaultValue(dbRoot.databaseVersion.quadtreeVersion, that._quadPacketVersion); + } + var providers = that.providers; + var providerInfo = defaultValue(dbRoot.providerInfo, []); + var count = providerInfo.length; + for(var i=0;itrue 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 {Object} options Object with the following properties: + * @param {ArrayBuffer} options.buffer The buffer containing terrain data. + * @param {Number} options.negativeAltitudeExponentBias Multiplier for negative terrain heights that are encoded as very small positive values. + * @param {Number} options.negativeElevationThreshold Threshold for negative values + * @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 PositionBit ValueChild Tile
    01Southwest
    12Southeast
    24Northeast
    38Northwest
    + * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance; + * otherwise, false. + * @param {Credit[]} [options.credits] Array of credits for this tile. * - * @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; + * var buffer = ... + * var childTileMask = ... + * var terrainData = new Cesium.GoogleEarthEnterpriseTerrainData({ + * buffer : heightBuffer, + * childTileMask : childTileMask * }); * - * @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); + * @see TerrainData + * @see HeightTerrainData + * @see QuantizedMeshTerrainData */ - function TimeInterval(options) { + function GoogleEarthEnterpriseTerrainData(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(); + + this._buffer = options.buffer; + this._credits = options.credits; + this._negativeAltitudeExponentBias = options.negativeAltitudeExponentBias; + this._negativeElevationThreshold = options.negativeElevationThreshold; - /** - * Gets or sets the stop time of this interval. - * @type {JulianDate} - */ - this.stop = defined(options.stop) ? JulianDate.clone(options.stop) : new JulianDate(); + // Convert from google layout to layout of other providers + // 3 2 -> 2 3 + // 0 1 -> 0 1 + var googleChildTileMask = defaultValue(options.childTileMask, 15); + var childTileMask = googleChildTileMask & 3; // Bottom row is identical + childTileMask |= (googleChildTileMask & 4) ? 8 : 0; // NE + childTileMask |= (googleChildTileMask & 8) ? 4 : 0; // NW - /** - * Gets or sets the data associated with this interval. - * @type {Object} - */ - this.data = options.data; + this._childTileMask = childTileMask; - /** - * Gets or sets whether or not the start time is included in this interval. - * @type {Boolean} - * @default true - */ - this.isStartIncluded = defaultValue(options.isStartIncluded, true); + this._createdByUpsampling = defaultValue(options.createdByUpsampling, false); - /** - * Gets or sets whether or not the stop time is included in this interval. - * @type {Boolean} - * @default true - */ - this.isStopIncluded = defaultValue(options.isStopIncluded, true); + this._skirtHeight = undefined; + this._bufferType = this._buffer.constructor; + this._mesh = undefined; + this._minimumHeight = undefined; + this._maximumHeight = undefined; + this._vertexCountWithoutSkirts = undefined; + this._skirtIndex = undefined; } - defineProperties(TimeInterval.prototype, { + defineProperties(GoogleEarthEnterpriseTerrainData.prototype, { /** - * Gets whether or not this interval is empty. - * @memberof TimeInterval.prototype - * @type {Boolean} - * @readonly + * An array of credits for this tile + * @memberof GoogleEarthEnterpriseTerrainData.prototype + * @type {Credit[]} */ - isEmpty : { + credits : { get : function() { - var stopComparedToStart = JulianDate.compare(this.stop, this.start); - return stopComparedToStart < 0 || (stopComparedToStart === 0 && (!this.isStartIncluded || !this.isStopIncluded)); + return this._credits; + } + }, + /** + * 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 GoogleEarthEnterpriseTerrainData.prototype + * @type {Uint8Array|Image|Canvas} + */ + waterMask : { + get : function() { + return undefined; } } }); - var scratchInterval = { - start : undefined, - stop : undefined, - isStartIncluded : undefined, - isStopIncluded : undefined, - data : undefined - }; + var taskProcessor = new TaskProcessor('createVerticesFromGoogleEarthEnterpriseBuffer'); + + var nativeRectangleScratch = new Rectangle(); + var rectangleScratch = new Rectangle(); /** - * Creates a new instance from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} interval. + * Creates a {@link TerrainMesh} from this terrain data. * - * @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. + * @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. */ - TimeInterval.fromIso8601 = function(options, result) { + GoogleEarthEnterpriseTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) { - 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; + var ellipsoid = tilingScheme.ellipsoid; + tilingScheme.tileXYToNativeRectangle(x, y, level, nativeRectangleScratch); + tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch); + exaggeration = defaultValue(exaggeration, 1.0); - if (!defined(result)) { - scratchInterval.start = start; - scratchInterval.stop = stop; - scratchInterval.isStartIncluded = isStartIncluded; - scratchInterval.isStopIncluded = isStopIncluded; - scratchInterval.data = data; - return new TimeInterval(scratchInterval); - } + // Compute the center of the tile for RTC rendering. + var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangleScratch)); - result.start = start; - result.stop = stop; - result.isStartIncluded = isStartIncluded; - result.isStopIncluded = isStopIncluded; - result.data = data; - return result; - }; + var levelZeroMaxError = 40075.16; // From Google's Doc + var thisLevelMaxError = levelZeroMaxError / (1 << level); + this._skirtHeight = Math.min(thisLevelMaxError * 8.0, 1000.0); - /** - * 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) { - - return JulianDate.toIso8601(timeInterval.start, precision) + '/' + JulianDate.toIso8601(timeInterval.stop, precision); - }; + var verticesPromise = taskProcessor.scheduleTask({ + buffer : this._buffer, + nativeRectangle : nativeRectangleScratch, + rectangle : rectangleScratch, + relativeToCenter : center, + ellipsoid : ellipsoid, + skirtHeight : this._skirtHeight, + exaggeration : exaggeration, + includeWebMercatorT : true, + negativeAltitudeExponentBias: this._negativeAltitudeExponentBias, + negativeElevationThreshold: this._negativeElevationThreshold + }); - /** - * 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)) { + if (!defined(verticesPromise)) { + // Postponed 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)))); + var that = this; + return verticesPromise + .then(function(result) { + that._mesh = new TerrainMesh( + center, + new Float32Array(result.vertices), + new Uint16Array(result.indices), + result.minimumHeight, + result.maximumHeight, + result.boundingSphere3D, + result.occludeePointInScaledSpace, + result.numberOfAttributes, + result.orientedBoundingBox, + TerrainEncoding.clone(result.encoding), + exaggeration); + + that._vertexCountWithoutSkirts = result.vertexCountWithoutSkirts; + that._skirtIndex = result.skirtIndex; + that._minimumHeight = result.minimumHeight; + that._maximumHeight = result.maximumHeight; + + // Free memory received from server after mesh is created. + that._buffer = undefined; + return that._mesh; + }); }; /** - * 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. + * Computes the terrain height at a specified longitude and latitude. * - * @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. + * @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. */ - TimeInterval.equalsEpsilon = function(left, right, epsilon, dataComparer) { - - 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)))); + GoogleEarthEnterpriseTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) { + var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0); + var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0); + + if (!defined(this._mesh)) { + return interpolateHeight(this, u, v, rectangle); + } + + return interpolateMeshHeight(this, u, v); }; + var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh'); + /** - * Computes the intersection of two intervals, optionally merging their data. + * 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 {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. + * @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. */ - TimeInterval.intersect = function(left, right, result, mergeCallback) { + GoogleEarthEnterpriseTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) { - if (!defined(right)) { - return TimeInterval.clone(TimeInterval.EMPTY, result); + var mesh = this._mesh; + if (!defined(this._mesh)) { + return undefined; } - var leftStart = left.start; - var leftStop = left.stop; + var isEastChild = thisX * 2 !== descendantX; + var isNorthChild = thisY * 2 === descendantY; - var rightStart = right.start; - var rightStop = right.stop; + var ellipsoid = tilingScheme.ellipsoid; + var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel); - var intersectsStartRight = JulianDate.greaterThanOrEquals(rightStart, leftStart) && JulianDate.greaterThanOrEquals(leftStop, rightStart); - var intersectsStartLeft = !intersectsStartRight && JulianDate.lessThanOrEquals(rightStart, leftStart) && JulianDate.lessThanOrEquals(leftStart, rightStop); + 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 (!intersectsStartRight && !intersectsStartLeft) { - return TimeInterval.clone(TimeInterval.EMPTY, result); + if (!defined(upsamplePromise)) { + // Postponed + return undefined; } - var leftIsStartIncluded = left.isStartIncluded; - var leftIsStopIncluded = left.isStopIncluded; - var rightIsStartIncluded = right.isStartIncluded; - var rightIsStopIncluded = right.isStopIncluded; - var leftLessThanRight = JulianDate.lessThan(leftStop, rightStop); + var that = this; + return upsamplePromise + .then(function(result) { + var quantizedVertices = new Uint16Array(result.vertices); + var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices); - 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; + var skirtHeight = that._skirtHeight; + + // Use QuantizedMeshTerrainData since we have what we need already parsed + return new QuantizedMeshTerrainData({ + quantizedVertices : quantizedVertices, + indices : indicesTypedArray, + 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 : skirtHeight, + southSkirtHeight : skirtHeight, + eastSkirtHeight : skirtHeight, + northSkirtHeight : skirtHeight, + childTileMask : 0, + createdByUpsampling : true, + credits : that._credits + }); + }); }; /** - * Checks if the specified date is inside the provided interval. + * 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 {TimeInterval} timeInterval The interval. - * @param {JulianDate} julianDate The date to check. - * @returns {Boolean} true if the interval contains the specified date, false otherwise. + * @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. */ - TimeInterval.contains = function(timeInterval, julianDate) { + GoogleEarthEnterpriseTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) { - if (timeInterval.isEmpty) { - return false; - } - - var startComparedToDate = JulianDate.compare(timeInterval.start, julianDate); - if (startComparedToDate === 0) { - return timeInterval.isStartIncluded; + var bitNumber = 2; // northwest child + if (childX !== thisX * 2) { + ++bitNumber; // east child } - - var dateComparedToStop = JulianDate.compare(julianDate, timeInterval.stop); - if (dateComparedToStop === 0) { - return timeInterval.isStopIncluded; + if (childY !== thisY * 2) { + bitNumber -= 2; // south child } - 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); + return (this._childTileMask & (1 << bitNumber)) !== 0; }; /** - * Creates a string representing this TimeInterval in ISO8601 format. + * 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 {String} A string representing this TimeInterval in ISO8601 format. + * @returns {Boolean} True if this instance was created by upsampling; otherwise, false. */ - TimeInterval.prototype.toString = function() { - return TimeInterval.toIso8601(this); + GoogleEarthEnterpriseTerrainData.prototype.wasCreatedByUpsampling = function() { + return this._createdByUpsampling; }; - /** - * 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; -}); + var texCoordScratch0 = new Cartesian2(); + var texCoordScratch1 = new Cartesian2(); + var texCoordScratch2 = new Cartesian2(); + var barycentricCoordinateScratch = new Cartesian3(); -/*global define*/ -define('Core/Iso8601',[ - './freezeObject', - './JulianDate', - './TimeInterval' - ], function( - freezeObject, - JulianDate, - TimeInterval) { - 'use strict'; + function interpolateMeshHeight(terrainData, u, v) { + var mesh = terrainData._mesh; + var vertices = mesh.vertices; + var encoding = mesh.encoding; + var indices = mesh.indices; - 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 - })); + 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]; - /** - * 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, + var uv0 = encoding.decodeTextureCoordinates(vertices, i0, texCoordScratch0); + var uv1 = encoding.decodeTextureCoordinates(vertices, i1, texCoordScratch1); + var uv2 = encoding.decodeTextureCoordinates(vertices, i2, texCoordScratch2); - /** - * 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, + 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; + } + } - /** - * 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 - }; + // Position does not lie in any triangle in this mesh. + return undefined; + } - return Iso8601; -}); + var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT; + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT; + var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT; + var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT; -/*global define*/ -define('Core/KeyboardEventModifier',[ - './freezeObject' - ], function( - freezeObject) { - 'use strict'; + function interpolateHeight(terrainData, u, v, rectangle) { + var buffer = terrainData._buffer; + var quad = 0; // SW + var uStart = 0.0; + var vStart = 0.0; + if (v > 0.5) { // Upper row + if (u > 0.5) { // NE + quad = 2; + uStart = 0.5; + } else { // NW + quad = 3; + } + vStart = 0.5; + } else if (u > 0.5) { // SE + quad = 1; + uStart = 0.5; + } - /** - * 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, + var dv = new DataView(buffer); + var offset = 0; + for (var q = 0; q < quad; ++q) { + offset += dv.getUint32(offset, true); + offset += sizeOfUint32; + } + offset += sizeOfUint32; // Skip length of quad + offset += 2 * sizeOfDouble; // Skip origin - /** - * Represents the control key being held down. - * - * @type {Number} - * @constant - */ - CTRL : 1, + // Read sizes + var xSize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); + offset += sizeOfDouble; + var ySize = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0); + offset += sizeOfDouble; - /** - * Represents the alt key being held down. - * - * @type {Number} - * @constant - */ - ALT : 2 - }; + // Samples per quad + var xScale = rectangle.width / xSize / 2; + var yScale = rectangle.height / ySize / 2; - return freezeObject(KeyboardEventModifier); -}); + // Number of points + var numPoints = dv.getInt32(offset, true); + offset += sizeOfInt32; -/*global define*/ -define('Core/LagrangePolynomialApproximation',[ - './defined' - ], function( - defined) { - 'use strict'; + // Number of faces + var numIndices = dv.getInt32(offset, true) * 3; + offset += sizeOfInt32; - /** - * An {@link InterpolationAlgorithm} for performing Lagrange interpolation. - * - * @exports LagrangePolynomialApproximation - */ - var LagrangePolynomialApproximation = { - type : 'Lagrange' - }; + offset += sizeOfInt32; // Skip Level - /** - * 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); - }; + var uBuffer = new Array(numPoints); + var vBuffer = new Array(numPoints); + var heights = new Array(numPoints); + var i; + for (i = 0; i < numPoints; ++i) { + uBuffer[i] = uStart + (dv.getUint8(offset++) * xScale); + vBuffer[i] = vStart + (dv.getUint8(offset++) * yScale); - /** - * 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); + // Height is stored in units of (1/EarthRadius) or (1/6371010.0) + heights[i] = (dv.getFloat32(offset, true) * 6371010.0); + offset += sizeOfFloat; } - var i; - var j; - var length = xTable.length; - - for (i = 0; i < yStride; i++) { - result[i] = 0; + var indices = new Array(numIndices); + for (i = 0; i < numIndices; ++i) { + indices[i] = dv.getUint16(offset, true); + offset += sizeOfUint16; } - for (i = 0; i < length; i++) { - var coefficient = 1; + for (i = 0; i < numIndices; i += 3) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + var i2 = indices[i + 2]; - for (j = 0; j < length; j++) { - if (j !== i) { - var diffX = xTable[i] - xTable[j]; - coefficient *= (x - xTable[j]) / diffX; - } - } + var u0 = uBuffer[i0]; + var u1 = uBuffer[i1]; + var u2 = uBuffer[i2]; - for (j = 0; j < yStride; j++) { - result[j] += coefficient * yTable[i * yStride + j]; + 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) { + return barycentric.x * heights[i0] + + barycentric.y * heights[i1] + + barycentric.z * heights[i2]; } } - return result; - }; + // Position does not lie in any triangle in this mesh. + return undefined; + } - return LagrangePolynomialApproximation; + return GoogleEarthEnterpriseTerrainData; }); -/*global define*/ -define('Core/LinearApproximation',[ +define('Core/GoogleEarthEnterpriseTerrainProvider',[ + '../ThirdParty/when', + './Credit', + './defaultValue', './defined', - './DeveloperError' + './defineProperties', + './DeveloperError', + './Event', + './GeographicTilingScheme', + './GoogleEarthEnterpriseMetadata', + './GoogleEarthEnterpriseTerrainData', + './HeightmapTerrainData', + './JulianDate', + './loadArrayBuffer', + './Math', + './Rectangle', + './Request', + './RequestState', + './RequestType', + './RuntimeError', + './TaskProcessor', + './TileProviderError' ], function( + when, + Credit, + defaultValue, defined, - DeveloperError) { + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + GoogleEarthEnterpriseTerrainData, + HeightmapTerrainData, + JulianDate, + loadArrayBuffer, + CesiumMath, + Rectangle, + Request, + RequestState, + RequestType, + RuntimeError, + TaskProcessor, + TileProviderError) { 'use strict'; - /** - * An {@link InterpolationAlgorithm} for performing linear interpolation. - * - * @exports LinearApproximation - */ - var LinearApproximation = { - type : 'Linear' + var TerrainState = { + UNKNOWN : 0, + NONE : 1, + SELF : 2, + PARENT : 3 }; - /** - * 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; - }; + var julianDateScratch = new JulianDate(); - /** - * 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 (!defined(result)) { - result = new Array(yStride); - } + function TerrainCache() { + this._terrainCache = {}; + this._lastTidy = JulianDate.now(); + } - var i; - var y0; - var y1; - var x0 = xTable[0]; - var x1 = xTable[1]; + TerrainCache.prototype.add = function(quadKey, buffer) { + this._terrainCache[quadKey] = { + buffer : buffer, + timestamp : JulianDate.now() + }; + }; - - for (i = 0; i < yStride; i++) { - y0 = yTable[i]; - y1 = yTable[i + yStride]; - result[i] = (((y1 - y0) * x) + (x1 * y0) - (x0 * y1)) / (x1 - x0); + TerrainCache.prototype.get = function(quadKey) { + var terrainCache = this._terrainCache; + var result = terrainCache[quadKey]; + if (defined(result)) { + delete this._terrainCache[quadKey]; + return result.buffer; } - - 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/loadCRN',[ - './CompressedTextureBuffer', - './defined', - './DeveloperError', - './loadArrayBuffer', - './TaskProcessor', - '../ThirdParty/when' -], function( - CompressedTextureBuffer, - defined, - DeveloperError, - loadArrayBuffer, - TaskProcessor, - when) { - 'use strict'; + TerrainCache.prototype.tidy = function() { + JulianDate.now(julianDateScratch); + if (JulianDate.secondsDifference(julianDateScratch, this._lastTidy) > 10) { + var terrainCache = this._terrainCache; + var keys = Object.keys(terrainCache); + var count = keys.length; + for (var i = 0; i < count; ++i) { + var k = keys[i]; + var e = terrainCache[k]; + if (JulianDate.secondsDifference(julianDateScratch, e.timestamp) > 10) { + delete terrainCache[k]; + } + } - var transcodeTaskProcessor = new TaskProcessor('transcodeCRNToDXT', Number.POSITIVE_INFINITY); + JulianDate.clone(julianDateScratch, this._lastTidy); + } + }; /** - * Asynchronously loads and parses the given URL to a CRN file or parses the raw binary data of a CRN file. - * Returns a promise that will resolve to an object containing the image buffer, width, height and format once loaded, - * or reject if the URL failed to load or failed to parse the data. 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. + * Provides tiled terrain using the Google Earth Enterprise REST API. * - * @exports loadCRN + * @alias GoogleEarthEnterpriseTerrainProvider + * @constructor * - * @param {String|Promise.|ArrayBuffer} urlOrBuffer The URL of the binary data, a promise for the URL, or an ArrayBuffer. - * @param {Object} [headers] HTTP headers to send with the requests. - * @returns {Promise.} A promise that will resolve to the requested data when loaded. + * @param {Object} options Object with the following properties: + * @param {String} options.url The url of the Google Earth Enterprise server hosting the imagery. + * @param {GoogleEarthEnterpriseMetadata} options.metadata A metadata object that can be used to share metadata requests with a GoogleEarthEnterpriseImageryProvider. + * @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 {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. * - * @exception {RuntimeError} Unsupported compressed format. + * @see GoogleEarthEnterpriseImageryProvider + * @see CesiumTerrainProvider * * @example - * // load a single URL asynchronously - * Cesium.loadCRN('some/url').then(function(textureData) { - * var width = textureData.width; - * var height = textureData.height; - * var format = textureData.internalFormat; - * var arrayBufferView = textureData.bufferView; - * // use the data to create a texture - * }).otherwise(function(error) { - * // an error occurred + * var geeMetadata = new GoogleEarthEnterpriseMetadata('http://www.earthenterprise.org/3d'); + * var gee = new Cesium.GoogleEarthEnterpriseTerrainProvider({ + * metadata : geeMetadata * }); * - * @see {@link https://github.com/BinomialLLC/crunch|crunch DXTc texture compression and transcoding library} * @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 loadCRN(urlOrBuffer, headers) { + function GoogleEarthEnterpriseTerrainProvider(options) { + options = defaultValue(options, {}); + - var loadPromise; - if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) { - loadPromise = when.resolve(urlOrBuffer); + var metadata; + if (defined(options.metadata)) { + metadata = this._metadata = options.metadata; } else { - loadPromise = loadArrayBuffer(urlOrBuffer, headers); + metadata = this._metadata = new GoogleEarthEnterpriseMetadata({ + url : options.url, + proxy : options.proxy + }); } + this._proxy = defaultValue(options.proxy, this._metadata.proxy); - return loadPromise.then(function(data) { - var transferrableObjects = []; - if (data instanceof ArrayBuffer) { - transferrableObjects.push(data); - } else if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) { - transferrableObjects.push(data.buffer); - } else { - // data is a view of an array buffer. need to copy so it is transferrable to web worker - data = data.slice(0, data.length); - transferrableObjects.push(data.buffer); - } - - return transcodeTaskProcessor.scheduleTask(data, transferrableObjects); - }).then(function(compressedTextureBuffer) { - return CompressedTextureBuffer.clone(compressedTextureBuffer); + this._tilingScheme = new GeographicTilingScheme({ + numberOfLevelZeroTilesX : 2, + numberOfLevelZeroTilesY : 2, + rectangle : new Rectangle(-CesiumMath.PI, -CesiumMath.PI, CesiumMath.PI, CesiumMath.PI), + ellipsoid : options.ellipsoid }); - } - return loadCRN; -}); -/*global define*/ -define('Core/loadImage',[ - '../ThirdParty/when', - './defaultValue', - './defined', - './DeveloperError', - './isCrossOriginUrl', - './TrustedServers' - ], function( - when, - defaultValue, - defined, - DeveloperError, - isCrossOriginUrl, - TrustedServers) { - 'use strict'; + var credit = options.credit; + if (typeof credit === 'string') { + credit = new Credit(credit); + } + this._credit = credit; - var dataUriRegex = /^data:/; + // Pulled from Google's documentation + this._levelZeroMaximumGeometricError = 40075.16; - /** - * 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) { - - allowCrossOrigin = defaultValue(allowCrossOrigin, true); + this._terrainCache = new TerrainCache(); + this._terrainPromises = {}; + this._terrainRequests = {}; - 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/loadImageFromTypedArray',[ - '../ThirdParty/when', - './defined', - './DeveloperError', - './loadImage' - ], function( - when, - defined, - DeveloperError, - loadImage) { - 'use strict'; - - /** - * @private - */ - function loadImageFromTypedArray(uint8Array, format) { - - 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); - } + this._errorEvent = new Event(); - return loadBlob(url).then(function(blob) { - var blobUrl = window.URL.createObjectURL(blob); + this._ready = false; + var that = this; + var metadataError; + this._readyPromise = metadata.readyPromise + .then(function(result) { + if (!metadata.terrainPresent) { + var e = new RuntimeError('The server ' + metadata.url + ' doesn\'t have terrain'); + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, e.message, undefined, undefined, undefined, e); + return when.reject(e); + } - 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); + TileProviderError.handleSuccess(metadataError); + that._ready = result; + return result; + }) + .otherwise(function(e) { + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, e.message, undefined, undefined, undefined, e); + return when.reject(e); }); - }); } - 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('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, - - isPacked : function(pixelDatatype) { - return 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; - }, - - sizeInBytes : function(pixelDatatype) { - switch (pixelDatatype) { - case PixelDatatype.UNSIGNED_BYTE: - return 1; - case PixelDatatype.UNSIGNED_SHORT: - case PixelDatatype.UNSIGNED_SHORT_4_4_4_4: - case PixelDatatype.UNSIGNED_SHORT_5_5_5_1: - case PixelDatatype.UNSIGNED_SHORT_5_6_5: - return 2; - case PixelDatatype.UNSIGNED_INT: - case PixelDatatype.FLOAT: - case PixelDatatype.UNSIGNED_INT_24_8: - return 4; - } - }, - - 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('Core/PixelFormat',[ - '../Renderer/PixelDatatype', - './freezeObject', - './WebGLConstants' - ], function( - PixelDatatype, - 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, - - /** - * A pixel format containing red, green, and blue channels that is DXT1 compressed. - * - * @type {Number} - * @constant - */ - RGB_DXT1 : WebGLConstants.COMPRESSED_RGB_S3TC_DXT1_EXT, - - /** - * A pixel format containing red, green, blue, and alpha channels that is DXT1 compressed. - * - * @type {Number} - * @constant - */ - RGBA_DXT1 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT1_EXT, - - /** - * A pixel format containing red, green, blue, and alpha channels that is DXT3 compressed. - * - * @type {Number} - * @constant - */ - RGBA_DXT3 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT3_EXT, - - /** - * A pixel format containing red, green, blue, and alpha channels that is DXT5 compressed. - * - * @type {Number} - * @constant - */ - RGBA_DXT5 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT5_EXT, - - /** - * A pixel format containing red, green, and blue channels that is PVR 4bpp compressed. - * - * @type {Number} - * @constant - */ - RGB_PVRTC_4BPPV1 : WebGLConstants.COMPRESSED_RGB_PVRTC_4BPPV1_IMG, - - /** - * A pixel format containing red, green, and blue channels that is PVR 2bpp compressed. - * - * @type {Number} - * @constant - */ - RGB_PVRTC_2BPPV1 : WebGLConstants.COMPRESSED_RGB_PVRTC_2BPPV1_IMG, - - /** - * A pixel format containing red, green, blue, and alpha channels that is PVR 4bpp compressed. - * - * @type {Number} - * @constant - */ - RGBA_PVRTC_4BPPV1 : WebGLConstants.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, - - /** - * A pixel format containing red, green, blue, and alpha channels that is PVR 2bpp compressed. - * - * @type {Number} - * @constant - */ - RGBA_PVRTC_2BPPV1 : WebGLConstants.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, - - /** - * A pixel format containing red, green, and blue channels that is ETC1 compressed. - * - * @type {Number} - * @constant - */ - RGB_ETC1 : WebGLConstants.COMPRESSED_RGB_ETC1_WEBGL, - + defineProperties(GoogleEarthEnterpriseTerrainProvider.prototype, { /** - * @private + * Gets the name of the Google Earth Enterprise server url hosting the imagery. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {String} + * @readonly */ - componentsLength : function(pixelFormat) { - switch (pixelFormat) { - // Many GPUs store RGB as RGBA internally - // https://devtalk.nvidia.com/default/topic/699479/general-graphics-programming/rgb-auto-converted-to-rgba/post/4142379/#4142379 - case PixelFormat.RGB: - case PixelFormat.RGBA: - return 4; - case PixelFormat.LUMINANCE_ALPHA: - return 2; - case PixelFormat.ALPHA: - case PixelFormat.LUMINANCE: - return 1; - default: - return 1; + url : { + get : function() { + return this._metadata.url; } }, /** - * @private + * Gets the proxy used by this provider. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Proxy} + * @readonly */ - 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 || - pixelFormat === PixelFormat.RGB_DXT1 || - pixelFormat === PixelFormat.RGBA_DXT1 || - pixelFormat === PixelFormat.RGBA_DXT3 || - pixelFormat === PixelFormat.RGBA_DXT5 || - pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 || - pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 || - pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 || - pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1 || - pixelFormat === PixelFormat.RGB_ETC1; + proxy : { + get : function() { + return this._proxy; + } }, /** - * @private + * Gets the tiling scheme used by this provider. This function should + * not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {TilingScheme} + * @readonly */ - isColorFormat : function(pixelFormat) { - return pixelFormat === PixelFormat.ALPHA || - pixelFormat === PixelFormat.RGB || - pixelFormat === PixelFormat.RGBA || - pixelFormat === PixelFormat.LUMINANCE || - pixelFormat === PixelFormat.LUMINANCE_ALPHA; + tilingScheme : { + get : function() { + + return this._tilingScheme; + } }, /** - * @private + * 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 GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Event} + * @readonly */ - isDepthFormat : function(pixelFormat) { - return pixelFormat === PixelFormat.DEPTH_COMPONENT || - pixelFormat === PixelFormat.DEPTH_STENCIL; + errorEvent : { + get : function() { + return this._errorEvent; + } }, /** - * @private + * Gets a value indicating whether or not the provider is ready for use. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Boolean} + * @readonly */ - isCompressedFormat : function(pixelFormat) { - return pixelFormat === PixelFormat.RGB_DXT1 || - pixelFormat === PixelFormat.RGBA_DXT1 || - pixelFormat === PixelFormat.RGBA_DXT3 || - pixelFormat === PixelFormat.RGBA_DXT5 || - pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 || - pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 || - pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 || - pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1 || - pixelFormat === PixelFormat.RGB_ETC1; + ready : { + get : function() { + return this._ready; + } }, /** - * @private + * Gets a promise that resolves to true when the provider is ready for use. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Promise.} + * @readonly */ - isDXTFormat : function(pixelFormat) { - return pixelFormat === PixelFormat.RGB_DXT1 || - pixelFormat === PixelFormat.RGBA_DXT1 || - pixelFormat === PixelFormat.RGBA_DXT3 || - pixelFormat === PixelFormat.RGBA_DXT5; + readyPromise : { + get : function() { + return this._readyPromise; + } }, /** - * @private + * 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 GoogleEarthEnterpriseTerrainProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Credit} + * @readonly */ - isPVRTCFormat : function(pixelFormat) { - return pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 || - pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 || - pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 || - pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1; + credit : { + get : function() { + return this._credit; + } }, /** - * @private + * 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 GoogleEarthEnterpriseTerrainProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Boolean} */ - isETC1Format : function(pixelFormat) { - return pixelFormat === PixelFormat.RGB_ETC1; + hasWaterMask : { + get : function() { + return false; + } }, /** - * @private + * Gets a value indicating whether or not the requested tiles include vertex normals. + * This function should not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {Boolean} */ - compressedTextureSizeInBytes : function(pixelFormat, width, height) { - switch (pixelFormat) { - case PixelFormat.RGB_DXT1: - case PixelFormat.RGBA_DXT1: - case PixelFormat.RGB_ETC1: - return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8; - - case PixelFormat.RGBA_DXT3: - case PixelFormat.RGBA_DXT5: - return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16; - - case PixelFormat.RGB_PVRTC_4BPPV1: - case PixelFormat.RGBA_PVRTC_4BPPV1: - return Math.floor((Math.max(width, 8) * Math.max(height, 8) * 4 + 7) / 8); - - case PixelFormat.RGB_PVRTC_2BPPV1: - case PixelFormat.RGBA_PVRTC_2BPPV1: - return Math.floor((Math.max(width, 16) * Math.max(height, 8) * 2 + 7) / 8); - - default: - return 0; + hasVertexNormals : { + get : function() { + return false; } }, /** - * @private + * Gets an object that can be used to determine availability of terrain from this provider, such as + * at points and in rectangles. This function should not be called before + * {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true. This property may be undefined if availability + * information is not available. + * @memberof GoogleEarthEnterpriseTerrainProvider.prototype + * @type {TileAvailability} */ - textureSizeInBytes : function(pixelFormat, pixelDatatype, width, height) { - var componentsLength = PixelFormat.componentsLength(pixelFormat); - if (PixelDatatype.isPacked(pixelDatatype)) { - componentsLength = 1; + availability : { + get : function() { + return undefined; } - return componentsLength * PixelDatatype.sizeInBytes(pixelDatatype) * width * height; } - }; + }); - return freezeObject(PixelFormat); -}); + var taskProcessor = new TaskProcessor('decodeGoogleEarthEnterprisePacket', Number.POSITIVE_INFINITY); -/*global define*/ -define('Core/loadKTX',[ - '../ThirdParty/when', - './CompressedTextureBuffer', - './defined', - './DeveloperError', - './loadArrayBuffer', - './PixelFormat', - './RuntimeError' - ], function( - when, - CompressedTextureBuffer, - defined, - DeveloperError, - loadArrayBuffer, - PixelFormat, - RuntimeError) { - 'use strict'; + // If the tile has its own terrain, then you can just use its child bitmask. If it was requested using it's parent + // then you need to check all of its children to see if they have terrain. + function computeChildMask(quadKey, info, metadata) { + var childMask = info.getChildBitmask(); + if (info.terrainState === TerrainState.PARENT) { + childMask = 0; + for (var i = 0; i < 4; ++i) { + var child = metadata.getTileInformationFromQuadKey(quadKey + i.toString()); + if (defined(child) && child.hasTerrain()) { + childMask |= (1 << i); + } + } + } + + return childMask; + } /** - * Asynchronously loads and parses the given URL to a KTX file or parses the raw binary data of a KTX file. - * Returns a promise that will resolve to an object containing the image buffer, width, height and format once loaded, - * or reject if the URL failed to load or failed to parse the data. 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. - *

    - * The following are part of the KTX format specification but are not supported: - *

      - *
    • Big-endian files
    • - *
    • Metadata
    • - *
    • 3D textures
    • - *
    • Texture Arrays
    • - *
    • Cubemaps
    • - *
    • Mipmaps
    • - *
    - *

    - * - * @exports loadKTX - * - * @param {String|Promise.|ArrayBuffer} urlOrBuffer The URL of the binary data, a promise for the URL, or an ArrayBuffer. - * @param {Object} [headers] HTTP headers to send with the requests. - * @returns {Promise.} A promise that will resolve to the requested data when loaded. - * - * @exception {RuntimeError} Invalid KTX file. - * @exception {RuntimeError} File is the wrong endianness. - * @exception {RuntimeError} glInternalFormat is not a valid format. - * @exception {RuntimeError} glType must be zero when the texture is compressed. - * @exception {RuntimeError} The type size for compressed textures must be 1. - * @exception {RuntimeError} glFormat must be zero when the texture is compressed. - * @exception {RuntimeError} Generating mipmaps for a compressed texture is unsupported. - * @exception {RuntimeError} The base internal format must be the same as the format for uncompressed textures. - * @exception {RuntimeError} 3D textures are not supported. - * @exception {RuntimeError} Texture arrays are not supported. - * @exception {RuntimeError} Cubemaps are not supported. + * Requests the geometry for a given tile. This function should not be called before + * {@link GoogleEarthEnterpriseTerrainProvider#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. * - * @example - * // load a single URL asynchronously - * Cesium.loadKTX('some/url').then(function(ktxData) { - * var width = ktxData.width; - * var height = ktxData.height; - * var format = ktxData.internalFormat; - * var arrayBufferView = ktxData.bufferView; - * // use the data to create a texture - * }).otherwise(function(error) { - * // an error occurred - * }); + * @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 {Request} [request] The request object. Intended for internal use only. + * @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. * - * @see {@link https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/|KTX file format} - * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} - * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} + * @exception {DeveloperError} This function must not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready} + * returns true. */ - function loadKTX(urlOrBuffer, headers) { + GoogleEarthEnterpriseTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { - var loadPromise; - if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) { - loadPromise = when.resolve(urlOrBuffer); - } else { - loadPromise = loadArrayBuffer(urlOrBuffer, headers); - } - - return loadPromise.then(function(data) { - return parseKTX(data); - }); - } - - var fileIdentifier = [0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A]; - var endiannessTest = 0x04030201; - - var sizeOfUint32 = 4; - - function parseKTX(data) { - var byteBuffer = new Uint8Array(data); + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); + var terrainCache = this._terrainCache; + var metadata = this._metadata; + var info = metadata.getTileInformationFromQuadKey(quadKey); - var isKTX = true; - for (var i = 0; i < fileIdentifier.length; ++i) { - if (fileIdentifier[i] !== byteBuffer[i]) { - isKTX = false; - break; - } + // Check if this tile is even possibly available + if (!defined(info)) { + return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } - if (!isKTX) { - throw new RuntimeError('Invalid KTX file.'); + var terrainState = info.terrainState; + if (!defined(terrainState)) { + // First time we have tried to load this tile, so set terrain state to UNKNOWN + terrainState = info.terrainState = TerrainState.UNKNOWN; } - var view; - var byteOffset; - - if (defined(data.buffer)) { - view = new DataView(data.buffer); - byteOffset = data.byteOffset; - } else { - view = new DataView(data); - byteOffset = 0; + // If its in the cache, return it + var buffer = terrainCache.get(quadKey); + if (defined(buffer)) { + var credit = metadata.providers[info.terrainProvider]; + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : computeChildMask(quadKey, info, metadata), + credits : defined(credit) ? [credit] : undefined, + negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias, + negativeElevationThreshold: metadata.negativeAltitudeThreshold + }); } - byteOffset += 12; // skip identifier + // Clean up the cache + terrainCache.tidy(); - var endianness = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - if (endianness !== endiannessTest) { - throw new RuntimeError('File is the wrong endianness.'); + // We have a tile, check to see if no ancestors have terrain or that we know for sure it doesn't + if (!info.ancestorHasTerrain) { + // We haven't reached a level with terrain, so return the ellipsoid + return new HeightmapTerrainData({ + buffer : new Uint8Array(16 * 16), + width : 16, + height : 16 + }); + } else if (terrainState === TerrainState.NONE) { + // Already have info and there isn't any terrain here + return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } - var glType = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var glTypeSize = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var glFormat = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var glInternalFormat = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var glBaseInternalFormat = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var pixelWidth = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var pixelHeight = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var pixelDepth = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var numberOfArrayElements = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var numberOfFaces = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var numberOfMipmapLevels = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - var bytesOfKeyValueByteSize = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - // skip metadata - byteOffset += bytesOfKeyValueByteSize; - - var imageSize = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32; - - var texture; - if (defined(data.buffer)) { - texture = new Uint8Array(data.buffer, byteOffset, imageSize); - } else { - texture = new Uint8Array(data, byteOffset, imageSize); + // Figure out where we are getting the terrain and what version + var parentInfo; + var q = quadKey; + var terrainVersion = -1; + switch (terrainState) { + case TerrainState.SELF: // We have terrain and have retrieved it before + terrainVersion = info.terrainVersion; + break; + case TerrainState.PARENT: // We have terrain in our parent + q = q.substring(0, q.length - 1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + terrainVersion = parentInfo.terrainVersion; + break; + case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet + if (info.hasTerrain()) { + terrainVersion = info.terrainVersion; // We should have terrain + } else { + q = q.substring(0, q.length - 1); + parentInfo = metadata.getTileInformationFromQuadKey(q); + if (defined(parentInfo) && parentInfo.hasTerrain()) { + terrainVersion = parentInfo.terrainVersion; // Try checking in the parent + } + } + break; } - // Some tools use a sized internal format. - // See table 2: https://www.opengl.org/sdk/docs/man/html/glTexImage2D.xhtml - if (glInternalFormat === 0x8051) { // GL_RGB8 - glInternalFormat = PixelFormat.RGB; - } else if (glInternalFormat === 0x8058) { // GL_RGBA8 - glInternalFormat = PixelFormat.RGBA; + // We can't figure out where to get the terrain + if (terrainVersion < 0) { + return when.reject(new RuntimeError('Terrain tile doesn\'t exist')); } - if (!PixelFormat.validate(glInternalFormat)) { - throw new RuntimeError('glInternalFormat is not a valid format.'); - } + // Load that terrain + var terrainPromises = this._terrainPromises; + var terrainRequests = this._terrainRequests; + var url = buildTerrainUrl(this, q, terrainVersion); + var sharedPromise; + var sharedRequest; + if (defined(terrainPromises[q])) { // Already being loaded possibly from another child, so return existing promise + sharedPromise = terrainPromises[q]; + sharedRequest = terrainRequests[q]; + } else { // Create new request for terrain + sharedRequest = request; + var requestPromise = loadArrayBuffer(url, undefined, sharedRequest); - if (PixelFormat.isCompressedFormat(glInternalFormat)) { - if (glType !== 0) { - throw new RuntimeError('glType must be zero when the texture is compressed.'); - } - if (glTypeSize !== 1) { - throw new RuntimeError('The type size for compressed textures must be 1.'); - } - if (glFormat !== 0) { - throw new RuntimeError('glFormat must be zero when the texture is compressed.'); - } - if (numberOfMipmapLevels === 0) { - throw new RuntimeError('Generating mipmaps for a compressed texture is unsupported.'); - } - } else { - if (glBaseInternalFormat !== glFormat) { - throw new RuntimeError('The base internal format must be the same as the format for uncompressed textures.'); + if (!defined(requestPromise)) { + return undefined; // Throttled } - } - if (pixelDepth !== 0) { - throw new RuntimeError('3D textures are unsupported.'); - } + sharedPromise = requestPromise + .then(function(terrain) { + if (defined(terrain)) { + return taskProcessor.scheduleTask({ + buffer : terrain, + type : 'Terrain', + key : metadata.key + }, [terrain]) + .then(function(terrainTiles) { + // Add requested tile and mark it as SELF + var requestedInfo = metadata.getTileInformationFromQuadKey(q); + requestedInfo.terrainState = TerrainState.SELF; + terrainCache.add(q, terrainTiles[0]); + var provider = requestedInfo.terrainProvider; - if (numberOfArrayElements !== 0) { - throw new RuntimeError('Texture arrays are unsupported.'); - } - if (numberOfFaces !== 1) { - throw new RuntimeError('Cubemaps are unsupported.'); - } + // Add children to cache + var count = terrainTiles.length - 1; + for (var j = 0; j < count; ++j) { + var childKey = q + j.toString(); + var child = metadata.getTileInformationFromQuadKey(childKey); + if (defined(child)) { + terrainCache.add(childKey, terrainTiles[j + 1]); + child.terrainState = TerrainState.PARENT; + if (child.terrainProvider === 0) { + child.terrainProvider = provider; + } + } + } + }); + } - // Only use the level 0 mipmap - if (PixelFormat.isCompressedFormat(glInternalFormat) && numberOfMipmapLevels > 1) { - var levelSize = PixelFormat.compressedTextureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight); - texture = texture.slice(0, levelSize); - } + return when.reject(new RuntimeError('Failed to load terrain.')); + }); - return new CompressedTextureBuffer(glInternalFormat, pixelWidth, pixelHeight, texture); - } + terrainPromises[q] = sharedPromise; // Store promise without delete from terrainPromises + terrainRequests[q] = sharedRequest; - return loadKTX; -}); + // Set promise so we remove from terrainPromises just one time + sharedPromise = sharedPromise + .always(function() { + delete terrainPromises[q]; + delete terrainRequests[q]; + }); + } -/*global define*/ -define('Core/loadXML',[ - './loadWithXhr' - ], function( - loadWithXhr) { - 'use strict'; + return sharedPromise + .then(function() { + var buffer = terrainCache.get(quadKey); + if (defined(buffer)) { + var credit = metadata.providers[info.terrainProvider]; + return new GoogleEarthEnterpriseTerrainData({ + buffer : buffer, + childTileMask : computeChildMask(quadKey, info, metadata), + credits : defined(credit) ? [credit] : undefined, + negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias, + negativeElevationThreshold: metadata.negativeAltitudeThreshold + }); + } + + return when.reject(new RuntimeError('Failed to load terrain.')); + }) + .otherwise(function(error) { + if (sharedRequest.state === RequestState.CANCELLED) { + request.state = sharedRequest.state; + return when.reject(error); + } + info.terrainState = TerrainState.NONE; + return when.reject(error); + }); + }; /** - * 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. - * + * Gets the maximum geometric error allowed in a tile at a given level. * - * @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} + * @param {Number} level The tile level for which to get the maximum geometric error. + * @returns {Number} The maximum geometric error. */ - 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 = { + GoogleEarthEnterpriseTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) { + return this._levelZeroMaximumGeometricError / (1 << level); }; /** - * 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. + * Determines whether data for a tile is available to be loaded. * - * @type {String} + * @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. */ - MapboxApi.defaultAccessToken = undefined; + GoogleEarthEnterpriseTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { + var metadata = this._metadata; + var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level); - 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.'; + var info = metadata.getTileInformation(x, y, level); + if (info === null) { + return false; + } + if (defined(info)) { + if (!info.ancestorHasTerrain) { + return true; // We'll just return the ellipsoid + } - MapboxApi.getAccessToken = function(providedToken) { - if (defined(providedToken)) { - return providedToken; - } + var terrainState = info.terrainState; + if (terrainState === TerrainState.NONE) { + return false; // Terrain is not available + } - if (!defined(MapboxApi.defaultAccessToken)) { - if (!printedMapboxWarning) { - console.log(errorString); - printedMapboxWarning = true; + if (!defined(terrainState) || (terrainState === TerrainState.UNKNOWN)) { + info.terrainState = TerrainState.UNKNOWN; + if (!info.hasTerrain()) { + quadKey = quadKey.substring(0, quadKey.length - 1); + var parentInfo = metadata.getTileInformationFromQuadKey(quadKey); + if (!defined(parentInfo) || !parentInfo.hasTerrain()) { + return false; + } + } } - return 'pk.eyJ1IjoiYW5hbHl0aWNhbGdyYXBoaWNzIiwiYSI6ImNpd204Zm4wejAwNzYyeW5uNjYyZmFwdWEifQ.7i-VIZZWX8pd1bTfxIVj9g'; + + return true; } - return MapboxApi.defaultAccessToken; + if (metadata.isValid(quadKey)) { + // We will need this tile, so request metadata and return false for now + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.TERRAIN + }); + metadata.populateSubtree(x, y, level, request); + } + return false; }; - MapboxApi.getErrorCredit = function(providedToken) { - if (defined(providedToken) || defined(MapboxApi.defaultAccessToken)) { - return undefined; - } + // + // Functions to handle imagery packets + // + function buildTerrainUrl(terrainProvider, quadKey, version) { + version = (defined(version) && version > 0) ? version : 1; + var terrainUrl = terrainProvider.url + 'flatfile?f1c-0' + quadKey + '-t.' + version.toString(); - if (!defined(errorCredit)) { - errorCredit = new Credit(errorString); + var proxy = terrainProvider._proxy; + if (defined(proxy)) { + terrainUrl = proxy.getURL(terrainUrl); } - return errorCredit; - }; + return terrainUrl; + } - return MapboxApi; + return GoogleEarthEnterpriseTerrainProvider; }); -/*global define*/ -define('Core/MapProjection',[ - './defineProperties', - './DeveloperError' +define('Core/HeadingPitchRange',[ + './defaultValue', + './defined' ], function( - defineProperties, - DeveloperError) { + defaultValue, + defined) { 'use strict'; /** - * Defines how geodetic ellipsoid coordinates ({@link Cartographic}) project to a - * flat map like Cesium's 2D and Columbus View modes. - * - * @alias MapProjection + * 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 * - * @see GeographicProjection - * @see WebMercatorProjection + * @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 MapProjection() { - DeveloperError.throwInstantiationError(); - } - - defineProperties(MapProjection.prototype, { + function HeadingPitchRange(heading, pitch, range) { /** - * Gets the {@link Ellipsoid}. - * - * @memberof MapProjection.prototype - * - * @type {Ellipsoid} - * @readonly + * Heading is the rotation from the local north direction where a positive angle is increasing eastward. + * @type {Number} */ - ellipsoid : { - get : DeveloperError.throwInstantiationError - } - }); + this.heading = defaultValue(heading, 0.0); - /** - * 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; + /** + * 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); + } /** - * Unprojects projection-specific map {@link Cartesian3} coordinates, in meters, to {@link Cartographic} - * coordinates, in radians. - * - * @memberof MapProjection - * @function + * Duplicates a HeadingPitchRange instance. * - * @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. + * @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) */ - MapProjection.prototype.unproject = DeveloperError.throwInstantiationError; + HeadingPitchRange.clone = function(hpr, result) { + if (!defined(hpr)) { + return undefined; + } + if (!defined(result)) { + result = new HeadingPitchRange(); + } - return MapProjection; + result.heading = hpr.heading; + result.pitch = hpr.pitch; + result.range = hpr.range; + return result; + }; + + return HeadingPitchRange; }); -/*global define*/ -define('Core/Matrix2',[ - './Cartesian2', - './Check', +define('Core/HermitePolynomialApproximation',[ './defaultValue', './defined', - './defineProperties', - './freezeObject' + './DeveloperError', + './Math' ], function( - Cartesian2, - Check, defaultValue, defined, - defineProperties, - freezeObject) { + DeveloperError, + CesiumMath) { '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); - } + var factorial = CesiumMath.factorial; - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - Matrix2.packedLength = 4; + function calculateCoefficientTerm(x, zIndices, xTable, derivOrder, termOrder, reservedIndices) { + var result = 0; + var reserved; + var i; + var j; - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); + 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; + } + } - array[startingIndex++] = value[0]; - array[startingIndex++] = value[1]; - array[startingIndex++] = value[2]; - array[startingIndex++] = value[3]; + if (!reserved) { + reservedIndices.push(i); + result += calculateCoefficientTerm(x, zIndices, xTable, derivOrder - 1, termOrder, reservedIndices); + reservedIndices.splice(reservedIndices.length - 1, 1); + } + } - return array; - }; + return result; + } - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); + 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 (!defined(result)) { - result = new Matrix2(); + if (!reserved) { + result *= x - xTable[zIndices[i]]; + } } - result[0] = array[startingIndex++]; - result[1] = array[startingIndex++]; - result[2] = array[startingIndex++]; - result[3] = array[startingIndex++]; return result; - }; + } /** - * Duplicates a Matrix2 instance. + * An {@link InterpolationAlgorithm} for performing Hermite interpolation. * - * @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) + * @exports HermitePolynomialApproximation */ - Matrix2.clone = function(matrix, result) { - if (!defined(matrix)) { - return undefined; - } - if (!defined(result)) { - return new Matrix2(matrix[0], matrix[2], - matrix[1], matrix[3]); - } - result[0] = matrix[0]; - result[1] = matrix[1]; - result[2] = matrix[2]; - result[3] = matrix[3]; - return result; + var HermitePolynomialApproximation = { + type : 'Hermite' }; /** - * 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); + * Given the desired degree, returns the number of data points required for interpolation. * - * // 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); + * @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. */ - Matrix2.fromArray = function(array, startingIndex, result) { - - 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; - }; + HermitePolynomialApproximation.getRequiredDataPoints = function(degree, inputOrder) { + inputOrder = defaultValue(inputOrder, 0); - /** - * 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) { - return Matrix2.clone(values, result); + return Math.max(Math.floor((degree + 1) / (inputOrder + 1)), 2); }; /** - * Creates a Matrix2 instance from a row-major order array. - * The resulting matrix will be in column-major order. + * Interpolates values using Hermite Polynomial Approximation. * - * @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. + * @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. */ - Matrix2.fromRowMajorArray = function(values, result) { - + HermitePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) { if (!defined(result)) { - return new Matrix2(values[0], values[1], - values[2], values[3]); + result = new Array(yStride); } - 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(result)) { - return new Matrix2( - scale.x, 0.0, - 0.0, scale.y); + 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; + } + } } - result[0] = scale.x; - result[1] = 0.0; - result[2] = 0.0; - result[3] = scale.y; return result; }; + var arrayScratch = []; + /** - * Computes a Matrix2 instance representing a uniform scale. + * Interpolates values using Hermite Polynomial Approximation. * - * @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. + * @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. * - * @example - * // Creates - * // [2.0, 0.0] - * // [0.0, 2.0] - * var m = Cesium.Matrix2.fromUniformScale(2.0); + * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided. */ - Matrix2.fromUniformScale = function(scale, result) { - + HermitePolynomialApproximation.interpolate = function(x, xTable, yTable, yStride, inputOrder, outputOrder, result) { + var resultLength = yStride * (outputOrder + 1); if (!defined(result)) { - return new Matrix2( - scale, 0.0, - 0.0, scale); + result = new Array(resultLength); + } + for (var r = 0; r < resultLength; r++) { + result[r] = 0; } - result[0] = scale; - result[1] = 0.0; - result[2] = 0.0; - result[3] = scale; - return result; - }; + 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)); + var i; + for (i = 0; i < length; i++) { + for (var j = 0; j < (inputOrder + 1); j++) { + zIndices[i * (inputOrder + 1) + j] = i; + } + } - /** - * 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) { - - var cosAngle = Math.cos(angle); - var sinAngle = Math.sin(angle); + var zIndiceslength = zIndices.length; + var coefficients = arrayScratch; + var highestNonZeroCoef = fillCoefficientList(coefficients, zIndices, xTable, yTable, yStride, inputOrder); + var reservedIndices = []; - if (!defined(result)) { - return new Matrix2( - cosAngle, -sinAngle, - sinAngle, cosAngle); + 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; + } + } } - 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(result)) { - return [matrix[0], matrix[1], matrix[2], matrix[3]]; + 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); + } + } } - 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) { - - return column * 2 + row; - }; + return highestNonZero; + } + + return HermitePolynomialApproximation; +}); + +define('Core/IauOrientationParameters',[],function() { + 'use strict'; /** - * Retrieves a copy of the matrix column at the provided index as a Cartesian2 instance. + * 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. + *

    * - * @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. + * @exports IauOrientationParameters * - * @exception {DeveloperError} index must be 0 or 1. + * @private */ - Matrix2.getColumn = function(matrix, index, result) { - - var startIndex = index * 2; - var x = matrix[startIndex]; - var y = matrix[startIndex + 1]; + 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; - result.x = x; - result.y = y; - return result; - }; + /** + * 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; +}); + +define('Core/Iau2000Orientation',[ + './defined', + './IauOrientationParameters', + './JulianDate', + './Math', + './TimeConstants' + ], function( + defined, + IauOrientationParameters, + JulianDate, + CesiumMath, + TimeConstants) { + 'use strict'; /** - * Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian2 instance. + * 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. * - * @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. + * @exports Iau2000Orientation * - * @exception {DeveloperError} index must be 0 or 1. + * @private */ - Matrix2.setColumn = function(matrix, index, cartesian, result) { - - result = Matrix2.clone(matrix, result); - var startIndex = index * 2; - result[startIndex] = cartesian.x; - result[startIndex + 1] = cartesian.y; - return result; - }; + 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(); /** - * 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. + * Compute the orientation parameters for the Moon. * - * @exception {DeveloperError} index must be 0 or 1. + * @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. */ - Matrix2.getRow = function(matrix, index, result) { - - var x = matrix[index]; - var y = matrix[index + 2]; + 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; - result.x = x; - result.y = y; return result; }; + return Iau2000Orientation; +}); + +define('Core/IauOrientationAxes',[ + './Cartesian3', + './defined', + './Iau2000Orientation', + './JulianDate', + './Math', + './Matrix3', + './Quaternion' + ], function( + Cartesian3, + defined, + Iau2000Orientation, + JulianDate, + CesiumMath, + Matrix3, + Quaternion) { + 'use strict'; + /** - * Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian2 instance. + * 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 {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. + * @param {IauOrientationAxes~ComputeFunction} [computeFunction] The function that computes the {@link IauOrientationParameters} given a {@link JulianDate}. * - * @exception {DeveloperError} index must be 0 or 1. + * @see Iau2000Orientation + * + * @private */ - Matrix2.setRow = function(matrix, index, cartesian, result) { - - result = Matrix2.clone(matrix, result); - result[index] = cartesian.x; - result[index + 2] = cartesian.y; - return result; - }; + function IauOrientationAxes(computeFunction) { + if (!defined(computeFunction) || typeof computeFunction !== 'function') { + computeFunction = Iau2000Orientation.ComputeMoon; + } - var scratchColumn = new Cartesian2(); + 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; - /** - * 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) { - - 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(); + var rotMtxScratch = new Matrix3(); + var quatScratch = new Quaternion(); /** - * Computes the maximum scale assuming the matrix is an affine transformation. - * The maximum scale is the maximum length of the column vectors. + * Computes a rotation from ICRF to a Globe's Fixed axes. * - * @param {Matrix2} matrix The matrix. - * @returns {Number} The maximum scale. + * @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. */ - Matrix2.getMaximumScale = function(matrix) { - Matrix2.getScale(matrix, scratchScale); - return Cartesian2.maximumComponent(scratchScale); - }; + IauOrientationAxes.prototype.evaluate = function(date, result) { + if (!defined(date)) { + date = JulianDate.now(); + } - /** - * 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) { - - 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]; + var alphaDeltaW = this._computeFunction(date); + var precMtx = computeRotationMatrix(alphaDeltaW.rightAscension, alphaDeltaW.declination, result); - result[0] = column0Row0; - result[1] = column0Row1; - result[2] = column1Row0; - result[3] = column1Row1; - return 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; }; /** - * 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. + * 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. */ - Matrix2.add = function(left, right, result) { - - 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; - }; + + return IauOrientationAxes; +}); + +define('Core/InterpolationAlgorithm',[ + './DeveloperError' + ], function( + DeveloperError) { + 'use strict'; /** - * Computes the difference of two matrices. + * The interface for interpolation algorithms. * - * @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. + * @exports InterpolationAlgorithm + * + * @see LagrangePolynomialApproximation + * @see LinearApproximation + * @see HermitePolynomialApproximation */ - Matrix2.subtract = function(left, right, result) { - - 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; - }; + var InterpolationAlgorithm = {}; /** - * 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. + * Gets the name of this interpolation algorithm. + * @type {String} */ - Matrix2.multiplyByVector = function(matrix, cartesian, result) { - - 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; - }; + InterpolationAlgorithm.type = undefined; /** - * Computes the product of a matrix and a scalar. + * Given the desired degree, returns the number of data points required for interpolation. + * @function * - * @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. + * @param {Number} degree The desired degree of interpolation. + * @returns {Number} The number of required data points needed for the desired degree of interpolation. */ - Matrix2.multiplyByScalar = function(matrix, scalar, result) { - - result[0] = matrix[0] * scalar; - result[1] = matrix[1] * scalar; - result[2] = matrix[2] * scalar; - result[3] = matrix[3] * scalar; - return result; - }; + InterpolationAlgorithm.getRequiredDataPoints = DeveloperError.throwInstantiationError; /** - * 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. - * + * Performs zero order interpolation. + * @function * - * @example - * // Instead of Cesium.Matrix2.multiply(m, Cesium.Matrix2.fromScale(scale), m); - * Cesium.Matrix2.multiplyByScale(m, scale, m); + * @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. * - * @see Matrix2.fromScale - * @see Matrix2.multiplyByUniformScale + * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided. */ - Matrix2.multiplyByScale = function(matrix, scale, result) { - - 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; - }; + InterpolationAlgorithm.interpolateOrderZero = DeveloperError.throwInstantiationError; /** - * 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) { - - result[0] = -matrix[0]; - result[1] = -matrix[1]; - result[2] = -matrix[2]; - result[3] = -matrix[3]; - return result; - }; + * 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; +}); + +define('Core/TimeInterval',[ + './Check', + './defaultValue', + './defined', + './defineProperties', + './freezeObject', + './JulianDate' + ], function( + Check, + defaultValue, + defined, + defineProperties, + freezeObject, + JulianDate) { + 'use strict'; /** - * Computes the transpose of the provided matrix. + * 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}. * - * @param {Matrix2} matrix The matrix to transpose. - * @param {Matrix2} result The object onto which to store the result. - * @returns {Matrix2} The modified result parameter. + * @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); */ - Matrix2.transpose = function(matrix, result) { - - var column0Row0 = matrix[0]; - var column0Row1 = matrix[2]; - var column1Row0 = matrix[1]; - var column1Row1 = matrix[3]; + 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(); - result[0] = column0Row0; - result[1] = column0Row1; - result[2] = column1Row0; - result[3] = column1Row1; - return result; + /** + * 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 }; /** - * Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements. + * Creates a new instance from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} interval. * - * @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. + * @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. */ - Matrix2.abs = function(matrix, result) { + TimeInterval.fromIso8601 = function(options, result) { - result[0] = Math.abs(matrix[0]); - result[1] = Math.abs(matrix[1]); - result[2] = Math.abs(matrix[2]); - result[3] = Math.abs(matrix[3]); + 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; }; /** - * Compares the provided matrices componentwise and returns - * true if they are equal, false otherwise. + * Creates an ISO8601 representation of the provided interval. * - * @param {Matrix2} [left] The first matrix. - * @param {Matrix2} [right] The second matrix. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @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. */ - 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]); + TimeInterval.toIso8601 = function(timeInterval, precision) { + + return JulianDate.toIso8601(timeInterval.start, precision) + '/' + JulianDate.toIso8601(timeInterval.stop, precision); }; /** - * @private + * 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. */ - 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]; + 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 the provided matrices componentwise and returns - * true if they are within the provided epsilon, - * false otherwise. + * Compares two instances and returns true if they are equal, 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. + * @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. */ - Matrix2.equalsEpsilon = function(left, right, epsilon) { - - 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); + 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)))); }; /** - * An immutable Matrix2 instance initialized to the identity matrix. + * 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. * - * @type {Matrix2} - * @constant + * @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. */ - Matrix2.IDENTITY = freezeObject(new Matrix2(1.0, 0.0, - 0.0, 1.0)); + TimeInterval.equalsEpsilon = function(left, right, epsilon, dataComparer) { + + 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)))); + }; /** - * An immutable Matrix2 instance initialized to the zero matrix. + * Computes the intersection of two intervals, optionally merging their data. * - * @type {Matrix2} - * @constant + * @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. */ - Matrix2.ZERO = freezeObject(new Matrix2(0.0, 0.0, - 0.0, 0.0)); + TimeInterval.intersect = function(left, right, result, mergeCallback) { + + if (!defined(right)) { + return TimeInterval.clone(TimeInterval.EMPTY, result); + } - /** - * 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; + var leftStart = left.start; + var leftStop = left.stop; - /** - * 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; + var rightStart = right.start; + var rightStop = right.stop; - /** - * 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; + 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; + }; /** - * The index into Matrix2 for column 1, row 1. - * - * @type {Number} - * @constant + * Checks if the specified date is inside the provided interval. * - * @example - * var matrix = new Cesium.Matrix2(); - * matrix[Cesium.Matrix2.COLUMN1ROW1] = 5.0; // set column 1, row 1 to 5.0 + * @param {TimeInterval} timeInterval The interval. + * @param {JulianDate} julianDate The date to check. + * @returns {Boolean} true if the interval contains the specified date, false otherwise. */ - Matrix2.COLUMN1ROW1 = 3; + TimeInterval.contains = function(timeInterval, julianDate) { + + if (timeInterval.isEmpty) { + return false; + } - defineProperties(Matrix2.prototype, { - /** - * Gets the number of items in the collection. - * @memberof Matrix2.prototype - * - * @type {Number} - */ - length : { - get : function() { - return Matrix2.packedLength; - } + 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 the provided Matrix2 instance. + * Duplicates this 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. + * @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. */ - Matrix2.prototype.clone = function(result) { - return Matrix2.clone(this, result); + TimeInterval.prototype.clone = function(result) { + return TimeInterval.clone(this, result); }; /** - * Compares this matrix to the provided matrix componentwise and returns + * Compares this instance against the provided instance componentwise and returns * true if they are equal, false otherwise. * - * @param {Matrix2} [right] The right hand side matrix. + * @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. */ - Matrix2.prototype.equals = function(right) { - return Matrix2.equals(this, right); + TimeInterval.prototype.equals = function(right, dataComparer) { + return TimeInterval.equals(this, right, dataComparer); }; /** - * Compares this matrix to the provided matrix componentwise and returns + * Compares this instance against the provided instance componentwise and returns * true if they are within the provided epsilon, * false otherwise. * - * @param {Matrix2} [right] The right hand side matrix. + * @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. */ - Matrix2.prototype.equalsEpsilon = function(right, epsilon) { - return Matrix2.equalsEpsilon(this, right, epsilon); + TimeInterval.prototype.equalsEpsilon = function(right, epsilon, dataComparer) { + return TimeInterval.equalsEpsilon(this, right, epsilon, dataComparer); }; /** - * Creates a string representing this Matrix with each row being - * on a separate line and in the format '(column0, column1)'. + * Creates a string representing this TimeInterval in ISO8601 format. * - * @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1)'. + * @returns {String} A string representing this TimeInterval in ISO8601 format. */ - Matrix2.prototype.toString = function() { - return '(' + this[0] + ', ' + this[2] + ')\n' + - '(' + this[1] + ', ' + this[3] + ')'; + TimeInterval.prototype.toString = function() { + return TimeInterval.toIso8601(this); }; - 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 + * An immutable empty interval. * - * @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. + * @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 * - * @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); + * @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 mergeSort(array, comparator, userDefinedObject) { - - var length = array.length; - var scratchLength = Math.ceil(length * 0.5); - // preallocate space in scratch arrays - leftScratchArray.length = scratchLength; - rightScratchArray.length = scratchLength; + /** + * 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. + */ - sort(array, comparator, userDefinedObject, 0, length - 1); + return TimeInterval; +}); - // trim scratch arrays - leftScratchArray.length = 0; - rightScratchArray.length = 0; - } +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 + })); /** - * A function used to compare two items while performing a merge sort. - * @callback mergeSort~Comparator + * Constants related to ISO8601 support. * - * @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. + * @exports Iso8601 * - * @example - * function compareNumbers(a, b, userDefinedObject) { - * return a - b; - * } + * @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' + * + * @type {JulianDate} + * @constant + */ + MINIMUM_VALUE : MINIMUM_VALUE, - return mergeSort; + /** + * A {@link JulianDate} representing the latest time representable by an ISO8601 date. + * This is equivalent to the date string '9999-12-31T24:00:00Z' + * + * @type {JulianDate} + * @constant + */ + 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' + * + * @type {JulianDate} + * @constant + */ + MAXIMUM_INTERVAL : MAXIMUM_INTERVAL + }; + + return Iso8601; }); -/*global define*/ -define('Core/NearFarScalar',[ - './defaultValue', - './defined', - './DeveloperError' +define('Core/KeyboardEventModifier',[ + './freezeObject' ], function( - defaultValue, - defined, - DeveloperError) { + freezeObject) { '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. + * This enumerated type is for representing keyboard modifiers. These are keys + * that are held down in addition to other event types. * - * @see Packable + * @exports KeyboardEventModifier */ - function NearFarScalar(near, nearValue, far, farValue) { - /** - * The lower bound of the camera range. - * @type {Number} - * @default 0.0 - */ - this.near = defaultValue(near, 0.0); + var KeyboardEventModifier = { /** - * The value at the lower bound of the camera range. + * Represents the shift key being held down. + * * @type {Number} - * @default 0.0 + * @constant */ - this.nearValue = defaultValue(nearValue, 0.0); + SHIFT : 0, + /** - * The upper bound of the camera range. + * Represents the control key being held down. + * * @type {Number} - * @default 1.0 + * @constant */ - this.far = defaultValue(far, 1.0); + CTRL : 1, + /** - * The value at the upper bound of the camera range. + * Represents the alt key being held down. + * * @type {Number} - * @default 0.0 + * @constant */ - this.farValue = defaultValue(farValue, 0.0); - } + ALT : 2 + }; + + return freezeObject(KeyboardEventModifier); +}); + +define('Core/LagrangePolynomialApproximation',[ + './defined' + ], function( + defined) { + 'use strict'; /** - * Duplicates a NearFarScalar instance. + * An {@link InterpolationAlgorithm} for performing Lagrange interpolation. * - * @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) + * @exports LagrangePolynomialApproximation */ - 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; + var LagrangePolynomialApproximation = { + type : 'Lagrange' }; - /** - * The number of elements used to pack the object into an array. - * @type {Number} + * 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. */ - NearFarScalar.packedLength = 4; + LagrangePolynomialApproximation.getRequiredDataPoints = function(degree) { + return Math.max(degree + 1.0, 2); + }; /** - * 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. + * Interpolates values using Lagrange Polynomial Approximation. * - * @returns {Number[]} The array that was packed into + * @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. */ - NearFarScalar.pack = function(value, array, startingIndex) { - - startingIndex = defaultValue(startingIndex, 0); + LagrangePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) { + if (!defined(result)) { + result = new Array(yStride); + } - array[startingIndex++] = value.near; - array[startingIndex++] = value.nearValue; - array[startingIndex++] = value.far; - array[startingIndex] = value.farValue; + var i; + var j; + var length = xTable.length; - return array; - }; + for (i = 0; i < yStride; i++) { + result[i] = 0; + } - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); + for (i = 0; i < length; i++) { + var coefficient = 1; - if (!defined(result)) { - result = new NearFarScalar(); + 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]; + } } - result.near = array[startingIndex++]; - result.nearValue = array[startingIndex++]; - result.far = array[startingIndex++]; - result.farValue = array[startingIndex]; + return result; }; + return LagrangePolynomialApproximation; +}); + +define('Core/LinearApproximation',[ + './defined', + './DeveloperError' + ], function( + defined, + DeveloperError) { + 'use strict'; + /** - * Compares the provided NearFarScalar and returns true if they are equal, - * false otherwise. + * An {@link InterpolationAlgorithm} for performing linear interpolation. * - * @param {NearFarScalar} [left] The first NearFarScalar. - * @param {NearFarScalar} [right] The second NearFarScalar. - * @returns {Boolean} true if left and right are equal; otherwise false. + * @exports LinearApproximation */ - 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)); + var LinearApproximation = { + type : 'Linear' }; /** - * Duplicates this instance. + * 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. * - * @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); + LinearApproximation.getRequiredDataPoints = function(degree) { + return 2; }; /** - * Compares this instance to the provided NearFarScalar and returns true if they are equal, - * false otherwise. + * Interpolates values using linear approximation. * - * @param {NearFarScalar} [right] The right hand side NearFarScalar. - * @returns {Boolean} true if left and right are equal; otherwise false. + * @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. */ - NearFarScalar.prototype.equals = function(right) { - return NearFarScalar.equals(this, right); + LinearApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) { + + if (!defined(result)) { + result = new Array(yStride); + } + + var i; + var y0; + var y1; + var x0 = xTable[0]; + var x1 = xTable[1]; + + + 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 NearFarScalar; + return LinearApproximation; }); -/*global define*/ -define('Core/Visibility',[ - './freezeObject' +define('Core/loadBlob',[ + './loadWithXhr' ], function( - freezeObject) { + loadWithXhr) { '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. + * 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 Visibility + * @exports loadBlob + * + * @param {String} url The URL of the data. + * @param {Object} [headers] HTTP headers to send with the requests. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. + * + * @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} */ - 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 - }; + function loadBlob(url, headers, request) { + return loadWithXhr({ + url : url, + responseType : 'blob', + headers : headers, + request : request + }); + } - return freezeObject(Visibility); + return loadBlob; }); -/*global define*/ -define('Core/Occluder',[ - './BoundingSphere', - './Cartesian3', - './defaultValue', +define('Core/loadCRN',[ + '../ThirdParty/when', + './CompressedTextureBuffer', './defined', - './defineProperties', './DeveloperError', - './Ellipsoid', - './Math', - './Rectangle', - './Visibility' + './loadArrayBuffer', + './TaskProcessor' ], function( - BoundingSphere, - Cartesian3, - defaultValue, + when, + CompressedTextureBuffer, defined, - defineProperties, DeveloperError, - Ellipsoid, - CesiumMath, - Rectangle, - Visibility) { + loadArrayBuffer, + TaskProcessor) { 'use strict'; + var transcodeTaskProcessor = new TaskProcessor('transcodeCRNToDXT', Number.POSITIVE_INFINITY); + /** - * 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. + * Asynchronously loads and parses the given URL to a CRN file or parses the raw binary data of a CRN file. + * Returns a promise that will resolve to an object containing the image buffer, width, height and format once loaded, + * or reject if the URL failed to load or failed to parse the data. 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. * - * @alias Occluder + * @exports loadCRN * - * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder. - * @param {Cartesian3} cameraPosition The coordinate of the viewer/camera. + * @param {String|ArrayBuffer} urlOrBuffer The URL of the binary data or an ArrayBuffer. + * @param {Object} [headers] HTTP headers to send with the requests. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} A promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * - * @constructor + * @exception {RuntimeError} Unsupported compressed format. * * @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); + * // load a single URL asynchronously + * Cesium.loadCRN('some/url').then(function(textureData) { + * var width = textureData.width; + * var height = textureData.height; + * var format = textureData.internalFormat; + * var arrayBufferView = textureData.bufferView; + * // use the data to create a texture + * }).otherwise(function(error) { + * // an error occurred + * }); + * + * @see {@link https://github.com/BinomialLLC/crunch|crunch DXTc texture compression and transcoding library} + * @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 Occluder(occluderBoundingSphere, cameraPosition) { + function loadCRN(urlOrBuffer, headers, request) { - this._occluderPosition = Cartesian3.clone(occluderBoundingSphere.center); - this._occluderRadius = occluderBoundingSphere.radius; + var loadPromise; + if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) { + loadPromise = when.resolve(urlOrBuffer); + } else { + loadPromise = loadArrayBuffer(urlOrBuffer, headers, request); + } - this._horizonDistance = 0.0; - this._horizonPlaneNormal = undefined; - this._horizonPlanePosition = undefined; - this._cameraPosition = undefined; + if (!defined(loadPromise)) { + return undefined; + } - // cameraPosition fills in the above values - this.cameraPosition = cameraPosition; + return loadPromise.then(function(data) { + var transferrableObjects = []; + if (data instanceof ArrayBuffer) { + transferrableObjects.push(data); + } else if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) { + transferrableObjects.push(data.buffer); + } else { + // data is a view of an array buffer. need to copy so it is transferrable to web worker + data = data.slice(0, data.length); + transferrableObjects.push(data.buffer); + } + + return transcodeTaskProcessor.scheduleTask(data, transferrableObjects); + }).then(function(compressedTextureBuffer) { + return CompressedTextureBuffer.clone(compressedTextureBuffer); + }); } - var scratchCartesian3 = new Cartesian3(); + return loadCRN; +}); - 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) { - - 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; - } - } - }); +define('Core/loadImage',[ + '../ThirdParty/when', + './Check', + './defaultValue', + './defined', + './DeveloperError', + './isCrossOriginUrl', + './isDataUri', + './Request', + './RequestScheduler', + './TrustedServers' + ], function( + when, + Check, + defaultValue, + defined, + DeveloperError, + isCrossOriginUrl, + isDataUri, + Request, + RequestScheduler, + TrustedServers) { + 'use strict'; /** - * Creates an occluder from a bounding sphere and the camera position. + * 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. * - * @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(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. + * @exports loadImage * - * @param {Cartesian3} occludee The point surrounding the occludee object. - * @returns {Boolean} true if the occludee is visible; otherwise false. + * @param {String} url The source URL of the image. + * @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. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * * * @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 + * // load a single image asynchronously + * Cesium.loadImage('some/image/url.png').then(function(image) { + * // use the loaded image + * }).otherwise(function(error) { + * // an error occurred + * }); * - * @see Occluder#computeVisibility + * // 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} */ - 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); + function loadImage(url, allowCrossOrigin, request) { + + allowCrossOrigin = defaultValue(allowCrossOrigin, true); + + request = defined(request) ? request : new Request(); + request.url = url; + request.requestFunction = function() { + var crossOrigin; + + // data URIs can't have allowCrossOrigin set. + if (isDataUri(url) || !allowCrossOrigin) { + crossOrigin = false; + } else { + crossOrigin = isCrossOriginUrl(url); } - } - return false; - }; - var occludeePositionScratch = new Cartesian3(); + var deferred = when.defer(); - /** - * 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; + loadImage.createImage(url, crossOrigin, deferred); - 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; - } + return deferred.promise; + }; - // 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; - } + return RequestScheduler.request(request); + } - // The occludee completely encompasses the occluder - return true; + // 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 = ''; + } } - return false; + image.src = url; }; - var tempScratch = new Cartesian3(); + loadImage.defaultCreateImage = loadImage.createImage; + + return loadImage; +}); + +define('Core/loadImageFromTypedArray',[ + '../ThirdParty/when', + './Check', + './defined', + './DeveloperError', + './loadImage' + ], function( + when, + Check, + defined, + DeveloperError, + loadImage) { + 'use strict'; + /** - * 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 + * @private */ - Occluder.prototype.computeVisibility = function(occludeeBS) { + function loadImageFromTypedArray(uint8Array, format, request) { - // 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; + var blob = new Blob([uint8Array], { + type : format + }); - if (occludeeRadius > this._occluderRadius) { - return Visibility.FULL; - } + var blobUrl = window.URL.createObjectURL(blob); + return loadImage(blobUrl, false, request).then(function(image) { + window.URL.revokeObjectURL(blobUrl); + return image; + }, function(error) { + window.URL.revokeObjectURL(blobUrl); + return when.reject(error); + }); + } - 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; - } + return loadImageFromTypedArray; +}); - // 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; - } +define('Core/loadImageViaBlob',[ + '../ThirdParty/when', + './defined', + './isDataUri', + './loadBlob', + './loadImage' + ], function( + when, + defined, + isDataUri, + loadBlob, + loadImage) { + 'use strict'; - //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; - } + var xhrBlobSupported = (function() { + try { + var xhr = new XMLHttpRequest(); + xhr.open('GET', '#', true); + xhr.responseType = 'blob'; + return xhr.responseType === 'blob'; + } catch (e) { + return false; } - 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. + * 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. * - * @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. + * @exports loadImageViaBlob + * + * @param {String} url The source URL of the image. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. * - * @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); + * // 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} */ - Occluder.computeOccludeePoint = function(occluderBoundingSphere, occludeePosition, positions) { - - var occludeePos = Cartesian3.clone(occludeePosition); - var occluderPosition = Cartesian3.clone(occluderBoundingSphere.center); - var occluderRadius = occluderBoundingSphere.radius; - var numPositions = positions.length; - - - // 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; - } + function loadImageViaBlob(url, request) { + if (!xhrBlobSupported || isDataUri(url)) { + return loadImage(url, undefined, request); } - //Verify that the dot is not near 90 degress - if (dot < 0.00174532836589830883577820272085) { + + var blobPromise = loadBlob(url, undefined, request); + if (!defined(blobPromise)) { return undefined; } - var distance = occluderRadius / dot; - return Cartesian3.add(occluderPosition, Cartesian3.multiplyByScalar(occluderPlaneNormal, distance, occludeePointScratch), occludeePointScratch); - }; + return blobPromise.then(function(blob) { + var blobUrl = window.URL.createObjectURL(blob); - var computeOccludeePointFromRectangleScratch = []; - /** - * Computes a point that can be used as the occludee position to the visibility functions from a 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) { - - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - var positions = Rectangle.subsample(rectangle, ellipsoid, 0.0, computeOccludeePointFromRectangleScratch); - var bs = BoundingSphere.fromPoints(positions); + 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); + }); + }); + } - // 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 loadImageViaBlob; +}); - return undefined; - }; +define('Renderer/PixelDatatype',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; - 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); - }; + /** + * @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, - 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; - }; + isPacked : function(pixelDatatype) { + return 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; + }, - 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; + sizeInBytes : function(pixelDatatype) { + switch (pixelDatatype) { + case PixelDatatype.UNSIGNED_BYTE: + return 1; + case PixelDatatype.UNSIGNED_SHORT: + case PixelDatatype.UNSIGNED_SHORT_4_4_4_4: + case PixelDatatype.UNSIGNED_SHORT_5_5_5_1: + case PixelDatatype.UNSIGNED_SHORT_5_6_5: + return 2; + case PixelDatatype.UNSIGNED_INT: + case PixelDatatype.FLOAT: + case PixelDatatype.UNSIGNED_INT_24_8: + return 4; + } + }, - //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; + 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)); } - - //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; + return freezeObject(PixelDatatype); }); -/*global define*/ -define('Core/Packable',[ - './DeveloperError' +define('Core/PixelFormat',[ + '../Renderer/PixelDatatype', + './freezeObject', + './WebGLConstants' ], function( - DeveloperError) { + PixelDatatype, + freezeObject, + WebGLConstants) { '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 + * The format of a pixel, i.e., the number of components it has and what they represent. * - * @see PackableForInterpolation + * @exports PixelFormat */ - var Packable = { + var PixelFormat = { /** - * The number of elements used to pack the object into an array. + * A pixel format containing a depth value. + * * @type {Number} + * @constant */ - packedLength : undefined, + DEPTH_COMPONENT : WebGLConstants.DEPTH_COMPONENT, /** - * Stores the provided instance into the provided array. - * @function + * A pixel format containing a depth and stencil value, most often used with {@link PixelDatatype.UNSIGNED_INT_24_8}. * - * @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. + * @type {Number} + * @constant */ - pack : DeveloperError.throwInstantiationError, + DEPTH_STENCIL : WebGLConstants.DEPTH_STENCIL, /** - * Retrieves an instance from a packed array. - * @function + * A pixel format containing an alpha channel. * - * @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. + * @type {Number} + * @constant */ - unpack : DeveloperError.throwInstantiationError - }; - - return Packable; -}); + ALPHA : WebGLConstants.ALPHA, -/*global define*/ -define('Core/PackableForInterpolation',[ - './DeveloperError' - ], function( - DeveloperError) { - 'use strict'; + /** + * A pixel format containing red, green, and blue channels. + * + * @type {Number} + * @constant + */ + RGB : WebGLConstants.RGB, - /** - * 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. + * A pixel format containing red, green, blue, and alpha channels. + * * @type {Number} + * @constant */ - packedInterpolationLength : undefined, + RGBA : WebGLConstants.RGBA, /** - * Converts a packed array into a form suitable for interpolation. - * @function + * A pixel format containing a luminance (intensity) channel. * - * @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. + * @type {Number} + * @constant */ - convertPackedArrayForInterpolation : DeveloperError.throwInstantiationError, + LUMINANCE : WebGLConstants.LUMINANCE, /** - * Retrieves an instance from a packed array converted with {@link PackableForInterpolation.convertPackedArrayForInterpolation}. - * @function + * A pixel format containing luminance (intensity) and alpha channels. * - * @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. + * @type {Number} + * @constant */ - unpackInterpolationResult : DeveloperError.throwInstantiationError - }; + LUMINANCE_ALPHA : WebGLConstants.LUMINANCE_ALPHA, - return PackableForInterpolation; -}); + /** + * A pixel format containing red, green, and blue channels that is DXT1 compressed. + * + * @type {Number} + * @constant + */ + RGB_DXT1 : WebGLConstants.COMPRESSED_RGB_S3TC_DXT1_EXT, -/* - This library rewrites the Canvas2D "measureText" function - so that it returns a more complete metrics object. + /** + * A pixel format containing red, green, blue, and alpha channels that is DXT1 compressed. + * + * @type {Number} + * @constant + */ + RGBA_DXT1 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT1_EXT, -** ----------------------------------------------------------------------------- + /** + * A pixel format containing red, green, blue, and alpha channels that is DXT3 compressed. + * + * @type {Number} + * @constant + */ + RGBA_DXT3 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT3_EXT, - CHANGELOG: + /** + * A pixel format containing red, green, blue, and alpha channels that is DXT5 compressed. + * + * @type {Number} + * @constant + */ + RGBA_DXT5 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT5_EXT, - 2012-01-21 - Whitespace handling added by Joe Turner - (https://github.com/oampo) + /** + * A pixel format containing red, green, and blue channels that is PVR 4bpp compressed. + * + * @type {Number} + * @constant + */ + RGB_PVRTC_4BPPV1 : WebGLConstants.COMPRESSED_RGB_PVRTC_4BPPV1_IMG, -** ----------------------------------------------------------------------------- -*/ -/** - @license - fontmetrics.js - https://github.com/Pomax/fontmetrics.js + /** + * A pixel format containing red, green, and blue channels that is PVR 2bpp compressed. + * + * @type {Number} + * @constant + */ + RGB_PVRTC_2BPPV1 : WebGLConstants.COMPRESSED_RGB_PVRTC_2BPPV1_IMG, - Copyright (C) 2011 by Mike "Pomax" Kamermans + /** + * A pixel format containing red, green, blue, and alpha channels that is PVR 4bpp compressed. + * + * @type {Number} + * @constant + */ + RGBA_PVRTC_4BPPV1 : WebGLConstants.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, - 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: + /** + * A pixel format containing red, green, blue, and alpha channels that is PVR 2bpp compressed. + * + * @type {Number} + * @constant + */ + RGBA_PVRTC_2BPPV1 : WebGLConstants.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. + /** + * A pixel format containing red, green, and blue channels that is ETC1 compressed. + * + * @type {Number} + * @constant + */ + RGB_ETC1 : WebGLConstants.COMPRESSED_RGB_ETC1_WEBGL, - 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"; + /** + * @private + */ + componentsLength : function(pixelFormat) { + switch (pixelFormat) { + // Many GPUs store RGB as RGBA internally + // https://devtalk.nvidia.com/default/topic/699479/general-graphics-programming/rgb-auto-converted-to-rgba/post/4142379/#4142379 + case PixelFormat.RGB: + case PixelFormat.RGBA: + return 4; + case PixelFormat.LUMINANCE_ALPHA: + return 2; + case PixelFormat.ALPHA: + case PixelFormat.LUMINANCE: + return 1; + default: + return 1; + } + }, - // 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."); - } + /** + * @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 || + pixelFormat === PixelFormat.RGB_DXT1 || + pixelFormat === PixelFormat.RGBA_DXT1 || + pixelFormat === PixelFormat.RGBA_DXT3 || + pixelFormat === PixelFormat.RGBA_DXT5 || + pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 || + pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 || + pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 || + pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1 || + pixelFormat === PixelFormat.RGB_ETC1; + }, - // 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)'; + /** + * @private + */ + isColorFormat : function(pixelFormat) { + return pixelFormat === PixelFormat.ALPHA || + pixelFormat === PixelFormat.RGB || + pixelFormat === PixelFormat.RGBA || + pixelFormat === PixelFormat.LUMINANCE || + pixelFormat === PixelFormat.LUMINANCE_ALPHA; + }, - ctx.beginPath(); - ctx.moveTo(xstart,0); - ctx.lineTo(xstart,h); - ctx.closePath(); - ctx.stroke(); + /** + * @private + */ + isDepthFormat : function(pixelFormat) { + return pixelFormat === PixelFormat.DEPTH_COMPONENT || + pixelFormat === PixelFormat.DEPTH_STENCIL; + }, - ctx.beginPath(); - ctx.moveTo(xstart+metrics.bounds.maxx,0); - ctx.lineTo(xstart+metrics.bounds.maxx,h); - ctx.closePath(); - ctx.stroke(); + /** + * @private + */ + isCompressedFormat : function(pixelFormat) { + return pixelFormat === PixelFormat.RGB_DXT1 || + pixelFormat === PixelFormat.RGBA_DXT1 || + pixelFormat === PixelFormat.RGBA_DXT3 || + pixelFormat === PixelFormat.RGBA_DXT5 || + pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 || + pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 || + pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 || + pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1 || + pixelFormat === PixelFormat.RGB_ETC1; + }, - ctx.beginPath(); - ctx.moveTo(0,h/2-metrics.ascent); - ctx.lineTo(w,h/2-metrics.ascent); - ctx.closePath(); - ctx.stroke(); + /** + * @private + */ + isDXTFormat : function(pixelFormat) { + return pixelFormat === PixelFormat.RGB_DXT1 || + pixelFormat === PixelFormat.RGBA_DXT1 || + pixelFormat === PixelFormat.RGBA_DXT3 || + pixelFormat === PixelFormat.RGBA_DXT5; + }, - 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; + /** + * @private + */ + isPVRTCFormat : function(pixelFormat) { + return pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 || + pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 || + pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 || + pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1; + }, - // 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); + /** + * @private + */ + isETC1Format : function(pixelFormat) { + return pixelFormat === PixelFormat.RGB_ETC1; + }, - // make some initial guess at the text leading (using the standard TeX ratio) - metrics.leading = 1.2 * fontSize; + /** + * @private + */ + compressedTextureSizeInBytes : function(pixelFormat, width, height) { + switch (pixelFormat) { + case PixelFormat.RGB_DXT1: + case PixelFormat.RGBA_DXT1: + case PixelFormat.RGB_ETC1: + return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8; - // 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); + case PixelFormat.RGBA_DXT3: + case PixelFormat.RGBA_DXT5: + return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16; - // 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; + case PixelFormat.RGB_PVRTC_4BPPV1: + case PixelFormat.RGBA_PVRTC_4BPPV1: + return Math.floor((Math.max(width, 8) * Math.max(height, 8) * 4 + 7) / 8); - var w = canvas.width, - h = canvas.height, - baseline = h/2; + case PixelFormat.RGB_PVRTC_2BPPV1: + case PixelFormat.RGBA_PVRTC_2BPPV1: + return Math.floor((Math.max(width, 16) * Math.max(height, 8) * 2 + 7) / 8); - // 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); + default: + return 0; + } + }, - if (stroke) { - ctx.strokeStyle = "black"; - ctx.lineWidth = context2D.lineWidth; - ctx.strokeText(textstring, (padding / 2), baseline); + /** + * @private + */ + textureSizeInBytes : function(pixelFormat, pixelDatatype, width, height) { + var componentsLength = PixelFormat.componentsLength(pixelFormat); + if (PixelDatatype.isPacked(pixelDatatype)) { + componentsLength = 1; + } + return componentsLength * PixelDatatype.sizeInBytes(pixelDatatype) * width * height; } + }; - if (fill) { - ctx.fillStyle = "black"; - ctx.fillText(textstring, padding / 2, baseline); - } + return freezeObject(PixelFormat); +}); - 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; +define('Core/loadKTX',[ + '../ThirdParty/when', + './Check', + './CompressedTextureBuffer', + './defined', + './loadArrayBuffer', + './PixelFormat', + './RuntimeError' + ], function( + when, + Check, + CompressedTextureBuffer, + defined, + loadArrayBuffer, + PixelFormat, + RuntimeError) { + 'use strict'; /** - * Writes the given text into a new canvas. The canvas will be sized to fit the text. - * If text is blank, returns undefined. + * Asynchronously loads and parses the given URL to a KTX file or parses the raw binary data of a KTX file. + * Returns a promise that will resolve to an object containing the image buffer, width, height and format once loaded, + * or reject if the URL failed to load or failed to parse the data. 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. + *

    + * The following are part of the KTX format specification but are not supported: + *

      + *
    • Big-endian files
    • + *
    • Metadata
    • + *
    • 3D textures
    • + *
    • Texture Arrays
    • + *
    • Cubemaps
    • + *
    • Mipmaps
    • + *
    + *

    * - * @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. + * @exports loadKTX + * + * @param {String|ArrayBuffer} urlOrBuffer The URL of the binary data or an ArrayBuffer. + * @param {Object} [headers] HTTP headers to send with the requests. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} A promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. + * + * @exception {RuntimeError} Invalid KTX file. + * @exception {RuntimeError} File is the wrong endianness. + * @exception {RuntimeError} glInternalFormat is not a valid format. + * @exception {RuntimeError} glType must be zero when the texture is compressed. + * @exception {RuntimeError} The type size for compressed textures must be 1. + * @exception {RuntimeError} glFormat must be zero when the texture is compressed. + * @exception {RuntimeError} Generating mipmaps for a compressed texture is unsupported. + * @exception {RuntimeError} The base internal format must be the same as the format for uncompressed textures. + * @exception {RuntimeError} 3D textures are not supported. + * @exception {RuntimeError} Texture arrays are not supported. + * @exception {RuntimeError} Cubemaps are not supported. + * + * @example + * // load a single URL asynchronously + * Cesium.loadKTX('some/url').then(function(ktxData) { + * var width = ktxData.width; + * var height = ktxData.height; + * var format = ktxData.internalFormat; + * var arrayBufferView = ktxData.bufferView; + * // use the data to create a texture + * }).otherwise(function(error) { + * // an error occurred + * }); + * + * @see {@link https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/|KTX file format} + * @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 writeTextToCanvas(text, options) { - if (text === '') { + function loadKTX(urlOrBuffer, headers, request) { + + var loadPromise; + if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) { + loadPromise = when.resolve(urlOrBuffer); + } else { + loadPromise = loadArrayBuffer(urlOrBuffer, headers, request); + } + + if (!defined(loadPromise)) { 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; + return loadPromise.then(function(data) { + return parseKTX(data); + }); + } - var canvas = document.createElement('canvas'); - canvas.width = 1; - canvas.height = 1; - canvas.style.font = font; + var fileIdentifier = [0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A]; + var endiannessTest = 0x04030201; - var context2D = canvas.getContext('2d'); + var sizeOfUint32 = 4; - 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'; + function parseKTX(data) { + var byteBuffer = new Uint8Array(data); + + var isKTX = true; + for (var i = 0; i < fileIdentifier.length; ++i) { + if (fileIdentifier[i] !== byteBuffer[i]) { + isKTX = false; + break; } } - context2D.font = font; - context2D.lineJoin = 'round'; - context2D.lineWidth = strokeWidth; - context2D[imageSmoothingEnabledName] = false; + if (!isKTX) { + throw new RuntimeError('Invalid KTX file.'); + } - // textBaseline needs to be set before the measureText call. It won't work otherwise. - // It's magic. - context2D.textBaseline = defaultValue(options.textBaseline, 'bottom'); + var view; + var byteOffset; - // 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); + if (defined(data.buffer)) { + view = new DataView(data.buffer); + byteOffset = data.byteOffset; + } else { + view = new DataView(data); + byteOffset = 0; + } - var dimensions = measureText(context2D, text, stroke, fill); - canvas.dimensions = dimensions; + byteOffset += 12; // skip identifier - document.body.removeChild(canvas); - canvas.style.visibility = ''; + var endianness = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + if (endianness !== endiannessTest) { + throw new RuntimeError('File is the wrong endianness.'); + } - //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; + var glType = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var glTypeSize = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var glFormat = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var glInternalFormat = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var glBaseInternalFormat = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var pixelWidth = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var pixelHeight = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var pixelDepth = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var numberOfArrayElements = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var numberOfFaces = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var numberOfMipmapLevels = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var bytesOfKeyValueByteSize = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; - //Expand the width to include the starting position. - var width = Math.ceil(dimensions.width) + x + doublePadding; + // skip metadata + byteOffset += bytesOfKeyValueByteSize; - //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; + var imageSize = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; - canvas.width = width; - canvas.height = height; + var texture; + if (defined(data.buffer)) { + texture = new Uint8Array(data.buffer, byteOffset, imageSize); + } else { + texture = new Uint8Array(data, byteOffset, imageSize); + } - // Properties must be explicitly set again after changing width and height - context2D.font = font; - context2D.lineJoin = 'round'; - context2D.lineWidth = strokeWidth; - context2D[imageSmoothingEnabledName] = false; + // Some tools use a sized internal format. + // See table 2: https://www.opengl.org/sdk/docs/man/html/glTexImage2D.xhtml + if (glInternalFormat === 0x8051) { // GL_RGB8 + glInternalFormat = PixelFormat.RGB; + } else if (glInternalFormat === 0x8058) { // GL_RGBA8 + glInternalFormat = PixelFormat.RGBA; + } - // Draw background - if (backgroundColor !== Color.TRANSPARENT) { - context2D.fillStyle = backgroundColor.toCssColorString(); - context2D.fillRect(0, 0, canvas.width, canvas.height); + if (!PixelFormat.validate(glInternalFormat)) { + throw new RuntimeError('glInternalFormat is not a valid format.'); } - if (stroke) { - var strokeColor = defaultValue(options.strokeColor, Color.BLACK); - context2D.strokeStyle = strokeColor.toCssColorString(); - context2D.strokeText(text, x + padding, y); + if (PixelFormat.isCompressedFormat(glInternalFormat)) { + if (glType !== 0) { + throw new RuntimeError('glType must be zero when the texture is compressed.'); + } + if (glTypeSize !== 1) { + throw new RuntimeError('The type size for compressed textures must be 1.'); + } + if (glFormat !== 0) { + throw new RuntimeError('glFormat must be zero when the texture is compressed.'); + } + } else if (glBaseInternalFormat !== glFormat) { + throw new RuntimeError('The base internal format must be the same as the format for uncompressed textures.'); } - if (fill) { - var fillColor = defaultValue(options.fillColor, Color.WHITE); - context2D.fillStyle = fillColor.toCssColorString(); - context2D.fillText(text, x + padding, y); + if (pixelDepth !== 0) { + throw new RuntimeError('3D textures are unsupported.'); } - return canvas; + if (numberOfArrayElements !== 0) { + throw new RuntimeError('Texture arrays are unsupported.'); + } + if (numberOfFaces !== 1) { + throw new RuntimeError('Cubemaps are unsupported.'); + } + + // Only use the level 0 mipmap + if (numberOfMipmapLevels > 1) { + var levelSize = PixelFormat.isCompressedFormat(glInternalFormat) ? + PixelFormat.compressedTextureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight) : + PixelFormat.textureSizeInBytes(glInternalFormat, pixelWidth, pixelHeight); + texture = new Uint8Array(texture.buffer, texture.byteOffset, levelSize); + } + + return new CompressedTextureBuffer(glInternalFormat, pixelWidth, pixelHeight, texture); } - return writeTextToCanvas; + return loadKTX; }); -/*global define*/ -define('Core/PinBuilder',[ - './buildModuleUrl', - './Color', - './defined', - './DeveloperError', - './loadImage', - './writeTextToCanvas' +define('Core/loadXML',[ + './loadWithXhr' ], function( - buildModuleUrl, - Color, - defined, - DeveloperError, - loadImage, - writeTextToCanvas) { + loadWithXhr) { '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. - *
    + * 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. * - * @alias PinBuilder + * @exports loadXML + * + * @param {String} url The URL to request. + * @param {Object} [headers] HTTP headers to send with the request. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority. + * + * + * @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, request) { + return loadWithXhr({ + url : url, + responseType : 'document', + headers : headers, + overrideMimeType : 'text/xml', + request : request + }); + } + + return loadXML; +}); + +define('Core/ManagedArray',[ + './Check', + './defaultValue', + './defineProperties' + ], function( + Check, + defaultValue, + defineProperties) { + 'use strict'; + + /** + * A wrapper around arrays so that the internal length of the array can be manually managed. + * + * @alias ManagedArray * @constructor + * @private * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Map%20Pins.html|Cesium Sandcastle PinBuilder Demo} + * @param {Number} [length=0] The initial length of the array. */ - function PinBuilder() { - this._cache = {}; + function ManagedArray(length) { + length = defaultValue(length, 0); + this._array = new Array(length); + this._length = length; } + defineProperties(ManagedArray.prototype, { + /** + * Gets or sets the length of the array. + * If the set length is greater than the length of the internal array, the internal array is resized. + * + * @type Number + */ + length : { + get : function() { + return this._length; + }, + set : function(length) { + this._length = length; + if (length > this._array.length) { + this._array.length = length; + } + } + }, + + /** + * Gets the internal array. + * + * @type Array + * @readonly + */ + values : { + get : function() { + return this._array; + } + } + }); + /** - * Creates an empty pin of the specified color and size. + * Gets the element at an index. * - * @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. + * @param {Number} index The index to get. */ - PinBuilder.prototype.fromColor = function(color, size) { - return createPin(undefined, undefined, color, size, this._cache); + ManagedArray.prototype.get = function(index) { + + return this._array[index]; }; /** - * Creates a pin with the specified icon, color, and size. + * Sets the element at an index. Resizes the array if index is greater than the length of the array. * - * @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. + * @param {Number} index The index to set. + * @param {*} value The value to set at index. */ - PinBuilder.prototype.fromUrl = function(url, color, size) { - return createPin(url, undefined, color, size, this._cache); + ManagedArray.prototype.set = function(index, value) { + + if (index >= this.length) { + this.length = index + 1; + } + this._array[index] = value; }; /** - * Creates a pin with the specified {@link https://www.mapbox.com/maki/|maki} icon identifier, color, and size. + * Push an element into the array. + */ + ManagedArray.prototype.push = function(element) { + var index = this.length++; + this._array[index] = element; + }; + + /** + * Pop an element from the array. * - * @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. + * @returns {*} The last element in the array. */ - PinBuilder.prototype.fromMakiIconId = function(id, color, size) { - return createPin(buildModuleUrl('Assets/Textures/maki/' + encodeURIComponent(id) + '.png'), undefined, color, size, this._cache); + ManagedArray.prototype.pop = function() { + return this._array[--this.length]; }; /** - * 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. + * Resize the internal array if length > _array.length. * - * @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. + * @param {Number} length The length. */ - PinBuilder.prototype.fromText = function(text, color, size) { + ManagedArray.prototype.reserve = function(length) { - return createPin(undefined, text, color, size, this._cache); + if (length > this._array.length) { + this._array.length = length; + } }; - 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(); - } + /** + * Resize the array. + * + * @param {Number} length The length. + */ + ManagedArray.prototype.resize = function(length) { + + this.length = length; + }; - //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; + /** + * Trim the internal array to the specified length. Defaults to the current length. + * + * @param {Number} [length] The length. + */ + ManagedArray.prototype.trim = function(length) { + length = defaultValue(length, this.length); + this._array.length = length; + }; - if (image.width > image.height) { - sizeY = imageSize * (image.height / image.width); - } else if (image.width < image.height) { - sizeX = imageSize * (image.width / image.height); - } + return ManagedArray; +}); - //x and y are the center of the pin box - var x = Math.round((size - sizeX) / 2); - var y = Math.round(((7 / 24) * size) - (sizeY / 2)); +define('Core/MapboxApi',[ + './Credit', + './defined' + ], function( + Credit, + defined) { + 'use strict'; - 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); + var MapboxApi = { + }; - context2D.globalCompositeOperation = 'destination-over'; - context2D.fillStyle = Color.BLACK.toCssColorString(); - context2D.fillRect(x - 1, y - 1, sizeX + 2, sizeY + 2); + /** + * 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; - context2D.globalCompositeOperation = 'destination-out'; - context2D.drawImage(image, x, y, sizeX, sizeY); + 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.'; - context2D.globalCompositeOperation = 'destination-over'; - context2D.fillStyle = Color.WHITE.toCssColorString(); - context2D.fillRect(x - 1, y - 2, sizeX + 2, sizeY + 2); - } - 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); + MapboxApi.getAccessToken = function(providedToken) { + if (defined(providedToken)) { + return providedToken; + } - var item = cache[id]; - if (defined(item)) { - return item; + if (!defined(MapboxApi.defaultAccessToken)) { + if (!printedMapboxWarning) { + console.log(errorString); + printedMapboxWarning = true; + } + return 'pk.eyJ1IjoiYW5hbHl0aWNhbGdyYXBoaWNzIiwiYSI6ImNpd204Zm4wejAwNzYyeW5uNjYyZmFwdWEifQ.7i-VIZZWX8pd1bTfxIVj9g'; } - var canvas = document.createElement('canvas'); - canvas.width = size; - canvas.height = size; + return MapboxApi.defaultAccessToken; + }; - var context2D = canvas.getContext("2d"); - drawPin(context2D, color, size); + MapboxApi.getErrorCredit = function(providedToken) { + if (defined(providedToken) || defined(MapboxApi.defaultAccessToken)) { + return undefined; + } - 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); + if (!defined(errorCredit)) { + errorCredit = new Credit(errorString); } - cache[id] = canvas; - return canvas; - } + return errorCredit; + }; - return PinBuilder; + return MapboxApi; }); -/*global define*/ -define('Core/pointInsideTriangle',[ - './barycentricCoordinates', - './Cartesian3' +define('Core/MapProjection',[ + './defineProperties', + './DeveloperError' ], function( - barycentricCoordinates, - Cartesian3) { + defineProperties, + DeveloperError) { 'use strict'; - var coords = new Cartesian3(); + /** + * 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 + } + }); /** - * Determines if a point is inside a triangle. + * Projects {@link Cartographic} coordinates, in radians, to projection-specific map coordinates, in meters. * - * @exports pointInsideTriangle + * @memberof MapProjection + * @function * - * @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. + * @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. * - * @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)); + * @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. */ - function pointInsideTriangle(point, p0, p1, p2) { - barycentricCoordinates(point, p0, p1, p2, coords); - return (coords.x > 0.0) && (coords.y > 0.0) && (coords.z > 0); - } + MapProjection.prototype.unproject = DeveloperError.throwInstantiationError; - return pointInsideTriangle; + return MapProjection; }); -/*global define*/ -define('Core/Queue',[ - './defineProperties' +define('Core/Matrix2',[ + './Cartesian2', + './Check', + './defaultValue', + './defined', + './defineProperties', + './freezeObject' ], function( - defineProperties) { + Cartesian2, + Check, + defaultValue, + defined, + defineProperties, + freezeObject) { 'use strict'; /** - * A queue that can enqueue items at the end, and dequeue items from the front. - * - * @alias Queue + * 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 Queue() { - this._array = []; - this._offset = 0; - this._length = 0; + 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); } - defineProperties(Queue.prototype, { - /** - * The length of the queue. - * - * @memberof Queue.prototype - * - * @type {Number} - * @readonly - */ - length : { - get : function() { - return this._length; - } - } - }); + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + Matrix2.packedLength = 4; /** - * Enqueues the specified item. + * Stores the provided instance into the provided array. * - * @param {Object} item The item to enqueue. + * @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 */ - Queue.prototype.enqueue = function(item) { - this._array.push(item); - this._length++; + Matrix2.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); + + array[startingIndex++] = value[0]; + array[startingIndex++] = value[1]; + array[startingIndex++] = value[2]; + array[startingIndex++] = value[3]; + + return array; }; /** - * Dequeues an item. Returns undefined if the queue is empty. + * Retrieves an instance from a packed array. * - * @returns {Object} The the dequeued item. + * @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. */ - 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; + Matrix2.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); - offset++; - if ((offset > 10) && (offset * 2 > array.length)) { - //compact array - this._array = array.slice(offset); - offset = 0; + if (!defined(result)) { + result = new Matrix2(); } - this._offset = offset; - this._length--; - - return item; + result[0] = array[startingIndex++]; + result[1] = array[startingIndex++]; + result[2] = array[startingIndex++]; + result[3] = array[startingIndex++]; + return result; }; /** - * Returns the item at the front of the queue. Returns undefined if the queue is empty. + * Duplicates a Matrix2 instance. * - * @returns {Object} The item at the front of the queue. + * @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) */ - Queue.prototype.peek = function() { - if (this._length === 0) { + Matrix2.clone = function(matrix, result) { + if (!defined(matrix)) { return undefined; } - - return this._array[this._offset]; + if (!defined(result)) { + return new Matrix2(matrix[0], matrix[2], + matrix[1], matrix[3]); + } + result[0] = matrix[0]; + result[1] = matrix[1]; + result[2] = matrix[2]; + result[3] = matrix[3]; + return result; }; /** - * Check whether this queue contains the specified item. + * Creates a Matrix2 from 4 consecutive elements in an array. * - * @param {Object} item The item to search for. + * @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); */ - Queue.prototype.contains = function(item) { - return this._array.indexOf(item) !== -1; + Matrix2.fromArray = function(array, startingIndex, result) { + + 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; }; /** - * Remove all items from the queue. + * 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. */ - Queue.prototype.clear = function() { - this._array.length = this._offset = this._length = 0; + Matrix2.fromColumnMajorArray = function(values, result) { + + return Matrix2.clone(values, result); }; /** - * Sort the items in the queue in-place. + * Creates a Matrix2 instance from a row-major order array. + * The resulting matrix will be in column-major order. * - * @param {Queue~Comparator} compareFunction A function that defines the sort 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. */ - Queue.prototype.sort = function(compareFunction) { - if (this._offset > 0) { - //compact array - this._array = this._array.slice(this._offset); - this._offset = 0; + Matrix2.fromRowMajorArray = function(values, result) { + + if (!defined(result)) { + return new Matrix2(values[0], values[1], + values[2], values[3]); } - - this._array.sort(compareFunction); + result[0] = values[0]; + result[1] = values[2]; + result[2] = values[1]; + result[3] = values[3]; + return result; }; /** - * A function used to compare two items while sorting a queue. - * @callback Queue~Comparator + * Computes a Matrix2 instance representing a non-uniform scale. * - * @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. + * @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 - * function compareNumbers(a, b) { - * return a - b; - * } + * // 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(result)) { + return new Matrix2( + scale.x, 0.0, + 0.0, scale.y); + } - 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'; + result[0] = scale.x; + result[1] = 0.0; + result[2] = 0.0; + result[3] = scale.y; + return result; + }; /** - * @private + * 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); */ - 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]); - } - } + Matrix2.fromUniformScale = function(scale, result) { + + if (!defined(result)) { + return new Matrix2( + scale, 0.0, + 0.0, scale); } - return numComponents; + result[0] = scale; + result[1] = 0.0; + result[2] = 0.0; + result[3] = scale; + return result; }; - 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); - } - } + /** + * 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) { + + var cosAngle = Math.cos(angle); + var sinAngle = Math.sin(angle); - if (defined(holes)) { - var holesLength = holes.length; - for (var j = 0; j < holesLength; ++j) { - stack.push(holes[j]); - } - } + if (!defined(result)) { + return new Matrix2( + cosAngle, -sinAngle, + sinAngle, cosAngle); } - - return startingIndex; + result[0] = cosAngle; + result[1] = sinAngle; + result[2] = -sinAngle; + result[3] = cosAngle; + return result; }; - 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; + /** + * 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(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; + }; - return { - positions : positions, - holes : holes, - startingIndex : startingIndex - }; + /** + * 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) { + + return column * 2 + row; }; - 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]; - } + /** + * 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) { + + var startIndex = index * 2; + var x = matrix[startIndex]; + var y = matrix[startIndex + 1]; - 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); + result.x = x; + result.y = y; + return result; }; - 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 = []; - } + /** + * 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) { + + result = Matrix2.clone(matrix, result); + var startIndex = index * 2; + result[startIndex] = cartesian.x; + result[startIndex + 1] = cartesian.y; + return result; + }; - var positions = result; - positions.length = numVertices * 3; + /** + * 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) { + + var x = matrix[index]; + var y = matrix[index + 2]; - 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]; - } + result.x = x; + result.y = y; + return result; + }; - return positions; + /** + * 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) { + + result = Matrix2.clone(matrix, result); + result[index] = cartesian.x; + result[index + 2] = cartesian.y; + return result; }; - var scaleToGeodeticHeightN1 = new Cartesian3(); - var scaleToGeodeticHeightN2 = new Cartesian3(); - var scaleToGeodeticHeightP1 = new Cartesian3(); - var scaleToGeodeticHeightP2 = new Cartesian3(); + var scratchColumn = new Cartesian2(); - PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid, perPositionHeight) { - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + /** + * 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) { + + 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 n1 = scaleToGeodeticHeightN1; - var n2 = scaleToGeodeticHeightN2; - var p = scaleToGeodeticHeightP1; - var p2 = scaleToGeodeticHeightP2; + var scratchScale = new Cartesian2(); - if (defined(geometry) && defined(geometry.attributes) && defined(geometry.attributes.position)) { - var positions = geometry.attributes.position.values; - var length = positions.length / 2; + /** + * 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); + }; - for ( var i = 0; i < length; i += 3) { - Cartesian3.fromArray(positions, i, p); + /** + * 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) { + + 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]; - 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; + result[0] = column0Row0; + result[1] = column0Row1; + result[2] = column1Row0; + result[3] = column1Row1; + return result; + }; - 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; + /** + * 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) { + + 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; }; - 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 = []; + /** + * 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) { + + 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; + }; - var queue = new Queue(); - queue.enqueue(polygonHierarchy); + /** + * 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) { + + var x = matrix[0] * cartesian.x + matrix[2] * cartesian.y; + var y = matrix[1] * cartesian.x + matrix[3] * cartesian.y; - while (queue.length !== 0) { - var outerNode = queue.dequeue(); - var outerRing = outerNode.positions; - var holes = outerNode.holes; + result.x = x; + result.y = y; + return result; + }; - outerRing = arrayRemoveDuplicates(outerRing, Cartesian3.equalsEpsilon, true); - if (outerRing.length < 3) { - continue; - } + /** + * 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) { + + result[0] = matrix[0] * scalar; + result[1] = matrix[1] * scalar; + result[2] = matrix[2] * scalar; + result[3] = matrix[3] * scalar; + return result; + }; - var positions2D = tangentPlane.projectPointsOntoPlane(outerRing); - var holeIndices = []; + /** + * 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) { + + 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; + }; - var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); - if (originalWindingOrder === WindingOrder.CLOCKWISE) { - positions2D.reverse(); - outerRing = outerRing.slice().reverse(); - } + /** + * 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) { + + result[0] = -matrix[0]; + result[1] = -matrix[1]; + result[2] = -matrix[2]; + result[3] = -matrix[3]; + return result; + }; - var positions = outerRing.slice(); - var numChildren = defined(holes) ? holes.length : 0; - var polygonHoles = []; - var i; - var j; + /** + * 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) { + + var column0Row0 = matrix[0]; + var column0Row1 = matrix[2]; + var column1Row0 = matrix[1]; + var column1Row1 = matrix[3]; - for (i = 0; i < numChildren; i++) { - var hole = holes[i]; - var holePositions = arrayRemoveDuplicates(hole.positions, Cartesian3.equalsEpsilon, true); - if (holePositions.length < 3) { - continue; - } + result[0] = column0Row0; + result[1] = column0Row1; + result[2] = column1Row0; + result[3] = column1Row1; + return result; + }; - var holePositions2D = tangentPlane.projectPointsOntoPlane(holePositions); + /** + * 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) { + + result[0] = Math.abs(matrix[0]); + result[1] = Math.abs(matrix[1]); + result[2] = Math.abs(matrix[2]); + result[3] = Math.abs(matrix[3]); - originalWindingOrder = PolygonPipeline.computeWindingOrder2D(holePositions2D); - if (originalWindingOrder === WindingOrder.CLOCKWISE) { - holePositions2D.reverse(); - holePositions = holePositions.slice().reverse(); - } + return result; + }; - polygonHoles.push(holePositions); - holeIndices.push(positions.length); - positions = positions.concat(holePositions); - positions2D = positions2D.concat(holePositions2D); + /** + * 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]); + }; - var numGrandchildren = 0; - if (defined(hole.holes)) { - numGrandchildren = hole.holes.length; - } + /** + * @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]; + }; - for (j = 0; j < numGrandchildren; j++) { - queue.enqueue(hole.holes[j]); - } - } + /** + * 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) { + + 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); + }; - 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]); - } - } - } + /** + * 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)); - hierarchy.push({ - outerRing : outerRing, - holes : polygonHoles - }); - polygons.push({ - positions : positions, - positions2D : positions2D, - holes : holeIndices - }); - } + /** + * 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)); - return { - hierarchy : hierarchy, - polygons : polygons - }; - }; + /** + * 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; - PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat) { - var indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes); + /** + * 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; - /* If polygon is completely unrenderable, just use the first three vertices */ - if (indices.length < 3) { - indices = [0, 1, 2]; - } + /** + * 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; - var positions = polygon.positions; + /** + * 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; - 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; + defineProperties(Matrix2.prototype, { + /** + * Gets the number of items in the collection. + * @memberof Matrix2.prototype + * + * @type {Number} + */ + length : { + get : function() { + return Matrix2.packedLength; } - 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); - } + /** + * 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); + }; - return geometry; - } + /** + * 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); + }; - return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); + /** + * 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); }; - var computeWallIndicesSubdivided = []; - var p1Scratch = new Cartesian3(); - var p2Scratch = new Cartesian3(); + /** + * 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] + ')'; + }; - PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) { - var edgePositions; - var topEdgeLength; - var i; - var p1; - var p2; + return Matrix2; +}); - var length = positions.length; - var index = 0; +define('Core/mergeSort',[ + './defined', + './DeveloperError' + ], function( + defined, + DeveloperError) { + 'use strict'; - if (!perPositionHeight) { - var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + var leftScratchArray = []; + var rightScratchArray = []; - var numVertices = 0; - for (i = 0; i < length; i++) { - numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); - } + function merge(array, compare, userDefinedObject, start, middle, end) { + var leftLength = middle - start + 1; + var rightLength = end - middle; - topEdgeLength = (numVertices + length) * 3; - edgePositions = new Array(topEdgeLength * 2); - for (i = 0; i < length; i++) { - p1 = positions[i]; - p2 = positions[(i + 1) % length]; + var left = leftScratchArray; + var right = rightScratchArray; - 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]; - } + var i; + var j; - edgePositions[index] = p2.x; - edgePositions[index + topEdgeLength] = p2.x; - ++index; + for (i = 0; i < leftLength; ++i) { + left[i] = array[start + i]; + } - edgePositions[index] = p2.y; - edgePositions[index + topEdgeLength] = p2.y; - ++index; + for (j = 0; j < rightLength; ++j) { + right[j] = array[middle + j + 1]; + } - 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; + 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; } } + } - length = edgePositions.length; - var indices = IndexDatatype.createTypedArray(length / 3, length - positions.length * 6); - var edgeIndex = 0; - length /= 6; + function sort(array, compare, userDefinedObject, start, end) { + if (start >= end) { + return; + } - for (i = 0; i < length; i++) { - var UL = i; - var UR = UL + 1; - var LL = UL + length; - var LR = LL + 1; + 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); + } - p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch); - p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch); - if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON14)) { - continue; - } + /** + * 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) { + + var length = array.length; + var scratchLength = Math.ceil(length * 0.5); - indices[edgeIndex++] = UL; - indices[edgeIndex++] = LL; - indices[edgeIndex++] = UR; - indices[edgeIndex++] = UR; - indices[edgeIndex++] = LL; - indices[edgeIndex++] = LR; - } + // preallocate space in scratch arrays + leftScratchArray.length = scratchLength; + rightScratchArray.length = scratchLength; - return new Geometry({ - attributes : new GeometryAttributes({ - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : edgePositions - }) - }), - indices : indices, - primitiveType : PrimitiveType.TRIANGLES - }); - }; + sort(array, comparator, userDefinedObject, 0, length - 1); - return PolygonGeometryLibrary; + // 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/PolygonGeometry',[ - './BoundingRectangle', - './BoundingSphere', - './Cartesian2', - './Cartesian3', - './Cartographic', - './Check', - './ComponentDatatype', +define('Core/NearFarScalar',[ './defaultValue', './defined', - './defineProperties', - './DeveloperError', - './Ellipsoid', - './EllipsoidTangentPlane', - './Geometry', - './GeometryAttribute', - './GeometryInstance', - './GeometryPipeline', - './IndexDatatype', - './Math', - './Matrix3', - './PolygonGeometryLibrary', - './PolygonPipeline', - './Quaternion', - './Rectangle', - './VertexFormat', - './WindingOrder' + './DeveloperError' ], function( - BoundingRectangle, - BoundingSphere, - Cartesian2, - Cartesian3, - Cartographic, - Check, - ComponentDatatype, defaultValue, defined, - defineProperties, - DeveloperError, - Ellipsoid, - EllipsoidTangentPlane, - Geometry, - GeometryAttribute, - GeometryInstance, - GeometryPipeline, - IndexDatatype, - CesiumMath, - Matrix3, - PolygonGeometryLibrary, - PolygonPipeline, - Quaternion, - Rectangle, - VertexFormat, - WindingOrder) { + DeveloperError) { '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); + /** + * 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); + } - if (defined(st)) { - minX = Math.min(minX, st.x); - maxX = Math.max(maxX, st.x); + /** + * 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; + } - minY = Math.min(minY, st.y); - maxY = Math.max(maxY, st.y); - } + if (!defined(result)) { + return new NearFarScalar(nearFarScalar.near, nearFarScalar.nearValue, nearFarScalar.far, nearFarScalar.farValue); } - result.x = minX; - result.y = minY; - result.width = maxX - minX; - result.height = maxY - minY; + result.near = nearFarScalar.near; + result.nearValue = nearFarScalar.nearValue; + result.far = nearFarScalar.far; + result.farValue = nearFarScalar.farValue; 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); - } + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + NearFarScalar.packedLength = 4; - var scratchBoundingRectangle = new BoundingRectangle(); - var scratchPosition = new Cartesian3(); - var scratchNormal = new Cartesian3(); - var scratchTangent = new Cartesian3(); - var scratchBitangent = new Cartesian3(); - var p1Scratch = new Cartesian3(); - var p2Scratch = new Cartesian3(); - var scratchPerPosNormal = new Cartesian3(); - var scratchPerPosTangent = new Cartesian3(); - var scratchPerPosBitangent = 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; - var shadowVolume = options.shadowVolume; - if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent || shadowVolume) { - // 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 bitangents = vertexFormat.bitangent ? new Float32Array(length) : undefined; - var extrudeNormals = shadowVolume ? new Float32Array(length) : undefined; - - var textureCoordIndex = 0; - var attrIndex = 0; - - var normal = scratchNormal; - var tangent = scratchTangent; - var bitangent = scratchBitangent; - var recomputeNormal = true; - - var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, stRotation, appendTextureCoordinatesQuaternion); - var textureMatrix = Matrix3.fromQuaternion(rotation, appendTextureCoordinatesMatrix3); + /** + * 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) { + + startingIndex = defaultValue(startingIndex, 0); - var bottomOffset = 0; - var bottomOffset2 = 0; + array[startingIndex++] = value.near; + array[startingIndex++] = value.nearValue; + array[startingIndex++] = value.far; + array[startingIndex] = value.farValue; - if (top && bottom) { - bottomOffset = length / 2; - bottomOffset2 = length / 3; + return array; + }; - length /= 2; - } + /** + * 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) { + + startingIndex = defaultValue(startingIndex, 0); - for ( var i = 0; i < length; i += 3) { - var position = Cartesian3.fromArray(flatPositions, i, appendTextureCoordinatesCartesian3); + if (!defined(result)) { + result = new NearFarScalar(); + } + result.near = array[startingIndex++]; + result.nearValue = array[startingIndex++]; + result.far = array[startingIndex++]; + result.farValue = array[startingIndex]; + return result; + }; - 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); + /** + * 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)); + }; - 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; - } + /** + * 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); + }; - textureCoordIndex += 2; - } + /** + * 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); + }; - if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent || shadowVolume) { - var attrIndex1 = attrIndex + 1; - var attrIndex2 = attrIndex + 2; + return NearFarScalar; +}); - if (wall) { - if (i + 3 < length) { - var p1 = Cartesian3.fromArray(flatPositions, i + 3, p1Scratch); +define('Core/Visibility',[ + './freezeObject' + ], function( + freezeObject) { + 'use strict'; - 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; - } + /** + * 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, - if (Cartesian3.equalsEpsilon(p1, position, CesiumMath.EPSILON10)) { // if we've reached a corner - recomputeNormal = true; - } - } + /** + * Represents that part, but not all, of an object is visible + * + * @type {Number} + * @constant + */ + PARTIAL : 0, - if (vertexFormat.tangent || vertexFormat.bitangent) { - bitangent = ellipsoid.geodeticSurfaceNormal(position, bitangent); - if (vertexFormat.tangent) { - tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent); - } - } - } else { - normal = ellipsoid.geodeticSurfaceNormal(position, normal); - if (vertexFormat.tangent || vertexFormat.bitangent) { - 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.bitangent) { - scratchPerPosBitangent = Cartesian3.normalize(Cartesian3.cross(scratchPerPosNormal, scratchPerPosTangent, scratchPerPosBitangent), scratchPerPosBitangent); - } - } + /** + * Represents that an object is visible in its entirety. + * + * @type {Number} + * @constant + */ + FULL : 1 + }; - tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); - tangent = Cartesian3.normalize(Matrix3.multiplyByVector(textureMatrix, tangent, tangent), tangent); - if (vertexFormat.bitangent) { - bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent); - } - } - } + return freezeObject(Visibility); +}); - 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; - } +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'; - if ((top && !perPositionHeight) || wall) { - normals[attrIndex] = normal.x; - normals[attrIndex1] = normal.y; - normals[attrIndex2] = normal.z; - } - } + /** + * 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) { + + this._occluderPosition = Cartesian3.clone(occluderBoundingSphere.center); + this._occluderRadius = occluderBoundingSphere.radius; - if (shadowVolume) { - if (wall) { - normal = ellipsoid.geodeticSurfaceNormal(position, normal); - } - extrudeNormals[attrIndex + bottomOffset] = -normal.x; - extrudeNormals[attrIndex1 + bottomOffset] = -normal.y; - extrudeNormals[attrIndex2 + bottomOffset] = -normal.z; - } + this._horizonDistance = 0.0; + this._horizonPlaneNormal = undefined; + this._horizonPlanePosition = undefined; + this._cameraPosition = undefined; - 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; - } + // cameraPosition fills in the above values + this.cameraPosition = cameraPosition; + } - 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; - } - } - } + var scratchCartesian3 = new Cartesian3(); - if (vertexFormat.bitangent) { - if (bottom) { - bitangents[attrIndex + bottomOffset] = bitangent.x; - bitangents[attrIndex1 + bottomOffset] = bitangent.y; - bitangents[attrIndex2 + bottomOffset] = bitangent.z; - } - if (top) { - if (perPositionHeight) { - bitangents[attrIndex] = scratchPerPosBitangent.x; - bitangents[attrIndex1] = scratchPerPosBitangent.y; - bitangents[attrIndex2] = scratchPerPosBitangent.z; - } else { - bitangents[attrIndex] = bitangent.x; - bitangents[attrIndex1] = bitangent.y; - bitangents[attrIndex2] = bitangent.z; - } - } - } - attrIndex += 3; - } + defineProperties(Occluder.prototype, { + /** + * The position of the occluder. + * @memberof Occluder.prototype + * @type {Cartesian3} + */ + position: { + get: function() { + return this._occluderPosition; } + }, - if (vertexFormat.st) { - geometry.attributes.st = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : textureCoordinates - }); + /** + * The radius of the occluder. + * @memberof Occluder.prototype + * @type {Number} + */ + radius: { + get: function() { + return this._occluderRadius; } + }, - if (vertexFormat.normal) { - geometry.attributes.normal = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : normals - }); - } + /** + * The position of the camera. + * @memberof Occluder.prototype + * @type {Cartesian3} + */ + cameraPosition: { + set: function(cameraPosition) { + + cameraPosition = Cartesian3.clone(cameraPosition, this._cameraPosition); - if (vertexFormat.tangent) { - geometry.attributes.tangent = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : tangents - }); - } + var cameraToOccluderVec = Cartesian3.subtract(this._occluderPosition, cameraPosition, scratchCartesian3); + var invCameraToOccluderDistance = Cartesian3.magnitudeSquared(cameraToOccluderVec); + var occluderRadiusSqrd = this._occluderRadius * this._occluderRadius; - if (vertexFormat.bitangent) { - geometry.attributes.bitangent = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : bitangents - }); - } + 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; + } - if (shadowVolume) { - geometry.attributes.extrudeDirection = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : extrudeNormals - }); + this._horizonDistance = horizonDistance; + this._horizonPlaneNormal = horizonPlaneNormal; + this._horizonPlanePosition = horizonPlanePosition; + this._cameraPosition = cameraPosition; } } - 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; + /** + * 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(result)) { + return new Occluder(occluderBoundingSphere, cameraPosition); + } - for (i = 0; i < ilength; i += 3) { - var i0 = newIndices[i] + length; - var i1 = newIndices[i + 1] + length; - var i2 = newIndices[i + 2] + length; + Cartesian3.clone(occluderBoundingSphere.center, result._occluderPosition); + result._occluderRadius = occluderBoundingSphere.radius; + result.cameraPosition = cameraPosition; - newIndices[i + ilength] = i2; - newIndices[i + 1 + ilength] = i1; - newIndices[i + 2 + ilength] = i0; - } + return result; + }; - 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]; - } + var tempVecScratch = new Cartesian3(); - topGeo.indices = newIndices; + /** + * 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); } - - 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(); } + return false; + }; - var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight); - geos.walls.push(new GeometryInstance({ - geometry : wallGeo - })); + var occludeePositionScratch = new Cartesian3(); - var holes = hierarchy.holes; - for (i = 0; i < holes.length; i++) { - var hole = holes[i]; + /** + * 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; - tangentPlane = EllipsoidTangentPlane.fromPoints(hole, ellipsoid); - positions2D = tangentPlane.projectPointsOntoPlane(hole, createGeometryFromPositionsExtrudedPositions); + 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; + } - windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); - if (windingOrder === WindingOrder.COUNTER_CLOCKWISE) { - hole = hole.slice().reverse(); + // 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; } - wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity); - geos.walls.push(new GeometryInstance({ - geometry : wallGeo - })); + // The occludee completely encompasses the occluder + return true; } - return geos; - } + return false; + }; + var tempScratch = new Cartesian3(); /** - * 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. + * Determine to what extent an occludee is visible (not visible, partially visible, or fully visible). * - * @see PolygonGeometry#createGeometry - * @see PolygonGeometry#fromPositions + * @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. * - * @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); + * 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 * - * // 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); + * @see Occluder#isVisible */ - function PolygonGeometry(options) { + Occluder.prototype.computeVisibility = function(occludeeBS) { - 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 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 (!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); - } + if (occludeeRadius > this._occluderRadius) { + return Visibility.FULL; } - 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._shadowVolume = defaultValue(options.shadowVolume, false); - this._workerName = 'createPolygonGeometry'; + 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; + } - var positions = polygonHierarchy.positions; - if (!defined(positions) || positions.length < 3) { - this._rectangle = new Rectangle(); - } else { - this._rectangle = Rectangle.fromCartesianArray(positions, ellipsoid); - } + // 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; + } - /** - * 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 + 10; - } + //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(); /** - * A description of a polygon from an array of positions. Polygon geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}. + * 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 {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 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. - * @returns {PolygonGeometry} + * @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 - * // 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 + * 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); */ - PolygonGeometry.fromPositions = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + Occluder.computeOccludeePoint = function(occluderBoundingSphere, occludeePosition, positions) { + + var occludeePos = Cartesian3.clone(occludeePosition); + var occluderPosition = Cartesian3.clone(occluderBoundingSphere.center); + var occluderRadius = occluderBoundingSphere.radius; + var numPositions = positions.length; - 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); + // 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 = []; /** - * 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. + * Computes a point that can be used as the occludee position to the visibility functions from a rectangle. * - * @returns {Number[]} The array that was packed into + * @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. */ - PolygonGeometry.pack = function(value, array, startingIndex) { + Occluder.computeOccludeePointFromRectangle = function(rectangle, ellipsoid) { - 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; + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + var positions = Rectangle.subsample(rectangle, ellipsoid, 0.0, computeOccludeePointFromRectangleScratch); + var bs = BoundingSphere.fromPoints(positions); - 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._shadowVolume ? 1.0 : 0.0; - array[startingIndex] = value.packedLength; + // 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 array; + return undefined; }; - var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE); - var scratchVertexFormat = new VertexFormat(); - var scratchRectangle = new Rectangle(); + 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); + }; - //Only used to avoid inaability to default construct. - var dummyOptions = { - polygonHierarchy : {} + 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; }; - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); + 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; - var polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex); - startingIndex = polygonHierarchy.startingIndex; - delete polygonHierarchy.startingIndex; + //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; + } - var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); - startingIndex += Ellipsoid.packedLength; + //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)); - var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); - startingIndex += VertexFormat.packedLength; + //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); - var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle); - startingIndex += Rectangle.packedLength; + //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; + }; - 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 shadowVolume = array[startingIndex++] === 1.0; - var packedLength = array[startingIndex]; + return Occluder; +}); - if (!defined(result)) { - result = new PolygonGeometry(dummyOptions); - } +define('Core/Packable',[ + './DeveloperError' + ], function( + DeveloperError) { + 'use strict'; - 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._shadowVolume = shadowVolume; - result.packedLength = packedLength; - return result; + /** + * 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; +}); + +define('Core/PackableForInterpolation',[ + './DeveloperError' + ], function( + DeveloperError) { + 'use strict'; + /** - * Computes the geometric representation of a polygon, including its vertices, indices, and a bounding sphere. + * 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. * - * @param {PolygonGeometry} polygonGeometry A description of the polygon. - * @returns {Geometry|undefined} The computed vertices and indices. + * @exports PackableForInterpolation + * + * @see Packable */ - 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 PackableForInterpolation = { + /** + * The number of elements used to store the object into an array in its interpolatable form. + * @type {Number} + */ + packedInterpolationLength : undefined, - var outerPositions = polygonHierarchy.positions; - if (outerPositions.length < 3) { - return; - } + /** + * 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, - var tangentPlane = EllipsoidTangentPlane.fromPoints(outerPositions, ellipsoid); + /** + * 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 + }; - var results = PolygonGeometryLibrary.polygonsFromHierarchy(polygonHierarchy, perPositionHeight, tangentPlane, ellipsoid); - var hierarchy = results.hierarchy; - var polygons = results.polygons; + return PackableForInterpolation; +}); - if (hierarchy.length === 0) { - return; - } +/* + This library rewrites the Canvas2D "measureText" function + so that it returns a more complete metrics object. - outerPositions = hierarchy[0].outerRing; - var boundingRectangle = computeBoundingRectangle(tangentPlane, outerPositions, stRotation, scratchBoundingRectangle); +** ----------------------------------------------------------------------------- - var geometry; - var geometries = []; + CHANGELOG: - var options = { - perPositionHeight: perPositionHeight, - vertexFormat: vertexFormat, - geometry: undefined, - tangentPlane: tangentPlane, - boundingRectangle: boundingRectangle, - ellipsoid: ellipsoid, - stRotation: stRotation, - bottom: false, - top: true, - wall: false - }; + 2012-01-21 - Whitespace handling added by Joe Turner + (https://github.com/oampo) - var i; +** ----------------------------------------------------------------------------- +*/ +/** + @license + fontmetrics.js - https://github.com/Pomax/fontmetrics.js - if (extrude) { - options.top = closeTop; - options.bottom = closeBottom; - options.shadowVolume = polygonGeometry._shadowVolume; - for (i = 0; i < polygons.length; i++) { - geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat); + Copyright (C) 2011 by Mike "Pomax" Kamermans - 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); - } + 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: - 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); - } - } + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. - 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); + 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. +**/ +define('ThirdParty/measureText',[],function() { + /*jshint strict:false*/ +/* + var NAME = "FontMetrics Library" + var VERSION = "1-2012.0121.1300"; - var attributes = geometry.attributes; - var boundingSphere = BoundingSphere.fromVertices(attributes.position.values); + // 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."); + } - if (!vertexFormat.position) { - delete attributes.position; - } + // 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)'; - return new Geometry({ - attributes : attributes, - indices : geometry.indices, - primitiveType : geometry.primitiveType, - boundingSphere : boundingSphere - }); - }; + ctx.beginPath(); + ctx.moveTo(xstart,0); + ctx.lineTo(xstart,h); + ctx.closePath(); + ctx.stroke(); - /** - * @private - */ - PolygonGeometry.createShadowVolume = function(polygonGeometry, minHeightFunc, maxHeightFunc) { - var granularity = polygonGeometry._granularity; - var ellipsoid = polygonGeometry._ellipsoid; + ctx.beginPath(); + ctx.moveTo(xstart+metrics.bounds.maxx,0); + ctx.lineTo(xstart+metrics.bounds.maxx,h); + ctx.closePath(); + ctx.stroke(); - var minHeight = minHeightFunc(granularity, ellipsoid); - var maxHeight = maxHeightFunc(granularity, ellipsoid); + ctx.beginPath(); + ctx.moveTo(0,h/2-metrics.ascent); + ctx.lineTo(w,h/2-metrics.ascent); + ctx.closePath(); + ctx.stroke(); - return new PolygonGeometry({ - polygonHierarchy : polygonGeometry._polygonHierarchy, - ellipsoid : ellipsoid, - stRotation : polygonGeometry._stRotation, - granularity : granularity, - perPositionHeight : false, - extrudedHeight : minHeight, - height : maxHeight, - vertexFormat : VertexFormat.POSITION_ONLY, - shadowVolume: true - }); - }; + 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; - defineProperties(PolygonGeometry.prototype, { - /** - * @private - */ - rectangle : { - get : function() { - return this._rectangle; - } + // 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); } - }); - return PolygonGeometry; -}); + if (fill) { + ctx.fillStyle = "black"; + ctx.fillText(textstring, padding / 2, baseline); + } -/*global define*/ -define('Core/PolygonHierarchy',[ - './defined' - ], function( - defined) { - 'use strict'; + var pixelData = ctx.getImageData(0, 0, w, h).data; - /** - * 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 : []; + // 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; - /** - * An array of polygon hierarchies defining holes in the polygon. - * @type {PolygonHierarchy[]} - */ - this.holes = defined(holes) ? holes : []; + // 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); } - return PolygonHierarchy; + // 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/PolygonOutlineGeometry',[ - './arrayRemoveDuplicates', - './BoundingSphere', - './Cartesian3', - './Check', - './ComponentDatatype', +define('Core/writeTextToCanvas',[ + '../ThirdParty/measureText', + './Color', './defaultValue', './defined', - './DeveloperError', - './Ellipsoid', - './EllipsoidTangentPlane', - './Geometry', - './GeometryAttribute', - './GeometryAttributes', - './GeometryInstance', - './GeometryPipeline', - './IndexDatatype', - './Math', - './PolygonGeometryLibrary', - './PolygonPipeline', - './PrimitiveType', - './Queue', - './WindingOrder' + './DeveloperError' ], function( - arrayRemoveDuplicates, - BoundingSphere, - Cartesian3, - Check, - ComponentDatatype, + measureText, + Color, defaultValue, defined, - DeveloperError, - Ellipsoid, - EllipsoidTangentPlane, - Geometry, - GeometryAttribute, - GeometryAttributes, - GeometryInstance, - GeometryPipeline, - IndexDatatype, - CesiumMath, - PolygonGeometryLibrary, - PolygonPipeline, - PrimitiveType, - Queue, - WindingOrder) { + DeveloperError) { '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 imageSmoothingEnabledName; - var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); - if (originalWindingOrder === WindingOrder.CLOCKWISE) { - positions2D.reverse(); - positions = positions.slice().reverse(); + /** + * 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 (text === '') { + return undefined; } - var subdividedPositions; - var i; + 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 length = positions.length; - var index = 0; + var canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + canvas.style.font = font; - 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; + 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'; } } - 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; + context2D.font = font; + context2D.lineJoin = 'round'; + context2D.lineWidth = strokeWidth; + context2D[imageSmoothingEnabledName] = false; - return new GeometryInstance({ - geometry : new Geometry({ - attributes : new GeometryAttributes({ - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : subdividedPositions - }) - }), - indices : indices, - primitiveType : PrimitiveType.LINES - }) - }); - } + // textBaseline needs to be set before the measureText call. It won't work otherwise. + // It's magic. + context2D.textBaseline = defaultValue(options.textBaseline, 'bottom'); - function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight) { - var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid); - var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions); + // 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 originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); - if (originalWindingOrder === WindingOrder.CLOCKWISE) { - positions2D.reverse(); - positions = positions.slice().reverse(); - } + var dimensions = measureText(context2D, text, stroke, fill); + canvas.dimensions = dimensions; - var subdividedPositions; - var i; + document.body.removeChild(canvas); + canvas.style.visibility = ''; - var length = positions.length; - var corners = new Array(length); - var index = 0; + //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; - if (!perPositionHeight) { - var numVertices = 0; - for (i = 0; i < length; i++) { - numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); - } + //Expand the width to include the starting position. + var width = Math.ceil(dimensions.width) + x + doublePadding; - 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]; + //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; - subdividedPositions[index++] = p0.x; - subdividedPositions[index++] = p0.y; - subdividedPositions[index++] = p0.z; - subdividedPositions[index++] = p1.x; - subdividedPositions[index++] = p1.y; - subdividedPositions[index++] = p1.z; - } - } + canvas.width = width; + canvas.height = height; - length = subdividedPositions.length / (3 * 2); - var cornersLength = corners.length; + // Properties must be explicitly set again after changing width and height + context2D.font = font; + context2D.lineJoin = 'round'; + context2D.lineWidth = strokeWidth; + context2D[imageSmoothingEnabledName] = false; - var indicesSize = ((length * 2) + cornersLength) * 2; - var indices = IndexDatatype.createTypedArray(length, indicesSize); + // Draw background + if (backgroundColor !== Color.TRANSPARENT) { + context2D.fillStyle = backgroundColor.toCssColorString(); + context2D.fillRect(0, 0, canvas.width, canvas.height); + } - 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; + if (stroke) { + var strokeColor = defaultValue(options.strokeColor, Color.BLACK); + context2D.strokeStyle = strokeColor.toCssColorString(); + context2D.strokeText(text, x + padding, y); } - for (i = 0; i < cornersLength; i++) { - var corner = corners[i]; - indices[index++] = corner; - indices[index++] = corner + length; + if (fill) { + var fillColor = defaultValue(options.fillColor, Color.WHITE); + context2D.fillStyle = fillColor.toCssColorString(); + context2D.fillText(text, x + padding, y); } - return new GeometryInstance({ - geometry : new Geometry({ - attributes : new GeometryAttributes({ - position : new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : subdividedPositions - }) - }), - indices : indices, - primitiveType : PrimitiveType.LINES - }) - }); + return canvas; } + return writeTextToCanvas; +}); + +define('Core/PinBuilder',[ + './buildModuleUrl', + './Color', + './defined', + './DeveloperError', + './loadImage', + './writeTextToCanvas' + ], function( + buildModuleUrl, + Color, + defined, + DeveloperError, + loadImage, + writeTextToCanvas) { + 'use strict'; + /** - * A description of the outline of a polygon on the ellipsoid. The polygon is defined by a polygon hierarchy. + * 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 PolygonOutlineGeometry + * @alias PinBuilder * @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); + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Map%20Pins.html|Cesium Sandcastle PinBuilder Demo} */ - function PolygonOutlineGeometry(options) { - - 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; + function PinBuilder() { + this._cache = {}; } /** - * 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. + * Creates an empty pin of the specified color and size. * - * @returns {Number[]} The array that was packed into + * @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. */ - PolygonOutlineGeometry.pack = function(value, array, startingIndex) { - - 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; + PinBuilder.prototype.fromColor = function(color, size) { + return createPin(undefined, undefined, color, size, this._cache); + }; - return array; + /** + * 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) { + return createPin(url, undefined, color, size, this._cache); }; - var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE); - var dummyOptions = { - polygonHierarchy : {} + /** + * 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) { + return createPin(buildModuleUrl('Assets/Textures/maki/' + encodeURIComponent(id) + '.png'), undefined, color, size, this._cache); }; /** - * Retrieves an instance from a packed array. + * 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 {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. + * @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. */ - PolygonOutlineGeometry.unpack = function(array, startingIndex, result) { + PinBuilder.prototype.fromText = function(text, color, size) { - startingIndex = defaultValue(startingIndex, 0); + return createPin(undefined, text, color, size, this._cache); + }; - var polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex); - startingIndex = polygonHierarchy.startingIndex; - delete polygonHierarchy.startingIndex; + var colorScratch = new Color(); - var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); - startingIndex += Ellipsoid.packedLength; + //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(); + } - 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++]; + //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 (!defined(result)) { - result = new PolygonOutlineGeometry(dummyOptions); + if (image.width > image.height) { + sizeY = imageSize * (image.height / image.width); + } else if (image.width < image.height) { + sizeX = imageSize * (image.width / image.height); } - 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; + //x and y are the center of the pin box + var x = Math.round((size - sizeX) / 2); + var y = Math.round(((7 / 24) * size) - (sizeY / 2)); - return result; - }; + 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 + 2, sizeY + 2); + + context2D.globalCompositeOperation = 'destination-out'; + context2D.drawImage(image, x, y, sizeX, sizeY); + + context2D.globalCompositeOperation = 'destination-over'; + context2D.fillStyle = Color.WHITE.toCssColorString(); + context2D.fillRect(x - 1, y - 2, sizeX + 2, sizeY + 2); + } + + 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; +}); + +define('Core/pointInsideTriangle',[ + './barycentricCoordinates', + './Cartesian3' + ], function( + barycentricCoordinates, + Cartesian3) { + 'use strict'; + + var coords = new Cartesian3(); /** - * A description of a polygon outline from an array of positions. + * Determines if a point is inside a triangle. * - * @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} + * @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 - * // 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 + * // 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)); */ - PolygonOutlineGeometry.fromPositions = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + function pointInsideTriangle(point, p0, p1, p2) { + barycentricCoordinates(point, p0, p1, p2, coords); + return (coords.x > 0.0) && (coords.y > 0.0) && (coords.z > 0); + } - - 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); - }; + return pointInsideTriangle; +}); + +define('Core/Queue',[ + './defineProperties' + ], function( + defineProperties) { + 'use strict'; /** - * Computes the geometric representation of a polygon outline, including its vertices, indices, and a bounding sphere. + * A queue that can enqueue items at the end, and dequeue items from the front. * - * @param {PolygonOutlineGeometry} polygonGeometry A description of the polygon outline. - * @returns {Geometry|undefined} The computed vertices and indices. + * @alias Queue + * @constructor */ - 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; + function Queue() { + this._array = []; + this._offset = 0; + this._length = 0; + } - // 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; + defineProperties(Queue.prototype, { + /** + * The length of the queue. + * + * @memberof Queue.prototype + * + * @type {Number} + * @readonly + */ + length : { + get : function() { + return this._length; } + } + }); - 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); + /** + * Enqueues the specified item. + * + * @param {Object} item The item to enqueue. + */ + Queue.prototype.enqueue = function(item) { + this._array.push(item); + this._length++; + }; - var numGrandchildren = 0; - if (defined(hole.holes)) { - numGrandchildren = hole.holes.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; + } - for ( var j = 0; j < numGrandchildren; j++) { - queue.enqueue(hole.holes[j]); - } - } + var array = this._array; + var offset = this._offset; + var item = array[offset]; + array[offset] = undefined; - polygons.push(outerRing); + offset++; + if ((offset > 10) && (offset * 2 > array.length)) { + //compact array + this._array = array.slice(offset); + offset = 0; } - if (polygons.length === 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; } - var geometry; - var geometries = []; - var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + return this._array[this._offset]; + }; - 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); - } - } + /** + * 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; + }; - geometry = GeometryPipeline.combineInstances(geometries)[0]; - var boundingSphere = BoundingSphere.fromVertices(geometry.attributes.position.values); + /** + * Remove all items from the queue. + */ + Queue.prototype.clear = function() { + this._array.length = this._offset = this._length = 0; + }; - return new Geometry({ - attributes : geometry.attributes, - indices : geometry.indices, - primitiveType : geometry.primitiveType, - boundingSphere : boundingSphere - }); + /** + * 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); }; - return PolygonOutlineGeometry; + /** + * 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/PolylineGeometry',[ +define('Core/PolygonGeometryLibrary',[ './arrayRemoveDuplicates', - './BoundingSphere', './Cartesian3', - './Color', './ComponentDatatype', './defaultValue', './defined', - './DeveloperError', './Ellipsoid', './Geometry', './GeometryAttribute', './GeometryAttributes', - './GeometryType', + './GeometryPipeline', './IndexDatatype', './Math', - './PolylinePipeline', + './PolygonPipeline', './PrimitiveType', - './VertexFormat' + './Queue', + './WindingOrder' ], function( arrayRemoveDuplicates, - BoundingSphere, Cartesian3, - Color, ComponentDatatype, defaultValue, defined, - DeveloperError, Ellipsoid, Geometry, GeometryAttribute, GeometryAttributes, - GeometryType, + GeometryPipeline, IndexDatatype, CesiumMath, - PolylinePipeline, + PolygonPipeline, PrimitiveType, - VertexFormat) { + Queue, + WindingOrder) { 'use strict'; - var scratchInterpolateColorsArray = []; + /** + * @private + */ + var PolygonGeometryLibrary = {}; - function interpolateColors(p0, p1, color0, color1, numPoints) { - var colors = scratchInterpolateColorsArray; - colors.length = numPoints; - var i; + PolygonGeometryLibrary.computeHierarchyPackedLength = function(polygonHierarchy) { + var numComponents = 0; + var stack = [polygonHierarchy]; + while (stack.length > 0) { + var hierarchy = stack.pop(); + if (!defined(hierarchy)) { + continue; + } - var r0 = color0.red; - var g0 = color0.green; - var b0 = color0.blue; - var a0 = color0.alpha; + numComponents += 2; - var r1 = color1.red; - var g1 = color1.green; - var b1 = color1.blue; - var a1 = color1.alpha; + var positions = hierarchy.positions; + var holes = hierarchy.holes; - if (Color.equals(color0, color1)) { - for (i = 0; i < numPoints; i++) { - colors[i] = Color.clone(color0); + if (defined(positions)) { + numComponents += positions.length * Cartesian3.packedLength; } - 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); + if (defined(holes)) { + var length = holes.length; + for (var i = 0; i < length; ++i) { + stack.push(holes[i]); + } + } } - 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); - - - 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; + return numComponents; + }; - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 4; - } + PolygonGeometryLibrary.packPolygonHierarchy = function(polygonHierarchy, array, startingIndex) { + var stack = [polygonHierarchy]; + while (stack.length > 0) { + var hierarchy = stack.pop(); + if (!defined(hierarchy)) { + continue; + } - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); + var positions = hierarchy.positions; + var holes = hierarchy.holes; - var i; + array[startingIndex++] = defined(positions) ? positions.length : 0; + array[startingIndex++] = defined(holes) ? holes.length : 0; - var positions = value._positions; - var length = positions.length; - array[startingIndex++] = length; + if (defined(positions)) { + var positionsLength = positions.length; + for (var i = 0; i < positionsLength; ++i, startingIndex += 3) { + Cartesian3.pack(positions[i], array, startingIndex); + } + } - for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { - Cartesian3.pack(positions[i], array, startingIndex); + if (defined(holes)) { + var holesLength = holes.length; + for (var j = 0; j < holesLength; ++j) { + stack.push(holes[j]); + } + } } - var colors = value._colors; - length = defined(colors) ? colors.length : 0.0; - array[startingIndex++] = length; + return startingIndex; + }; - for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { - Color.pack(colors[i], array, startingIndex); - } + PolygonGeometryLibrary.unpackPolygonHierarchy = function(array, startingIndex) { + var positionsLength = array[startingIndex++]; + var holesLength = array[startingIndex++]; - Ellipsoid.pack(value._ellipsoid, array, startingIndex); - startingIndex += Ellipsoid.packedLength; + var positions = new Array(positionsLength); + var holes = holesLength > 0 ? new Array(holesLength) : undefined; - VertexFormat.pack(value._vertexFormat, array, startingIndex); - startingIndex += VertexFormat.packedLength; + for (var i = 0; i < positionsLength; ++i, startingIndex += Cartesian3.packedLength) { + positions[i] = Cartesian3.unpack(array, startingIndex); + } - array[startingIndex++] = value._width; - array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0; - array[startingIndex++] = value._followSurface ? 1.0 : 0.0; - array[startingIndex] = value._granularity; + for (var j = 0; j < holesLength; ++j) { + holes[j] = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex); + startingIndex = holes[j].startingIndex; + delete holes[j].startingIndex; + } - return array; + return { + positions : positions, + holes : holes, + startingIndex : startingIndex + }; }; - 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 + 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); }; - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); + PolygonGeometryLibrary.subdivideLine = function(p0, p1, minDistance, result) { + var numVertices = PolygonGeometryLibrary.subdivideLineCount(p0, p1, minDistance); + var length = Cartesian3.distance(p0, p1); + var distanceBetweenVertices = length / numVertices; - var i; + if (!defined(result)) { + result = []; + } - var length = array[startingIndex++]; - var positions = new Array(length); + var positions = result; + positions.length = numVertices * 3; - for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { - positions[i] = Cartesian3.unpack(array, startingIndex); + 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]; } - length = array[startingIndex++]; - var colors = length > 0 ? new Array(length) : undefined; + return positions; + }; - for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { - colors[i] = Color.unpack(array, startingIndex); - } + var scaleToGeodeticHeightN1 = new Cartesian3(); + var scaleToGeodeticHeightN2 = new Cartesian3(); + var scaleToGeodeticHeightP1 = new Cartesian3(); + var scaleToGeodeticHeightP2 = new Cartesian3(); - var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); - startingIndex += Ellipsoid.packedLength; + PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid, perPositionHeight) { + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); - startingIndex += VertexFormat.packedLength; + var n1 = scaleToGeodeticHeightN1; + var n2 = scaleToGeodeticHeightN2; + var p = scaleToGeodeticHeightP1; + var p2 = scaleToGeodeticHeightP2; - var width = array[startingIndex++]; - var colorsPerVertex = array[startingIndex++] === 1.0; - var followSurface = array[startingIndex++] === 1.0; - var granularity = array[startingIndex]; + if (defined(geometry) && defined(geometry.attributes) && defined(geometry.attributes.position)) { + var positions = geometry.attributes.position.values; + var length = positions.length / 2; - 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); - } + for ( var i = 0; i < length; i += 3) { + Cartesian3.fromArray(positions, i, p); - 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; + 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; - return result; + 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; }; - var scratchCartesian3 = new Cartesian3(); - var scratchPosition = new Cartesian3(); - var scratchPrevPosition = new Cartesian3(); - var scratchNextPosition = new Cartesian3(); + 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 = []; - /** - * 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 queue = new Queue(); + queue.enqueue(polygonHierarchy); - var i; - var j; - var k; + while (queue.length !== 0) { + var outerNode = queue.dequeue(); + var outerRing = outerNode.positions; + var holes = outerNode.holes; - var positions = arrayRemoveDuplicates(polylineGeometry._positions, Cartesian3.equalsEpsilon); - var positionsLength = positions.length; + outerRing = arrayRemoveDuplicates(outerRing, Cartesian3.equalsEpsilon, true); + if (outerRing.length < 3) { + continue; + } - // 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; - } + var positions2D = tangentPlane.projectPointsOntoPlane(outerRing); + var holeIndices = []; - if (followSurface) { - var heights = PolylinePipeline.extractHeights(positions, ellipsoid); - var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (originalWindingOrder === WindingOrder.CLOCKWISE) { + positions2D.reverse(); + outerRing = outerRing.slice().reverse(); + } - if (defined(colors)) { - var colorLength = 1; - for (i = 0; i < positionsLength - 1; ++i) { - colorLength += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance); + 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 newColors = new Array(colorLength); - var newColorIndex = 0; + var holePositions2D = tangentPlane.projectPointsOntoPlane(holePositions); - for (i = 0; i < positionsLength - 1; ++i) { - var p0 = positions[i]; - var p1 = positions[i+1]; - var c0 = colors[i]; + originalWindingOrder = PolygonPipeline.computeWindingOrder2D(holePositions2D); + if (originalWindingOrder === WindingOrder.CLOCKWISE) { + holePositions2D.reverse(); + holePositions = holePositions.slice().reverse(); + } - 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); - } - } + 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; } - newColors[newColorIndex] = Color.clone(colors[colors.length-1]); - colors = newColors; + for (j = 0; j < numGrandchildren; j++) { + queue.enqueue(hole.holes[j]); + } + } - scratchInterpolateColorsArray.length = 0; + 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]); + } + } } - positions = PolylinePipeline.generateCartesianArc({ - positions: positions, - minDistance: minDistance, - ellipsoid: ellipsoid, - height: heights + hierarchy.push({ + outerRing : outerRing, + holes : polygonHoles + }); + polygons.push({ + positions : positions, + positions2D : positions2D, + holes : holeIndices }); } - 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; + return { + hierarchy : hierarchy, + polygons : polygons + }; + }; - var positionIndex = 0; - var expandAndWidthIndex = 0; - var stIndex = 0; - var colorIndex = 0; - var position; + PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat) { + var indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes); - 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]; - } + /* If polygon is completely unrenderable, just use the first three vertices */ + if (indices.length < 3) { + indices = [0, 1, 2]; + } - Cartesian3.clone(position, scratchPrevPosition); - Cartesian3.clone(positions[j], scratchPosition); + var positions = polygon.positions; - 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]; + 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 + }); - 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]; - } + if (vertexFormat.normal) { + return GeometryPipeline.computeNormal(geometry); } - var startK = j === 0 ? 2 : 0; - var endK = j === positionsLength - 1 ? 2 : 4; + return geometry; + } - for (k = startK; k < endK; ++k) { - Cartesian3.pack(scratchPosition, finalPositions, positionIndex); - Cartesian3.pack(scratchPrevPosition, prevPositions, positionIndex); - Cartesian3.pack(scratchNextPosition, nextPositions, positionIndex); - positionIndex += 3; + return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity); + }; - var direction = (k - 2 < 0) ? -1.0 : 1.0; - expandAndWidth[expandAndWidthIndex++] = 2 * (k % 2) - 1; // expand direction - expandAndWidth[expandAndWidthIndex++] = direction * width; + var computeWallIndicesSubdivided = []; + var p1Scratch = new Cartesian3(); + var p2Scratch = new Cartesian3(); - if (vertexFormat.st) { - st[stIndex++] = j / (positionsLength - 1); - st[stIndex++] = Math.max(expandAndWidth[expandAndWidthIndex - 2], 0.0); - } + PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) { + var edgePositions; + var topEdgeLength; + var i; + var p1; + var p2; - if (defined(finalColors)) { - var color = (k < 2) ? color0 : color1; + var length = positions.length; + var index = 0; - finalColors[colorIndex++] = Color.floatToByte(color.red); - finalColors[colorIndex++] = Color.floatToByte(color.green); - finalColors[colorIndex++] = Color.floatToByte(color.blue); - finalColors[colorIndex++] = Color.floatToByte(color.alpha); - } - } - } + if (!perPositionHeight) { + var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); - var attributes = new GeometryAttributes(); + var numVertices = 0; + for (i = 0; i < length; i++) { + numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance); + } - attributes.position = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : finalPositions - }); + topEdgeLength = (numVertices + length) * 3; + edgePositions = new Array(topEdgeLength * 2); + for (i = 0; i < length; i++) { + p1 = positions[i]; + p2 = positions[(i + 1) % length]; - attributes.prevPosition = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : prevPositions - }); + 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]; + } - attributes.nextPosition = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : nextPositions - }); + edgePositions[index] = p2.x; + edgePositions[index + topEdgeLength] = p2.x; + ++index; - attributes.expandAndWidth = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : expandAndWidth - }); + edgePositions[index] = p2.y; + edgePositions[index + topEdgeLength] = p2.y; + ++index; - if (vertexFormat.st) { - attributes.st = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : st - }); + 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; + } } - if (defined(finalColors)) { - attributes.color = new GeometryAttribute({ - componentDatatype : ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 4, - values : finalColors, - normalize : true - }); - } + length = edgePositions.length; + var indices = IndexDatatype.createTypedArray(length / 3, length - positions.length * 6); + var edgeIndex = 0; + length /= 6; - 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; + for (i = 0; i < length; i++) { + var UL = i; + var UR = UL + 1; + var LL = UL + length; + var LR = LL + 1; - indices[indicesIndex++] = index + 1; - indices[indicesIndex++] = index + 2; - indices[indicesIndex++] = index + 3; + p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch); + p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch); + if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON14)) { + continue; + } - index += 4; + 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 : BoundingSphere.fromPoints(positions), - geometryType : GeometryType.POLYLINES + attributes : new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : edgePositions + }) + }), + indices : indices, + primitiveType : PrimitiveType.TRIANGLES }); }; - return PolylineGeometry; + return PolygonGeometryLibrary; }); -/*global define*/ -define('Core/PolylineVolumeGeometry',[ - './arrayRemoveDuplicates', +define('Core/PolygonGeometry',[ './BoundingRectangle', './BoundingSphere', './Cartesian2', './Cartesian3', + './Cartographic', + './Check', './ComponentDatatype', - './CornerType', './defaultValue', './defined', + './defineProperties', './DeveloperError', './Ellipsoid', + './EllipsoidTangentPlane', './Geometry', './GeometryAttribute', - './GeometryAttributes', + './GeometryInstance', './GeometryPipeline', './IndexDatatype', './Math', - './oneTimeWarning', + './Matrix3', + './PolygonGeometryLibrary', './PolygonPipeline', - './PolylineVolumeGeometryLibrary', - './PrimitiveType', + './Quaternion', + './Rectangle', './VertexFormat', './WindingOrder' ], function( - arrayRemoveDuplicates, BoundingRectangle, BoundingSphere, Cartesian2, Cartesian3, + Cartographic, + Check, ComponentDatatype, - CornerType, defaultValue, defined, + defineProperties, DeveloperError, Ellipsoid, + EllipsoidTangentPlane, Geometry, GeometryAttribute, - GeometryAttributes, + GeometryInstance, GeometryPipeline, IndexDatatype, CesiumMath, - oneTimeWarning, + Matrix3, + PolygonGeometryLibrary, PolygonPipeline, - PolylineVolumeGeometryLibrary, - PrimitiveType, + Quaternion, + Rectangle, 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 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); + } } - 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; + result.x = minX; + result.y = minY; + result.width = maxX - minX; + result.height = maxY - minY; + return result; + } - indices[index++] = ul; - indices[index++] = ll; - indices[index++] = ur; - indices[index++] = ur; - indices[index++] = ll; - indices[index++] = lr; + 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 scratchBitangent = new Cartesian3(); + var p1Scratch = new Cartesian3(); + var p2Scratch = new Cartesian3(); + var scratchPerPosNormal = new Cartesian3(); + var scratchPerPosTangent = new Cartesian3(); + var scratchPerPosBitangent = 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; + var shadowVolume = options.shadowVolume; + if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent || shadowVolume) { + // 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); + } } - ll = shapeLength * 2 - 2 + i * shapeLength * 2; - ul = ll + 1; - ur = ul + offset; - lr = ll + offset; + var tangents = vertexFormat.tangent ? new Float32Array(length) : undefined; + var bitangents = vertexFormat.bitangent ? new Float32Array(length) : undefined; + var extrudeNormals = shadowVolume ? new Float32Array(length) : undefined; - indices[index++] = ul; - indices[index++] = ll; - indices[index++] = ur; - indices[index++] = ur; - indices[index++] = ll; - indices[index++] = lr; - } + var textureCoordIndex = 0; + var attrIndex = 0; - if (vertexFormat.st || vertexFormat.tangent || vertexFormat.bitangent) { // st required for tangent/bitangent 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; + var normal = scratchNormal; + var tangent = scratchTangent; + var bitangent = scratchBitangent; + 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.bitangent || shadowVolume) { + 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.bitangent) { + bitangent = ellipsoid.geodeticSurfaceNormal(position, bitangent); + if (vertexFormat.tangent) { + tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent); + } + } + } else { + normal = ellipsoid.geodeticSurfaceNormal(position, normal); + if (vertexFormat.tangent || vertexFormat.bitangent) { + 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.bitangent) { + scratchPerPosBitangent = Cartesian3.normalize(Cartesian3.cross(scratchPerPosNormal, scratchPerPosTangent, scratchPerPosBitangent), scratchPerPosBitangent); + } + } + + tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); + tangent = Cartesian3.normalize(Matrix3.multiplyByVector(textureMatrix, tangent, tangent), tangent); + if (vertexFormat.bitangent) { + bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent); + } + } + } + + 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 (shadowVolume) { + if (wall) { + normal = ellipsoid.geodeticSurfaceNormal(position, normal); + } + extrudeNormals[attrIndex + bottomOffset] = -normal.x; + extrudeNormals[attrIndex1 + bottomOffset] = -normal.y; + extrudeNormals[attrIndex2 + bottomOffset] = -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.bitangent) { + if (bottom) { + bitangents[attrIndex + bottomOffset] = bitangent.x; + bitangents[attrIndex1 + bottomOffset] = bitangent.y; + bitangents[attrIndex2 + bottomOffset] = bitangent.z; + } + if (top) { + if (perPositionHeight) { + bitangents[attrIndex] = scratchPerPosBitangent.x; + bitangents[attrIndex1] = scratchPerPosBitangent.y; + bitangents[attrIndex2] = scratchPerPosBitangent.z; + } else { + bitangents[attrIndex] = bitangent.x; + bitangents[attrIndex1] = bitangent.y; + bitangents[attrIndex2] = bitangent.z; + } + } + } + attrIndex += 3; } - 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; + + if (vertexFormat.st) { + geometry.attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : textureCoordinates + }); } - for (j = 0; j < shapeLength; j++) { - s = (length - 1) * lengthSt; - t = heightSt * (shape[j].y + heightOffset); - st[stindex++] = s; - st[stindex++] = t; + + if (vertexFormat.normal) { + geometry.attributes.normal = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : normals + }); } - attributes.st = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2, - values : new Float32Array(st) - }); + if (vertexFormat.tangent) { + geometry.attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : tangents + }); + } + + if (vertexFormat.bitangent) { + geometry.attributes.bitangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : bitangents + }); + } + + if (shadowVolume) { + geometry.attributes.extrudeDirection = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : extrudeNormals + }); + } } + return geometry; + } - 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; + 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 + }); - 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 - }); + var outerRing = hierarchy.outerRing; + var tangentPlane = EllipsoidTangentPlane.fromPoints(outerRing, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(outerRing, createGeometryFromPositionsExtrudedPositions); - if (vertexFormat.normal) { - geometry = GeometryPipeline.computeNormal(geometry); + var windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D); + if (windingOrder === WindingOrder.CLOCKWISE) { + outerRing = outerRing.slice().reverse(); } - if (vertexFormat.tangent || vertexFormat.bitangent) { - try { - geometry = GeometryPipeline.computeTangentAndBitangent(geometry); - } catch (e) { - oneTimeWarning('polyline-volume-tangent-bitangent', 'Unable to compute tangents and bitangents for polyline volume geometry'); - //TODO https://github.com/AnalyticalGraphicsInc/cesium/issues/3609 - } + var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight); + geos.walls.push(new GeometryInstance({ + geometry : wallGeo + })); - if (!vertexFormat.tangent) { - geometry.attributes.tangent = undefined; - } - if (!vertexFormat.bitangent) { - geometry.attributes.bitangent = undefined; - } - if (!vertexFormat.st) { - geometry.attributes.st = undefined; + 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 geometry; + return geos; } /** - * A description of a polyline with a volume (a 2D shape extruded along a polyline). + * 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 PolylineVolumeGeometry + * @alias PolygonGeometry * @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 {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 {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. - * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners. + * @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 PolylineVolumeGeometry#createGeometry + * @see PolygonGeometry#createGeometry + * @see PolygonGeometry#fromPositions * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo} + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon 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; - * } + * // 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); * - * 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) + * // 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 PolylineVolumeGeometry(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var positions = options.polylinePositions; - var shape = options.shapePositions; - + function PolygonGeometry(options) { - 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 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 numComponents = 1 + positions.length * Cartesian3.packedLength; - numComponents += 1 + shape.length * Cartesian2.packedLength; + 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._shadowVolume = defaultValue(options.shadowVolume, false); + 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 = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 2; + this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 10; } + /** + * 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 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. + * @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); + + + 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 {PolylineVolumeGeometry} value The value to pack. + * @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 */ - PolylineVolumeGeometry.pack = function(value, array, startingIndex) { + PolygonGeometry.pack = function(value, array, startingIndex) { 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); - } + startingIndex = PolygonGeometryLibrary.packPolygonHierarchy(value._polygonHierarchy, array, startingIndex); Ellipsoid.pack(value._ellipsoid, array, startingIndex); startingIndex += Ellipsoid.packedLength; @@ -66675,21 +68496,30 @@ define('Core/PolylineVolumeGeometry',[ VertexFormat.pack(value._vertexFormat, array, startingIndex); startingIndex += VertexFormat.packedLength; - array[startingIndex++] = value._cornerType; - array[startingIndex] = value._granularity; + 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._shadowVolume ? 1.0 : 0.0; + array[startingIndex] = value.packedLength; 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 + var scratchRectangle = new Rectangle(); + + //Only used to avoid inaability to default construct. + var dummyOptions = { + polygonHierarchy : {} }; /** @@ -66697,28 +68527,15 @@ define('Core/PolylineVolumeGeometry',[ * * @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. + * @param {PolygonGeometry} [result] The object into which to store the result. */ - PolylineVolumeGeometry.unpack = function(array, startingIndex, result) { + PolygonGeometry.unpack = function(array, startingIndex, result) { 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 polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex); + startingIndex = polygonHierarchy.startingIndex; + delete polygonHierarchy.startingIndex; var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); startingIndex += Ellipsoid.packedLength; @@ -66726,258 +68543,581 @@ define('Core/PolylineVolumeGeometry',[ var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); startingIndex += VertexFormat.packedLength; - var cornerType = array[startingIndex++]; - var granularity = array[startingIndex]; + 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 shadowVolume = array[startingIndex++] === 1.0; + var packedLength = array[startingIndex]; if (!defined(result)) { - scratchOptions.polylinePositions = positions; - scratchOptions.shapePositions = shape; - scratchOptions.cornerType = cornerType; - scratchOptions.granularity = granularity; - return new PolylineVolumeGeometry(scratchOptions); + result = new PolygonGeometry(dummyOptions); } - result._positions = positions; - result._shape = shape; + result._polygonHierarchy = polygonHierarchy; result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid); result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat); - result._cornerType = cornerType; + 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._shadowVolume = shadowVolume; + result.packedLength = packedLength; return result; }; - var brScratch = new BoundingRectangle(); - /** - * Computes the geometric representation of a polyline with a volume, including its vertices, indices, and a bounding sphere. + * Computes the geometric representation of a polygon, including its vertices, indices, and a bounding sphere. * - * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polyline volume. + * @param {PolygonGeometry} polygonGeometry A description of the polygon. * @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; - } + 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; - if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) { - shape2D.reverse(); + var outerPositions = polygonHierarchy.positions; + if (outerPositions.length < 3) { + return; } - 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'; + var tangentPlane = EllipsoidTangentPlane.fromPoints(outerPositions, ellipsoid); - function computeAttributes(positions, shape) { - var attributes = new GeometryAttributes(); - attributes.position = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : positions - }); + var results = PolygonGeometryLibrary.polygonsFromHierarchy(polygonHierarchy, perPositionHeight, tangentPlane, ellipsoid); + var hierarchy = results.hierarchy; + var polygons = results.polygons; - 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; + if (hierarchy.length === 0) { + return; } - 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; + outerPositions = hierarchy[0].outerRing; + var boundingRectangle = computeBoundingRectangle(tangentPlane, outerPositions, stRotation, scratchBoundingRectangle); - 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; + var geometries = []; - var geometry = new Geometry({ - attributes : attributes, - indices : IndexDatatype.createTypedArray(vertexCount, indices), - boundingSphere : BoundingSphere.fromVertices(positions), - primitiveType : PrimitiveType.LINES - }); + var options = { + perPositionHeight: perPositionHeight, + vertexFormat: vertexFormat, + geometry: undefined, + tangentPlane: tangentPlane, + boundingRectangle: boundingRectangle, + ellipsoid: ellipsoid, + stRotation: stRotation, + bottom: false, + top: true, + wall: false + }; - return geometry; - } + var i; - /** - * 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. + if (extrude) { + options.top = closeTop; + options.bottom = closeBottom; + options.shadowVolume = polygonGeometry._shadowVolume; + 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, + shadowVolume: true + }); + }; + + defineProperties(PolygonGeometry.prototype, { + /** + * @private + */ + rectangle : { + get : function() { + return this._rectangle; + } + } + }); + + return PolygonGeometry; +}); + +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 * - * @see PolylineVolumeOutlineGeometry#createGeometry + * @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; +}); + +define('Core/PolygonOutlineGeometry',[ + './arrayRemoveDuplicates', + './BoundingSphere', + './Cartesian3', + './Check', + './ComponentDatatype', + './defaultValue', + './defined', + './DeveloperError', + './Ellipsoid', + './EllipsoidTangentPlane', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './GeometryInstance', + './GeometryPipeline', + './IndexDatatype', + './Math', + './PolygonGeometryLibrary', + './PolygonPipeline', + './PrimitiveType', + './Queue', + './WindingOrder' + ], function( + arrayRemoveDuplicates, + BoundingSphere, + Cartesian3, + Check, + 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 - * 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; - * } + * // 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); * - * var volumeOutline = new Cesium.PolylineVolumeOutlineGeometry({ - * polylinePositions : Cesium.Cartesian3.fromDegreesArray([ - * -72.0, 40.0, - * -70.0, 35.0 - * ]), - * shapePositions : computeCircle(100000.0) + * // 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 PolylineVolumeOutlineGeometry(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var positions = options.polylinePositions; - var shape = options.shapePositions; - + function PolygonOutlineGeometry(options) { - 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 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 numComponents = 1 + positions.length * Cartesian3.packedLength; - numComponents += 1 + shape.length * Cartesian2.packedLength; + 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 = numComponents + Ellipsoid.packedLength + 2; + this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 6; } /** * Stores the provided instance into the provided array. * - * @param {PolylineVolumeOutlineGeometry} value The value to pack. + * @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 */ - PolylineVolumeOutlineGeometry.pack = function(value, array, startingIndex) { + PolygonOutlineGeometry.pack = function(value, array, startingIndex) { 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); - } + startingIndex = PolygonGeometryLibrary.packPolygonHierarchy(value._polygonHierarchy, array, startingIndex); Ellipsoid.pack(value._ellipsoid, array, startingIndex); startingIndex += Ellipsoid.packedLength; - array[startingIndex++] = value._cornerType; - array[startingIndex] = value._granularity; + 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 scratchOptions = { - polylinePositions : undefined, - shapePositions : undefined, - ellipsoid : scratchEllipsoid, - height : undefined, - cornerType : undefined, - granularity : undefined + var dummyOptions = { + polygonHierarchy : {} }; /** @@ -66985,1187 +69125,946 @@ define('Core/PolylineVolumeOutlineGeometry',[ * * @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. + * @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. */ - PolylineVolumeOutlineGeometry.unpack = function(array, startingIndex, result) { + PolygonOutlineGeometry.unpack = function(array, startingIndex, result) { 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 polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex); + startingIndex = polygonHierarchy.startingIndex; + delete polygonHierarchy.startingIndex; var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); startingIndex += Ellipsoid.packedLength; - var cornerType = array[startingIndex++]; - var granularity = array[startingIndex]; + 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)) { - scratchOptions.polylinePositions = positions; - scratchOptions.shapePositions = shape; - scratchOptions.cornerType = cornerType; - scratchOptions.granularity = granularity; - return new PolylineVolumeOutlineGeometry(scratchOptions); + result = new PolygonOutlineGeometry(dummyOptions); } - result._positions = positions; - result._shape = shape; + result._polygonHierarchy = polygonHierarchy; result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid); - result._cornerType = cornerType; + result._height = height; + result._extrudedHeight = extrudedHeight; result._granularity = granularity; + result._extrude = extrude; + result._perPositionHeight = perPositionHeight; + result.packedLength = packedLength; return result; }; - var brScratch = new BoundingRectangle(); + /** + * 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); + + + 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 the outline of a polyline with a volume, including its vertices, indices, and a bounding sphere. + * Computes the geometric representation of a polygon outline, including its vertices, indices, and a bounding sphere. * - * @param {PolylineVolumeOutlineGeometry} polylineVolumeOutlineGeometry A description of the polyline volume outline. + * @param {PolygonOutlineGeometry} polygonGeometry A description of the polygon 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); + 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; - if (cleanPositions.length < 2 || shape2D.length < 3) { + // 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; } - if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) { - shape2D.reverse(); + 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); + } } - var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); - var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry, false); - return computeAttributes(computedPositions, shape2D); + 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 PolylineVolumeOutlineGeometry; + return PolygonOutlineGeometry; }); -/*global define*/ -define('Core/QuaternionSpline',[ +define('Core/PolylineGeometry',[ + './arrayRemoveDuplicates', + './BoundingSphere', + './Cartesian3', + './Color', + './ComponentDatatype', './defaultValue', './defined', - './defineProperties', './DeveloperError', - './Quaternion', - './Spline' + './Ellipsoid', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './GeometryType', + './IndexDatatype', + './Math', + './PolylinePipeline', + './PrimitiveType', + './VertexFormat' ], function( + arrayRemoveDuplicates, + BoundingSphere, + Cartesian3, + Color, + ComponentDatatype, defaultValue, defined, - defineProperties, DeveloperError, - Quaternion, - Spline) { + Ellipsoid, + Geometry, + GeometryAttribute, + GeometryAttributes, + GeometryType, + IndexDatatype, + CesiumMath, + PolylinePipeline, + PrimitiveType, + VertexFormat) { '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; - } + var scratchInterpolateColorsArray = []; - function createEvaluateFunction(spline) { - var points = spline.points; - var quads = spline.innerQuadrangles; - var times = spline.times; + function interpolateColors(p0, p1, color0, color1, numPoints) { + var colors = scratchInterpolateColorsArray; + colors.length = numPoints; + var i; - // use slerp interpolation for 2 points - if (points.length < 3) { - var t0 = times[0]; - var invSpan = 1.0 / (times[1] - t0); + var r0 = color0.red; + var g0 = color0.green; + var b0 = color0.blue; + var a0 = color0.alpha; - var q0 = points[0]; - var q1 = points[1]; + var r1 = color1.red; + var g1 = color1.green; + var b1 = color1.blue; + var a1 = color1.alpha; - return function(time, result) { - if (!defined(result)){ - result = new Quaternion(); - } - var u = (time - t0) * invSpan; - return Quaternion.fastSlerp(q0, q1, u, result); - }; + if (Color.equals(color0, color1)) { + for (i = 0; i < numPoints; i++) { + colors[i] = Color.clone(color0); + } + return colors; } - // 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 redPerVertex = (r1 - r0) / numPoints; + var greenPerVertex = (g1 - g0) / numPoints; + var bluePerVertex = (b1 - b0) / numPoints; + var alphaPerVertex = (a1 - a0) / numPoints; - var q0 = points[i]; - var q1 = points[i + 1]; - var s0 = quads[i]; - var s1 = quads[i + 1]; + for (i = 0; i < numPoints; i++) { + colors[i] = new Color(r0 + i * redPerVertex, g0 + i * greenPerVertex, b0 + i * bluePerVertex, a0 + i * alphaPerVertex); + } - return Quaternion.fastSquad(q0, q1, s0, s1, u, result); - }; + return colors; } /** - * A spline that uses spherical quadrangle (squad) interpolation to create a quaternion curve. - * The generated curve is in the class C1. + * 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 QuaternionSpline + * @alias PolylineGeometry * @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. + * @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} points.length must be greater than or equal to 2. - * @exception {DeveloperError} times.length must be equal to points.length. + * @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 HermiteSpline - * @see CatmullRomSpline - * @see LinearSpline + * @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 QuaternionSpline(options) { + function PolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - var points = options.points; - var times = options.times; - var firstInnerQuadrangle = options.firstInnerQuadrangle; - var lastInnerQuadrangle = options.lastInnerQuadrangle; + var positions = options.positions; + var colors = options.colors; + var width = defaultValue(options.width, 1.0); + var colorsPerVertex = defaultValue(options.colorsPerVertex, false); - 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; - } - }, + 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'; - /** - * An array of {@link Quaternion} control points. - * - * @memberof QuaternionSpline.prototype - * - * @type {Quaternion[]} - * @readonly - */ - points : { - get : function() { - return this._points; - } - }, + var numComponents = 1 + positions.length * Cartesian3.packedLength; + numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1; /** - * An array of {@link Quaternion} inner quadrangles for the control points. - * - * @memberof QuaternionSpline.prototype - * - * @type {Quaternion[]} - * @readonly + * The number of elements used to pack the object into an array. + * @type {Number} */ - innerQuadrangles : { - get : function() { - return this._innerQuadrangles; - } - } - }); + this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 4; + } /** - * Finds an index i in times such that the parameter - * time is in the interval [times[i], times[i + 1]]. - * @function + * Stores the provided instance into the provided array. * - * @param {Number} time The time. - * @returns {Number} The index for the element at the start of the interval. + * @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. * - * @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. + * @returns {Number[]} The array that was packed into */ - QuaternionSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval; + PolylineGeometry.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); - /** - * 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); - }; + var i; - return QuaternionSpline; -}); + var positions = value._positions; + var length = positions.length; + array[startingIndex++] = length; -/*global define*/ -define('Core/RectangleGeometryLibrary',[ - './Cartesian3', - './Cartographic', - './defined', - './DeveloperError', - './GeographicProjection', - './Math', - './Matrix2', - './Rectangle' -], function( - Cartesian3, - Cartographic, - defined, - DeveloperError, - GeographicProjection, - CesiumMath, - Matrix2, - Rectangle) { - 'use strict'; + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + Cartesian3.pack(positions[i], array, startingIndex); + } - var cos = Math.cos; - var sin = Math.sin; - var sqrt = Math.sqrt; + var colors = value._colors; + length = defined(colors) ? colors.length : 0.0; + array[startingIndex++] = length; - /** - * @private - */ - var RectangleGeometryLibrary = {}; + for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { + Color.pack(colors[i], array, startingIndex); + } - /** - * @private - */ - RectangleGeometryLibrary.computePosition = function(options, row, col, position, st) { - var radiiSquared = options.ellipsoid.radiiSquared; - var nwCorner = options.nwCorner; - var rectangle = options.rectangle; + Ellipsoid.pack(value._ellipsoid, array, startingIndex); + startingIndex += Ellipsoid.packedLength; - var stLatitude = nwCorner.latitude - options.granYCos * row + col * options.granXSin; - var cosLatitude = cos(stLatitude); - var nZ = sin(stLatitude); - var kZ = radiiSquared.z * nZ; + VertexFormat.pack(value._vertexFormat, array, startingIndex); + startingIndex += VertexFormat.packedLength; - var stLongitude = nwCorner.longitude + row * options.granYSin + col * options.granXCos; - var nX = cosLatitude * cos(stLongitude); - var nY = cosLatitude * sin(stLongitude); + array[startingIndex++] = value._width; + array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0; + array[startingIndex++] = value._followSurface ? 1.0 : 0.0; + array[startingIndex] = value._granularity; - var kX = radiiSquared.x * nX; - var kY = radiiSquared.y * nY; + return array; + }; - var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ)); + 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 + }; - position.x = kX / gamma; - position.y = kY / gamma; - position.z = kZ / gamma; + /** + * 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) { + + startingIndex = defaultValue(startingIndex, 0); - 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 - }; - } + var i; - /** - * @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 length = array[startingIndex++]; + var positions = new Array(length); - 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); + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + positions[i] = Cartesian3.unpack(array, startingIndex); } - 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); - } + length = array[startingIndex++]; + var colors = length > 0 ? new Array(length) : undefined; - var granYCos = granularityY; - var granXCos = granularityX; - var granYSin = 0.0; - var granXSin = 0.0; + for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { + colors[i] = Color.unpack(array, startingIndex); + } - var options = { - granYCos : granYCos, - granYSin : granYSin, - granXCos : granXCos, - granXSin : granXSin, - ellipsoid : ellipsoid, - surfaceHeight : surfaceHeight, - extrudedHeight : extrudedHeight, - nwCorner : nwCorner, - rectangle : rectangle, - width: width, - height: height - }; + var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); + startingIndex += Ellipsoid.packedLength; - 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; + var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); + startingIndex += VertexFormat.packedLength; - - options.granYCos = rotationOptions.granYCos; - options.granYSin = rotationOptions.granYSin; - options.granXCos = rotationOptions.granXCos; - options.granXSin = rotationOptions.granXSin; + var width = array[startingIndex++]; + var colorsPerVertex = array[startingIndex++] === 1.0; + var followSurface = array[startingIndex++] === 1.0; + var granularity = array[startingIndex]; - rectangle.north = north; - rectangle.south = south; - rectangle.east = east; - rectangle.west = west; + 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); } - 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; - } + 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 options; + return result; }; - return RectangleGeometryLibrary; -}); + var scratchCartesian3 = new Cartesian3(); + var scratchPosition = new Cartesian3(); + var scratchPrevPosition = new Cartesian3(); + var scratchNextPosition = new Cartesian3(); -/*global define*/ -define('Core/RectangleGeometry',[ - './BoundingSphere', - './Cartesian2', - './Cartesian3', - './Cartographic', - './Check', - './ComponentDatatype', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError', - './Ellipsoid', - './Geometry', - './GeometryAttribute', - './GeometryAttributes', - './GeometryInstance', - './GeometryPipeline', - './IndexDatatype', - './Math', - './Matrix3', - './PolygonPipeline', - './PrimitiveType', - './Quaternion', - './Rectangle', - './RectangleGeometryLibrary', - './VertexFormat' -], function( - BoundingSphere, - Cartesian2, - Cartesian3, - Cartographic, - Check, - ComponentDatatype, - defaultValue, - defined, - defineProperties, - DeveloperError, - Ellipsoid, - Geometry, - GeometryAttribute, - GeometryAttributes, - GeometryInstance, - GeometryPipeline, - IndexDatatype, - CesiumMath, - Matrix3, - PolygonPipeline, - PrimitiveType, - Quaternion, - Rectangle, - RectangleGeometryLibrary, - VertexFormat) { - 'use strict'; + /** + * 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 positionScratch = new Cartesian3(); - var normalScratch = new Cartesian3(); - var tangentScratch = new Cartesian3(); - var bitangentScratch = new Cartesian3(); - var rectangleScratch = new Rectangle(); - var stScratch = new Cartesian2(); - var bottomBoundingSphere = new BoundingSphere(); - var topBoundingSphere = new BoundingSphere(); + var i; + var j; + var k; - function createAttributes(vertexFormat, attributes) { - var geo = new Geometry({ - attributes : new GeometryAttributes(), - primitiveType : PrimitiveType.TRIANGLES - }); + var positions = arrayRemoveDuplicates(polylineGeometry._positions, Cartesian3.equalsEpsilon); + var positionsLength = positions.length; - 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.bitangent) { - geo.attributes.bitangent = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : attributes.bitangents - }); + // 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; } - return geo; - } - function calculateAttributes(positions, vertexFormat, ellipsoid, tangentRotationMatrix) { - var length = positions.length; + if (followSurface) { + var heights = PolylinePipeline.extractHeights(positions, ellipsoid); + var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); - var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined; - var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined; - var bitangents = (vertexFormat.bitangent) ? new Float32Array(length) : undefined; + if (defined(colors)) { + var colorLength = 1; + for (i = 0; i < positionsLength - 1; ++i) { + colorLength += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance); + } - var attrIndex = 0; - var bitangent = bitangentScratch; - var tangent = tangentScratch; - var normal = normalScratch; - if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) { - for (var i = 0; i < length; i += 3) { - var p = Cartesian3.fromArray(positions, i, positionScratch); - var attrIndex1 = attrIndex + 1; - var attrIndex2 = attrIndex + 2; + var newColors = new Array(colorLength); + var newColorIndex = 0; - normal = ellipsoid.geodeticSurfaceNormal(p, normal); - if (vertexFormat.tangent || vertexFormat.bitangent) { - Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); - Matrix3.multiplyByVector(tangentRotationMatrix, tangent, tangent); - Cartesian3.normalize(tangent, tangent); + for (i = 0; i < positionsLength - 1; ++i) { + var p0 = positions[i]; + var p1 = positions[i+1]; + var c0 = colors[i]; - if (vertexFormat.bitangent) { - Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent); + 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); + } } } - 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.bitangent) { - bitangents[attrIndex] = bitangent.x; - bitangents[attrIndex1] = bitangent.y; - bitangents[attrIndex2] = bitangent.z; - } - attrIndex += 3; + newColors[newColorIndex] = Color.clone(colors[colors.length-1]); + colors = newColors; + + scratchInterpolateColorsArray.length = 0; } - } - return createAttributes(vertexFormat, { - positions : positions, - normals : normals, - tangents : tangents, - bitangents : bitangents - }); - } - var v1Scratch = new Cartesian3(); - var v2Scratch = new Cartesian3(); + positions = PolylinePipeline.generateCartesianArc({ + positions: positions, + minDistance: minDistance, + ellipsoid: ellipsoid, + height: heights + }); + } - function calculateAttributesWall(positions, vertexFormat, ellipsoid) { - var length = positions.length; + positionsLength = positions.length; + var size = positionsLength * 4.0 - 4.0; - var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined; - var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined; - var bitangents = (vertexFormat.bitangent) ? new Float32Array(length) : undefined; + 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 normalIndex = 0; - var tangentIndex = 0; - var bitangentIndex = 0; - var recomputeNormal = true; + var positionIndex = 0; + var expandAndWidthIndex = 0; + var stIndex = 0; + var colorIndex = 0; + var position; - var bitangent = bitangentScratch; - var tangent = tangentScratch; - var normal = normalScratch; - if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) { - for (var i = 0; i < length; i += 6) { - var p = Cartesian3.fromArray(positions, i, positionScratch); - 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; - } + 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]; + } - if (Cartesian3.equalsEpsilon(p1, p, CesiumMath.EPSILON10)) { // if we've reached a corner - recomputeNormal = true; - } + Cartesian3.clone(position, scratchPrevPosition); + Cartesian3.clone(positions[j], scratchPosition); - if (vertexFormat.tangent || vertexFormat.bitangent) { - bitangent = ellipsoid.geodeticSurfaceNormal(p, bitangent); - if (vertexFormat.tangent) { - tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent); - } - } + 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]; + } - 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; - } + Cartesian3.clone(position, scratchNextPosition); - 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; + var color0, color1; + if (defined(finalColors)) { + if (j !== 0 && !colorsPerVertex) { + color0 = colors[j - 1]; + } else { + color0 = colors[j]; } - if (vertexFormat.bitangent) { - bitangents[bitangentIndex++] = bitangent.x; - bitangents[bitangentIndex++] = bitangent.y; - bitangents[bitangentIndex++] = bitangent.z; - bitangents[bitangentIndex++] = bitangent.x; - bitangents[bitangentIndex++] = bitangent.y; - bitangents[bitangentIndex++] = bitangent.z; + if (j !== positionsLength - 1) { + color1 = colors[j]; } } - } - - return createAttributes(vertexFormat, { - positions : positions, - normals : normals, - tangents : tangents, - bitangents : bitangents - }); - } - - 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; + var startK = j === 0 ? 2 : 0; + var endK = j === positionsLength - 1 ? 2 : 4; - for (var row = 0; row < height; ++row) { - for (var col = 0; col < width; ++col) { - RectangleGeometryLibrary.computePosition(options, row, col, position, st); + for (k = startK; k < endK; ++k) { + Cartesian3.pack(scratchPosition, finalPositions, positionIndex); + Cartesian3.pack(scratchPrevPosition, prevPositions, positionIndex); + Cartesian3.pack(scratchNextPosition, nextPositions, positionIndex); + positionIndex += 3; - positions[posIndex++] = position.x; - positions[posIndex++] = position.y; - positions[posIndex++] = position.z; + var direction = (k - 2 < 0) ? -1.0 : 1.0; + expandAndWidth[expandAndWidthIndex++] = 2 * (k % 2) - 1; // expand direction + expandAndWidth[expandAndWidthIndex++] = direction * width; if (vertexFormat.st) { - textureCoordinates[stIndex++] = st.x; - textureCoordinates[stIndex++] = st.y; + st[stIndex++] = j / (positionsLength - 1); + st[stIndex++] = Math.max(expandAndWidth[expandAndWidthIndex - 2], 0.0); + } - 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 (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); } } } - 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 attributes = new GeometryAttributes(); - var geo = calculateAttributes(positions, vertexFormat, ellipsoid, options.tangentRotationMatrix); + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : finalPositions + }); - 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; - } + 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 + }); - geo.indices = indices; if (vertexFormat.st) { - geo.attributes.st = new GeometryAttribute({ + attributes.st = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, - values : textureCoordinates + values : st }); } - 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; - } + if (defined(finalColors)) { + attributes.color = new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + values : finalColors, + normalize : true + }); + } - 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; - } + 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; - var scratchVertexFormat = new VertexFormat(); + indices[indicesIndex++] = index + 1; + indices[indicesIndex++] = index + 2; + indices[indicesIndex++] = index + 3; - function constructExtrudedRectangle(options) { - var shadowVolume = options.shadowVolume; - var vertexFormat = options.vertexFormat; - var surfaceHeight = options.surfaceHeight; - var extrudedHeight = options.extrudedHeight; - var minHeight = Math.min(extrudedHeight, surfaceHeight); - var maxHeight = Math.max(extrudedHeight, surfaceHeight); + index += 4; + } - var height = options.height; - var width = options.width; - var ellipsoid = options.ellipsoid; - var i; + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.TRIANGLES, + boundingSphere : BoundingSphere.fromPoints(positions), + geometryType : GeometryType.POLYLINES + }); + }; - if (shadowVolume) { - options.vertexFormat = VertexFormat.clone(vertexFormat, scratchVertexFormat); - options.vertexFormat.normal = true; - } - var topBottomGeo = constructRectangle(options); - if (CesiumMath.equalsEpsilon(minHeight, maxHeight, CesiumMath.EPSILON10)) { - return topBottomGeo; - } + return PolylineGeometry; +}); - 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; +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'; - var normals = (vertexFormat.normal) ? new Float32Array(newLength) : undefined; - var tangents = (vertexFormat.tangent) ? new Float32Array(newLength) : undefined; - var bitangents = (vertexFormat.bitangent) ? new Float32Array(newLength) : undefined; - var textures = (vertexFormat.st) ? new Float32Array(newLength / 3 * 2) : undefined; - var topSt; - var topNormals; - if (vertexFormat.normal) { - 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 (shadowVolume) { - topNormals = topBottomGeo.attributes.normal.values; - if (!vertexFormat.normal) { - topBottomGeo.attributes.normal = undefined; - } - var extrudeNormals = new Float32Array(newLength); - for (i = 0; i < length; i++) { - topNormals[i] = -topNormals[i]; - } - extrudeNormals.set(topNormals, length); //only get normals for bottom layer that's going to be pushed down - topBottomGeo.attributes.extrudeDirection = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, + function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat) { + var attributes = new GeometryAttributes(); + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : extrudeNormals + values : combinedPositions }); } + var shapeLength = shape.length; + var vertexCount = combinedPositions.length / 3; + var length = (vertexCount - shapeLength * 2) / (shapeLength * 2); + var firstEndIndices = PolygonPipeline.triangulate(shape); - 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.bitangent) { - var topBitangents = topBottomGeo.attributes.bitangent.values; - bitangents.set(topBitangents); - bitangents.set(topBitangents, length); - topBottomGeo.attributes.bitangent.values = bitangents; - } - 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 wallExtrudeNormals = shadowVolume ? new Float32Array(wallCount * 3) : undefined; - var wallTextures = (vertexFormat.st) ? new Float32Array(wallCount * 2) : undefined; + 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; - var posIndex = 0; - var stIndex = 0; - var extrudeNormalIndex = 0; - var area = width * height; - var threeI; - for (i = 0; i < area; i += width) { - threeI = i * 3; - wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); - posIndex += 6; - if (vertexFormat.st) { - wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); - stIndex += 4; - } - if (shadowVolume) { - extrudeNormalIndex += 3; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; + 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; - for (i = area - width; i < area; i++) { - threeI = i * 3; - wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); - posIndex += 6; - if (vertexFormat.st) { - wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); - stIndex += 4; - } - if (shadowVolume) { - extrudeNormalIndex += 3; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; - } + indices[index++] = ul; + indices[index++] = ll; + indices[index++] = ur; + indices[index++] = ur; + indices[index++] = ll; + indices[index++] = lr; } - for (i = area - 1; i > 0; i -= width) { - threeI = i * 3; - wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); - posIndex += 6; - if (vertexFormat.st) { - wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); - stIndex += 4; - } - if (shadowVolume) { - extrudeNormalIndex += 3; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; + if (vertexFormat.st || vertexFormat.tangent || vertexFormat.bitangent) { // st required for tangent/bitangent 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 (i = width - 1; i >= 0; i--) { - threeI = i * 3; - wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); - posIndex += 6; - if (vertexFormat.st) { - wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); - stIndex += 4; + for (j = 0; j < shapeLength; j++) { + s = 0; + t = heightSt * (shape[j].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; } - if (shadowVolume) { - extrudeNormalIndex += 3; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; - wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; + for (j = 0; j < shapeLength; j++) { + s = (length - 1) * lengthSt; + t = heightSt * (shape[j].y + heightOffset); + st[stindex++] = s; + st[stindex++] = t; } - } - - var geo = calculateAttributesWall(wallPositions, vertexFormat, ellipsoid); - if (vertexFormat.st) { - geo.attributes.st = new GeometryAttribute({ + attributes.st = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, - values : wallTextures - }); - } - if (shadowVolume) { - geo.attributes.extrudeDirection = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : wallExtrudeNormals + values : new Float32Array(st) }); } - var wallIndices = IndexDatatype.createTypedArray(wallCount, perimeterPositions * 6); + 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; - 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; + indices[index++] = v0; + indices[index++] = v1; + indices[index++] = v2; + indices[index++] = v2 + shapeLength; + indices[index++] = v1 + shapeLength; + indices[index++] = v0 + shapeLength; } - 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()]; + var geometry = new Geometry({ + attributes : attributes, + indices : indices, + boundingSphere : BoundingSphere.fromVertices(combinedPositions), + primitiveType : PrimitiveType.TRIANGLES + }); - function computeRectangle(rectangle, ellipsoid, rotation) { - if (rotation === 0.0) { - return Rectangle.clone(rectangle); + if (vertexFormat.normal) { + geometry = GeometryPipeline.computeNormal(geometry); } - 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); + if (vertexFormat.tangent || vertexFormat.bitangent) { + try { + geometry = GeometryPipeline.computeTangentAndBitangent(geometry); + } catch (e) { + oneTimeWarning('polyline-volume-tangent-bitangent', 'Unable to compute tangents and bitangents for polyline volume geometry'); + //TODO https://github.com/AnalyticalGraphicsInc/cesium/issues/3609 + } - Matrix3.fromQuaternion(scratchQuaternion, scratchRotationMatrix); - for (var i = 0; i < 4; ++i) { - // Apply the rotation - Matrix3.multiplyByVector(scratchRotationMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]); + if (!vertexFormat.tangent) { + geometry.attributes.tangent = undefined; + } + if (!vertexFormat.bitangent) { + geometry.attributes.bitangent = undefined; + } + if (!vertexFormat.st) { + geometry.attributes.st = undefined; + } } - ellipsoid.cartesianArrayToCartographicArray(scratchRectanglePoints, scratchCartographicPoints); - - return Rectangle.fromCartographicArray(scratchCartographicPoints); + return geometry; } /** - * 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}. + * A description of a polyline with a volume (a 2D shape extruded along a polyline). * - * @alias RectangleGeometry + * @alias PolylineVolumeGeometry * @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 {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 {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. + * @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 RectangleGeometry#createGeometry + * @see PolylineVolumeGeometry#createGeometry * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo} + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo} * * @example - * // 1. create a 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); + * 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; + * } * - * // 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 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) * }); - * var geometry = Cesium.RectangleGeometry.createGeometry(rectangle); */ - function RectangleGeometry(options) { + function PolylineVolumeGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - var rectangle = options.rectangle; + var positions = options.polylinePositions; + var shape = options.shapePositions; - var rotation = defaultValue(options.rotation, 0.0); - this._rectangle = rectangle; - this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._positions = positions; + this._shape = shape; this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84)); - this._surfaceHeight = defaultValue(options.height, 0.0); - this._rotation = rotation; - this._stRotation = defaultValue(options.stRotation, 0.0); + this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT)); - this._extrudedHeight = defaultValue(options.extrudedHeight, 0.0); - this._extrude = defined(options.extrudedHeight); - this._closeTop = defaultValue(options.closeTop, true); - this._closeBottom = defaultValue(options.closeBottom, true); - this._shadowVolume = defaultValue(options.shadowVolume, false); - this._workerName = 'createRectangleGeometry'; - this._rotatedRectangle = computeRectangle(this._rectangle, this._ellipsoid, rotation); - } + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._workerName = 'createPolylineVolumeGeometry'; - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - RectangleGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 9; + 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 {RectangleGeometry} value The value to pack. + * @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 */ - RectangleGeometry.pack = function(value, array, startingIndex) { + PolylineVolumeGeometry.pack = function(value, array, startingIndex) { startingIndex = defaultValue(startingIndex, 0); - Rectangle.pack(value._rectangle, array, startingIndex); - startingIndex += Rectangle.packedLength; + 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; @@ -68173,37 +70072,21 @@ define('Core/RectangleGeometry',[ 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; - array[startingIndex] = value._shadowVolume ? 1.0 : 0.0; + array[startingIndex++] = value._cornerType; + array[startingIndex] = value._granularity; 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, + polylinePositions : undefined, + shapePositions : undefined, ellipsoid : scratchEllipsoid, vertexFormat : scratchVertexFormat, - granularity : undefined, - height : undefined, - rotation : undefined, - stRotation : undefined, - extrudedHeight : undefined, - closeTop : undefined, - closeBottom : undefined, - shadowVolume : undefined + cornerType : undefined, + granularity : undefined }; /** @@ -68211,15 +70094,28 @@ define('Core/RectangleGeometry',[ * * @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. + * @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. */ - RectangleGeometry.unpack = function(array, startingIndex, result) { + PolylineVolumeGeometry.unpack = function(array, startingIndex, result) { startingIndex = defaultValue(startingIndex, 0); - var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle); - startingIndex += Rectangle.packedLength; + 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; @@ -68227,169 +70123,65 @@ define('Core/RectangleGeometry',[ 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; - var shadowVolume = array[startingIndex] === 1.0; + var cornerType = array[startingIndex++]; + var granularity = array[startingIndex]; if (!defined(result)) { + scratchOptions.polylinePositions = positions; + scratchOptions.shapePositions = shape; + scratchOptions.cornerType = cornerType; scratchOptions.granularity = granularity; - scratchOptions.height = surfaceHeight; - scratchOptions.rotation = rotation; - scratchOptions.stRotation = stRotation; - scratchOptions.extrudedHeight = extrude ? extrudedHeight : undefined; - scratchOptions.closeTop = closeTop; - scratchOptions.closeBottom = closeBottom; - scratchOptions.shadowVolume = shadowVolume; - return new RectangleGeometry(scratchOptions); + return new PolylineVolumeGeometry(scratchOptions); } - result._rectangle = Rectangle.clone(rectangle, result._rectangle); + 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; - 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; - result._shadowVolume = shadowVolume; return result; }; - var tangentRotationMatrixScratch = new Matrix3(); - var nwScratch = new Cartographic(); - var stNwScratch = new Cartographic(); - var quaternionScratch = new Quaternion(); - var centerScratch = new Cartographic(); + var brScratch = new BoundingRectangle(); + /** - * Computes the geometric representation of a rectangle, including its vertices, indices, and a bounding sphere. + * Computes the geometric representation of a polyline with a volume, including its vertices, indices, and a bounding sphere. * - * @param {RectangleGeometry} rectangleGeometry A description of the rectangle. + * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polyline volume. * @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; + PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) { + var positions = polylineVolumeGeometry._positions; + var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); + var shape2D = polylineVolumeGeometry._shape; + shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D); - var geometry; - var boundingSphere; - rectangle = rectangleGeometry._rectangle; - if (extrude) { - options.shadowVolume = rectangleGeometry._shadowVolume; - 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 (cleanPositions.length < 2 || shape2D.length < 3) { + return undefined; } - if (!vertexFormat.position) { - delete geometry.attributes.position; + if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) { + shape2D.reverse(); } + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); - return new Geometry({ - attributes : 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, - shadowVolume : true - }); + var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeGeometry, true); + return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeGeometry._vertexFormat); }; - defineProperties(RectangleGeometry.prototype, { - /** - * @private - */ - rectangle : { - get : function() { - return this._rotatedRectangle; - } - } - }); - - return RectangleGeometry; + return PolylineVolumeGeometry; }); -/*global define*/ -define('Core/RectangleOutlineGeometry',[ +define('Core/PolylineVolumeOutlineGeometry',[ + './arrayRemoveDuplicates', + './BoundingRectangle', './BoundingSphere', + './Cartesian2', './Cartesian3', - './Cartographic', './ComponentDatatype', + './CornerType', './defaultValue', './defined', './DeveloperError', @@ -68400,14 +70192,17 @@ define('Core/RectangleOutlineGeometry',[ './IndexDatatype', './Math', './PolygonPipeline', + './PolylineVolumeGeometryLibrary', './PrimitiveType', - './Rectangle', - './RectangleGeometryLibrary' + './WindingOrder' ], function( + arrayRemoveDuplicates, + BoundingRectangle, BoundingSphere, + Cartesian2, Cartesian3, - Cartographic, ComponentDatatype, + CornerType, defaultValue, defined, DeveloperError, @@ -68418,226 +70213,167 @@ define('Core/RectangleOutlineGeometry',[ IndexDatatype, CesiumMath, PolygonPipeline, + PolylineVolumeGeometryLibrary, PrimitiveType, - Rectangle, - RectangleGeometryLibrary) { + WindingOrder) { '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({ + function computeAttributes(positions, shape) { + var attributes = new GeometryAttributes(); + 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 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; } - 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; + indices[index++] = shapeLength - 1 + offset; + indices[index++] = offset; - 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; + i = shapeCount - 1; + offset = i * shapeLength; + for (j = 0; j < shapeLength - 1; j++) { + indices[index++] = j + offset; + indices[index++] = j + offset + 1; } - indices[index++] = length - 1; - indices[index++] = 0; - indices[index++] = length + length - 1; - indices[index++] = length; + indices[index++] = shapeLength - 1 + offset; + indices[index++] = offset; - 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; + 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; + } + } - geo.indices = indices; + var geometry = new Geometry({ + attributes : attributes, + indices : IndexDatatype.createTypedArray(vertexCount, indices), + boundingSphere : BoundingSphere.fromVertices(positions), + primitiveType : PrimitiveType.LINES + }); - return geo; + return geometry; } /** - * A description of the outline of a a cartographic rectangle on an ellipsoid centered at the origin. + * A description of a polyline with a volume (a 2D shape extruded along a polyline). * - * @alias RectangleOutlineGeometry + * @alias PolylineVolumeOutlineGeometry * @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 {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 {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. + * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners. * - * @see RectangleOutlineGeometry#createGeometry + * @see PolylineVolumeOutlineGeometry#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 + * 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) * }); - * var geometry = Cesium.RectangleOutlineGeometry.createGeometry(rectangle); */ - function RectangleOutlineGeometry(options) { + function PolylineVolumeOutlineGeometry(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; + var positions = options.polylinePositions; + var shape = options.shapePositions; - this._rectangle = rectangle; - this._granularity = granularity; - this._ellipsoid = ellipsoid; - this._surfaceHeight = surfaceHeight; - this._rotation = rotation; - this._extrudedHeight = extrudedHeight; - this._workerName = 'createRectangleOutlineGeometry'; - } + 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'; - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - RectangleOutlineGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + 5; + 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 {RectangleOutlineGeometry} value The value to pack. + * @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 */ - RectangleOutlineGeometry.pack = function(value, array, startingIndex) { + PolylineVolumeOutlineGeometry.pack = function(value, array, startingIndex) { startingIndex = defaultValue(startingIndex, 0); - Rectangle.pack(value._rectangle, array, startingIndex); - startingIndex += Rectangle.packedLength; + 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._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); + array[startingIndex++] = value._cornerType; + array[startingIndex] = value._granularity; return array; }; - var scratchRectangle = new Rectangle(); var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE); var scratchOptions = { - rectangle : scratchRectangle, + polylinePositions : undefined, + shapePositions : undefined, ellipsoid : scratchEllipsoid, - granularity : undefined, height : undefined, - rotation : undefined, - extrudedHeight : undefined + cornerType : undefined, + granularity : undefined }; /** @@ -68645,4751 +70381,4211 @@ define('Core/RectangleOutlineGeometry',[ * * @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. + * @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. */ - RectangleOutlineGeometry.unpack = function(array, startingIndex, result) { + PolylineVolumeOutlineGeometry.unpack = function(array, startingIndex, result) { startingIndex = defaultValue(startingIndex, 0); - var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle); - startingIndex += Rectangle.packedLength; + 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 granularity = array[startingIndex++]; - var height = array[startingIndex++]; - var rotation = array[startingIndex++]; - var hasExtrudedHeight = array[startingIndex++]; - var extrudedHeight = array[startingIndex]; + var cornerType = array[startingIndex++]; + var granularity = array[startingIndex]; if (!defined(result)) { + scratchOptions.polylinePositions = positions; + scratchOptions.shapePositions = shape; + scratchOptions.cornerType = cornerType; scratchOptions.granularity = granularity; - scratchOptions.height = height; - scratchOptions.rotation = rotation; - scratchOptions.extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined; - return new RectangleOutlineGeometry(scratchOptions); + return new PolylineVolumeOutlineGeometry(scratchOptions); } - result._rectangle = Rectangle.clone(rectangle, result._rectangle); + result._positions = positions; + result._shape = shape; result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid); - result._surfaceHeight = height; - result._rotation = rotation; - result._extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined; + result._cornerType = cornerType; + result._granularity = granularity; return result; }; - var nwScratch = new Cartographic(); + var brScratch = new BoundingRectangle(); + /** - * Computes the geometric representation of an outline of a rectangle, including its vertices, indices, and a bounding sphere. + * Computes the geometric representation of the outline of a polyline with a volume, including its vertices, indices, and a bounding sphere. * - * @param {RectangleOutlineGeometry} rectangleGeometry A description of the rectangle outline. + * @param {PolylineVolumeOutlineGeometry} polylineVolumeOutlineGeometry A description of the polyline volume 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; + PolylineVolumeOutlineGeometry.createGeometry = function(polylineVolumeOutlineGeometry) { + var positions = polylineVolumeOutlineGeometry._positions; + var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); + var shape2D = polylineVolumeOutlineGeometry._shape; + shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D); - if ((CesiumMath.equalsEpsilon(rectangle.north, rectangle.south, CesiumMath.EPSILON10) || - (CesiumMath.equalsEpsilon(rectangle.east, rectangle.west, CesiumMath.EPSILON10)))) { + if (cleanPositions.length < 2 || shape2D.length < 3) { 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, + if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) { + shape2D.reverse(); + } + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); - /** - * The inertial frame. - * - * @type {Number} - * @constant - */ - INERTIAL : 1 + var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry, false); + return computeAttributes(computedPositions, shape2D); }; - return freezeObject(ReferenceFrame); + return PolylineVolumeOutlineGeometry; }); -/*global define*/ -define('Core/requestAnimationFrame',[ +define('Core/QuaternionSpline',[ + './defaultValue', './defined', - './getTimestamp' + './defineProperties', + './DeveloperError', + './Quaternion', + './Spline' ], function( + defaultValue, defined, - getTimestamp) { + defineProperties, + DeveloperError, + Quaternion, + Spline) { 'use strict'; - if (typeof window === 'undefined') { - return; - } + function computeInnerQuadrangles(points, firstInnerQuadrangle, lastInnerQuadrangle) { + var length = points.length; + var quads = new Array(length); - var implementation = window.requestAnimationFrame; + quads[0] = defined(firstInnerQuadrangle) ? firstInnerQuadrangle : points[0]; + quads[length - 1] = defined(lastInnerQuadrangle) ? lastInnerQuadrangle : points[length - 1]; - (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; - } + for (var i = 1; i < length - 1; ++i) { + quads[i] = Quaternion.computeInnerQuadrangle(points[i - 1], points[i], points[i + 1], new Quaternion()); } - // build an implementation based on setTimeout - if (!defined(implementation)) { - var msPerFrame = 1000.0 / 60.0; - var lastFrameTime = 0; - implementation = function(callback) { - var currentTime = getTimestamp(); + return quads; + } - // 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; + function createEvaluateFunction(spline) { + var points = spline.points; + var quads = spline.innerQuadrangles; + var times = spline.times; - return setTimeout(function() { - callback(lastFrameTime); - }, delay); + // 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 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. + * A spline that uses spherical quadrangle (squad) interpolation to create a quaternion curve. + * The generated curve is in the class C1. * - * @exports requestAnimationFrame + * @alias QuaternionSpline + * @constructor * - * @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. + * @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. * - * @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} + * @see HermiteSpline + * @see CatmullRomSpline + * @see LinearSpline + * @see WeightSpline */ - 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); + 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; + + + 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; + } + } + }); + /** - * A function that will be called when the next frame should be drawn. - * @callback requestAnimationFrame~Callback + * Finds an index i in times such that the parameter + * time is in the interval [times[i], times[i + 1]]. + * @function * - * @param {Number} timestamp A timestamp for the frame, in milliseconds. + * @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; - return requestAnimationFrame; + /** + * 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/sampleTerrain',[ - '../ThirdParty/when', +define('Core/RectangleGeometryLibrary',[ + './Cartesian3', + './Cartographic', './defined', - './DeveloperError' + './DeveloperError', + './GeographicProjection', + './Math', + './Matrix2', + './Rectangle' ], function( - when, + Cartesian3, + Cartographic, defined, - DeveloperError) { + DeveloperError, + GeographicProjection, + CesiumMath, + Matrix2, + Rectangle) { 'use strict'; + var cos = Math.cos; + var sin = Math.sin; + var sqrt = Math.sqrt; + /** - * 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/v1/tilesets/world/tiles' - * }); - * 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. - * }); + * @private */ - function sampleTerrain(terrainProvider, level, positions) { - - var deferred = when.defer(); - - function doSamplingWhenReady() { - if (terrainProvider.ready) { - when(doSampling(terrainProvider, level, positions), function(updatedPositions) { - deferred.resolve(updatedPositions); - }); - } else { - setTimeout(doSamplingWhenReady, 10); - } - } + var RectangleGeometryLibrary = {}; - doSamplingWhenReady(); + /** + * @private + */ + RectangleGeometryLibrary.computePosition = function(options, row, col, position, st) { + var radiiSquared = options.ellipsoid.radiiSquared; + var nwCorner = options.nwCorner; + var rectangle = options.rectangle; - return deferred.promise; - } + var stLatitude = nwCorner.latitude - options.granYCos * row + col * options.granXSin; + var cosLatitude = cos(stLatitude); + var nZ = sin(stLatitude); + var kZ = radiiSquared.z * nZ; - function doSampling(terrainProvider, level, positions) { - var tilingScheme = terrainProvider.tilingScheme; + var stLongitude = nwCorner.longitude + row * options.granYSin + col * options.granXCos; + var nX = cosLatitude * cos(stLongitude); + var nY = cosLatitude * sin(stLongitude); - var i; + var kX = radiiSquared.x * nX; + var kY = radiiSquared.y * nY; - // 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(); + var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ)); - 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); - } + position.x = kX / gamma; + position.y = kY / gamma; + position.z = kZ / gamma; - // Now append to array of points for the tile - tileRequestSet[key].positions.push(positions[i]); - } + 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; - // 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); + 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; + } } + }; - 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); - } - }; - } + var rotationMatrixScratch = new Matrix2(); + var nwCartesian = new Cartesian3(); + var centerScratch = new Cartographic(); + var centerCartesian = new Cartesian3(); + var proj = new GeographicProjection(); - function createMarkFailedFunction(tileRequest) { - var tilePositions = tileRequest.positions; - return function() { - for (var i = 0; i < tilePositions.length; ++i) { - var position = tilePositions[i]; - position.height = undefined; - } - }; - } + function getRotationOptions(nwCorner, rotation, granularityX, granularityY, center, width, height) { + var cosRotation = Math.cos(rotation); + var granYCos = granularityY * cosRotation; + var granXCos = granularityX * cosRotation; - return sampleTerrain; -}); + var sinRotation = Math.sin(rotation); + var granYSin = granularityY * sinRotation; + var granXSin = granularityX * sinRotation; -/*global define*/ -define('Core/sampleTerrainMostDetailed',[ - '../ThirdParty/when', - './defined', - './sampleTerrain', - './DeveloperError' -], function( - when, - defined, - sampleTerrain, - DeveloperError) { - "use strict"; + nwCartesian = proj.project(nwCorner, nwCartesian); - /** - * Initiates a sampleTerrain() request at the maximum available tile level for a terrain dataset. - * - * @exports sampleTerrainMostDetailed - * - * @param {TerrainProvider} terrainProvider The terrain provider from which to query 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. This - * promise will reject if the terrain provider's `availability` property is undefined. - * - * @example - * // Query the terrain height of two Cartographic positions - * var terrainProvider = new Cesium.CesiumTerrainProvider({ - * url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles' - * }); - * var positions = [ - * Cesium.Cartographic.fromDegrees(86.925145, 27.988257), - * Cesium.Cartographic.fromDegrees(87.0, 28.0) - * ]; - * var promise = Cesium.sampleTerrainMostDetailed(terrainProvider, positions); - * Cesium.when(promise, function(updatedPositions) { - * // positions[0].height and positions[1].height have been updated. - * // updatedPositions is just a reference to positions. - * }); - */ - function sampleTerrainMostDetailed(terrainProvider, positions) { - - return terrainProvider.readyPromise.then(function() { - var byLevel = []; + 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); - var availability = terrainProvider.availability; + width -= 1; + height -= 1; - - for (var i = 0; i < positions.length; ++i) { - var position = positions[i]; - var maxLevel = availability.computeMaximumLevelAtPosition(position); + var latitude = nwCorner.latitude; + var latitude0 = latitude + width * granXSin; + var latitude1 = latitude - granYCos * height; + var latitude2 = latitude - granYCos * height + width * granXSin; - var atLevel = byLevel[maxLevel]; - if (!defined(atLevel)) { - byLevel[maxLevel] = atLevel = []; - } - atLevel.push(position); - } + var north = Math.max(latitude, latitude0, latitude1, latitude2); + var south = Math.min(latitude, latitude0, latitude1, latitude2); - return when.all(byLevel.map(function(positionsAtLevel, index) { - if (defined(positionsAtLevel)) { - return sampleTerrain(terrainProvider, index, positionsAtLevel); - } - })).then(function() { - return positions; - }); - }); - } + var longitude = nwCorner.longitude; + var longitude0 = longitude + width * granXCos; + var longitude1 = longitude + height * granYSin; + var longitude2 = longitude + height * granYSin + width * granXCos; - return sampleTerrainMostDetailed; -}); + var east = Math.max(longitude, longitude0, longitude1, longitude2); + var west = Math.min(longitude, longitude0, longitude1, longitude2); -/*global define*/ -define('Core/ScreenSpaceEventType',[ - './freezeObject' - ], function( - freezeObject) { - 'use strict'; + return { + north: north, + south: south, + east: east, + west: west, + granYCos : granYCos, + granYSin : granYSin, + granXCos : granXCos, + granXSin : granXSin, + nwCorner : nwCorner + }; + } /** - * This enumerated type is for classifying mouse events: down, up, click, double click, move and move while a button is held down. - * - * @exports ScreenSpaceEventType + * @private */ - 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, + 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; - /** - * Represents a mouse left button down event. - * - * @type {Number} - * @constant - */ - RIGHT_DOWN : 5, + 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); + } - /** - * Represents a mouse right button up event. - * - * @type {Number} - * @constant - */ - RIGHT_UP : 6, + 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); + } - /** - * Represents a mouse right click event. - * - * @type {Number} - * @constant - */ - RIGHT_CLICK : 7, + var granYCos = granularityY; + var granXCos = granularityX; + var granYSin = 0.0; + var granXSin = 0.0; - /** - * Represents a mouse middle button down event. - * - * @type {Number} - * @constant - */ - MIDDLE_DOWN : 10, + var options = { + granYCos : granYCos, + granYSin : granYSin, + granXCos : granXCos, + granXSin : granXSin, + ellipsoid : ellipsoid, + surfaceHeight : surfaceHeight, + extrudedHeight : extrudedHeight, + nwCorner : nwCorner, + rectangle : rectangle, + width: width, + height: height + }; - /** - * Represents a mouse middle button up event. - * - * @type {Number} - * @constant - */ - MIDDLE_UP : 11, + 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; - /** - * Represents a mouse middle click event. - * - * @type {Number} - * @constant - */ - MIDDLE_CLICK : 12, + + options.granYCos = rotationOptions.granYCos; + options.granYSin = rotationOptions.granYSin; + options.granXCos = rotationOptions.granXCos; + options.granXSin = rotationOptions.granXSin; - /** - * Represents a mouse move event. - * - * @type {Number} - * @constant - */ - MOUSE_MOVE : 15, + rectangle.north = north; + rectangle.south = south; + rectangle.east = east; + rectangle.west = west; + } - /** - * Represents a mouse wheel event. - * - * @type {Number} - * @constant - */ - WHEEL : 16, + if (stRotation !== 0) { + rotation = rotation - stRotation; + stNwCorner = Rectangle.northwest(rectangle, stNwCorner); - /** - * Represents the start of a two-finger event on a touch surface. - * - * @type {Number} - * @constant - */ - PINCH_START : 17, + var stRotationOptions = getRotationOptions(stNwCorner, rotation, granularityX, granularityY, center, width, height); - /** - * Represents the end of a two-finger event on a touch surface. - * - * @type {Number} - * @constant - */ - PINCH_END : 18, + 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; + } - /** - * Represents a change of a two-finger event on a touch surface. - * - * @type {Number} - * @constant - */ - PINCH_MOVE : 19 + return options; }; - return freezeObject(ScreenSpaceEventType); + return RectangleGeometryLibrary; }); -/*global define*/ -define('Core/ScreenSpaceEventHandler',[ - './AssociativeArray', +define('Core/RectangleGeometry',[ + './BoundingSphere', './Cartesian2', + './Cartesian3', + './Cartographic', + './Check', + './ComponentDatatype', './defaultValue', './defined', - './destroyObject', + './defineProperties', './DeveloperError', - './FeatureDetection', - './getTimestamp', - './KeyboardEventModifier', - './ScreenSpaceEventType' + './Ellipsoid', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './GeometryInstance', + './GeometryPipeline', + './IndexDatatype', + './Math', + './Matrix3', + './PolygonPipeline', + './PrimitiveType', + './Quaternion', + './Rectangle', + './RectangleGeometryLibrary', + './VertexFormat' ], function( - AssociativeArray, + BoundingSphere, Cartesian2, + Cartesian3, + Cartographic, + Check, + ComponentDatatype, defaultValue, defined, - destroyObject, + defineProperties, DeveloperError, - FeatureDetection, - getTimestamp, - KeyboardEventModifier, - ScreenSpaceEventType) { + Ellipsoid, + Geometry, + GeometryAttribute, + GeometryAttributes, + GeometryInstance, + GeometryPipeline, + IndexDatatype, + CesiumMath, + Matrix3, + PolygonPipeline, + PrimitiveType, + Quaternion, + Rectangle, + RectangleGeometryLibrary, + VertexFormat) { '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); + var positionScratch = new Cartesian3(); + var normalScratch = new Cartesian3(); + var tangentScratch = new Cartesian3(); + var bitangentScratch = new Cartesian3(); + var rectangleScratch = new Rectangle(); + var stScratch = new Cartesian2(); + var bottomBoundingSphere = new BoundingSphere(); + var topBoundingSphere = new BoundingSphere(); - screenSpaceEventHandler._removalFunctions.push(function() { - element.removeEventListener(domType, listener, false); + function createAttributes(vertexFormat, attributes) { + var geo = new Geometry({ + attributes : new GeometryAttributes(), + primitiveType : PrimitiveType.TRIANGLES }); - } - - 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, 'mousemove', alternateElement, handleMouseMove); - registerListener(screenSpaceEventHandler, 'drag', alternateElement, handleDrag); // Jay Barra 4/15/2016 - registerListener(screenSpaceEventHandler, 'dragend', alternateElement, handleMouseUp); // Jay Barra 4/22/2016 - registerListener(screenSpaceEventHandler, 'touchstart', element, handleTouchStart); - registerListener(screenSpaceEventHandler, 'touchend', alternateElement, handleTouchEnd); - registerListener(screenSpaceEventHandler, 'touchmove', alternateElement, handleTouchMove); - registerListener(screenSpaceEventHandler, 'touchcancel', alternateElement, handleTouchEnd); + 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 + }); } - - 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'; + if (vertexFormat.tangent) { + geo.attributes.tangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : attributes.tangents + }); } - - registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel); - } - - function unregisterListeners(screenSpaceEventHandler) { - var removalFunctions = screenSpaceEventHandler._removalFunctions; - for (var i = 0; i < removalFunctions.length; ++i) { - removalFunctions[i](); + if (vertexFormat.bitangent) { + geo.attributes.bitangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : attributes.bitangents + }); } + return geo; } - 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; - } + function calculateAttributes(positions, vertexFormat, ellipsoid, tangentRotationMatrix) { + var length = positions.length; - var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); - Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition); - Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition); + var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined; + var bitangents = (vertexFormat.bitangent) ? new Float32Array(length) : undefined; - var modifier = getModifier(event); + var attrIndex = 0; + var bitangent = bitangentScratch; + var tangent = tangentScratch; + var normal = normalScratch; + if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) { + for (var i = 0; i < length; i += 3) { + var p = Cartesian3.fromArray(positions, i, positionScratch); + var attrIndex1 = attrIndex + 1; + var attrIndex2 = attrIndex + 2; - var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier); + normal = ellipsoid.geodeticSurfaceNormal(p, normal); + if (vertexFormat.tangent || vertexFormat.bitangent) { + Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent); + Matrix3.multiplyByVector(tangentRotationMatrix, tangent, tangent); + Cartesian3.normalize(tangent, tangent); - if (defined(action)) { - Cartesian2.clone(position, mouseDownEvent.position); - //acevedo - sending dom event to emp3 Cesium engine. - mouseDownEvent.domEvent = event; - action(mouseDownEvent); + if (vertexFormat.bitangent) { + Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent); + } + } - event.preventDefault(); + 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.bitangent) { + bitangents[attrIndex] = bitangent.x; + bitangents[attrIndex1] = bitangent.y; + bitangents[attrIndex2] = bitangent.z; + } + attrIndex += 3; + } } + return createAttributes(vertexFormat, { + positions : positions, + normals : normals, + tangents : tangents, + bitangents : bitangents + }); } - var mouseUpEvent = { - position : new Cartesian2() - }; - var mouseClickEvent = { - position : new Cartesian2() - }; - + var v1Scratch = new Cartesian3(); + var v2Scratch = new Cartesian3(); - function handleDrag(screenSpaceEventHandler, event) { // Jay Barra 4/15/2016 possibly make this handleDragStart instead - screenSpaceEventHandler._isDragging = true; + function calculateAttributesWall(positions, vertexFormat, ellipsoid) { + var length = positions.length; - var modifier = getModifier(event); + var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined; + var bitangents = (vertexFormat.bitangent) ? new Float32Array(length) : undefined; - var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); - var previousPosition = screenSpaceEventHandler._primaryPreviousPosition; + var normalIndex = 0; + var tangentIndex = 0; + var bitangentIndex = 0; + var recomputeNormal = true; - var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier); + var bitangent = bitangentScratch; + var tangent = tangentScratch; + var normal = normalScratch; + if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) { + for (var i = 0; i < length; i += 6) { + var p = Cartesian3.fromArray(positions, i, positionScratch); + 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 (defined(action)) { - Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition); - Cartesian2.clone(position, mouseMoveEvent.endPosition); - //acevedo - mouseMoveEvent.domEvent = event; - action(mouseMoveEvent); - } + if (Cartesian3.equalsEpsilon(p1, p, CesiumMath.EPSILON10)) { // if we've reached a corner + recomputeNormal = true; + } - Cartesian2.clone(position, previousPosition); + if (vertexFormat.tangent || vertexFormat.bitangent) { + bitangent = ellipsoid.geodeticSurfaceNormal(p, bitangent); + if (vertexFormat.tangent) { + tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent); + } + } - event.preventDefault(); - }; + 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.bitangent) { + bitangents[bitangentIndex++] = bitangent.x; + bitangents[bitangentIndex++] = bitangent.y; + bitangents[bitangentIndex++] = bitangent.z; + bitangents[bitangentIndex++] = bitangent.x; + bitangents[bitangentIndex++] = bitangent.y; + bitangents[bitangentIndex++] = bitangent.z; + } + } + } + return createAttributes(vertexFormat, { + positions : positions, + normals : normals, + tangents : tangents, + bitangents : bitangents + }); + } - function handleMouseUp(screenSpaceEventHandler, event) { - if (screenSpaceEventHandler._isDragging) { // Jay Barra 4/15/2016 - delete screenSpaceEventHandler._isDragging; - } - if (!canProcessMouseEvent(screenSpaceEventHandler)) { - return; - } + function constructRectangle(options) { + var vertexFormat = options.vertexFormat; + var ellipsoid = options.ellipsoid; + var size = options.size; + var height = options.height; + var width = options.width; - var button = event.button; - screenSpaceEventHandler._buttonDown = undefined; + var positions = (vertexFormat.position) ? new Float64Array(size * 3) : undefined; + var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : 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 posIndex = 0; + var stIndex = 0; - var modifier = getModifier(event); + var position = positionScratch; + var st = stScratch; - var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier); - var clickAction = screenSpaceEventHandler.getInputAction(clickScreenSpaceEventType, modifier); + var minX = Number.MAX_VALUE; + var minY = Number.MAX_VALUE; + var maxX = -Number.MAX_VALUE; + var maxY = -Number.MAX_VALUE; - if (defined(action) || defined(clickAction)) { - var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); + for (var row = 0; row < height; ++row) { + for (var col = 0; col < width; ++col) { + RectangleGeometryLibrary.computePosition(options, row, col, position, st); - if (defined(action)) { - Cartesian2.clone(position, mouseUpEvent.position); - //acevedo - mouseUpEvent.domEvent = event; - action(mouseUpEvent); - } + positions[posIndex++] = position.x; + positions[posIndex++] = position.y; + positions[posIndex++] = position.z; - 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 (vertexFormat.st) { + textureCoordinates[stIndex++] = st.x; + textureCoordinates[stIndex++] = st.y; - if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) { - Cartesian2.clone(position, mouseClickEvent.position); - //acevedo - mouseClickEvent.domEvent = event; - clickAction(mouseClickEvent); + minX = Math.min(minX, st.x); + minY = Math.min(minY, st.y); + maxX = Math.max(maxX, st.x); + maxY = Math.max(maxY, st.y); } } } - } - - var mouseMoveEvent = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() - }; - function handleMouseMove(screenSpaceEventHandler, event) { - if (!canProcessMouseEvent(screenSpaceEventHandler)) { - return; + 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 modifier = getModifier(event); - - var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); - var previousPosition = screenSpaceEventHandler._primaryPreviousPosition; - - var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier); + var geo = calculateAttributes(positions, vertexFormat, ellipsoid, options.tangentRotationMatrix); - if (defined(action)) { - Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition); - Cartesian2.clone(position, mouseMoveEvent.endPosition); - //acevedo - mouseMoveEvent.domEvent = event; - action(mouseMoveEvent); + 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; } - Cartesian2.clone(position, previousPosition); + geo.indices = indices; + if (vertexFormat.st) { + geo.attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : textureCoordinates + }); + } - /* if (defined(screenSpaceEventHandler._buttonDown)) { // Jay Barra 4/15/2016 commented out - event.preventDefault(); - } */ + return geo; } - var mouseDblClickEvent = { - position : new Cartesian2() - }; + 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 handleDblClick(screenSpaceEventHandler, event) { - var button = event.button; + 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; + } - var screenSpaceEventType; - if (button === MouseButton.LEFT) { - screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK; - } else { - return; - } + var scratchVertexFormat = new VertexFormat(); - var modifier = getModifier(event); + function constructExtrudedRectangle(options) { + var shadowVolume = options.shadowVolume; + 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 action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier); + var height = options.height; + var width = options.width; + var ellipsoid = options.ellipsoid; + var i; - if (defined(action)) { - getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position); - //acevedo - mouseDblClickEvent.domEvent = event; - action(mouseDblClickEvent); + if (shadowVolume) { + options.vertexFormat = VertexFormat.clone(vertexFormat, scratchVertexFormat); + options.vertexFormat.normal = true; + } + var topBottomGeo = constructRectangle(options); + if (CesiumMath.equalsEpsilon(minHeight, maxHeight, CesiumMath.EPSILON10)) { + return topBottomGeo; } - } - 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; + 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; - // 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; + var normals = (vertexFormat.normal) ? new Float32Array(newLength) : undefined; + var tangents = (vertexFormat.tangent) ? new Float32Array(newLength) : undefined; + var bitangents = (vertexFormat.bitangent) ? new Float32Array(newLength) : undefined; + var textures = (vertexFormat.st) ? new Float32Array(newLength / 3 * 2) : undefined; + var topSt; + var topNormals; + if (vertexFormat.normal) { + topNormals = topBottomGeo.attributes.normal.values; + normals.set(topNormals); + for (i = 0; i < length; i++) { + topNormals[i] = -topNormals[i]; } - } 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; + normals.set(topNormals, length); + topBottomGeo.attributes.normal.values = normals; } - - if (!defined(delta)) { - return; + if (shadowVolume) { + topNormals = topBottomGeo.attributes.normal.values; + if (!vertexFormat.normal) { + topBottomGeo.attributes.normal = undefined; + } + var extrudeNormals = new Float32Array(newLength); + for (i = 0; i < length; i++) { + topNormals[i] = -topNormals[i]; + } + extrudeNormals.set(topNormals, length); //only get normals for bottom layer that's going to be pushed down + topBottomGeo.attributes.extrudeDirection = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : extrudeNormals + }); } - var modifier = getModifier(event); - var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.WHEEL, modifier); - - if (defined(action)) { - action(delta); - - event.preventDefault(); + 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; } - } - - 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())); + if (vertexFormat.bitangent) { + var topBitangents = topBottomGeo.attributes.bitangent.values; + bitangents.set(topBitangents); + bitangents.set(topBitangents, length); + topBottomGeo.attributes.bitangent.values = bitangents; + } + if (vertexFormat.st) { + topSt = topBottomGeo.attributes.st.values; + textures.set(topSt); + textures.set(topSt, length / 3 * 2); + topBottomGeo.attributes.st.values = textures; } - 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))); + 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; - function handleTouchEnd(screenSpaceEventHandler, event) { - gotTouchEvent(screenSpaceEventHandler); + var perimeterPositions = 2 * width + 2 * height - 4; + var wallCount = (perimeterPositions + 4) * 2; - var changedTouches = event.changedTouches; + var wallPositions = new Float64Array(wallCount * 3); + var wallExtrudeNormals = shadowVolume ? new Float32Array(wallCount * 3) : undefined; + var wallTextures = (vertexFormat.st) ? new Float32Array(wallCount * 2) : undefined; - 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); + var posIndex = 0; + var stIndex = 0; + var extrudeNormalIndex = 0; + var area = width * height; + var threeI; + for (i = 0; i < area; i += width) { + threeI = i * 3; + wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); + posIndex += 6; + if (vertexFormat.st) { + wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); + stIndex += 4; } - - 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); - //acevedo - touchClickEvent.domEvent = event; - clickAction(touchClickEvent); - } - } + if (shadowVolume) { + extrudeNormalIndex += 3; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; } - - // 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(); + for (i = area - width; i < area; i++) { + threeI = i * 3; + wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); + posIndex += 6; + if (vertexFormat.st) { + wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); + stIndex += 4; } - } - - 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); - //acevedo - touchStartEvent.domEvent = event; - action(touchStartEvent); + if (shadowVolume) { + extrudeNormalIndex += 3; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; } - - 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); - //acevedo - touch2StartEvent.domEvent = event; - 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(); + for (i = area - 1; i > 0; i -= width) { + threeI = i * 3; + wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); + posIndex += 6; + if (vertexFormat.st) { + wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); + stIndex += 4; + } + if (shadowVolume) { + extrudeNormalIndex += 3; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; } } - } - - 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); + for (i = width - 1; i >= 0; i--) { + threeI = i * 3; + wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions); + posIndex += 6; + if (vertexFormat.st) { + wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt); + stIndex += 4; + } + if (shadowVolume) { + extrudeNormalIndex += 3; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1]; + wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2]; } } - fireTouchMoveEvents(screenSpaceEventHandler, event); - - var previousPositions = screenSpaceEventHandler._previousPositions; + var geo = calculateAttributesWall(wallPositions, vertexFormat, ellipsoid); - for (i = 0; i < length; ++i) { - touch = changedTouches[i]; - identifier = touch.identifier; - Cartesian2.clone(positions.get(identifier), previousPositions.get(identifier)); + if (vertexFormat.st) { + geo.attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : wallTextures + }); } - } - - var touchMoveEvent = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() - }; - var touchPinchMovementEvent = { - distance : { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() - }, - angleAndHeight : { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() + if (shadowVolume) { + geo.attributes.extrudeDirection = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : wallExtrudeNormals + }); } - }; - - 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); - //acevedo - touchMoveEvent.domEvent = event; - 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); + var wallIndices = IndexDatatype.createTypedArray(wallCount, perimeterPositions * 6); - Cartesian2.fromElements(prevAngle, prevCY, touchPinchMovementEvent.angleAndHeight.startPosition); - Cartesian2.fromElements(angle, cY, touchPinchMovementEvent.angleAndHeight.endPosition); - //acevedo - touchPinchMovementEvent.domEvent = event; - action(touchPinchMovementEvent); + 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; } - } - function handlePointerDown(screenSpaceEventHandler, event) { - event.target.setPointerCapture(event.pointerId); + geo.indices = wallIndices; - if (event.pointerType === 'touch') { - var positions = screenSpaceEventHandler._positions; + geo = GeometryPipeline.combineInstances([ + new GeometryInstance({ + geometry : topBottomGeo + }), + new GeometryInstance({ + geometry : geo + }) + ]); - var identifier = event.pointerId; - positions.set(identifier, getPosition(screenSpaceEventHandler, event, new Cartesian2())); + return geo[0]; + } - fireTouchEvents(screenSpaceEventHandler, event); + 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()]; - var previousPositions = screenSpaceEventHandler._previousPositions; - previousPositions.set(identifier, Cartesian2.clone(positions.get(identifier))); - } else { - handleMouseDown(screenSpaceEventHandler, event); + function computeRectangle(rectangle, ellipsoid, rotation) { + if (rotation === 0.0) { + return Rectangle.clone(rectangle); } - } - function handlePointerUp(screenSpaceEventHandler, event) { - if (event.pointerType === 'touch') { - var positions = screenSpaceEventHandler._positions; + Rectangle.northeast(rectangle, scratchCartographicPoints[0]); + Rectangle.northwest(rectangle, scratchCartographicPoints[1]); + Rectangle.southeast(rectangle, scratchCartographicPoints[2]); + Rectangle.southwest(rectangle, scratchCartographicPoints[3]); - var identifier = event.pointerId; - positions.remove(identifier); + ellipsoid.cartographicArrayToCartesianArray(scratchCartographicPoints, scratchRectanglePoints); - fireTouchEvents(screenSpaceEventHandler, event); + var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.center(rectangle, scratchCartesian3)); + Quaternion.fromAxisAngle(surfaceNormal, rotation, scratchQuaternion); - var previousPositions = screenSpaceEventHandler._previousPositions; - previousPositions.remove(identifier); - } else { - handleMouseUp(screenSpaceEventHandler, event); + Matrix3.fromQuaternion(scratchQuaternion, scratchRotationMatrix); + for (var i = 0; i < 4; ++i) { + // Apply the rotation + Matrix3.multiplyByVector(scratchRotationMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]); } - } - - 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); + ellipsoid.cartesianArrayToCartographicArray(scratchRectanglePoints, scratchCartographicPoints); - var previousPositions = screenSpaceEventHandler._previousPositions; - Cartesian2.clone(positions.get(identifier), previousPositions.get(identifier)); - } else { - handleMouseMove(screenSpaceEventHandler, event); - } + return Rectangle.fromCartographicArray(scratchCartographicPoints); } /** - * Handles user input events. Custom functions can be added to be executed on - * when the user enters input. + * 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 ScreenSpaceEventHandler + * @alias RectangleGeometry + * @constructor * - * @param {Canvas} [element=document] The element to add events to. + * @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. * - * @constructor + * @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 a 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 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; + function RectangleGeometry(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this._element = defaultValue(element, document); + var rectangle = options.rectangle; - registerListeners(this); + + var rotation = defaultValue(options.rotation, 0.0); + this._rectangle = rectangle; + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84)); + this._surfaceHeight = defaultValue(options.height, 0.0); + this._rotation = rotation; + this._stRotation = defaultValue(options.stRotation, 0.0); + this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT)); + this._extrudedHeight = defaultValue(options.extrudedHeight, 0.0); + this._extrude = defined(options.extrudedHeight); + this._closeTop = defaultValue(options.closeTop, true); + this._closeBottom = defaultValue(options.closeBottom, true); + this._shadowVolume = defaultValue(options.shadowVolume, false); + this._workerName = 'createRectangleGeometry'; + this._rotatedRectangle = computeRectangle(this._rectangle, this._ellipsoid, rotation); } /** - * 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 + * The number of elements used to pack the object into an array. + * @type {Number} */ - ScreenSpaceEventHandler.prototype.setInputAction = function(action, type, modifier) { - - var key = getInputEventKey(type, modifier); - this._inputEvents[key] = action; - }; + RectangleGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 9; /** - * Returns the function to be executed on an input event. + * Stores the provided instance into the provided array. * - * @param {Number} type The ScreenSpaceEventType of input event. - * @param {Number} [modifier] A KeyboardEventModifier key that is held when a type - * event occurs. + * @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. * - * @see ScreenSpaceEventHandler#setInputAction - * @see ScreenSpaceEventHandler#removeInputAction + * @returns {Number[]} The array that was packed into */ - ScreenSpaceEventHandler.prototype.getInputAction = function(type, modifier) { + RectangleGeometry.pack = function(value, array, startingIndex) { - var key = getInputEventKey(type, modifier); - return this._inputEvents[key]; + 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; + array[startingIndex] = value._shadowVolume ? 1.0 : 0.0; + + return array; }; - /** - * 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) { - - var key = getInputEventKey(type, modifier); - delete this._inputEvents[key]; + var scratchRectangle = new Rectangle(); + var scratchRotatedRectangle = new Rectangle(); + var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE); + var scratchOptions = { + rectangle : scratchRectangle, + ellipsoid : scratchEllipsoid, + vertexFormat : scratchVertexFormat, + granularity : undefined, + height : undefined, + rotation : undefined, + stRotation : undefined, + extrudedHeight : undefined, + closeTop : undefined, + closeBottom : undefined, + shadowVolume : 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. + * Retrieves an instance from a packed array. * - * @see ScreenSpaceEventHandler#destroy + * @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. */ - ScreenSpaceEventHandler.prototype.isDestroyed = function() { - return false; + RectangleGeometry.unpack = function(array, startingIndex, result) { + + 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; + var shadowVolume = 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; + scratchOptions.shadowVolume = shadowVolume; + 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; + result._shadowVolume = shadowVolume; + + return result; }; + var tangentRotationMatrixScratch = new Matrix3(); + var nwScratch = new Cartographic(); + var stNwScratch = new Cartographic(); + var quaternionScratch = new Quaternion(); + var centerScratch = new Cartographic(); /** - * 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. - * + * Computes the geometric representation of a rectangle, including its vertices, indices, and a bounding sphere. * - * @example - * handler = handler && handler.destroy(); + * @param {RectangleGeometry} rectangleGeometry A description of the rectangle. + * @returns {Geometry|undefined} The computed vertices and indices. * - * @see ScreenSpaceEventHandler#isDestroyed + * @exception {DeveloperError} Rotated rectangle is invalid. */ - ScreenSpaceEventHandler.prototype.destroy = function() { - unregisterListeners(this); + 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; + } - return destroyObject(this); - }; + 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; - /** - * 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; + var options = RectangleGeometryLibrary.computeOptions(rectangleGeometry, rectangle, nwScratch, stNwScratch); - return ScreenSpaceEventHandler; -}); + 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); + } -/*global define*/ -define('Core/ShowGeometryInstanceAttribute',[ - './ComponentDatatype', - './defaultValue', - './defined', - './defineProperties', - './DeveloperError' - ], function( - ComponentDatatype, - defaultValue, - defined, - defineProperties, - DeveloperError) { - 'use strict'; + 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) { + options.shadowVolume = rectangleGeometry._shadowVolume; + 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 : geometry.attributes, + indices : geometry.indices, + primitiveType : geometry.primitiveType, + boundingSphere : boundingSphere + }); + }; /** - * 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 + * @private */ - 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); - } + RectangleGeometry.createShadowVolume = function(rectangleGeometry, minHeightFunc, maxHeightFunc) { + var granularity = rectangleGeometry._granularity; + var ellipsoid = rectangleGeometry._ellipsoid; - 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; - } - }, + var minHeight = minHeightFunc(granularity, ellipsoid); + var maxHeight = maxHeightFunc(granularity, ellipsoid); - /** - * 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; - } - }, + // 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, + shadowVolume : true + }); + }; + defineProperties(RectangleGeometry.prototype, { /** - * 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 + * @private */ - normalize : { + rectangle : { get : function() { - return false; + return this._rotatedRectangle; } } }); - /** - * 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(result)) { - return new Uint8Array([show]); - } - result[0] = show; - return result; - }; - - return ShowGeometryInstanceAttribute; + return RectangleGeometry; }); -/*global define*/ -define('Core/Simon1994PlanetaryPositions',[ +define('Core/RectangleOutlineGeometry',[ + './BoundingSphere', './Cartesian3', + './Cartographic', + './ComponentDatatype', + './defaultValue', './defined', './DeveloperError', - './JulianDate', + './Ellipsoid', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './IndexDatatype', './Math', - './Matrix3', - './TimeConstants', - './TimeStandard' + './PolygonPipeline', + './PrimitiveType', + './Rectangle', + './RectangleGeometryLibrary' ], function( + BoundingSphere, Cartesian3, + Cartographic, + ComponentDatatype, + defaultValue, defined, DeveloperError, - JulianDate, + Ellipsoid, + Geometry, + GeometryAttribute, + GeometryAttributes, + IndexDatatype, CesiumMath, - Matrix3, - TimeConstants, - TimeStandard) { + PolygonPipeline, + PrimitiveType, + Rectangle, + RectangleGeometryLibrary) { '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. + var bottomBoundingSphere = new BoundingSphere(); + var topBoundingSphere = new BoundingSphere(); + var positionScratch = new Cartesian3(); + var rectangleScratch = new Rectangle(); - //* 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 + function constructRectangle(options) { + var size = options.size; + var height = options.height; + var width = options.width; + var positions = new Float64Array(size * 3); - var g = 6.239996 + (0.0172019696544) * daysSinceJ2000InTerrestrialTime; - return 1.657e-3 * Math.sin(g + 1.671e-2 * Math.sin(g)); - } + 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; + } - var TdtMinusTai = 32.184; - var J2000d = 2451545; - function taiToTdb(date, result) { - //Converts TAI to TT - result = JulianDate.addSeconds(date, TdtMinusTai, result); + 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; + } - //Converts TT to TDB - var days = JulianDate.totalDays(result) - J2000d; - result = JulianDate.addSeconds(result, computeTdbMinusTtSpice(days), result); + 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; + } - return result; - } + 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 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 indicesSize = positions.length/3 * 2; + var indices = IndexDatatype.createTypedArray(positions.length / 3, indicesSize); - var perifocalToEquatorial = new Matrix3(); - function elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, longitudeOfNode, meanLongitude, result) { - if (inclination < 0.0) { - inclination = -inclination; - longitudeOfNode += CesiumMath.PI; + 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 radiusOfPeriapsis = semimajorAxis * (1.0 - eccentricity); - var argumentOfPeriapsis = longitudeOfPerigee - longitudeOfNode; - var rightAscensionOfAscendingNode = longitudeOfNode; - var trueAnomaly = meanAnomalyToTrueAnomaly(meanLongitude - longitudeOfPerigee, eccentricity); - var type = chooseOrbit(eccentricity, 0.0); + var geo = new Geometry({ + attributes : new GeometryAttributes(), + primitiveType : PrimitiveType.LINES + }); - - perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscensionOfAscendingNode, perifocalToEquatorial); - var semilatus = radiusOfPeriapsis * (1.0 + eccentricity); - var costheta = Math.cos(trueAnomaly); - var sintheta = Math.sin(trueAnomaly); + geo.attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + geo.indices = indices; - var denom = (1.0 + eccentricity * costheta); + return geo; + } - - 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; + 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; - return Matrix3.multiplyByVector(perifocalToEquatorial, result, result); - } + 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; - function chooseOrbit(eccentricity, tolerance) { - - if (eccentricity <= tolerance) { - return 'Circular'; - } else if (eccentricity < 1.0 - tolerance) { - return 'Elliptical'; - } else if (eccentricity <= 1.0 + tolerance) { - return 'Parabolic'; - } else { - return 'Hyperbolic'; + 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; } - } - - // Calculates the true anomaly given the mean anomaly and the eccentricity. - function meanAnomalyToTrueAnomaly(meanAnomaly, eccentricity) { - - 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) { - - 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)); + indices[index++] = length - 1; + indices[index++] = 0; + indices[index++] = length + length - 1; + indices[index++] = length; - // Perform Newton-Raphson iteration on Kepler's equation - var eccentricAnomaly = Number.MAX_VALUE; + 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; - 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; - } + geo.indices = indices; - - eccentricAnomaly = iterationValue + revs * CesiumMath.TWO_PI; - return eccentricAnomaly; + return geo; } - // Calculates the true anomaly given the eccentric anomaly and the eccentricity. - function eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity) { - - // 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; + /** + * 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); - return trueAnomaly; - } + 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; - // Calculates the transformation matrix to convert from the perifocal (PQW) coordinate - // system to inertial cartesian coordinates. - function perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscension, result) { - 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; + this._rectangle = rectangle; + this._granularity = granularity; + this._ellipsoid = ellipsoid; + this._surfaceHeight = surfaceHeight; + this._rotation = rotation; + this._extrudedHeight = extrudedHeight; + this._workerName = 'createRectangleOutlineGeometry'; } - // 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; + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + RectangleOutlineGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + 5; - 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. + * 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) { + + startingIndex = defaultValue(startingIndex, 0); - function computeSimonEarthMoonBarycenter(date, result) { + Rectangle.pack(value._rectangle, array, startingIndex); + startingIndex += Rectangle.packedLength; - // 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); + Ellipsoid.pack(value._ellipsoid, array, startingIndex); + startingIndex += Ellipsoid.packedLength; - 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); + 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); - // 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 array; + }; - return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, - longitudeOfNode, meanLongitude, result); - } + 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 + }; /** - * Gets a point describing the position of the moon according to the equations described in section 4. + * 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. */ - 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; + RectangleOutlineGeometry.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); - // 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; + var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle); + startingIndex += Rectangle.packedLength; - // 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); + var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); + startingIndex += Ellipsoid.packedLength; - // 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); + var granularity = array[startingIndex++]; + var height = array[startingIndex++]; + var rotation = array[startingIndex++]; + var hasExtrudedHeight = array[startingIndex++]; + var extrudedHeight = array[startingIndex]; - // 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; + if (!defined(result)) { + scratchOptions.granularity = granularity; + scratchOptions.height = height; + scratchOptions.rotation = rotation; + scratchOptions.extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined; + return new RectangleOutlineGeometry(scratchOptions); + } - // 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; + 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 elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, - longitudeOfNode, meanLongitude, result); - } + return result; + }; + var nwScratch = new Cartographic(); /** - * 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. + * Computes the geometric representation of an outline of a 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. */ - 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); - } + RectangleOutlineGeometry.createGeometry = function(rectangleGeometry) { + var rectangle = Rectangle.clone(rectangleGeometry._rectangle, rectangleScratch); + var ellipsoid = rectangleGeometry._ellipsoid; + var surfaceHeight = rectangleGeometry._surfaceHeight; + var extrudedHeight = rectangleGeometry._extrudedHeight; - // 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 options = RectangleGeometryLibrary.computeOptions(rectangleGeometry, rectangle, nwScratch); + options.size = 2*options.width + 2*options.height - 4; - 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(julianDate, result){ - if (!defined(julianDate)) { - julianDate = JulianDate.now(); - } + var geometry; + var boundingSphere; + rectangle = rectangleGeometry._rectangle; - if (!defined(result)) { - result = new Cartesian3(); + 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); } - //first forward transformation - translation = computeSimonEarthMoonBarycenter(julianDate, translation); - result = Cartesian3.negate(translation, result); - - //second forward transformation - computeSimonEarth(julianDate, translation); + return new Geometry({ + attributes : geometry.attributes, + indices : geometry.indices, + primitiveType : PrimitiveType.LINES, + boundingSphere : boundingSphere + }); + }; - Cartesian3.subtract(result, translation, result); - Matrix3.multiplyByVector(axesTransformation, result, result); + return RectangleOutlineGeometry; +}); - return result; - }; +define('Core/ReferenceFrame',[ + './freezeObject' + ], function( + freezeObject) { + 'use strict'; /** - * Computes the position of the Moon in the Earth-centered inertial frame + * Constants for identifying well-known reference frames. * - * @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 + * @exports ReferenceFrame */ - Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame = function(julianDate, result){ - if (!defined(julianDate)) { - julianDate = JulianDate.now(); - } - - result = computeSimonMoon(julianDate, result); - Matrix3.multiplyByVector(axesTransformation, result, result); + var ReferenceFrame = { + /** + * The fixed frame. + * + * @type {Number} + * @constant + */ + FIXED : 0, - return result; + /** + * The inertial frame. + * + * @type {Number} + * @constant + */ + INERTIAL : 1 }; - return Simon1994PlanetaryPositions; + return freezeObject(ReferenceFrame); }); -/*global define*/ -define('Core/SimplePolylineGeometry',[ - './BoundingSphere', - './Cartesian3', - './Color', - './ComponentDatatype', - './defaultValue', +define('Core/requestAnimationFrame',[ './defined', - './DeveloperError', - './Ellipsoid', - './Geometry', - './GeometryAttribute', - './GeometryAttributes', - './IndexDatatype', - './Math', - './PolylinePipeline', - './PrimitiveType' + './getTimestamp' ], function( - BoundingSphere, - Cartesian3, - Color, - ComponentDatatype, - defaultValue, defined, - DeveloperError, - Ellipsoid, - Geometry, - GeometryAttribute, - GeometryAttributes, - IndexDatatype, - CesiumMath, - PolylinePipeline, - PrimitiveType) { + getTimestamp) { '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; + if (typeof window === 'undefined') { + return; + } - var r1 = color1.red; - var g1 = color1.green; - var b1 = color1.blue; - var a1 = color1.alpha; + var implementation = window.requestAnimationFrame; - 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); + (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; } - return offset; } - var redPerVertex = (r1 - r0) / numPoints; - var greenPerVertex = (g1 - g0) / numPoints; - var bluePerVertex = (b1 - b0) / numPoints; - var alphaPerVertex = (a1 - a0) / numPoints; + // build an implementation based on setTimeout + if (!defined(implementation)) { + var msPerFrame = 1000.0 / 60.0; + var lastFrameTime = 0; + implementation = function(callback) { + var currentTime = getTimestamp(); - 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); - } + // 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 index; - } + return setTimeout(function() { + callback(lastFrameTime); + }, delay); + }; + } + })(); /** - * 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 + * 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. * - * @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. + * @exports requestAnimationFrame * - * @exception {DeveloperError} At least two positions are required. - * @exception {DeveloperError} colors has an invalid length. + * @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. * - * @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); + * // 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 SimplePolylineGeometry(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var positions = options.positions; - var colors = options.colors; - var colorsPerVertex = defaultValue(options.colorsPerVertex, false); + 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); + } - - 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'; + /** + * 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. + */ - var numComponents = 1 + positions.length * Cartesian3.packedLength; - numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1; + return requestAnimationFrame; +}); - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - this.packedLength = numComponents + Ellipsoid.packedLength + 3; - } +define('Core/sampleTerrain',[ + '../ThirdParty/when', + './Check' + ], function( + when, + Check) { + 'use strict'; /** - * Stores the provided instance into the provided array. + * 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. This function needs the + * terrain level of detail as input, if you need to get the altitude of the terrain as precisely + * as possible (i.e. with maximum level of detail) use {@link sampleTerrainMostDetailed}. * - * @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. + * @exports sampleTerrain * - * @returns {Number[]} The array that was packed into + * @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. + * + * @see sampleTerrainMostDetailed + * + * @example + * // Query the terrain height of two Cartographic positions + * var terrainProvider = new Cesium.CesiumTerrainProvider({ + * url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles' + * }); + * 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. + * }); */ - SimplePolylineGeometry.pack = function(value, array, startingIndex) { + function sampleTerrain(terrainProvider, level, positions) { - startingIndex = defaultValue(startingIndex, 0); + return terrainProvider.readyPromise.then(function() { return doSampling(terrainProvider, level, positions); }); + } + + function doSampling(terrainProvider, level, positions) { + var tilingScheme = terrainProvider.tilingScheme; var i; - var positions = value._positions; - var length = positions.length; - array[startingIndex++] = length; + // 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(); - for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { - Cartesian3.pack(positions[i], array, startingIndex); - } + 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); + } - var colors = value._colors; - length = defined(colors) ? colors.length : 0.0; - array[startingIndex++] = length; + // Now append to array of points for the tile + tileRequestSet[key].positions.push(positions[i]); + } - for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { - Color.pack(colors[i], array, startingIndex); + // 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); + var tilePromise = when(requestPromise, createInterpolateFunction(tileRequest), createMarkFailedFunction(tileRequest)); + tilePromises.push(tilePromise); } - Ellipsoid.pack(value._ellipsoid, array, startingIndex); - startingIndex += Ellipsoid.packedLength; + return when.all(tilePromises, function() { + return positions; + }); + } - array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0; - array[startingIndex++] = value._followSurface ? 1.0 : 0.0; - array[startingIndex] = value._granularity; + 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); + } + }; + } - return array; - }; + 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; +}); + +define('Core/sampleTerrainMostDetailed',[ + '../ThirdParty/when', + './defined', + './DeveloperError', + './sampleTerrain' + ], function( + when, + defined, + DeveloperError, + sampleTerrain) { + 'use strict'; /** - * Retrieves an instance from a packed array. + * Initiates a sampleTerrain() request at the maximum available tile level for a terrain dataset. * - * @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. + * @exports sampleTerrainMostDetailed + * + * @param {TerrainProvider} terrainProvider The terrain provider from which to query 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. This + * promise will reject if the terrain provider's `availability` property is undefined. + * + * @example + * // Query the terrain height of two Cartographic positions + * var terrainProvider = new Cesium.CesiumTerrainProvider({ + * url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles' + * }); + * var positions = [ + * Cesium.Cartographic.fromDegrees(86.925145, 27.988257), + * Cesium.Cartographic.fromDegrees(87.0, 28.0) + * ]; + * var promise = Cesium.sampleTerrainMostDetailed(terrainProvider, positions); + * Cesium.when(promise, function(updatedPositions) { + * // positions[0].height and positions[1].height have been updated. + * // updatedPositions is just a reference to positions. + * }); */ - SimplePolylineGeometry.unpack = function(array, startingIndex, result) { + function sampleTerrainMostDetailed(terrainProvider, positions) { - 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; + return terrainProvider.readyPromise.then(function() { + var byLevel = []; - for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { - colors[i] = Color.unpack(array, startingIndex); - } + var availability = terrainProvider.availability; - var ellipsoid = Ellipsoid.unpack(array, startingIndex); - startingIndex += Ellipsoid.packedLength; + + for (var i = 0; i < positions.length; ++i) { + var position = positions[i]; + var maxLevel = availability.computeMaximumLevelAtPosition(position); - var colorsPerVertex = array[startingIndex++] === 1.0; - var followSurface = array[startingIndex++] === 1.0; - var granularity = array[startingIndex]; + var atLevel = byLevel[maxLevel]; + if (!defined(atLevel)) { + byLevel[maxLevel] = atLevel = []; + } + atLevel.push(position); + } - if (!defined(result)) { - return new SimplePolylineGeometry({ - positions : positions, - colors : colors, - ellipsoid : ellipsoid, - colorsPerVertex : colorsPerVertex, - followSurface : followSurface, - granularity : granularity + return when.all(byLevel.map(function(positionsAtLevel, index) { + if (defined(positionsAtLevel)) { + return sampleTerrain(terrainProvider, index, positionsAtLevel); + } + })).then(function() { + return positions; }); - } - - result._positions = positions; - result._colors = colors; - result._ellipsoid = ellipsoid; - result._colorsPerVertex = colorsPerVertex; - result._followSurface = followSurface; - result._granularity = granularity; + }); + } - return result; - }; + return sampleTerrainMostDetailed; +}); - var scratchArray1 = new Array(2); - var scratchArray2 = new Array(2); - var generateArcOptionsScratch = { - positions : scratchArray1, - height: scratchArray2, - ellipsoid: undefined, - minDistance : undefined - }; +define('Core/ScreenSpaceEventType',[ + './freezeObject' + ], function( + freezeObject) { + 'use strict'; /** - * Computes the geometric representation of a simple polyline, including its vertices, indices, and a bounding sphere. + * This enumerated type is for classifying mouse events: down, up, click, double click, move and move while a button is held down. * - * @param {SimplePolylineGeometry} simplePolylineGeometry A description of the polyline. - * @returns {Geometry} The computed vertices and indices. + * @exports ScreenSpaceEventType */ - 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 ScreenSpaceEventType = { + /** + * Represents a mouse left button down event. + * + * @type {Number} + * @constant + */ + LEFT_DOWN : 0, - var i; - var length = positions.length; + /** + * Represents a mouse left button up event. + * + * @type {Number} + * @constant + */ + LEFT_UP : 1, - var positionValues; - var numberOfPositions; - var colorValues; - var color; - var offset = 0; + /** + * Represents a mouse left click event. + * + * @type {Number} + * @constant + */ + LEFT_CLICK : 2, - if (followSurface) { - var heights = PolylinePipeline.extractHeights(positions, ellipsoid); - var generateArcOptions = generateArcOptionsScratch; - generateArcOptions.minDistance = minDistance; - generateArcOptions.ellipsoid = ellipsoid; + /** + * Represents a mouse left double click event. + * + * @type {Number} + * @constant + */ + LEFT_DOUBLE_CLICK : 3, - if (perSegmentColors) { - var positionCount = 0; - for (i = 0; i < length - 1; i++) { - positionCount += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance) + 1; - } + /** + * Represents a mouse left button down event. + * + * @type {Number} + * @constant + */ + RIGHT_DOWN : 5, - positionValues = new Float64Array(positionCount * 3); - colorValues = new Uint8Array(positionCount * 4); + /** + * Represents a mouse right button up event. + * + * @type {Number} + * @constant + */ + RIGHT_UP : 6, - generateArcOptions.positions = scratchArray1; - generateArcOptions.height= scratchArray2; + /** + * Represents a mouse right click event. + * + * @type {Number} + * @constant + */ + RIGHT_CLICK : 7, - var ci = 0; - for (i = 0; i < length - 1; ++i) { - scratchArray1[0] = positions[i]; - scratchArray1[1] = positions[i + 1]; + /** + * Represents a mouse middle button down event. + * + * @type {Number} + * @constant + */ + MIDDLE_DOWN : 10, - scratchArray2[0] = heights[i]; - scratchArray2[1] = heights[i + 1]; + /** + * Represents a mouse middle button up event. + * + * @type {Number} + * @constant + */ + MIDDLE_UP : 11, - var pos = PolylinePipeline.generateArc(generateArcOptions); + /** + * Represents a mouse middle click event. + * + * @type {Number} + * @constant + */ + MIDDLE_CLICK : 12, - 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); - } - } + /** + * Represents a mouse move event. + * + * @type {Number} + * @constant + */ + MOUSE_MOVE : 15, - positionValues.set(pos, offset); - offset += pos.length; - } - } else { - generateArcOptions.positions = positions; - generateArcOptions.height= heights; - positionValues = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); + /** + * Represents a mouse wheel event. + * + * @type {Number} + * @constant + */ + WHEEL : 16, - if (defined(colors)) { - colorValues = new Uint8Array(positionValues.length / 3 * 4); + /** + * Represents the start of a two-finger event on a touch surface. + * + * @type {Number} + * @constant + */ + PINCH_START : 17, - 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); - } + /** + * Represents the end of a two-finger event on a touch surface. + * + * @type {Number} + * @constant + */ + PINCH_END : 18, - 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; + /** + * Represents a change of a two-finger event on a touch surface. + * + * @type {Number} + * @constant + */ + PINCH_MOVE : 19 + }; - var positionIndex = 0; - var colorIndex = 0; + return freezeObject(ScreenSpaceEventType); +}); - for (i = 0; i < length; ++i) { - var p = positions[i]; +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'; - if (perSegmentColors && i > 0) { - Cartesian3.pack(p, positionValues, positionIndex); - positionIndex += 3; + function getPosition(screenSpaceEventHandler, event, result) { + var element = screenSpaceEventHandler._element; + if (element === document) { + result.x = event.clientX; + result.y = event.clientY; + return result; + } - 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); - } + var rect = element.getBoundingClientRect(); + result.x = event.clientX - rect.left; + result.y = event.clientY - rect.top; + return result; + } - if (perSegmentColors && i === length - 1) { - break; - } + function getInputEventKey(type, modifier) { + var key = type; + if (defined(modifier)) { + key += '+' + modifier; + } + return key; + } - Cartesian3.pack(p, positionValues, positionIndex); - positionIndex += 3; + function getModifier(event) { + if (event.shiftKey) { + return KeyboardEventModifier.SHIFT; + } else if (event.ctrlKey) { + return KeyboardEventModifier.CTRL; + } else if (event.altKey) { + return KeyboardEventModifier.ALT; + } - 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); - } - } + 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); - var attributes = new GeometryAttributes(); - attributes.position = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : positionValues + screenSpaceEventHandler._removalFunctions.push(function() { + element.removeEventListener(domType, listener, false); }); + } - if (defined(colors)) { - attributes.color = new GeometryAttribute({ - componentDatatype : ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 4, - values : colorValues, - normalize : true - }); + 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); } - numberOfPositions = positionValues.length / 3; - var numberOfIndices = (numberOfPositions - 1) * 2; - var indices = IndexDatatype.createTypedArray(numberOfPositions, numberOfIndices); + registerListener(screenSpaceEventHandler, 'dblclick', element, handleDblClick); - var index = 0; - for (i = 0; i < numberOfPositions - 1; ++i) { - indices[index++] = i; - indices[index++] = i + 1; + // 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'; } - return new Geometry({ - attributes : attributes, - indices : indices, - primitiveType : PrimitiveType.LINES, - boundingSphere : BoundingSphere.fromPoints(positions) - }); - }; + registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel); + } - return SimplePolylineGeometry; -}); + function unregisterListeners(screenSpaceEventHandler) { + var removalFunctions = screenSpaceEventHandler._removalFunctions; + for (var i = 0; i < removalFunctions.length; ++i) { + removalFunctions[i](); + } + } -/*global define*/ -define('Core/SphereGeometry',[ - './Cartesian3', - './Check', - './defaultValue', - './defined', - './EllipsoidGeometry', - './VertexFormat' - ], function( - Cartesian3, - Check, - defaultValue, - defined, - EllipsoidGeometry, - VertexFormat) { - 'use strict'; + var mouseDownEvent = { + position : new Cartesian2() + }; - /** - * 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 - }; + function gotTouchEvent(screenSpaceEventHandler) { + screenSpaceEventHandler._lastSeenTouchEvent = getTimestamp(); + } - this._ellipsoidGeometry = new EllipsoidGeometry(ellipsoidOptions); - this._workerName = 'createSphereGeometry'; + function canProcessMouseEvent(screenSpaceEventHandler) { + return (getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent) > ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds; } - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - SphereGeometry.packedLength = EllipsoidGeometry.packedLength; + function handleMouseDown(screenSpaceEventHandler, event) { + if (!canProcessMouseEvent(screenSpaceEventHandler)) { + return; + } - /** - * 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) { - - return EllipsoidGeometry.pack(value._ellipsoidGeometry, array, startingIndex); - }; + var button = event.button; + screenSpaceEventHandler._buttonDown = button; - var scratchEllipsoidGeometry = new EllipsoidGeometry(); - var scratchOptions = { - radius : undefined, - radii : new Cartesian3(), - vertexFormat : new VertexFormat(), - stackPartitions : undefined, - slicePartitions : undefined - }; + 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; + } - /** - * 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; + var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); + Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition); + Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition); - if (!defined(result)) { - scratchOptions.radius = ellipsoidGeometry._radii.x; - return new SphereGeometry(scratchOptions); + var modifier = getModifier(event); + + var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier); + + if (defined(action)) { + Cartesian2.clone(position, mouseDownEvent.position); + //acevedo - sending dom event to emp3 Cesium engine. + mouseDownEvent.domEvent = event; + action(mouseDownEvent); + + event.preventDefault(); } + } - Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii); - result._ellipsoidGeometry = new EllipsoidGeometry(scratchOptions); - return result; + var mouseUpEvent = { + position : new Cartesian2() }; - - /** - * 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); + var mouseClickEvent = { + position : new Cartesian2() }; - return SphereGeometry; -}); + function handleMouseUp(screenSpaceEventHandler, event) { + if (!canProcessMouseEvent(screenSpaceEventHandler)) { + return; + } -/*global define*/ -define('Core/SphereOutlineGeometry',[ - './Cartesian3', - './Check', - './defaultValue', - './defined', - './EllipsoidOutlineGeometry' - ], function( - Cartesian3, - Check, - defaultValue, - defined, - EllipsoidOutlineGeometry) { - 'use strict'; + var button = event.button; + screenSpaceEventHandler._buttonDown = undefined; - /** - * 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 - }; + 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; + } - this._ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions); - this._workerName = 'createSphereOutlineGeometry'; - } + var modifier = getModifier(event); - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - SphereOutlineGeometry.packedLength = EllipsoidOutlineGeometry.packedLength; + var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier); + var clickAction = screenSpaceEventHandler.getInputAction(clickScreenSpaceEventType, modifier); - /** - * 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) { - - return EllipsoidOutlineGeometry.pack(value._ellipsoidGeometry, array, startingIndex); - }; + if (defined(action) || defined(clickAction)) { + var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); - var scratchEllipsoidGeometry = new EllipsoidOutlineGeometry(); - var scratchOptions = { - radius : undefined, - radii : new Cartesian3(), - stackPartitions : undefined, - slicePartitions : undefined, - subdivisions : undefined - }; + if (defined(action)) { + Cartesian2.clone(position, mouseUpEvent.position); + //acevedo + mouseUpEvent.domEvent = event; + action(mouseUpEvent); + } - /** - * 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(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 (!defined(result)) { - scratchOptions.radius = ellipsoidGeometry._radii.x; - return new SphereOutlineGeometry(scratchOptions); + if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) { + Cartesian2.clone(position, mouseClickEvent.position); + //acevedo + mouseClickEvent.domEvent = event; + clickAction(mouseClickEvent); + } + } } + } - Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii); - result._ellipsoidGeometry = new EllipsoidOutlineGeometry(scratchOptions); - return result; + var mouseMoveEvent = { + startPosition : new Cartesian2(), + endPosition : new Cartesian2() }; - /** - * 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); - }; + function handleMouseMove(screenSpaceEventHandler, event) { + if (!canProcessMouseEvent(screenSpaceEventHandler)) { + return; + } - return SphereOutlineGeometry; -}); + var modifier = getModifier(event); -/*global define*/ -define('Core/Spherical',[ - './Check', - './defaultValue', - './defined' - ], function( - Check, - defaultValue, - defined) { - 'use strict'; + var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition); + var previousPosition = screenSpaceEventHandler._primaryPreviousPosition; - /** - * 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); - } + var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier); - /** - * Converts the provided Cartesian3 into Spherical coordinates. - * - * @param {Cartesian3} cartesian3 The Cartesian3 to be converted to Spherical. - * @param {Spherical} [result] 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) { - - var x = cartesian3.x; - var y = cartesian3.y; - var z = cartesian3.z; - var radialSquared = x * x + y * y; + if (defined(action)) { + Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition); + Cartesian2.clone(position, mouseMoveEvent.endPosition); + //acevedo + mouseMoveEvent.domEvent = event; + action(mouseMoveEvent); + } - if (!defined(result)) { - result = new Spherical(); + Cartesian2.clone(position, previousPosition); + + if (defined(screenSpaceEventHandler._buttonDown)) { + event.preventDefault(); } + } - result.clock = Math.atan2(y, x); - result.cone = Math.atan2(Math.sqrt(radialSquared), z); - result.magnitude = Math.sqrt(radialSquared + z * z); - return result; + var mouseDblClickEvent = { + position : new Cartesian2() }; - /** - * 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; + function handleDblClick(screenSpaceEventHandler, event) { + var button = event.button; + + var screenSpaceEventType; + if (button === MouseButton.LEFT) { + screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK; + } else { + return; } - if (!defined(result)) { - return new Spherical(spherical.clock, spherical.cone, spherical.magnitude); + var modifier = getModifier(event); + + var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier); + + if (defined(action)) { + getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position); + //acevedo + mouseDblClickEvent.domEvent = event; + action(mouseDblClickEvent); } + } - result.clock = spherical.clock; - result.cone = spherical.cone; - result.magnitude = spherical.magnitude; - return result; - }; + 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; - /** - * 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(result)) { - return new Spherical(spherical.clock, spherical.cone, 1.0); + // 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; } - result.clock = spherical.clock; - result.cone = spherical.cone; - result.magnitude = 1.0; - return result; - }; + if (!defined(delta)) { + return; + } - /** - * 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)); - }; + var modifier = getModifier(event); + var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.WHEEL, modifier); - /** - * 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)); - }; + if (defined(action)) { + action(delta); - /** - * 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); - }; + event.preventDefault(); + } + } - /** - * 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); - }; + function handleTouchStart(screenSpaceEventHandler, event) { + gotTouchEvent(screenSpaceEventHandler); - /** - * 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); - }; + var changedTouches = event.changedTouches; - /** - * 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 + ')'; - }; + var i; + var length = changedTouches.length; + var touch; + var identifier; + var positions = screenSpaceEventHandler._positions; - return Spherical; -}); + for (i = 0; i < length; ++i) { + touch = changedTouches[i]; + identifier = touch.identifier; + positions.set(identifier, getPosition(screenSpaceEventHandler, touch, new Cartesian2())); + } -/*global define*/ -define('Core/subdivideArray',[ - './defined', - './DeveloperError' - ], function( - defined, - DeveloperError) { - 'use strict'; + fireTouchEvents(screenSpaceEventHandler, event); - /** - * 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) { - - 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; + var previousPositions = screenSpaceEventHandler._previousPositions; + + for (i = 0; i < length; ++i) { + touch = changedTouches[i]; + identifier = touch.identifier; + previousPositions.set(identifier, Cartesian2.clone(positions.get(identifier))); } - return result; } - return subdivideArray; -}); + function handleTouchEnd(screenSpaceEventHandler, event) { + gotTouchEvent(screenSpaceEventHandler); -/*global define*/ -define('Core/TerrainData',[ - './defineProperties', - './DeveloperError' - ], function( - defineProperties, - DeveloperError) { - 'use strict'; + var changedTouches = event.changedTouches; - /** - * 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(); - } + var i; + var length = changedTouches.length; + var touch; + var identifier; + var positions = screenSpaceEventHandler._positions; - defineProperties(TerrainData.prototype, { - /** - * An array of credits for this tile. - * @memberof TerrainData.prototype - * @type {Credit[]} - */ - credits : { - get : DeveloperError.throwInstantiationError - }, - /** - * 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 + for (i = 0; i < length; ++i) { + touch = changedTouches[i]; + identifier = touch.identifier; + positions.remove(identifier); } - }); - /** - * 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; + fireTouchEvents(screenSpaceEventHandler, event); - /** - * 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; + var previousPositions = screenSpaceEventHandler._previousPositions; - /** - * 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; + for (i = 0; i < length; ++i) { + touch = changedTouches[i]; + identifier = touch.identifier; + previousPositions.remove(identifier); + } + } - /** - * 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; + var touchStartEvent = { + position : new Cartesian2() + }; + var touch2StartEvent = { + position1 : new Cartesian2(), + position2 : new Cartesian2() + }; + var touchEndEvent = { + position : new Cartesian2() + }; + var touchClickEvent = { + position : new Cartesian2() + }; - /** - * 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; + function fireTouchEvents(screenSpaceEventHandler, event) { + var modifier = getModifier(event); + var positions = screenSpaceEventHandler._positions; + var previousPositions = screenSpaceEventHandler._previousPositions; + var numberOfTouches = positions.length; + var action; + var clickAction; - return TerrainData; -}); + 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); -/*global define*/ -define('Core/TilingScheme',[ - './defineProperties', - './DeveloperError' - ], function( - defineProperties, - DeveloperError) { - 'use strict'; + if (defined(action)) { + Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchEndEvent.position); - /** - * 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) { + action(touchEndEvent); } - defineProperties(TilingScheme.prototype, { - /** - * Gets the ellipsoid that is tiled by the tiling scheme. - * @memberof TilingScheme.prototype - * @type {Ellipsoid} - */ - ellipsoid: { - get : DeveloperError.throwInstantiationError - }, + if (numberOfTouches === 0) { + // releasing single touch, check for CLICK + clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_CLICK, modifier); - /** - * Gets the rectangle, in radians, covered by this tiling scheme. - * @memberof TilingScheme.prototype - * @type {Rectangle} - */ - rectangle : { - get : DeveloperError.throwInstantiationError + 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); + //acevedo + touchClickEvent.domEvent = event; + 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); + //acevedo + touchStartEvent.domEvent = event; + 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); + //acevedo + touch2StartEvent.domEvent = event; + 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; - /** - * Gets the map projection used by the tiling scheme. - * @memberof TilingScheme.prototype - * @type {MapProjection} - */ - projection : { - get : DeveloperError.throwInstantiationError + 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); + //acevedo + touchMoveEvent.domEvent = event; + 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); + //acevedo + touchPinchMovementEvent.domEvent = event; + 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); + } + } /** - * Gets the total number of tiles in the X direction at a specified level-of-detail. - * @function + * Handles user input events. Custom functions can be added to be executed on + * when the user enters input. * - * @param {Number} level The level-of-detail. - * @returns {Number} The number of tiles in the X direction at the given level. + * @alias ScreenSpaceEventHandler + * + * @param {Canvas} [element=document] The element to add events to. + * + * @constructor */ - TilingScheme.prototype.getNumberOfXTilesAtLevel = DeveloperError.throwInstantiationError; + 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); + } /** - * Gets the total number of tiles in the Y direction at a specified level-of-detail. - * @function + * Set a function to be executed on an input event. * - * @param {Number} level The level-of-detail. - * @returns {Number} The number of tiles in the Y direction at the given level. + * @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 */ - TilingScheme.prototype.getNumberOfYTilesAtLevel = DeveloperError.throwInstantiationError; + ScreenSpaceEventHandler.prototype.setInputAction = function(action, type, modifier) { + + var key = getInputEventKey(type, modifier); + this._inputEvents[key] = action; + }; /** - * Transforms a rectangle specified in geodetic radians to the native coordinate system - * of this tiling scheme. - * @function + * Returns the function to be executed on an input event. * - * @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. + * @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 */ - TilingScheme.prototype.rectangleToNativeRectangle = DeveloperError.throwInstantiationError; + ScreenSpaceEventHandler.prototype.getInputAction = function(type, modifier) { + + var key = getInputEventKey(type, modifier); + return this._inputEvents[key]; + }; /** - * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates - * of the tiling scheme. - * @function + * Removes the function to be executed on an input event. * - * @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. + * @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 */ - TilingScheme.prototype.tileXYToNativeRectangle = DeveloperError.throwInstantiationError; + ScreenSpaceEventHandler.prototype.removeInputAction = function(type, modifier) { + + var key = getInputEventKey(type, modifier); + delete this._inputEvents[key]; + }; /** - * Converts tile x, y coordinates and level to a cartographic rectangle in radians. - * @function + * 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. * - * @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. + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see ScreenSpaceEventHandler#destroy */ - TilingScheme.prototype.tileXYToRectangle = DeveloperError.throwInstantiationError; + ScreenSpaceEventHandler.prototype.isDestroyed = function() { + return false; + }; /** - * Calculates the tile x, y coordinates of the tile containing - * a given cartographic position. - * @function + * 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. * - * @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. + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * + * @example + * handler = handler && handler.destroy(); + * + * @see ScreenSpaceEventHandler#isDestroyed */ - TilingScheme.prototype.positionToTileXY = DeveloperError.throwInstantiationError; + ScreenSpaceEventHandler.prototype.destroy = function() { + unregisterListeners(this); - return TilingScheme; + 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. + * @type {Number} + * @default 800 + */ + ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800; + + return ScreenSpaceEventHandler; }); -/*global define*/ -define('Core/TimeIntervalCollection',[ - './binarySearch', +define('Core/ShowGeometryInstanceAttribute',[ + './ComponentDatatype', './defaultValue', './defined', './defineProperties', - './DeveloperError', - './Event', - './JulianDate', - './TimeInterval' + './DeveloperError' ], function( - binarySearch, + ComponentDatatype, defaultValue, defined, defineProperties, - DeveloperError, - Event, - JulianDate, - TimeInterval) { + DeveloperError) { '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 + * Value and type information for per-instance geometry attribute that determines if the geometry instance will be shown. + * + * @alias ShowGeometryInstanceAttribute * @constructor * - * @param {TimeInterval[]} [intervals] An array of intervals to add to the collection. + * @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 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; - } - }, + function ShowGeometryInstanceAttribute(show) { + show = defaultValue(show, true); /** - * Gets the stop time of the collection. - * @memberof TimeIntervalCollection.prototype - * @type {JulianDate} - * @readonly + * The values for the attributes stored in a typed array. + * + * @type Uint8Array + * + * @default [1.0] */ - stop : { - get : function() { - var intervals = this._intervals; - var length = intervals.length; - return length === 0 ? undefined : intervals[length - 1].stop; - } - }, + this.value = ShowGeometryInstanceAttribute.toValue(show); + } + defineProperties(ShowGeometryInstanceAttribute.prototype, { /** - * Gets whether or not the stop time is included in the collection. - * @memberof TimeIntervalCollection.prototype - * @type {Boolean} + * 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} */ - isStopIncluded : { + componentDatatype : { get : function() { - var intervals = this._intervals; - var length = intervals.length; - return length === 0 ? false : intervals[length - 1].isStopIncluded; + return ComponentDatatype.UNSIGNED_BYTE; } }, /** - * Gets the number of intervals in the collection. - * @memberof TimeIntervalCollection.prototype + * The number of components in the attributes, i.e., {@link ColorGeometryInstanceAttribute#value}. + * + * @memberof ShowGeometryInstanceAttribute.prototype + * * @type {Number} * @readonly + * + * @default 1 */ - length : { + componentsPerAttribute : { get : function() { - return this._intervals.length; + return 1; } }, /** - * Gets whether or not the collection is empty. - * @memberof TimeIntervalCollection.prototype + * 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 */ - isEmpty : { + normalize : { get : function() { - return this._intervals.length === 0; + return false; } } }); /** - * Compares this instance against the provided instance componentwise and returns - * true if they are equal, false otherwise. + * Converts a boolean show to a typed array that can be used to assign a show attribute. * - * @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 {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. * - * @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. + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true, attributes.show); */ - TimeIntervalCollection.prototype.get = function(index) { + ShowGeometryInstanceAttribute.toValue = function(show, result) { - 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); + if (!defined(result)) { + return new Uint8Array([show]); } + result[0] = show; + return result; }; - /** - * 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; - }; + return ShowGeometryInstanceAttribute; +}); - /** - * 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; - }; +define('Core/Simon1994PlanetaryPositions',[ + './Cartesian3', + './defined', + './DeveloperError', + './JulianDate', + './Math', + './Matrix3', + './TimeConstants', + './TimeStandard' + ], function( + Cartesian3, + defined, + DeveloperError, + JulianDate, + CesiumMath, + Matrix3, + TimeConstants, + TimeStandard) { + 'use strict'; /** - * Checks if the specified date is inside this collection. + * Contains functions for finding the Cartesian coordinates of the sun and the moon in the + * Earth-centered inertial frame. * - * @param {JulianDate} julianDate The date to check. - * @returns {Boolean} true if the collection contains the specified date, false otherwise. + * @exports Simon1994PlanetaryPositions */ - TimeIntervalCollection.prototype.contains = function(julianDate) { - return this.indexOf(julianDate) >= 0; - }; + var Simon1994PlanetaryPositions = {}; - var indexOfScratch = new TimeInterval(); + 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 + *--------------------------------------------------------------------*/ - /** - * 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) { - - 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; - } + //* 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. - if (index > 0 && intervals[index - 1].stop.equals(date) && intervals[index - 1].isStopIncluded) { - return index - 1; - } - return ~index; - } + //* 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 - index = ~index; - if (index > 0 && (index - 1) < intervals.length && TimeInterval.contains(intervals[index - 1], date)) { - return index - 1; - } - return ~index; - }; + var g = 6.239996 + (0.0172019696544) * daysSinceJ2000InTerrestrialTime; + return 1.657e-3 * Math.sin(g + 1.671e-2 * Math.sin(g)); + } - /** - * 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 TdtMinusTai = 32.184; + var J2000d = 2451545; + function taiToTdb(date, result) { + //Converts TAI to TT + result = JulianDate.addSeconds(date, TdtMinusTai, result); - 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]; - } + //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; } - 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 (interval.isEmpty) { - return; - } + 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); - var comparison; - var index; - var intervals = this._intervals; + + perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscensionOfAscendingNode, perifocalToEquatorial); + var semilatus = radiusOfPeriapsis * (1.0 + eccentricity); + var costheta = Math.cos(trueAnomaly); + var sintheta = Math.sin(trueAnomaly); - // 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; - } + var denom = (1.0 + eccentricity * costheta); - // Keep the list sorted by the start date - index = binarySearch(intervals, interval, compareIntervalStartTimes); - if (index < 0) { - index = ~index; + + var radius = semilatus / denom; + if (!defined(result)) { + result = new Cartesian3(radius * costheta, radius * sintheta, 0.0); } 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; - } + result.x = radius * costheta; + result.y = radius * sintheta; + result.z = 0.0; } - 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 - }); - } - } - } - } + return Matrix3.multiplyByVector(perifocalToEquatorial, result, result); + } - 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; - } + function chooseOrbit(eccentricity, tolerance) { + + if (eccentricity <= tolerance) { + return 'Circular'; + } else if (eccentricity < 1.0 - tolerance) { + return 'Elliptical'; + } else if (eccentricity <= 1.0 + tolerance) { + return 'Parabolic'; } + return 'Hyperbolic'; + } - // 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) { + // Calculates the true anomaly given the mean anomaly and the eccentricity. + function meanAnomalyToTrueAnomaly(meanAnomaly, eccentricity) { - if (interval.isEmpty) { - return false; - } + var eccentricAnomaly = meanAnomalyToEccentricAnomaly(meanAnomaly, eccentricity); + return eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity); + } - var result = false; - var intervals = this._intervals; + var maxIterationCount = 50; + var keplerEqConvergence = CesiumMath.EPSILON8; + // Calculates the eccentric anomaly given the mean anomaly and the eccentricity. + function meanAnomalyToEccentricAnomaly(meanAnomaly, eccentricity) { + + var revs = Math.floor(meanAnomaly / CesiumMath.TWO_PI); - var index = binarySearch(intervals, interval, compareIntervalStartTimes); - if (index < 0) { - index = ~index; - } + // Find angle in current revolution + meanAnomaly -= revs * CesiumMath.TWO_PI; - var intervalStart = interval.start; - var intervalStop = interval.stop; - var intervalIsStartIncluded = interval.isStartIncluded; - var intervalIsStopIncluded = interval.isStopIncluded; + // calculate starting value for iteration sequence + var iterationValue = meanAnomaly + (eccentricity * Math.sin(meanAnomaly)) / + (1.0 - Math.sin(meanAnomaly + eccentricity) + Math.sin(meanAnomaly)); - // 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; + // Perform Newton-Raphson iteration on Kepler's equation + var eccentricAnomaly = Number.MAX_VALUE; - 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 - }); - } + 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; } - // 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; + + eccentricAnomaly = iterationValue + revs * CesiumMath.TWO_PI; + return eccentricAnomaly; + } - intervals.splice(index, 0, new TimeInterval({ - start : indexInterval.start, - stop : indexInterval.start, - isStartIncluded : true, - isStopIncluded : true, - data : indexInterval.data - })); - ++index; - indexInterval = intervals[index]; - } + // Calculates the true anomaly given the eccentric anomaly and the eccentricity. + function eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity) { + + // Calculate the number of previous revolutions + var revs = Math.floor(eccentricAnomaly / CesiumMath.TWO_PI); - // 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]; - } + // Find angle in current revolution + eccentricAnomaly -= revs * CesiumMath.TWO_PI; - // 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; + // Calculate true anomaly from eccentric anomaly + var trueAnomalyX = Math.cos(eccentricAnomaly) - eccentricity; + var trueAnomalyY = Math.sin(eccentricAnomaly) * Math.sqrt(1 - eccentricity * eccentricity); - 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); - } - } + var trueAnomaly = Math.atan2(trueAnomalyY, trueAnomalyX); - // 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 - }); + // Ensure the correct quadrant + trueAnomaly = CesiumMath.zeroToTwoPi(trueAnomaly); + if (eccentricAnomaly < 0) + { + trueAnomaly -= CesiumMath.TWO_PI; } - if (result) { - this._changedEvent.raiseEvent(this); - } + // Add on previous revolutions + trueAnomaly += revs * CesiumMath.TWO_PI; - return result; - }; + return trueAnomaly; + } - /** - * 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) { + // Calculates the transformation matrix to convert from the perifocal (PQW) coordinate + // system to inertial cartesian coordinates. + function perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscension, result) { - var left = 0; - var right = 0; - var result = new TimeIntervalCollection(); - var intervals = this._intervals; - var otherIntervals = other._intervals; + var cosap = Math.cos(argumentOfPeriapsis); + var sinap = Math.sin(argumentOfPeriapsis); - 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 cosi = Math.cos(inclination); + var sini = Math.sin(inclination); - 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); - } - } + 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, - if (JulianDate.lessThan(leftInterval.stop, rightInterval.stop) || - (leftInterval.stop.equals(rightInterval.stop) && - !leftInterval.isStopIncluded && - rightInterval.isStopIncluded)) { - ++left; - } else { - ++right; - } - } + 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; - }; - - 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; + // From section 5.8 + var semiMajorAxis0 = 1.0000010178 * MetersPerAstronomicalUnit; + var meanLongitude0 = 100.46645683 * RadiansPerDegree; + var meanLongitude1 = 1295977422.83429 * RadiansPerArcSecond; - /** - * 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)); + // 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; - /** - * 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)); + 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; - /** - * 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)); - }; + 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; - /** - * 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)); - }; + var q1u = 10; + var q2u = 16002; + var q3u = 21863; + var q4u = 10931; + var q5u = 1473; + var q6u = 32004; + var q7u = 4387; + var q8u = 73; - return TranslationRotationScale; -}); + 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; -/*global define*/ -define('Core/VideoSynchronizer',[ - './defaultValue', - './defined', - './defineProperties', - './destroyObject', - './Iso8601', - './JulianDate' - ], function( - defaultValue, - defined, - defineProperties, - destroyObject, - Iso8601, - JulianDate) { - 'use strict'; + 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); /** - * 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} + * Gets a point describing the motion of the Earth-Moon barycenter according to the equations + * described in section 6. */ - function VideoSynchronizer(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this._clock = undefined; - this._element = undefined; - this._clockSubscription = undefined; - this._seekFunction = undefined; + function computeSimonEarthMoonBarycenter(date, result) { - this.clock = options.clock; - this.element = options.element; + // 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); - /** - * 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); + 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); - /** - * 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); + // 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; - this._seeking = false; - this._seekFunction = undefined; - this._firstTickAfterSeek = false; + return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, + longitudeOfNode, meanLongitude, result); } - 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; - } + /** + * 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; - if (defined(value)) { - this._clockSubscription = value.onTick.addEventListener(VideoSynchronizer.prototype._onTick, this); - } + // 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; - 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; + // 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); - if (oldValue === value) { - return; - } + // 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); - if (defined(oldValue)) { - oldValue.removeEventListener("seeked", this._seekFunction, false); - } + // 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; - if (defined(value)) { - this._seeking = false; - this._seekFunction = createSeekFunction(this); - value.addEventListener("seeked", this._seekFunction, false); - } + // 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; - this._element = value; - this._seeking = false; - this._firstTickAfterSeek = false; - } - } - }); + return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, + longitudeOfNode, meanLongitude, result); + } /** - * 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. + * 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. */ - VideoSynchronizer.prototype.destroy = function() { - this.element = undefined; - this.clock = undefined; - return destroyObject(this); - }; + 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(); /** - * Returns true if this object was destroyed; otherwise, false. + * Computes the position of the Sun in the Earth-centered inertial frame * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * @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 */ - VideoSynchronizer.prototype.isDestroyed = function() { - return false; - }; - - VideoSynchronizer.prototype._onTick = function(clock) { - var element = this._element; - if (!defined(element) || element.readyState < 2) { - return; + Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame= function(julianDate, result){ + if (!defined(julianDate)) { + julianDate = JulianDate.now(); } - var paused = element.paused; - var shouldAnimate = clock.shouldAnimate; - if (shouldAnimate === paused) { - if (shouldAnimate) { - element.play(); - } else { - element.pause(); - } + if (!defined(result)) { + result = new Cartesian3(); } - //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; - } + //first forward transformation + translation = computeSimonEarthMoonBarycenter(julianDate, translation); + result = Cartesian3.negate(translation, result); - element.playbackRate = clock.multiplier; + //second forward transformation + computeSimonEarth(julianDate, translation); - var clockTime = clock.currentTime; - var epoch = defaultValue(this.epoch, Iso8601.MINIMUM_VALUE); - var videoTime = JulianDate.secondsDifference(clockTime, epoch); + Cartesian3.subtract(result, translation, result); + Matrix3.multiplyByVector(axesTransformation, result, result); - 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; - } + return result; + }; - //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; + /** + * 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(julianDate, result){ + if (!defined(julianDate)) { + julianDate = JulianDate.now(); } - }; - function createSeekFunction(that) { - return function() { - that._seeking = false; - that._firstTickAfterSeek = true; - }; - } + result = computeSimonMoon(julianDate, result); + Matrix3.multiplyByVector(axesTransformation, result, result); - return VideoSynchronizer; + return result; + }; + + return Simon1994PlanetaryPositions; }); -/*global define*/ -define('Core/VRTheWorldTerrainProvider',[ - '../ThirdParty/when', - './Credit', +define('Core/SimplePolylineGeometry',[ + './BoundingSphere', + './Cartesian3', + './Color', + './ComponentDatatype', './defaultValue', './defined', - './defineProperties', './DeveloperError', './Ellipsoid', - './Event', - './GeographicTilingScheme', - './getImagePixels', - './HeightmapTerrainData', - './loadImage', - './loadXML', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './IndexDatatype', './Math', - './Rectangle', - './TerrainProvider', - './throttleRequestByServer', - './TileProviderError' + './PolylinePipeline', + './PrimitiveType' ], function( - when, - Credit, + BoundingSphere, + Cartesian3, + Color, + ComponentDatatype, defaultValue, defined, - defineProperties, DeveloperError, Ellipsoid, - Event, - GeographicTilingScheme, - getImagePixels, - HeightmapTerrainData, - loadImage, - loadXML, + Geometry, + GeometryAttribute, + GeometryAttributes, + IndexDatatype, CesiumMath, - Rectangle, - TerrainProvider, - throttleRequestByServer, - TileProviderError) { + PolylinePipeline, + PrimitiveType) { 'use strict'; - function DataRectangle(rectangle, maxLevel) { - this.rectangle = rectangle; - this.maxLevel = maxLevel; + 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 {@link TerrainProvider} that produces terrain geometry by tessellating height maps - * retrieved from a {@link http://vr-theworld.com/|VT MÄK VR-TheWorld server}. + * 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 VRTheWorldTerrainProvider + * @alias SimplePolylineGeometry * @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. + * @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 - * var terrainProvider = new Cesium.VRTheWorldTerrainProvider({ - * url : 'https://www.vr-theworld.com/vr-theworld/tiles1.0.0/73/' + * // 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 + * ]) * }); - * viewer.terrainProvider = terrainProvider; - * - * @see TerrainProvider + * var geometry = Cesium.SimplePolylineGeometry.createGeometry(polyline); */ - function VRTheWorldTerrainProvider(options) { + function SimplePolylineGeometry(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.positions; + var colors = options.colors; + var colorsPerVertex = defaultValue(options.colorsPerVertex, false); + - this._url = options.url; - if (this._url.length > 0 && this._url[this._url.length - 1] !== '/') { - this._url += '/'; - } + 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'; - this._errorEvent = new Event(); - this._ready = false; - this._readyPromise = when.defer(); + var numComponents = 1 + positions.length * Cartesian3.packedLength; + numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1; - this._proxy = options.proxy; + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + this.packedLength = numComponents + Ellipsoid.packedLength + 3; + } - 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 - }; + /** + * 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) { + + startingIndex = defaultValue(startingIndex, 0); - var credit = options.credit; - if (typeof credit === 'string') { - credit = new Credit(credit); - } - this._credit = credit; + var i; - this._tilingScheme = undefined; - this._rectangles = []; + var positions = value._positions; + var length = positions.length; + array[startingIndex++] = length; - var that = this; - var metadataError; - var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + Cartesian3.pack(positions[i], array, startingIndex); + } - 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 colors = value._colors; + length = defined(colors) ? colors.length : 0.0; + array[startingIndex++] = length; - 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)); + for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { + Color.pack(colors[i], array, startingIndex); + } - var dataRectangles = xml.getElementsByTagName('DataExtent'); + Ellipsoid.pack(value._ellipsoid, array, startingIndex); + startingIndex += Ellipsoid.packedLength; - for (var i = 0; i < dataRectangles.length; ++i) { - var dataRectangle = dataRectangles[i]; + array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0; + array[startingIndex++] = value._followSurface ? 1.0 : 0.0; + array[startingIndex] = value._granularity; - 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); + return array; + }; - that._rectangles.push(new DataRectangle(new Rectangle(west, south, east, north), maxLevel)); - } + /** + * 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) { + + startingIndex = defaultValue(startingIndex, 0); - that._ready = true; - that._readyPromise.resolve(true); - } + var i; - 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); - } + var length = array[startingIndex++]; + var positions = new Array(length); - function requestMetadata() { - when(loadXML(that._url), metadataSuccess, metadataFailure); + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + positions[i] = Cartesian3.unpack(array, startingIndex); } - requestMetadata(); - } + length = array[startingIndex++]; + var colors = length > 0 ? new Array(length) : undefined; - 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; - } - }, + for (i = 0; i < length; ++i, startingIndex += Color.packedLength) { + colors[i] = Color.unpack(array, startingIndex); + } - /** - * 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; - } - }, + var ellipsoid = Ellipsoid.unpack(array, startingIndex); + startingIndex += Ellipsoid.packedLength; - /** - * 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() { - - return this._tilingScheme; - } - }, + var colorsPerVertex = array[startingIndex++] === 1.0; + var followSurface = array[startingIndex++] === 1.0; + var granularity = array[startingIndex]; - /** - * Gets a value indicating whether or not the provider is ready for use. - * @memberof VRTheWorldTerrainProvider.prototype - * @type {Boolean} - */ - ready : { - get : function() { - return this._ready; - } - }, + if (!defined(result)) { + return new SimplePolylineGeometry({ + positions : positions, + colors : colors, + ellipsoid : ellipsoid, + colorsPerVertex : colorsPerVertex, + followSurface : followSurface, + granularity : granularity + }); + } - /** - * 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; - } - }, + result._positions = positions; + result._colors = colors; + result._ellipsoid = ellipsoid; + result._colorsPerVertex = colorsPerVertex; + result._followSurface = followSurface; + result._granularity = granularity; - /** - * 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; - } - }, + return result; + }; - /** - * 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; - } - } - }); + var scratchArray1 = new Array(2); + var scratchArray2 = new Array(2); + var generateArcOptionsScratch = { + positions : scratchArray1, + height: scratchArray2, + ellipsoid: undefined, + minDistance : undefined + }; /** - * 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. + * Computes the geometric representation of a simple polyline, including its vertices, indices, and a bounding sphere. * - * @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. + * @param {SimplePolylineGeometry} simplePolylineGeometry A description of the polyline. + * @returns {Geometry} The computed vertices and indices. */ - VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { - - 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) { - return this._levelZeroMaximumGeometricError / (1 << level); - }; - - var rectangleScratch = new Rectangle(); + 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; - function getChildMask(provider, x, y, level) { - var tilingScheme = provider._tilingScheme; - var rectangles = provider._rectangles; - var parentRectangle = tilingScheme.tileXYToRectangle(x, y, level); + var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); + var perSegmentColors = defined(colors) && !colorsPerVertex; - var childMask = 0; + var i; + var length = positions.length; - for (var i = 0; i < rectangles.length && childMask !== 15; ++i) { - var rectangle = rectangles[i]; - if (rectangle.maxLevel <= level) { - continue; - } + var positionValues; + var numberOfPositions; + var colorValues; + var color; + var offset = 0; - var testRectangle = rectangle.rectangle; + if (followSurface) { + var heights = PolylinePipeline.extractHeights(positions, ellipsoid); + var generateArcOptions = generateArcOptionsScratch; + generateArcOptions.minDistance = minDistance; + generateArcOptions.ellipsoid = ellipsoid; - 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 + if (perSegmentColors) { + var positionCount = 0; + for (i = 0; i < length - 1; i++) { + positionCount += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance) + 1; } - } - } - - 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; + positionValues = new Float64Array(positionCount * 3); + colorValues = new Uint8Array(positionCount * 4); - var cleanedPositions = new Array(length); - var cleanedTopHeights = new Array(length); - var cleanedBottomHeights = new Array(length); + generateArcOptions.positions = scratchArray1; + generateArcOptions.height= scratchArray2; - var v0 = positions[0]; - cleanedPositions[0] = v0; + var ci = 0; + for (i = 0; i < length - 1; ++i) { + scratchArray1[0] = positions[i]; + scratchArray1[1] = positions[i + 1]; - var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1); - if (hasTopHeights) { - c0.height = topHeights[0]; - } + scratchArray2[0] = heights[i]; + scratchArray2[1] = heights[i + 1]; - hasAllZeroHeights = hasAllZeroHeights && c0.height <= 0; + var pos = PolylinePipeline.generateArc(generateArcOptions); - cleanedTopHeights[0] = c0.height; + 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); + } + } - if (hasBottomHeights) { - cleanedBottomHeights[0] = bottomHeights[0]; - } else { - cleanedBottomHeights[0] = 0.0; - } + positionValues.set(pos, offset); + offset += pos.length; + } + } else { + generateArcOptions.positions = positions; + generateArcOptions.height= heights; + positionValues = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); - 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 (defined(colors)) { + colorValues = new Uint8Array(positionValues.length / 3 * 4); - if (!latLonEquals(c0, c1)) { - cleanedPositions[index] = v1; // Shallow copy! - cleanedTopHeights[index] = c1.height; + 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); + } - if (hasBottomHeights) { - cleanedBottomHeights[index] = bottomHeights[i]; - } else { - cleanedBottomHeights[index] = 0.0; + 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); } - - 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; + } else { + numberOfPositions = perSegmentColors ? length * 2 - 2 : length; + positionValues = new Float64Array(numberOfPositions * 3); + colorValues = defined(colors) ? new Uint8Array(numberOfPositions * 4) : undefined; - return { - positions: cleanedPositions, - topHeights: cleanedTopHeights, - bottomHeights: cleanedBottomHeights - }; - } + var positionIndex = 0; + var colorIndex = 0; - var positionsArrayScratch = new Array(2); - var heightsArrayScratch = new Array(2); - var generateArcOptionsScratch = { - positions : undefined, - height : undefined, - granularity : undefined, - ellipsoid : undefined - }; + for (i = 0; i < length; ++i) { + var p = positions[i]; - /** - * @private - */ - WallGeometryLibrary.computePositions = function(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, duplicateCorners) { - var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); + if (perSegmentColors && i > 0) { + Cartesian3.pack(p, positionValues, positionIndex); + positionIndex += 3; - if (!defined(o)) { - return; - } + 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); + } - wallPositions = o.positions; - maximumHeights = o.topHeights; - minimumHeights = o.bottomHeights; + if (perSegmentColors && i === length - 1) { + break; + } - if (wallPositions.length >= 3) { - // Order positions counter-clockwise - var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); - var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); + Cartesian3.pack(p, positionValues, positionIndex); + positionIndex += 3; - if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { - wallPositions.reverse(); - maximumHeights.reverse(); - minimumHeights.reverse(); + 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 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]; + var attributes = new GeometryAttributes(); + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positionValues + }); - bottomPositions.set(PolylinePipeline.generateArc(generateArcOptions), offset); + if (defined(colors)) { + attributes.color = new GeometryAttribute({ + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + values : colorValues, + normalize : true + }); + } - offset += pos.length; - } - } else { - generateArcOptions.positions = wallPositions; - generateArcOptions.height = maximumHeights; - topPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); + numberOfPositions = positionValues.length / 3; + var numberOfIndices = (numberOfPositions - 1) * 2; + var indices = IndexDatatype.createTypedArray(numberOfPositions, numberOfIndices); - generateArcOptions.height = minimumHeights; - bottomPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); + var index = 0; + for (i = 0; i < numberOfPositions - 1; ++i) { + indices[index++] = i; + indices[index++] = i + 1; } - return { - bottomPositions: bottomPositions, - topPositions: topPositions, - numCorners: numCorners - }; + return new Geometry({ + attributes : attributes, + indices : indices, + primitiveType : PrimitiveType.LINES, + boundingSphere : BoundingSphere.fromPoints(positions) + }); }; - return WallGeometryLibrary; + return SimplePolylineGeometry; }); -/*global define*/ -define('Core/WallGeometry',[ - './BoundingSphere', +define('Core/SphereGeometry',[ './Cartesian3', - './ComponentDatatype', + './Check', './defaultValue', './defined', - './DeveloperError', - './Ellipsoid', - './Geometry', - './GeometryAttribute', - './GeometryAttributes', - './IndexDatatype', - './Math', - './PrimitiveType', - './VertexFormat', - './WallGeometryLibrary' + './EllipsoidGeometry', + './VertexFormat' ], function( - BoundingSphere, Cartesian3, - ComponentDatatype, + Check, defaultValue, defined, - DeveloperError, - Ellipsoid, - Geometry, - GeometryAttribute, - GeometryAttributes, - IndexDatatype, - CesiumMath, - PrimitiveType, - VertexFormat, - WallGeometryLibrary) { + EllipsoidGeometry, + VertexFormat) { 'use strict'; - var scratchCartesian3Position1 = new Cartesian3(); - var scratchCartesian3Position2 = new Cartesian3(); - var scratchCartesian3Position3 = new Cartesian3(); - var scratchCartesian3Position4 = new Cartesian3(); - var scratchCartesian3Position5 = new Cartesian3(); - var scratchBitangent = 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. + * A description of a sphere centered at the origin. * - * @alias WallGeometry + * @alias SphereGeometry * @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 {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} 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 + * @exception {DeveloperError} options.slicePartitions cannot be less than three. + * @exception {DeveloperError} options.stackPartitions cannot be less than three. * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo} + * @see SphereGeometry#createGeometry * * @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 sphere = new Cesium.SphereGeometry({ + * radius : 100.0, + * vertexFormat : Cesium.VertexFormat.POSITION_ONLY * }); - * var geometry = Cesium.WallGeometry.createGeometry(wall); + * var geometry = Cesium.SphereGeometry.createGeometry(sphere); */ - function WallGeometry(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - var wallPositions = options.positions; - var maximumHeights = options.maximumHeights; - var minimumHeights = options.minimumHeights; - - - 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; - } + 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 + }; - /** - * The number of elements used to pack the object into an array. - * @type {Number} - */ - this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 1; + 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 {WallGeometry} value The value to pack. + * @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 */ - WallGeometry.pack = function(value, array, startingIndex) { + SphereGeometry.pack = function(value, array, startingIndex) { - startingIndex = defaultValue(startingIndex, 0); + return EllipsoidGeometry.pack(value._ellipsoidGeometry, array, startingIndex); + }; - var i; + var scratchEllipsoidGeometry = new EllipsoidGeometry(); + var scratchOptions = { + radius : undefined, + radii : new Cartesian3(), + vertexFormat : new VertexFormat(), + stackPartitions : undefined, + slicePartitions : undefined + }; - var positions = value._positions; - var length = positions.length; - array[startingIndex++] = length; + /** + * 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; - for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { - Cartesian3.pack(positions[i], array, startingIndex); + if (!defined(result)) { + scratchOptions.radius = ellipsoidGeometry._radii.x; + return new SphereGeometry(scratchOptions); } - var minimumHeights = value._minimumHeights; - length = defined(minimumHeights) ? minimumHeights.length : 0; - array[startingIndex++] = length; + Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii); + result._ellipsoidGeometry = new EllipsoidGeometry(scratchOptions); + return result; + }; - if (defined(minimumHeights)) { - for (i = 0; i < length; ++i) { - array[startingIndex++] = minimumHeights[i]; - } - } + /** + * 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); + }; - var maximumHeights = value._maximumHeights; - length = defined(maximumHeights) ? maximumHeights.length : 0; - array[startingIndex++] = length; + return SphereGeometry; +}); - if (defined(maximumHeights)) { - for (i = 0; i < length; ++i) { - array[startingIndex++] = maximumHeights[i]; - } - } +define('Core/SphereOutlineGeometry',[ + './Cartesian3', + './Check', + './defaultValue', + './defined', + './EllipsoidOutlineGeometry' + ], function( + Cartesian3, + Check, + defaultValue, + defined, + EllipsoidOutlineGeometry) { + 'use strict'; - Ellipsoid.pack(value._ellipsoid, array, startingIndex); - startingIndex += Ellipsoid.packedLength; + /** + * 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 + }; - VertexFormat.pack(value._vertexFormat, array, startingIndex); - startingIndex += VertexFormat.packedLength; + this._ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions); + this._workerName = 'createSphereOutlineGeometry'; + } - array[startingIndex] = value._granularity; + /** + * The number of elements used to pack the object into an array. + * @type {Number} + */ + SphereOutlineGeometry.packedLength = EllipsoidOutlineGeometry.packedLength; - return array; + /** + * 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) { + + return EllipsoidOutlineGeometry.pack(value._ellipsoidGeometry, array, startingIndex); }; - var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE); - var scratchVertexFormat = new VertexFormat(); + var scratchEllipsoidGeometry = new EllipsoidOutlineGeometry(); var scratchOptions = { - positions : undefined, - minimumHeights : undefined, - maximumHeights : undefined, - ellipsoid : scratchEllipsoid, - vertexFormat : scratchVertexFormat, - granularity : undefined + radius : undefined, + radii : new Cartesian3(), + stackPartitions : undefined, + slicePartitions : undefined, + subdivisions : undefined }; /** @@ -73397,877 +74593,434 @@ define('Core/WallGeometry',[ * * @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. + * @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. */ - WallGeometry.unpack = function(array, startingIndex, result) { - - startingIndex = defaultValue(startingIndex, 0); + 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; - var i; + if (!defined(result)) { + scratchOptions.radius = ellipsoidGeometry._radii.x; + return new SphereOutlineGeometry(scratchOptions); + } - var length = array[startingIndex++]; - var positions = new Array(length); + Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii); + result._ellipsoidGeometry = new EllipsoidOutlineGeometry(scratchOptions); + return result; + }; - for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { - positions[i] = Cartesian3.unpack(array, startingIndex); - } + /** + * 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); + }; - length = array[startingIndex++]; - var minimumHeights; + return SphereOutlineGeometry; +}); - if (length > 0) { - minimumHeights = new Array(length); - for (i = 0; i < length; ++i) { - minimumHeights[i] = array[startingIndex++]; - } - } +define('Core/Spherical',[ + './Check', + './defaultValue', + './defined' + ], function( + Check, + defaultValue, + defined) { + 'use strict'; - length = array[startingIndex++]; - var maximumHeights; + /** + * 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); + } - if (length > 0) { - maximumHeights = new Array(length); - for (i = 0; i < length; ++i) { - maximumHeights[i] = array[startingIndex++]; - } - } + /** + * Converts the provided Cartesian3 into Spherical coordinates. + * + * @param {Cartesian3} cartesian3 The Cartesian3 to be converted to Spherical. + * @param {Spherical} [result] 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) { + + var x = cartesian3.x; + var y = cartesian3.y; + var z = cartesian3.z; + var radialSquared = x * x + y * y; - var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); - startingIndex += Ellipsoid.packedLength; + if (!defined(result)) { + result = new Spherical(); + } - var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); - startingIndex += VertexFormat.packedLength; + result.clock = Math.atan2(y, x); + result.cone = Math.atan2(Math.sqrt(radialSquared), z); + result.magnitude = Math.sqrt(radialSquared + z * z); + return result; + }; - var granularity = array[startingIndex]; + /** + * 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)) { - scratchOptions.positions = positions; - scratchOptions.minimumHeights = minimumHeights; - scratchOptions.maximumHeights = maximumHeights; - scratchOptions.granularity = granularity; - return new WallGeometry(scratchOptions); + return new Spherical(spherical.clock, spherical.cone, spherical.magnitude); } - 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; + 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(result)) { + return new Spherical(spherical.clock, spherical.cone, 1.0); + } + + result.clock = spherical.clock; + result.cone = spherical.cone; + result.magnitude = 1.0; 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. + * Returns true if the first spherical is equal to the second spherical, false otherwise. * - * @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} + * @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. * - * @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); + * @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. * - * @see WallGeometry#createGeometry + * @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. */ - WallGeometry.fromConstantHeights = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var positions = options.positions; + Spherical.prototype.clone = function(result) { + return Spherical.clone(this, result); + }; - - var minHeights; - var maxHeights; + /** + * 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); + }; - var min = options.minimumHeight; - var max = options.maximumHeight; + /** + * 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 + ')'; + }; - 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; + return Spherical; +}); - for (var i = 0; i < length; ++i) { - if (doMin) { - minHeights[i] = min; - } +define('Core/subdivideArray',[ + './defined', + './DeveloperError' + ], function( + defined, + DeveloperError) { + 'use strict'; - if (doMax) { - maxHeights[i] = max; - } - } + /** + * 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) { + + 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; + } - var newOptions = { - positions : positions, - maximumHeights : maxHeights, - minimumHeights : minHeights, - ellipsoid : options.ellipsoid, - vertexFormat : options.vertexFormat - }; - return new WallGeometry(newOptions); - }; + return subdivideArray; +}); + +define('Core/TerrainData',[ + './defineProperties', + './DeveloperError' + ], function( + defineProperties, + DeveloperError) { + 'use strict'; /** - * Computes the geometric representation of a wall, including its vertices, indices, and a bounding sphere. + * Terrain data for a single tile. This type describes an + * interface and is not intended to be instantiated directly. * - * @param {WallGeometry} wallGeometry A description of the wall. - * @returns {Geometry|undefined} The computed vertices and indices. + * @alias TerrainData + * @constructor + * + * @see HeightmapTerrainData + * @see QuantizedMeshTerrainData */ - 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; + function TerrainData() { + DeveloperError.throwInstantiationError(); + } - var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, true); - if (!defined(pos)) { - return; + defineProperties(TerrainData.prototype, { + /** + * An array of credits for this tile. + * @memberof TerrainData.prototype + * @type {Credit[]} + */ + credits : { + get : DeveloperError.throwInstantiationError + }, + /** + * 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 } - - 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 bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined; - var textureCoordinates = vertexFormat.st ? new Float32Array(size / 3 * 2) : undefined; - - var positionIndex = 0; - var normalIndex = 0; - var bitangentIndex = 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 bitangent = scratchBitangent; - 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.bitangent) { - 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.bitangent) { - bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent); - } - } - - 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.bitangent) { - bitangents[bitangentIndex++] = bitangent.x; - bitangents[bitangentIndex++] = bitangent.y; - bitangents[bitangentIndex++] = bitangent.z; - - bitangents[bitangentIndex++] = bitangent.x; - bitangents[bitangentIndex++] = bitangent.y; - bitangents[bitangentIndex++] = bitangent.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.bitangent) { - attributes.bitangent = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3, - values : bitangents - }); - } - - 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 + * Computes the terrain height at a specified longitude and latitude. + * @function * - * @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); + * @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. */ - function WallOutlineGeometry(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - var wallPositions = options.positions; - var maximumHeights = options.maximumHeights; - var minimumHeights = options.minimumHeights; - - - 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; - } + TerrainData.prototype.interpolateHeight = DeveloperError.throwInstantiationError; /** - * 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. + * 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 * - * @returns {Number[]} The array that was packed into + * @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. */ - WallOutlineGeometry.pack = function(value, array, startingIndex) { - - 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 - }; + TerrainData.prototype.isChildAvailable = DeveloperError.throwInstantiationError; /** - * Retrieves an instance from a packed array. + * Creates a {@link TerrainMesh} from this terrain data. + * @function * - * @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. + * @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. */ - WallOutlineGeometry.unpack = function(array, startingIndex, result) { - - 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; - }; + TerrainData.prototype.createMesh = DeveloperError.throwInstantiationError; /** - * 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); + * Upsamples this terrain data for use by a descendant tile. + * @function * - * @see WallOutlineGeometry#createGeometry + * @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. */ - WallOutlineGeometry.fromConstantHeights = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var positions = options.positions; - - - 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); - }; + TerrainData.prototype.upsample = DeveloperError.throwInstantiationError; /** - * Computes the geometric representation of a wall outline, including its vertices, indices, and a bounding sphere. + * 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 * - * @param {WallOutlineGeometry} wallGeometry A description of the wall outline. - * @returns {Geometry|undefined} The computed vertices and indices. + * @returns {Boolean} True if this instance was created by upsampling; otherwise, false. */ - 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) - }); - }; + TerrainData.prototype.wasCreatedByUpsampling = DeveloperError.throwInstantiationError; - return WallOutlineGeometry; + return TerrainData; }); -/*global define*/ -define('Core/WebMercatorTilingScheme',[ - './Cartesian2', - './defaultValue', - './defined', +define('Core/TilingScheme',[ './defineProperties', - './Ellipsoid', - './Rectangle', - './WebMercatorProjection' + './DeveloperError' ], function( - Cartesian2, - defaultValue, - defined, defineProperties, - Ellipsoid, - Rectangle, - WebMercatorProjection) { + DeveloperError) { '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. + * 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 WebMercatorTilingScheme + * @alias TilingScheme * @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. + * @see WebMercatorTilingScheme + * @see GeographicTilingScheme */ - 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); - } + function TilingScheme(options) { + } - defineProperties(WebMercatorTilingScheme.prototype, { + defineProperties(TilingScheme.prototype, { /** - * Gets the ellipsoid that is tiled by this tiling scheme. - * @memberof WebMercatorTilingScheme.prototype + * Gets the ellipsoid that is tiled by the tiling scheme. + * @memberof TilingScheme.prototype * @type {Ellipsoid} */ - ellipsoid : { - get : function() { - return this._ellipsoid; - } + ellipsoid: { + get : DeveloperError.throwInstantiationError }, /** * Gets the rectangle, in radians, covered by this tiling scheme. - * @memberof WebMercatorTilingScheme.prototype + * @memberof TilingScheme.prototype * @type {Rectangle} */ rectangle : { - get : function() { - return this._rectangle; - } + get : DeveloperError.throwInstantiationError }, + /** - * Gets the map projection used by this tiling scheme. - * @memberof WebMercatorTilingScheme.prototype + * Gets the map projection used by the tiling scheme. + * @memberof TilingScheme.prototype * @type {MapProjection} */ projection : { - get : function() { - return this._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. */ - WebMercatorTilingScheme.prototype.getNumberOfXTilesAtLevel = function(level) { - return this._numberOfLevelZeroTilesX << 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. */ - WebMercatorTilingScheme.prototype.getNumberOfYTilesAtLevel = function(level) { - return this._numberOfLevelZeroTilesY << level; - }; + TilingScheme.prototype.getNumberOfYTilesAtLevel = DeveloperError.throwInstantiationError; /** * Transforms a 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 @@ -74275,25 +75028,12 @@ define('Core/WebMercatorTilingScheme',[ * @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; - }; + TilingScheme.prototype.rectangleToNativeRectangle = DeveloperError.throwInstantiationError; /** * Converts tile x, y coordinates and level to a 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. @@ -74303,31 +75043,11 @@ define('Core/WebMercatorTilingScheme',[ * @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; - }; + 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. @@ -74337,23 +75057,12 @@ define('Core/WebMercatorTilingScheme',[ * @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; - }; + 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. @@ -74362,5584 +75071,4418 @@ define('Core/WebMercatorTilingScheme',[ * @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) { - - return function() { - newFunction.apply(obj, arguments); - oldFunction.apply(obj, arguments); - }; - } + TilingScheme.prototype.positionToTileXY = DeveloperError.throwInstantiationError; - return wrapFunction; + return TilingScheme; }); -/*global define*/ -define('DataSources/ConstantProperty',[ - '../Core/defined', - '../Core/defineProperties', - '../Core/Event' +define('Core/TimeIntervalCollection',[ + './binarySearch', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './Event', + './GregorianDate', + './isLeapYear', + './Iso8601', + './JulianDate', + './TimeInterval' ], function( + binarySearch, + defaultValue, defined, defineProperties, - Event) { + DeveloperError, + Event, + GregorianDate, + isLeapYear, + Iso8601, + JulianDate, + TimeInterval) { 'use strict'; + function compareIntervalStartTimes(left, right) { + return JulianDate.compare(left.start, right.start); + } + /** - * A {@link Property} whose value does not change with respect to simulation time. - * - * @alias ConstantProperty + * A non-overlapping collection of {@link TimeInterval} instances sorted by start time. + * @alias TimeIntervalCollection * @constructor * - * @param {Object} [value] The property value. - * - * @see ConstantPositionProperty + * @param {TimeInterval[]} [intervals] An array of intervals to add to the collection. */ - function ConstantProperty(value) { - this._value = undefined; - this._hasClone = false; - this._hasEquals = false; - this._definitionChanged = new Event(); - this.setValue(value); + 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(ConstantProperty.prototype, { + defineProperties(TimeIntervalCollection.prototype, { /** - * Gets a value indicating if this property is constant. - * This property always returns true. - * @memberof ConstantProperty.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 */ - isConstant : { - value : true + isStartIncluded : { + get : function() { + var intervals = this._intervals; + return intervals.length === 0 ? false : intervals[0].isStartIncluded; + } }, + /** - * 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} + * Gets the stop time of the collection. + * @memberof TimeIntervalCollection.prototype + * @type {JulianDate} * @readonly */ - definitionChanged : { + stop : { get : function() { - return this._definitionChanged; + var intervals = this._intervals; + var length = intervals.length; + return length === 0 ? undefined : intervals[length - 1].stop; } - } - }); + }, - /** - * 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; - }; + /** + * 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; + } + }, - /** - * Sets the value of the property. - * - * @param {Object} value The property value. - */ - 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'; + /** + * Gets the number of intervals in the collection. + * @memberof TimeIntervalCollection.prototype + * @type {Number} + * @readonly + */ + length : { + get : function() { + return this._intervals.length; + } + }, - var changed = !hasEquals || !value.equals(oldValue); - if (changed) { - this._hasClone = hasClone; - this._hasEquals = hasEquals; - this._value = !hasClone ? value : value.clone(this._value); - this._definitionChanged.raiseEvent(this); + /** + * Gets whether or not the collection is empty. + * @memberof TimeIntervalCollection.prototype + * @type {Boolean} + * @readonly + */ + isEmpty : { + get : function() { + return this._intervals.length === 0; } } - }; + }); /** - * Compares this property to the provided property and returns + * Compares this instance against the provided instance componentwise 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. + * @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. */ - ConstantProperty.prototype.equals = function(other) { - return this === other || // - (other instanceof ConstantProperty && // - ((!this._hasEquals && (this._value === other._value)) || // - (this._hasEquals && this._value.equals(other._value)))); + 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 this property's value. + * Gets the interval at the specified index. * - * @returns {*} This property's value. + * @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. */ - ConstantProperty.prototype.valueOf = function() { - return this._value; + TimeIntervalCollection.prototype.get = function(index) { + + return this._intervals[index]; }; /** - * Creates a string representing this property's value. + * 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. * - * @returns {String} A string representing the property's value. + * @param {JulianDate} date The date to search for. + * @returns {TimeInterval|undefined} The interval containing the specified date, undefined if no such interval exists. */ - ConstantProperty.prototype.toString = function() { - return String(this._value); + TimeIntervalCollection.prototype.findIntervalContainingDate = function(date) { + var index = this.indexOf(date); + return index >= 0 ? this._intervals[index] : undefined; }; - return ConstantProperty; -}); + /** + * 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; + }; -/*global define*/ -define('DataSources/createPropertyDescriptor',[ - '../Core/defaultValue', - '../Core/defined', - './ConstantProperty' - ], function( - defaultValue, - defined, - ConstantProperty) { - 'use strict'; + /** + * 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(julianDate) { + return this.indexOf(julianDate) >= 0; + }; - 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 indexOfScratch = new TimeInterval(); - var hasValue = value !== undefined; - if (hasValue && (!defined(value) || !defined(value.getValue)) && defined(createPropertyCallback)) { - value = createPropertyCallback(value); - } + /** + * 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) { + + 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 (oldValue !== value) { - this[privateName] = value; - this._definitionChanged.raiseEvent(this, name, value, oldValue); + 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 (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 if (index > 0 && interval.isStartIncluded && intervals[index - 1].isStartIncluded && intervals[index - 1].start.equals(interval.start)) { + // 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. + --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 + }); + } } + } + } - if (defined(value) && defined(value.definitionChanged)) { - this[subscriptionName] = value.definitionChanged.addEventListener(function() { - this._definitionChanged.raiseEvent(this, name, value, value); - }, this); + 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; } - }; - } + } - function createConstantProperty(value) { - return new ConstantProperty(value); - } + // Add the new interval + intervals.splice(index, 0, interval); + this._changedEvent.raiseEvent(this); + }; /** - * 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 + * 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. */ - 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)); - } + TimeIntervalCollection.prototype.removeInterval = function(interval) { + + if (interval.isEmpty) { + return false; + } - return createPropertyDescriptor; -}); + var result = false; + var intervals = this._intervals; -/*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'; + 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; + }; /** - * 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. + * Creates a new instance that is the intersection of this collection and the provided collection. * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo} + * @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. */ - 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._disableDepthTestDistance = undefined; - this._disableDepthTestDistanceSubscription = undefined; - this._definitionChanged = new Event(); + TimeIntervalCollection.prototype.intersect = function(other, dataComparer, mergeCallback) { + + var left = 0; + var right = 0; + var result = new TimeIntervalCollection(); + var intervals = this._intervals; + var otherIntervals = other._intervals; - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); - } + 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))) { - 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; + 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; + }; - /** - * Gets or sets the Property specifying the Image, URI, or Canvas to use for the billboard. - * @memberof BillboardGraphics.prototype - * @type {Property} - */ - image : createPropertyDescriptor('image'), + /** + * Creates a new instance from a JulianDate array. + * + * @param {Object} options Object with the following properties: + * @param {JulianDate[]} options.julianDates An array of ISO 8601 dates. + * @param {Boolean} [options.isStartIncluded=true] true if start time is included in the interval, false otherwise. + * @param {Boolean} [options.isStopIncluded=true] true if stop time is included in the interval, false otherwise. + * @param {Boolean} [options.leadingInterval=false] true if you want to add a interval from Iso8601.MINIMUM_VALUE to start time, false otherwise. + * @param {Boolean} [options.trailingInterval=false] true if you want to add a interval from stop time to Iso8601.MAXIMUM_VALUE, false otherwise. + * @param {Function} [options.dataCallback] A function that will be return the data that is called with each interval before it is added to the collection. If unspecified, the data will be the index in the collection. + * @param {TimeIntervalCollection} [result] An existing instance to use for the result. + * @returns {TimeIntervalCollection} The modified result parameter or a new instance if none was provided. + */ + TimeIntervalCollection.fromJulianDateArray = function(options, result) { + + if (!defined(result)) { + result = new TimeIntervalCollection(); + } - /** - * 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'), + var julianDates = options.julianDates; + var length = julianDates.length; + var dataCallback = options.dataCallback; - /** - * 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'), + var isStartIncluded = defaultValue(options.isStartIncluded, true); + var isStopIncluded = defaultValue(options.isStopIncluded, true); + var leadingInterval = defaultValue(options.leadingInterval, false); + var trailingInterval = defaultValue(options.trailingInterval, false); + var interval; - /** - * 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'), + // Add a default interval, which will only end up being used up to first interval + var startIndex = 0; + if (leadingInterval) { + ++startIndex; + interval = new TimeInterval({ + start : Iso8601.MINIMUM_VALUE, + stop : julianDates[0], + isStartIncluded : true, + isStopIncluded : !isStartIncluded + }); + interval.data = defined(dataCallback) ? dataCallback(interval, result.length) : result.length; + result.addInterval(interval); + } - /** - * 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'), + for (var i = 0; i < length - 1; ++i) { + var startDate = julianDates[i]; + var endDate = julianDates[i + 1]; - /** - * Gets or sets the Property specifying the {@link HorizontalOrigin}. - * @memberof BillboardGraphics.prototype - * @type {Property} - * @default HorizontalOrigin.CENTER - */ - horizontalOrigin : createPropertyDescriptor('horizontalOrigin'), + interval = new TimeInterval({ + start : startDate, + stop : endDate, + isStartIncluded : (result.length === startIndex) ? isStartIncluded : true, + isStopIncluded : (i === (length - 2)) ? isStopIncluded : false + }); + interval.data = defined(dataCallback) ? dataCallback(interval, result.length) : result.length; + result.addInterval(interval); - /** - * Gets or sets the Property specifying the {@link VerticalOrigin}. - * @memberof BillboardGraphics.prototype - * @type {Property} - * @default VerticalOrigin.CENTER - */ - verticalOrigin : createPropertyDescriptor('verticalOrigin'), + startDate = endDate; + } - /** - * 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'), + if (trailingInterval) { + interval = new TimeInterval({ + start : julianDates[length - 1], + stop : Iso8601.MAXIMUM_VALUE, + isStartIncluded : !isStopIncluded, + isStopIncluded : true + }); + interval.data = defined(dataCallback) ? dataCallback(interval, result.length) : result.length; + result.addInterval(interval); + } - /** - * 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'), + return result; + }; - /** - * Gets or sets the Property specifying the {@link HeightReference}. - * @memberof BillboardGraphics.prototype - * @type {Property} - * @default HeightReference.NONE - */ - heightReference : createPropertyDescriptor('heightReference'), + var scratchGregorianDate = new GregorianDate(); + var monthLengths = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - /** - * 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'), + /** + * Adds duration represented as a GregorianDate to a JulianDate + * + * @param {JulianDate} julianDate The date. + * @param {GregorianDate} duration An duration represented as a GregorianDate. + * @param {JulianDate} result An existing instance to use for the result. + * @returns {JulianDate} The modified result parameter. + * + * @private + */ + function addToDate(julianDate, duration, result) { + if (!defined(result)) { + result = new JulianDate(); + } + JulianDate.toGregorianDate(julianDate, scratchGregorianDate); - /** - * Gets or sets the boolean Property specifying the visibility of the billboard. - * @memberof BillboardGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + var millisecond = scratchGregorianDate.millisecond + duration.millisecond; + var second = scratchGregorianDate.second + duration.second; + var minute = scratchGregorianDate.minute + duration.minute; + var hour = scratchGregorianDate.hour + duration.hour; + var day = scratchGregorianDate.day + duration.day; + var month = scratchGregorianDate.month + duration.month; + var year = scratchGregorianDate.year + duration.year; - /** - * 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'), + if (millisecond >= 1000) { + second += Math.floor(millisecond / 1000); + millisecond = millisecond % 1000; + } - /** - * 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'), + if (second >= 60) { + minute += Math.floor(second / 60); + second = second % 60; + } - /** - * 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'), + if (minute >= 60) { + hour += Math.floor(minute / 60); + minute = minute % 60; + } - /** - * 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'), + if (hour >= 24) { + day += Math.floor(hour / 24); + hour = hour % 24; + } - /** - * 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'), + // If days is greater than the month's length we need to remove those number of days, + // readjust month and year and repeat until days is less than the month's length. + monthLengths[2] = isLeapYear(year) ? 29 : 28; + while ((day > monthLengths[month]) || (month >= 13)) { + if (day > monthLengths[month]) { + day -= monthLengths[month]; + ++month; + } - /** - * 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'), + if (month >= 13) { + --month; + year += Math.floor(month / 12); + month = month % 12; + ++month; + } - /** - * 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'), + monthLengths[2] = isLeapYear(year) ? 29 : 28; + } - /** - * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. - * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. - * @memberof BillboardGraphics.prototype - * @type {Property} - */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') - }); + scratchGregorianDate.millisecond = millisecond; + scratchGregorianDate.second = second; + scratchGregorianDate.minute = minute; + scratchGregorianDate.hour = hour; + scratchGregorianDate.day = day; + scratchGregorianDate.month = month; + scratchGregorianDate.year = year; + + return JulianDate.fromGregorianDate(scratchGregorianDate, result); + } + + var scratchJulianDate = new JulianDate(); + var durationRegex = /P(?:([\d.,]+)Y)?(?:([\d.,]+)M)?(?:([\d.,]+)W)?(?:([\d.,]+)D)?(?:T(?:([\d.,]+)H)?(?:([\d.,]+)M)?(?:([\d.,]+)S)?)?/; /** - * Duplicates this instance. + * Parses ISO8601 duration string * - * @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. + * @param {String} iso8601 An ISO 8601 duration. + * @param {GregorianDate} result An existing instance to use for the result. + * @returns {Boolean} True is parsing succeeded, false otherwise + * + * @private */ - BillboardGraphics.prototype.clone = function(result) { - if (!defined(result)) { - return new BillboardGraphics(this); + function parseDuration(iso8601, result) { + if (!defined(iso8601) || iso8601.length === 0) { + return false; } - 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; - result.disableDepthTestDistance = this._disableDepthTestDistance; - return result; - }; + // Reset object + result.year = 0; + result.month = 0; + result.day = 0; + result.hour = 0; + result.minute = 0; + result.second = 0; + result.millisecond = 0; + + if (iso8601[0] === 'P') { + var matches = iso8601.match(durationRegex); + if (!defined(matches)) { + return false; + } + if (defined(matches[1])) { // Years + result.year = Number(matches[1].replace(',', '.')); + } + if (defined(matches[2])) { // Months + result.month = Number(matches[2].replace(',', '.')); + } + if (defined(matches[3])) { // Weeks + result.day = Number(matches[3].replace(',', '.')) * 7; + } + if (defined(matches[4])) { // Days + result.day += Number(matches[4].replace(',', '.')); + } + if (defined(matches[5])) { // Hours + result.hour = Number(matches[5].replace(',', '.')); + } + if (defined(matches[6])) { // Weeks + result.minute = Number(matches[6].replace(',', '.')); + } + if (defined(matches[7])) { // Seconds + var seconds = Number(matches[7].replace(',', '.')); + result.second = Math.floor(seconds); + result.millisecond = (seconds % 1) * 1000; + } + } else { + // They can technically specify the duration as a normal date with some caveats. Try our best to load it. + if (iso8601[iso8601.length - 1] !== 'Z') { // It's not a date, its a duration, so it always has to be UTC + iso8601 += 'Z'; + } + JulianDate.toGregorianDate(JulianDate.fromIso8601(iso8601, scratchJulianDate), result); + } + + // A duration of 0 will cause an infinite loop, so just make sure something is non-zero + return (result.year || result.month || result.day || result.hour || + result.minute || result.second || result.millisecond); + } + + var scratchDuration = new GregorianDate(); /** - * Assigns each unassigned property on this object to the value - * of the same property on the provided source object. + * Creates a new instance from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} time interval (start/end/duration). * - * @param {BillboardGraphics} source The object to be merged into this object. + * @param {Object} options Object with the following properties: + * @param {String} options.iso8601 An ISO 8601 interval. + * @param {Boolean} [options.isStartIncluded=true] true if start time is included in the interval, false otherwise. + * @param {Boolean} [options.isStopIncluded=true] true if stop time is included in the interval, false otherwise. + * @param {Boolean} [options.leadingInterval=false] true if you want to add a interval from Iso8601.MINIMUM_VALUE to start time, false otherwise. + * @param {Boolean} [options.trailingInterval=false] true if you want to add a interval from stop time to Iso8601.MAXIMUM_VALUE, false otherwise. + * @param {Function} [options.dataCallback] A function that will be return the data that is called with each interval before it is added to the collection. If unspecified, the data will be the index in the collection. + * @param {TimeIntervalCollection} [result] An existing instance to use for the result. + * @returns {TimeIntervalCollection} The modified result parameter or a new instance if none was provided. */ - BillboardGraphics.prototype.merge = function(source) { + TimeIntervalCollection.fromIso8601 = function(options, result) { - 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); - this.disableDepthTestDistance = defaultValue(this._disableDepthTestDistance, source.disableDepthTestDistance); + var dates = options.iso8601.split('/'); + var start = JulianDate.fromIso8601(dates[0]); + var stop = JulianDate.fromIso8601(dates[1]); + var julianDates = []; + + if (!parseDuration(dates[2], scratchDuration)) { + julianDates.push(start, stop); + } else { + var date = JulianDate.clone(start); + julianDates.push(date); + while (JulianDate.compare(date, stop) < 0) { + date = addToDate(date, scratchDuration); + var afterStop = (JulianDate.compare(stop, date) <= 0); + if (afterStop) { + JulianDate.clone(stop, date); + } + + julianDates.push(date); + } + } + + return TimeIntervalCollection.fromJulianDateArray({ + julianDates : julianDates, + isStartIncluded : options.isStartIncluded, + isStopIncluded : options.isStopIncluded, + leadingInterval : options.leadingInterval, + trailingInterval : options.trailingInterval, + dataCallback : options.dataCallback + }, result); }; - return BillboardGraphics; + /** + * Creates a new instance from a {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} date array. + * + * @param {Object} options Object with the following properties: + * @param {String[]} options.iso8601Dates An array of ISO 8601 dates. + * @param {Boolean} [options.isStartIncluded=true] true if start time is included in the interval, false otherwise. + * @param {Boolean} [options.isStopIncluded=true] true if stop time is included in the interval, false otherwise. + * @param {Boolean} [options.leadingInterval=false] true if you want to add a interval from Iso8601.MINIMUM_VALUE to start time, false otherwise. + * @param {Boolean} [options.trailingInterval=false] true if you want to add a interval from stop time to Iso8601.MAXIMUM_VALUE, false otherwise. + * @param {Function} [options.dataCallback] A function that will be return the data that is called with each interval before it is added to the collection. If unspecified, the data will be the index in the collection. + * @param {TimeIntervalCollection} [result] An existing instance to use for the result. + * @returns {TimeIntervalCollection} The modified result parameter or a new instance if none was provided. + */ + TimeIntervalCollection.fromIso8601DateArray = function(options, result) { + + return TimeIntervalCollection.fromJulianDateArray({ + julianDates : options.iso8601Dates.map(function(date) { + return JulianDate.fromIso8601(date); + }), + isStartIncluded : options.isStartIncluded, + isStopIncluded : options.isStopIncluded, + leadingInterval : options.leadingInterval, + trailingInterval : options.trailingInterval, + dataCallback : options.dataCallback + }, result); + }; + + /** + * Creates a new instance from a {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} duration array. + * + * @param {Object} options Object with the following properties: + * @param {JulianDate} options.epoch An date that the durations are relative to. + * @param {String} options.iso8601Durations An array of ISO 8601 durations. + * @param {Boolean} [options.relativeToPrevious=false] true if durations are relative to previous date, false if always relative to the epoch. + * @param {Boolean} [options.isStartIncluded=true] true if start time is included in the interval, false otherwise. + * @param {Boolean} [options.isStopIncluded=true] true if stop time is included in the interval, false otherwise. + * @param {Boolean} [options.leadingInterval=false] true if you want to add a interval from Iso8601.MINIMUM_VALUE to start time, false otherwise. + * @param {Boolean} [options.trailingInterval=false] true if you want to add a interval from stop time to Iso8601.MAXIMUM_VALUE, false otherwise. + * @param {Function} [options.dataCallback] A function that will be return the data that is called with each interval before it is added to the collection. If unspecified, the data will be the index in the collection. + * @param {TimeIntervalCollection} [result] An existing instance to use for the result. + * @returns {TimeIntervalCollection} The modified result parameter or a new instance if none was provided. + */ + TimeIntervalCollection.fromIso8601DurationArray = function(options, result) { + + var epoch = options.epoch; + var iso8601Durations = options.iso8601Durations; + var relativeToPrevious = defaultValue(options.relativeToPrevious, false); + var julianDates = []; + var date, previousDate; + + var length = iso8601Durations.length; + for (var i = 0; i < length; ++i) { + // Allow a duration of 0 on the first iteration, because then it is just the epoch + if (parseDuration(iso8601Durations[i], scratchDuration) || i === 0) { + if (relativeToPrevious && defined(previousDate)) { + date = addToDate(previousDate, scratchDuration); + } else { + date = addToDate(epoch, scratchDuration); + } + julianDates.push(date); + previousDate = date; + } + } + + return TimeIntervalCollection.fromJulianDateArray({ + julianDates : julianDates, + isStartIncluded : options.isStartIncluded, + isStopIncluded : options.isStopIncluded, + leadingInterval : options.leadingInterval, + trailingInterval : options.trailingInterval, + dataCallback : options.dataCallback + }, result); + }; + + return TimeIntervalCollection; }); -/*global define*/ -define('Scene/HeightReference',[ - '../Core/freezeObject' +define('Core/TranslationRotationScale',[ + './Cartesian3', + './defaultValue', + './defined', + './Quaternion' ], function( - freezeObject) { + Cartesian3, + defaultValue, + defined, + Quaternion) { 'use strict'; + var defaultScale = new Cartesian3(1.0, 1.0, 1.0); + var defaultTranslation = Cartesian3.ZERO; + var defaultRotation = Quaternion.IDENTITY; + /** - * Represents the position relative to the terrain. + * An affine transformation defined by a translation, rotation, and scale. + * @alias TranslationRotationScale + * @constructor * - * @exports HeightReference + * @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 HeightReference = { + var TranslationRotationScale = function(translation, rotation, scale) { /** - * The position is absolute. - * @type {Number} - * @constant + * Gets or sets the (x, y, z) translation to apply to the node. + * @type {Cartesian3} + * @default Cartesian3.ZERO */ - NONE : 0, + this.translation = Cartesian3.clone(defaultValue(translation, defaultTranslation)); /** - * The position is clamped to the terrain. - * @type {Number} - * @constant + * Gets or sets the (x, y, z, w) rotation to apply to the node. + * @type {Quaternion} + * @default Quaternion.IDENTITY */ - CLAMP_TO_GROUND : 1, + this.rotation = Quaternion.clone(defaultValue(rotation, defaultRotation)); /** - * The position height is the height above the terrain. - * @type {Number} - * @constant + * Gets or sets the (x, y, z) scaling to apply to the node. + * @type {Cartesian3} + * @default new Cartesian3(1.0, 1.0, 1.0) */ - RELATIVE_TO_GROUND : 2 + this.scale = Cartesian3.clone(defaultValue(scale, defaultScale)); }; - 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 + * Compares this instance against the provided instance and returns + * true if they are equal, false otherwise. * - * @see Billboard#horizontalOrigin - * @see Label#horizontalOrigin + * @param {TranslationRotationScale} [right] The right hand side TranslationRotationScale. + * @returns {Boolean} true if they are equal, false otherwise. */ - 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 + 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 freezeObject(HorizontalOrigin); + return TranslationRotationScale; }); -/*global define*/ -define('Scene/VerticalOrigin',[ - '../Core/freezeObject' +define('Core/VideoSynchronizer',[ + './defaultValue', + './defined', + './defineProperties', + './destroyObject', + './Iso8601', + './JulianDate' ], function( - freezeObject) { + defaultValue, + defined, + defineProperties, + destroyObject, + Iso8601, + JulianDate) { '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. - *

    - *
    - *
    - *
    + * Synchronizes a video element with a simulation clock. * - * @exports VerticalOrigin + * @alias VideoSynchronizer + * @constructor * - * @see Billboard#verticalOrigin - * @see Label#verticalOrigin + * @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} */ - var VerticalOrigin = { - /** - * The origin is at the vertical center between BASELINE and TOP. - * - * @type {Number} - * @constant - */ - CENTER : 0, + function VideoSynchronizer(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - /** - * The origin is at the bottom of the object. - * - * @type {Number} - * @constant - */ - BOTTOM : 1, + this._clock = undefined; + this._element = undefined; + this._clockSubscription = undefined; + this._seekFunction = undefined; + + this.clock = options.clock; + this.element = options.element; /** - * 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 + * Gets or sets the simulation time that marks the start of the video. + * @type {JulianDate} + * @default Iso8601.MINIMUM_VALUE */ - BASELINE : 2, + this.epoch = defaultValue(options.epoch, Iso8601.MINIMUM_VALUE); /** - * The origin is at the top of the object. - * + * 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} - * @constant + * @default 1.0 */ - TOP : -1 - }; - - return freezeObject(VerticalOrigin); -}); + this.tolerance = defaultValue(options.tolerance, 1.0); -/*global define*/ -define('DataSources/BoundingSphereState',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + this._seeking = false; + this._seekFunction = undefined; + this._firstTickAfterSeek = false; + } - /** - * 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, + defineProperties(VideoSynchronizer.prototype, { /** - * The BoundingSphere does not exist. - * @type BoundingSphereState - * @constant + * Gets or sets the clock used to drive the video element. + * + * @memberof VideoSynchronizer.prototype + * @type {Clock} */ - FAILED : 2 - }; + clock : { + get : function() { + return this._clock; + }, + set : function(value) { + var oldValue = this._clock; - return freezeObject(BoundingSphereState); -}); + if (oldValue === value) { + return; + } -/*global define*/ -define('DataSources/Property',[ - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError' - ], function( - defaultValue, - defined, - defineProperties, - DeveloperError) { - 'use strict'; + if (defined(oldValue)) { + this._clockSubscription(); + this._clockSubscription = undefined; + } - /** - * 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(); - } + if (defined(value)) { + this._clockSubscription = value.onTick.addEventListener(VideoSynchronizer.prototype._onTick, this); + } - 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 + this._clock = value; + } }, /** - * 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 + * Gets or sets the video element to synchronize. * - * @type {Event} - * @readonly + * @memberof VideoSynchronizer.prototype + * @type {HTMLVideoElement} */ - definitionChanged : { - get : DeveloperError.throwInstantiationError + 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; + } } }); /** - * Gets the value of the property at the provided time. - * @function + * Destroys and resources used by the object. Once an object is destroyed, it should not be used. * - * @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. + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ - Property.prototype.getValue = DeveloperError.throwInstantiationError; + VideoSynchronizer.prototype.destroy = function() { + this.element = undefined; + this.clock = undefined; + return destroyObject(this); + }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. - * @function + * Returns true if this object was destroyed; otherwise, false. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. - */ - Property.prototype.equals = DeveloperError.throwInstantiationError; - - /** - * @private + * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - Property.equals = function(left, right) { - return left === right || (defined(left) && left.equals(right)); + VideoSynchronizer.prototype.isDestroyed = function() { + return false; }; - /** - * @private - */ - Property.arrayEquals = function(left, right) { - if (left === right) { - return true; - } - if ((!defined(left) || !defined(right)) || (left.length !== right.length)) { - return false; + VideoSynchronizer.prototype._onTick = function(clock) { + var element = this._element; + if (!defined(element) || element.readyState < 2) { + return; } - var length = left.length; - for (var i = 0; i < length; i++) { - if (!Property.equals(left[i], right[i])) { - return false; + + var paused = element.paused; + var shouldAnimate = clock.shouldAnimate; + if (shouldAnimate === paused) { + if (shouldAnimate) { + element.play(); + } else { + element.pause(); } } - return true; - }; - /** - * @private - */ - Property.isConstant = function(property) { - return !defined(property) || property.isConstant; - }; + //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; + } - /** - * @private - */ - Property.getValueOrUndefined = function(property, time, result) { - return defined(property) ? property.getValue(time, result) : undefined; - }; + element.playbackRate = clock.multiplier; - /** - * @private - */ - Property.getValueOrDefault = function(property, time, valueDefault, result) { - return defined(property) ? defaultValue(property.getValue(time, result), valueDefault) : valueDefault; - }; + var clockTime = clock.currentTime; + var epoch = defaultValue(this.epoch, Iso8601.MINIMUM_VALUE); + var videoTime = JulianDate.secondsDifference(clockTime, epoch); - /** - * @private - */ - Property.getValueOrClonedDefault = function(property, time, valueDefault, result) { - var value; - if (defined(property)) { - value = property.getValue(time, result); + 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 (!defined(value)) { - value = valueDefault.clone(value); + + //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; } - return value; }; - return Property; + function createSeekFunction(that) { + return function() { + that._seeking = false; + that._firstTickAfterSeek = true; + }; + } + + return VideoSynchronizer; }); -/*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' +define('Core/VRTheWorldTerrainProvider',[ + '../ThirdParty/when', + './Credit', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './Ellipsoid', + './Event', + './GeographicTilingScheme', + './getImagePixels', + './HeightmapTerrainData', + './loadImage', + './loadXML', + './Math', + './Rectangle', + './Request', + './RequestType', + './TerrainProvider', + './TileProviderError' ], function( - AssociativeArray, - BoundingRectangle, - Cartesian2, - Cartesian3, - Color, + when, + Credit, + defaultValue, defined, - destroyObject, + defineProperties, DeveloperError, - DistanceDisplayCondition, - NearFarScalar, - HeightReference, - HorizontalOrigin, - VerticalOrigin, - BoundingSphereState, - Property) { + Ellipsoid, + Event, + GeographicTilingScheme, + getImagePixels, + HeightmapTerrainData, + loadImage, + loadXML, + CesiumMath, + Rectangle, + Request, + RequestType, + TerrainProvider, + TileProviderError) { '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 defaultDisableDepthTestDistance = 0.0; - - 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; + function DataRectangle(rectangle, maxLevel) { + this.rectangle = rectangle; + this.maxLevel = maxLevel; } /** - * A {@link Visualizer} which maps {@link Entity#billboard} to a {@link Billboard}. - * @alias BillboardVisualizer + * 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 {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities. - * @param {EntityCollection} entityCollection The entityCollection to visualize. + * @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 BillboardVisualizer(entityCluster, entityCollection) { + function VRTheWorldTerrainProvider(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - entityCollection.collectionChanged.addEventListener(BillboardVisualizer.prototype._onCollectionChanged, this); + this._url = options.url; + if (this._url.length > 0 && this._url[this._url.length - 1] !== '/') { + this._url += '/'; + } - this._cluster = entityCluster; - this._entityCollection = entityCollection; - this._items = new AssociativeArray(); - this._onCollectionChanged(entityCollection, entityCollection.values, [], []); - } + this._errorEvent = new Event(); + this._ready = false; + this._readyPromise = when.defer(); - /** - * 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) { - - var items = this._items.values; - var cluster = this._cluster; + this._proxy = options.proxy; - 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); + 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 + }; - if (show) { - position = Property.getValueOrUndefined(entity._position, time, position); - textureValue = Property.getValueOrUndefined(billboardGraphics._image, time); - show = defined(position) && defined(textureValue); + 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; } - if (!show) { - //don't bother creating or updating anything else - returnPrimitive(item, entity, cluster); - continue; + 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)); } - if (!Property.isConstant(entity._position)) { - cluster._clusterDirty = true; + 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; } + }, - if (!defined(billboard)) { - billboard = cluster.getBillboard(entity); - billboard.id = entity; - billboard.image = undefined; - item.billboard = billboard; + /** + * 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; } + }, - billboard.show = show; - if (!defined(billboard.image) || item.textureValue !== textureValue) { - billboard.image = textureValue; - item.textureValue = textureValue; + /** + * 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() { + + return this._tilingScheme; } - 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, time, defaultSizeInMeters); - billboard.distanceDisplayCondition = Property.getValueOrUndefined(billboardGraphics._distanceDisplayCondition, time, distanceDisplayCondition); - billboard.disableDepthTestDistance = Property.getValueOrDefault(billboardGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + }, - var subRegion = Property.getValueOrUndefined(billboardGraphics._imageSubRegion, time, boundingRectangle); - if (defined(subRegion)) { - billboard.setImageSubRegion(billboard._imageId, subRegion); + /** + * 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; } } - 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. + * 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 {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 + * @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 {Request} [request] The request object. Intended for internal use only. + * @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. */ - BillboardVisualizer.prototype.getBoundingSphere = function(entity, result) { + VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { - var item = this._items.get(entity.id); - if (!defined(item) || !defined(item.billboard)) { - return BoundingSphereState.FAILED; + 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 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); + var promise = loadImage(url, undefined, request); + if (!defined(promise)) { + return undefined; } - result.radius = 0; - return BoundingSphereState.DONE; + + 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 + }); + }); }; /** - * Returns true if this object was destroyed; otherwise, false. + * Gets the maximum geometric error allowed in a tile at a given level. * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * @param {Number} level The tile level for which to get the maximum geometric error. + * @returns {Number} The maximum geometric error. */ - BillboardVisualizer.prototype.isDestroyed = function() { - return false; + VRTheWorldTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) { + 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)); + } + /** - * Removes and destroys all primitives created by this instance. + * 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. */ - 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); + VRTheWorldTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) { + return undefined; }; - BillboardVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { - var i; - var entity; - var items = this._items; - var cluster = this._cluster; + return VRTheWorldTerrainProvider; +}); - 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)); - } +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; } - 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)); + 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; } - } else { - returnPrimitive(items.get(entity.id), entity, cluster); - items.remove(entity.id); + + Cartographic.clone(c1, c0); + ++index; + } else if (c0.height < c1.height) { + cleanedTopHeights[index - 1] = c1.height; } } - for (i = removed.length - 1; i > -1; i--) { - entity = removed[i]; - returnPrimitive(items.get(entity.id), entity, cluster); - items.remove(entity.id); + 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 }; - function returnPrimitive(item, entity, cluster) { - if (defined(item)) { - item.billboard = undefined; - cluster.removeBillboard(entity); + /** + * @private + */ + WallGeometryLibrary.computePositions = function(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, duplicateCorners) { + var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights); + + if (!defined(o)) { + return; } - } - return BillboardVisualizer; -}); + wallPositions = o.positions; + maximumHeights = o.topHeights; + minimumHeights = o.bottomHeights; -//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_bitangentEC;\n\ -varying vec2 v_st;\n\ -\n\ -void main()\n\ -{\n\ - vec3 positionToEyeEC = -v_positionEC;\n\ - mat3 tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(v_normalEC, v_tangentEC, v_bitangentEC);\n\ -\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\ -\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\ -\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 bitangent;\n\ -attribute vec2 st;\n\ -attribute float batchId;\n\ -\n\ -varying vec3 v_positionEC;\n\ -varying vec3 v_normalEC;\n\ -varying vec3 v_tangentEC;\n\ -varying vec3 v_bitangentEC;\n\ -varying vec2 v_st;\n\ -\n\ -void main()\n\ -{\n\ - vec4 p = czm_computePosition();\n\ -\n\ - v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ - v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ - v_tangentEC = czm_normal * tangent; // tangent in eye coordinates\n\ - v_bitangentEC = czm_normal * bitangent; // bitangent in eye coordinates\n\ - v_st = st;\n\ -\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\ -\n\ -void main()\n\ -{\n\ - vec3 positionToEyeEC = -v_positionEC; \n\ -\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\ -\n\ - czm_materialInput materialInput;\n\ - materialInput.normalEC = normalEC;\n\ - materialInput.positionToEyeEC = positionToEyeEC;\n\ - czm_material material = czm_getMaterial(materialInput);\n\ - \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\ -\n\ -varying vec3 v_positionEC;\n\ -varying vec3 v_normalEC;\n\ -\n\ -void main() \n\ -{\n\ - vec4 p = czm_computePosition();\n\ -\n\ - v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ - v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ - \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\ -\n\ -void main()\n\ -{\n\ - vec3 positionToEyeEC = -v_positionEC; \n\ -\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\ -\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\ - \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\ -\n\ -varying vec3 v_positionEC;\n\ -varying vec3 v_normalEC;\n\ -varying vec2 v_st;\n\ -\n\ -void main() \n\ -{\n\ - vec4 p = czm_computePosition();\n\ -\n\ - v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ - v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ - v_st = st;\n\ - \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); -}); + if (wallPositions.length >= 3) { + // Order positions counter-clockwise + var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid); + var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions); -/*global define*/ -define('Scene/BlendFunction',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) { + wallPositions.reverse(); + maximumHeights.reverse(); + minimumHeights.reverse(); + } + } - /** - * Determines how blending factors are computed. - * - * @exports BlendFunction - */ - var BlendFunction = { - /** - * The blend factor is zero. - * - * @type {Number} - * @constant - */ - ZERO : WebGLConstants.ZERO, + var length = wallPositions.length; + var numCorners = length - 2; + var topPositions; + var bottomPositions; - /** - * The blend factor is one. - * - * @type {Number} - * @constant - */ - ONE : WebGLConstants.ONE, + var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius); - /** - * The blend factor is the source color. - * - * @type {Number} - * @constant - */ - SOURCE_COLOR : WebGLConstants.SRC_COLOR, + var generateArcOptions = generateArcOptionsScratch; + generateArcOptions.minDistance = minDistance; + generateArcOptions.ellipsoid = ellipsoid; - /** - * The blend factor is one minus the source color. - * - * @type {Number} - * @constant - */ - ONE_MINUS_SOURCE_COLOR : WebGLConstants.ONE_MINUS_SRC_COLOR, + if (duplicateCorners) { + var count = 0; + var i; - /** - * The blend factor is the destination color. - * - * @type {Number} - * @constant - */ - DESTINATION_COLOR : WebGLConstants.DST_COLOR, + for (i = 0; i < length - 1; i++) { + count += PolylinePipeline.numberOfPoints(wallPositions[i], wallPositions[i+1], minDistance) + 1; + } - /** - * The blend factor is one minus the destination color. - * - * @type {Number} - * @constant - */ - ONE_MINUS_DESTINATION_COLOR : WebGLConstants.ONE_MINUS_DST_COLOR, + topPositions = new Float64Array(count * 3); + bottomPositions = new Float64Array(count * 3); - /** - * The blend factor is the source alpha. - * - * @type {Number} - * @constant - */ - SOURCE_ALPHA : WebGLConstants.SRC_ALPHA, + var generateArcPositions = positionsArrayScratch; + var generateArcHeights = heightsArrayScratch; + generateArcOptions.positions = generateArcPositions; + generateArcOptions.height = generateArcHeights; - /** - * The blend factor is one minus the source alpha. - * - * @type {Number} - * @constant - */ - ONE_MINUS_SOURCE_ALPHA : WebGLConstants.ONE_MINUS_SRC_ALPHA, + var offset = 0; + for (i = 0; i < length - 1; i++) { + generateArcPositions[0] = wallPositions[i]; + generateArcPositions[1] = wallPositions[i + 1]; - /** - * The blend factor is the destination alpha. - * - * @type {Number} - * @constant - */ - DESTINATION_ALPHA : WebGLConstants.DST_ALPHA, + generateArcHeights[0] = maximumHeights[i]; + generateArcHeights[1] = maximumHeights[i + 1]; - /** - * The blend factor is one minus the destination alpha. - * - * @type {Number} - * @constant - */ - ONE_MINUS_DESTINATION_ALPHA : WebGLConstants.ONE_MINUS_DST_ALPHA, + var pos = PolylinePipeline.generateArc(generateArcOptions); + topPositions.set(pos, offset); - /** - * The blend factor is the constant color. - * - * @type {Number} - * @constant - */ - CONSTANT_COLOR : WebGLConstants.CONSTANT_COLOR, + generateArcHeights[0] = minimumHeights[i]; + generateArcHeights[1] = minimumHeights[i + 1]; - /** - * The blend factor is one minus the constant color. - * - * @type {Number} - * @constant - */ - ONE_MINUS_CONSTANT_COLOR : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA, + bottomPositions.set(PolylinePipeline.generateArc(generateArcOptions), offset); - /** - * The blend factor is the constant alpha. - * - * @type {Number} - * @constant - */ - CONSTANT_ALPHA : WebGLConstants.CONSTANT_ALPHA, + offset += pos.length; + } + } else { + generateArcOptions.positions = wallPositions; + generateArcOptions.height = maximumHeights; + topPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); - /** - * The blend factor is one minus the constant alpha. - * - * @type {Number} - * @constant - */ - ONE_MINUS_CONSTANT_ALPHA : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA, + generateArcOptions.height = minimumHeights; + bottomPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions)); + } - /** - * The blend factor is the saturated source alpha. - * - * @type {Number} - * @constant - */ - SOURCE_ALPHA_SATURATE : WebGLConstants.SRC_ALPHA_SATURATE + return { + bottomPositions: bottomPositions, + topPositions: topPositions, + numCorners: numCorners + }; }; - return freezeObject(BlendFunction); + return WallGeometryLibrary; }); -/*global define*/ -define('Scene/BlendingState',[ - '../Core/freezeObject', - './BlendEquation', - './BlendFunction' +define('Core/WallGeometry',[ + './BoundingSphere', + './Cartesian3', + './ComponentDatatype', + './defaultValue', + './defined', + './DeveloperError', + './Ellipsoid', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './IndexDatatype', + './Math', + './PrimitiveType', + './VertexFormat', + './WallGeometryLibrary' ], function( - freezeObject, - BlendEquation, - BlendFunction) { + 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 scratchBitangent = new Cartesian3(); + var scratchTangent = new Cartesian3(); + var scratchNormal = new Cartesian3(); + /** - * 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}. - *

    + * 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. * - * @exports BlendingState + * @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); */ - 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 - }) - }; + function WallGeometry(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - return freezeObject(BlendingState); -}); + var wallPositions = options.positions; + var maximumHeights = options.maximumHeights; + var minimumHeights = options.minimumHeights; -/*global define*/ -define('Scene/CullFace',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + + var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - /** - * Determines which triangles, if any, are culled. - * - * @exports CullFace - */ - var CullFace = { - /** - * Front-facing triangles are culled. - * - * @type {Number} - * @constant - */ - FRONT : WebGLConstants.FRONT, + 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'; - /** - * Back-facing triangles are culled. - * - * @type {Number} - * @constant - */ - BACK : WebGLConstants.BACK, + var numComponents = 1 + wallPositions.length * Cartesian3.packedLength + 2; + if (defined(minimumHeights)) { + numComponents += minimumHeights.length; + } + if (defined(maximumHeights)) { + numComponents += maximumHeights.length; + } /** - * Both front-facing and back-facing triangles are culled. - * + * The number of elements used to pack the object into an array. * @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'; + this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 1; + } /** - * 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. + * Stores the provided instance into the provided array. * - * @see MaterialAppearance - * @see EllipsoidSurfaceAppearance - * @see PerInstanceColorAppearance - * @see DebugAppearance - * @see PolylineColorAppearance - * @see PolylineMaterialAppearance + * @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. * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Geometry%20and%20Appearances.html|Geometry and Appearances Demo} + * @returns {Number[]} The array that was packed into */ - function Appearance(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + WallGeometry.pack = function(value, array, startingIndex) { + + startingIndex = defaultValue(startingIndex, 0); - /** - * 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; + var i; - /** - * When true, the geometry is expected to appear translucent. - * - * @type {Boolean} - * - * @default true - */ - this.translucent = defaultValue(options.translucent, true); + var positions = value._positions; + var length = positions.length; + array[startingIndex++] = length; - this._vertexShaderSource = options.vertexShaderSource; - this._fragmentShaderSource = options.fragmentShaderSource; - this._renderState = options.renderState; - this._closed = defaultValue(options.closed, false); - } + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + Cartesian3.pack(positions[i], array, startingIndex); + } - defineProperties(Appearance.prototype, { - /** - * The GLSL source code for the vertex shader. - * - * @memberof Appearance.prototype - * - * @type {String} - * @readonly - */ - vertexShaderSource : { - get : function() { - return this._vertexShaderSource; - } - }, + var minimumHeights = value._minimumHeights; + length = defined(minimumHeights) ? minimumHeights.length : 0; + array[startingIndex++] = length; - /** - * 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; + if (defined(minimumHeights)) { + for (i = 0; i < length; ++i) { + array[startingIndex++] = minimumHeights[i]; } - }, + } - /** - * The WebGL fixed-function state to use when rendering the geometry. - * - * @memberof Appearance.prototype - * - * @type {Object} - * @readonly - */ - renderState : { - get : function() { - return this._renderState; - } - }, + var maximumHeights = value._maximumHeights; + length = defined(maximumHeights) ? maximumHeights.length : 0; + array[startingIndex++] = length; - /** - * When true, the geometry is expected to be closed. - * - * @memberof Appearance.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - closed : { - get : function() { - return this._closed; + if (defined(maximumHeights)) { + for (i = 0; i < length; ++i) { + array[startingIndex++] = maximumHeights[i]; } } - }); - /** - * 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); + Ellipsoid.pack(value._ellipsoid, array, startingIndex); + startingIndex += Ellipsoid.packedLength; - return parts.join('\n'); + VertexFormat.pack(value._vertexFormat, array, startingIndex); + startingIndex += VertexFormat.packedLength; + + array[startingIndex] = value._granularity; + + return array; }; - /** - * 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); + 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 }; /** - * 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. + * Retrieves an instance from a packed array. * - * @returns {Object} The render state. + * @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. */ - 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; - }; + WallGeometry.unpack = function(array, startingIndex, result) { + + startingIndex = defaultValue(startingIndex, 0); - /** - * @private - */ - Appearance.getDefaultRenderState = function(translucent, closed, existing) { - var rs = { - depthTest : { - enabled : true - } - }; + var i; - if (translucent) { - rs.depthMask = false; - rs.blending = BlendingState.ALPHA_BLEND; + var length = array[startingIndex++]; + var positions = new Array(length); + + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + positions[i] = Cartesian3.unpack(array, startingIndex); } - if (closed) { - rs.cull = { - enabled : true, - face : CullFace.BACK - }; + length = array[startingIndex++]; + var minimumHeights; + + if (length > 0) { + minimumHeights = new Array(length); + for (i = 0; i < length; ++i) { + minimumHeights[i] = array[startingIndex++]; + } } - if (defined(existing)) { - rs = combine(existing, rs, true); + length = array[startingIndex++]; + var maximumHeights; + + if (length > 0) { + maximumHeights = new Array(length); + for (i = 0; i < length; ++i) { + maximumHeights[i] = array[startingIndex++]; + } } - return rs; - }; + var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid); + startingIndex += Ellipsoid.packedLength; - return Appearance; -}); + var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat); + startingIndex += VertexFormat.packedLength; -/*global define*/ -define('Renderer/ContextLimits',[ - '../Core/defineProperties' - ], function( - defineProperties) { - 'use strict'; + 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; + }; /** - * @private + * 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 */ - 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 - }; + WallGeometry.fromConstantHeights = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.positions; - defineProperties(ContextLimits, { + + var minHeights; + var maxHeights; - /** - * 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; - } - }, + var min = options.minimumHeight; + var max = options.maximumHeight; - /** - * 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; - } - }, + 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; - /** - * 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; - } - }, + for (var i = 0; i < length; ++i) { + if (doMin) { + minHeights[i] = min; + } - /** - * 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; + if (doMax) { + maxHeights[i] = max; + } } - }, + } - /** - * 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; - } - }, + var newOptions = { + positions : positions, + maximumHeights : maxHeights, + minimumHeights : minHeights, + ellipsoid : options.ellipsoid, + vertexFormat : options.vertexFormat + }; + return new WallGeometry(newOptions); + }; - /** - * 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; - } - }, + /** + * 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; - /** - * The maximum number of vec4 varying variables supported by this WebGL implementation. - * The minimum is eight. Matrices and arrays count as multiple vec4s. - * @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; - } - }, + var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, true); + if (!defined(pos)) { + return; + } - /** - * 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; - } - }, + var bottomPositions = pos.bottomPositions; + var topPositions = pos.topPositions; + var numCorners = pos.numCorners; - /** - * 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; - } - }, + var length = topPositions.length; + var size = length * 2; - /** - * 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; - } - }, + 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 bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined; + var textureCoordinates = vertexFormat.st ? new Float32Array(size / 3 * 2) : undefined; - /** - * 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; - } - }, + var positionIndex = 0; + var normalIndex = 0; + var bitangentIndex = 0; + var tangentIndex = 0; + var stIndex = 0; - /** - * 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; - } - }, + // 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 bitangent = scratchBitangent; + 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; - /** - * 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; + // insert the upper point + positions[positionIndex++] = topPosition.x; + positions[positionIndex++] = topPosition.y; + positions[positionIndex++] = topPosition.z; } - }, - /** - * 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; - } - }, + if (vertexFormat.st) { + textureCoordinates[stIndex++] = s; + textureCoordinates[stIndex++] = 0.0; - /** - * 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; + textureCoordinates[stIndex++] = s; + textureCoordinates[stIndex++] = 1.0; } - }, - /** - * 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; - } - }, + if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) { + 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); + } - /** - * The maximum degree of anisotropy for texture filtering - * @memberof ContextLimits - * @type {Number} - */ - maximumTextureFilterAnisotropy : { - get: function () { - return ContextLimits._maximumTextureFilterAnisotropy; - } - }, + 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; + } - /** - * The maximum number of simultaneous outputs that may be written in a fragment shader. - * @memberof ContextLimits - * @type {Number} - */ - maximumDrawBuffers : { - get: function () { - return ContextLimits._maximumDrawBuffers; - } - }, + 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.bitangent) { + bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent); + } + } - /** - * The maximum number of color attachments supported. - * @memberof ContextLimits - * @type {Number} - */ - maximumColorAttachments : { - get: function () { - return ContextLimits._maximumColorAttachments; - } - }, + if (vertexFormat.normal) { + normals[normalIndex++] = normal.x; + normals[normalIndex++] = normal.y; + normals[normalIndex++] = normal.z; - /** - * High precision float supported (highp) in fragment shaders. - * @memberof ContextLimits - * @type {Boolean} - */ - highpFloatSupported : { - get: function () { - return ContextLimits._highpFloatSupported; + 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.bitangent) { + bitangents[bitangentIndex++] = bitangent.x; + bitangents[bitangentIndex++] = bitangent.y; + bitangents[bitangentIndex++] = bitangent.z; + + bitangents[bitangentIndex++] = bitangent.x; + bitangents[bitangentIndex++] = bitangent.y; + bitangents[bitangentIndex++] = bitangent.z; + } } - }, + } - /** - * High precision int supported (highp) in fragment shaders. - * @memberof ContextLimits - * @type {Boolean} - */ - highpIntSupported : { - get: function () { - return ContextLimits._highpIntSupported; + 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.bitangent) { + attributes.bitangent = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3, + values : bitangents + }); + } + + 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 ContextLimits; + return WallGeometry; }); -/*global define*/ -define('Renderer/CubeMapFace',[ - '../Core/defaultValue', - '../Core/defineProperties', - '../Core/DeveloperError', - './PixelDatatype' +define('Core/WallOutlineGeometry',[ + './BoundingSphere', + './Cartesian3', + './ComponentDatatype', + './defaultValue', + './defined', + './DeveloperError', + './Ellipsoid', + './Geometry', + './GeometryAttribute', + './GeometryAttributes', + './IndexDatatype', + './Math', + './PrimitiveType', + './WallGeometryLibrary' ], function( + BoundingSphere, + Cartesian3, + ComponentDatatype, defaultValue, - defineProperties, + defined, DeveloperError, - PixelDatatype) { + Ellipsoid, + Geometry, + GeometryAttribute, + GeometryAttributes, + IndexDatatype, + CesiumMath, + PrimitiveType, + WallGeometryLibrary) { 'use strict'; + var scratchCartesian3Position1 = new Cartesian3(); + var scratchCartesian3Position2 = new Cartesian3(); + /** - * @private + * 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 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; + function WallOutlineGeometry(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var wallPositions = options.positions; + var maximumHeights = options.maximumHeights; + var minimumHeights = options.minimumHeights; + + + 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; } - defineProperties(CubeMapFace.prototype, { - pixelFormat : { - get : function() { - return this._pixelFormat; - } - }, - pixelDatatype : { - get : function() { - return this._pixelDatatype; + /** + * 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) { + + 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]; } - }, - _target : { - get : function() { - return this._targetFace; + } + + 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 + }; /** - * 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. + * Retrieves an instance from a packed array. * - * @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]) - * }); + * @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. */ - CubeMapFace.prototype.copyFrom = function(source, xOffset, yOffset) { - xOffset = defaultValue(xOffset, 0); - yOffset = defaultValue(yOffset, 0); - + WallOutlineGeometry.unpack = function(array, startingIndex, result) { - var gl = this._gl; - var target = this._textureTarget; + startingIndex = defaultValue(startingIndex, 0); - // 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); + var i; - 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); + var length = array[startingIndex++]; + var positions = new Array(length); + + for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) { + positions[i] = Cartesian3.unpack(array, startingIndex); } - gl.bindTexture(target, null); + 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; }; /** - * Copies texels from the framebuffer to the cubemap's face. + * 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 {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. + * @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} * - * @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(); + * // 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 */ - 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); + WallOutlineGeometry.fromConstantHeights = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.positions; - 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); - }; + var minHeights; + var maxHeights; - return CubeMapFace; -}); + var min = options.minimumHeight; + var max = options.maximumHeight; -/*global define*/ -define('Renderer/MipmapHint',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + 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; - /** - * @private - */ - var MipmapHint = { - DONT_CARE : WebGLConstants.DONT_CARE, - FASTEST : WebGLConstants.FASTEST, - NICEST : WebGLConstants.NICEST, + for (var i = 0; i < length; ++i) { + if (doMin) { + minHeights[i] = min; + } - validate : function(mipmapHint) { - return ((mipmapHint === MipmapHint.DONT_CARE) || - (mipmapHint === MipmapHint.FASTEST) || - (mipmapHint === MipmapHint.NICEST)); + if (doMax) { + maxHeights[i] = max; + } + } } - }; - - return freezeObject(MipmapHint); -}); -/*global define*/ -define('Renderer/TextureMagnificationFilter',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + var newOptions = { + positions : positions, + maximumHeights : maxHeights, + minimumHeights : minHeights, + ellipsoid : options.ellipsoid + }; + return new WallOutlineGeometry(newOptions); + }; /** - * @private + * 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. */ - var TextureMagnificationFilter = { - NEAREST : WebGLConstants.NEAREST, - LINEAR : WebGLConstants.LINEAR, + WallOutlineGeometry.createGeometry = function(wallGeometry) { + var wallPositions = wallGeometry._positions; + var minimumHeights = wallGeometry._minimumHeights; + var maximumHeights = wallGeometry._maximumHeights; + var granularity = wallGeometry._granularity; + var ellipsoid = wallGeometry._ellipsoid; - validate : function(textureMagnificationFilter) { - return ((textureMagnificationFilter === TextureMagnificationFilter.NEAREST) || - (textureMagnificationFilter === TextureMagnificationFilter.LINEAR)); + var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, false); + if (!defined(pos)) { + return; } - }; - return freezeObject(TextureMagnificationFilter); -}); + var bottomPositions = pos.bottomPositions; + var topPositions = pos.topPositions; -/*global define*/ -define('Renderer/TextureMinificationFilter',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + var length = topPositions.length; + var size = length * 2; - /** - * @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, + var positions = new Float64Array(size); + var positionIndex = 0; - 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)); + // 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; } - }; - return freezeObject(TextureMinificationFilter); -}); + var attributes = new GeometryAttributes({ + position : new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }) + }); -/*global define*/ -define('Renderer/TextureWrap',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + var numVertices = size / 3; + size = 2 * numVertices - 4 + numVertices; + var indices = IndexDatatype.createTypedArray(numVertices, size); - /** - * @private - */ - var TextureWrap = { - CLAMP_TO_EDGE : WebGLConstants.CLAMP_TO_EDGE, - REPEAT : WebGLConstants.REPEAT, - MIRRORED_REPEAT : WebGLConstants.MIRRORED_REPEAT, + 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; - validate : function(textureWrap) { - return ((textureWrap === TextureWrap.CLAMP_TO_EDGE) || - (textureWrap === TextureWrap.REPEAT) || - (textureWrap === TextureWrap.MIRRORED_REPEAT)); + 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 freezeObject(TextureWrap); + return WallOutlineGeometry; }); -/*global define*/ -define('Renderer/Sampler',[ - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - './TextureMagnificationFilter', - './TextureMinificationFilter', - './TextureWrap' +define('Core/WebMercatorTilingScheme',[ + './Cartesian2', + './defaultValue', + './defined', + './defineProperties', + './Ellipsoid', + './Rectangle', + './WebMercatorProjection' ], function( + Cartesian2, defaultValue, defined, defineProperties, - DeveloperError, - TextureMagnificationFilter, - TextureMinificationFilter, - TextureWrap) { + Ellipsoid, + Rectangle, + WebMercatorProjection) { 'use strict'; /** - * @private + * 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 Sampler(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + function WebMercatorTilingScheme(options) { + options = defaultValue(options, {}); - 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; + this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._numberOfLevelZeroTilesX = defaultValue(options.numberOfLevelZeroTilesX, 1); + this._numberOfLevelZeroTilesY = defaultValue(options.numberOfLevelZeroTilesY, 1); - - this._wrapS = wrapS; - this._wrapT = wrapT; - this._minificationFilter = minificationFilter; - this._magnificationFilter = magnificationFilter; - this._maximumAnisotropy = maximumAnisotropy; + 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(Sampler.prototype, { - wrapS : { - get : function() { - return this._wrapS; - } - }, - wrapT : { - get : function() { - return this._wrapT; - } - }, - minificationFilter : { + defineProperties(WebMercatorTilingScheme.prototype, { + /** + * Gets the ellipsoid that is tiled by this tiling scheme. + * @memberof WebMercatorTilingScheme.prototype + * @type {Ellipsoid} + */ + ellipsoid : { get : function() { - return this._minificationFilter; + return this._ellipsoid; } }, - magnificationFilter : { + + /** + * Gets the rectangle, in radians, covered by this tiling scheme. + * @memberof WebMercatorTilingScheme.prototype + * @type {Rectangle} + */ + rectangle : { get : function() { - return this._magnificationFilter; + return this._rectangle; } }, - maximumAnisotropy : { + + /** + * Gets the map projection used by this tiling scheme. + * @memberof WebMercatorTilingScheme.prototype + * @type {MapProjection} + */ + projection : { get : function() { - return this._maximumAnisotropy; + return this._projection; } } }); - 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'; + /** + * 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; + }; - function CubeMap(options) { + /** + * 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; + }; - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - - 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]; - - - width = faces[0].width; - height = faces[0].height; + /** + * Transforms a 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)); - } else { - width = options.width; - height = options.height; + if (!defined(result)) { + return new Rectangle(southwest.x, southwest.y, northeast.x, northeast.y); } - var size = width; - var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA); - var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE); - - - var sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, size, size) * 6; - - // 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(); + result.west = southwest.x; + result.south = southwest.y; + result.east = northeast.x; + result.north = northeast.y; + return result; + }; - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(textureTarget, texture); + /** + * Converts tile x, y coordinates and level to a 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); - 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); - } - } + var xTileWidth = (this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x) / xTiles; + var west = this._rectangleSouthwestInMeters.x + x * xTileWidth; + var east = this._rectangleSouthwestInMeters.x + (x + 1) * xTileWidth; - 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); + var yTileHeight = (this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y) / yTiles; + var north = this._rectangleNortheastInMeters.y - y * yTileHeight; + var south = this._rectangleNortheastInMeters.y - (y + 1) * yTileHeight; - 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); + if (!defined(result)) { + return new Rectangle(west, south, east, north); } - 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._hasMipmap = false; - this._sizeInBytes = sizeInBytes; - this._preMultiplyAlpha = preMultiplyAlpha; - this._flipY = flipY; - this._sampler = undefined; + result.west = west; + result.south = south; + result.east = east; + result.north = north; + return result; + }; - 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); + /** + * 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); - this.sampler = defined(options.sampler) ? options.sampler : new Sampler(); - } + var projection = this._projection; + var southwest = projection.unproject(new Cartesian2(nativeRectangle.west, nativeRectangle.south)); + var northeast = projection.unproject(new Cartesian2(nativeRectangle.east, nativeRectangle.north)); - 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; + nativeRectangle.west = southwest.longitude; + nativeRectangle.south = southwest.latitude; + nativeRectangle.east = northeast.longitude; + nativeRectangle.north = northeast.latitude; + return nativeRectangle; + }; - var mipmap = - (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) || - (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) || - (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) || - (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); + /** + * 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; + } - // 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 xTiles = this.getNumberOfXTilesAtLevel(level); + var yTiles = this.getNumberOfYTilesAtLevel(level); - var gl = this._gl; - var target = this._textureTarget; + var overallWidth = this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x; + var xTileWidth = overallWidth / xTiles; + var overallHeight = this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y; + var yTileHeight = overallHeight / yTiles; - 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); + var projection = this._projection; - 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; - } - }, - sizeInBytes : { - get : function() { - if (this._hasMipmap) { - return Math.floor(this._sizeInBytes * 4 / 3); - } - return this._sizeInBytes; - } - }, - preMultiplyAlpha : { - get : function() { - return this._preMultiplyAlpha; - } - }, - flipY : { - get : function() { - return this._flipY; - } - }, + var webMercatorPosition = projection.project(position); + var distanceFromWest = webMercatorPosition.x - this._rectangleSouthwestInMeters.x; + var distanceFromNorth = this._rectangleNortheastInMeters.y - webMercatorPosition.y; - _target : { - get : function() { - return this._textureTarget; - } + var xTileCoordinate = distanceFromWest / xTileWidth | 0; + if (xTileCoordinate >= xTiles) { + xTileCoordinate = xTiles - 1; + } + var yTileCoordinate = distanceFromNorth / yTileHeight | 0; + if (yTileCoordinate >= yTiles) { + yTileCoordinate = yTiles - 1; } - }); - - /** - * 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); - - - this._hasMipmap = true; - - 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; - }; + if (!defined(result)) { + return new Cartesian2(xTileCoordinate, yTileCoordinate); + } - 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); + result.x = xTileCoordinate; + result.y = yTileCoordinate; + return result; }; - return CubeMap; + return WebMercatorTilingScheme; }); -/*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, +define('Core/WeightSpline',[ + './Check', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './Spline' +], function( + Check, defaultValue, defined, defineProperties, - destroyObject, DeveloperError, - CesiumMath, - PixelFormat, - WebGLConstants, - ContextLimits, - MipmapHint, - PixelDatatype, - Sampler, - TextureMagnificationFilter, - TextureMinificationFilter) { + Spline) { 'use strict'; - function Texture(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - - 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; - - var isCompressed = PixelFormat.isCompressedFormat(internalFormat); - - 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; - } - } - } - - - // 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 - if (isCompressed) { - gl.compressedTexImage2D(textureTarget, 0, internalFormat, width, height, 0, source.arrayBufferView); - } else { - 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); - - var sizeInBytes; - if (isCompressed) { - sizeInBytes = PixelFormat.compressedTextureSizeInBytes(pixelFormat, width, height); - } else { - sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, width, height); - } - - 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._hasMipmap = false; - this._sizeInBytes = sizeInBytes; - 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. + * A spline that linearly interpolates over an array of weight values used by morph targets. + * + * @alias WeightSpline + * @constructor * * @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. + * @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 {Number[]} options.weights The array of floating-point control weights given. The weights are ordered such + * that all weights for the targets are given in chronological order and order in which they appear in + * the glTF from which the morph targets come. This means for 2 targets, weights = [w(0,0), w(0,1), w(1,0), w(1,1) ...] + * where i and j in w(i,j) are the time indices and target indices, respectively. * - * @exception {DeveloperError} Invalid pixelFormat. - * @exception {DeveloperError} pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format. - * @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. + * @exception {DeveloperError} weights.length must be greater than or equal to 2. + * @exception {DeveloperError} times.length must be a factor of weights.length. * * * @example - * // Create a texture with the contents of the framebuffer. - * var t = Texture.fromFramebuffer({ - * context : context + * var times = [ 0.0, 1.5, 3.0, 4.5, 6.0 ]; + * var weights = [0.0, 1.0, 0.25, 0.75, 0.5, 0.5, 0.75, 0.25, 1.0, 0.0]; //Two targets + * var spline = new Cesium.WeightSpline({ + * times : times, + * weights : weights * }); * - * @see Sampler + * var p0 = spline.evaluate(times[0]); * - * @private + * @see LinearSpline + * @see HermiteSpline + * @see CatmullRomSpline + * @see QuaternionSpline */ - Texture.fromFramebuffer = function(options) { + function WeightSpline(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - 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; + var weights = options.weights; + var times = options.times; - 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 - } - }); + this._times = times; + this._weights = weights; + this._count = weights.length / times.length; - return texture; - }; + this._lastTimeIndex = 0; + } - defineProperties(Texture.prototype, { + defineProperties(WeightSpline.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} + * An array of times for the control weights. + * + * @memberof WeightSpline.prototype + * + * @type {Number[]} + * @readonly */ - 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; - } - }, - sizeInBytes : { + times : { get : function() { - if (this._hasMipmap) { - return Math.floor(this._sizeInBytes * 4 / 3); - } - return this._sizeInBytes; + return this._times; } }, - _target : { + + /** + * An array of floating-point array control weights. + * + * @memberof WeightSpline.prototype + * + * @type {Number[]} + * @readonly + */ + weights : { get : function() { - return this._textureTarget; + return this._weights; } } }); /** - * 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. + * Finds an index i in times such that the parameter + * time is in the interval [times[i], times[i + 1]]. + * @function * - * @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. + * @param {Number} time The time. + * @returns {Number} The index for the element at the start of the interval. * - * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL. - * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format. - * @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. + * @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. + */ + WeightSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval; + + /** + * Evaluates the curve at a given time. * - * @example - * texture.copyFrom({ - * width : 1, - * height : 1, - * arrayBufferView : new Uint8Array([255, 0, 0, 255]) - * }); + * @param {Number} time The time at which to evaluate the curve. + * @param {Number[]} [result] The object onto which to store the result. + * @returns {Number[]} 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. */ - Texture.prototype.copyFrom = function(source, xOffset, yOffset) { - xOffset = defaultValue(xOffset, 0); - yOffset = defaultValue(yOffset, 0); + WeightSpline.prototype.evaluate = function(time, result) { + var weights = this.weights; + var times = this.times; - - var gl = this._context._gl; - var target = this._textureTarget; + var i = this._lastTimeIndex = this.findTimeInterval(time, this._lastTimeIndex); + var u = (time - times[i]) / (times[i + 1] - times[i]); - // 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 (!defined(result)) { + result = new Array(this._count); + } - 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); + for (var j = 0; j < this._count; j++) { + var index = (i * this._count) + j; + result[j] = weights[index] * (1.0 - u) + weights[index + this._count] * u; } - gl.bindTexture(target, null); + return result; }; + return WeightSpline; +}); + +define('Core/wrapFunction',[ + './DeveloperError' + ], function( + DeveloperError) { + 'use strict'; + /** - * @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 + * 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. * - * @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} Cannot call copyFrom with a compressed texture pixel format. - * @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. + * @private */ - 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); - + function wrapFunction(obj, oldFunction, newFunction) { - var gl = this._context._gl; - var target = this._textureTarget; + return function() { + newFunction.apply(obj, arguments); + oldFunction.apply(obj, arguments); + }; + } - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(target, this._texture); - gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height); - gl.bindTexture(target, null); - }; + return wrapFunction; +}); + +define('DataSources/ConstantProperty',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/Event' + ], function( + defined, + defineProperties, + Event) { + 'use strict'; /** - * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional. + * A {@link Property} whose value does not change with respect to simulation time. * - * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL. - * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is a compressed format. - * @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. + * @alias ConstantProperty + * @constructor + * + * @param {Object} [value] The property value. + * + * @see ConstantPositionProperty */ - Texture.prototype.generateMipmap = function(hint) { - hint = defaultValue(hint, MipmapHint.DONT_CARE); + function ConstantProperty(value) { + this._value = undefined; + this._hasClone = false; + this._hasEquals = false; + this._definitionChanged = new Event(); + this.setValue(value); + } - - this._hasMipmap = true; + 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; + } + } + }); - var gl = this._context._gl; - var target = this._textureTarget; + /** + * 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; + }; - gl.hint(gl.GENERATE_MIPMAP_HINT, hint); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(target, this._texture); - gl.generateMipmap(target); - gl.bindTexture(target, null); + /** + * Sets the value of the property. + * + * @param {Object} value The property value. + */ + 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'; + + var changed = !hasEquals || !value.equals(oldValue); + if (changed) { + this._hasClone = hasClone; + this._hasEquals = hasEquals; + this._value = !hasClone ? value : value.clone(this._value); + this._definitionChanged.raiseEvent(this); + } + } }; - Texture.prototype.isDestroyed = function() { - return false; + /** + * 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)))); }; - Texture.prototype.destroy = function() { - this._context._gl.deleteTexture(this._texture); - return destroyObject(this); + /** + * Gets this property's value. + * + * @returns {*} This property's value. + */ + ConstantProperty.prototype.valueOf = function() { + return this._value; }; - return Texture; + /** + * Creates a string representing this property's value. + * + * @returns {String} A string representing the property's value. + */ + ConstantProperty.prototype.toString = function() { + return String(this._value); + }; + + return ConstantProperty; }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Materials/BumpMapMaterial',[],function() { +define('DataSources/createPropertyDescriptor',[ + '../Core/defaultValue', + '../Core/defined', + './ConstantProperty' + ], function( + defaultValue, + defined, + ConstantProperty) { 'use strict'; - return "uniform sampler2D image;\n\ -uniform float strength;\n\ -uniform vec2 repeat;\n\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - vec2 st = materialInput.st;\n\ - \n\ - vec2 centerPixel = fract(repeat * st);\n\ - float centerBump = texture2D(image, centerPixel).channel;\n\ - \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\ - \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\ - \n\ - vec3 normalTangentSpace = normalize(vec3(centerBump - rightBump, centerBump - topBump, clamp(1.0 - strength, 0.1, 1.0)));\n\ - vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace;\n\ - \n\ - material.normal = normalEC;\n\ - material.diffuse = vec3(0.01);\n\ - \n\ - return material;\n\ -}\n\ -"; + + 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 = value !== undefined; + if (hasValue && (!defined(value) || !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; }); -//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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - vec2 st = materialInput.st;\n\ - \n\ - // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights\n\ - float b = mod(floor(repeat.s * st.s) + floor(repeat.t * st.t), 2.0); // 0.0 or 1.0\n\ - \n\ - // Find the distance from the closest separator (region between two colors)\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\ - \n\ - vec4 currentColor = mix(lightColor, darkColor, b);\n\ - vec4 color = czm_antialias(lightColor, darkColor, currentColor, value, 0.03);\n\ - \n\ - material.diffuse = color.rgb;\n\ - material.alpha = color.a;\n\ - \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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ - \n\ - // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights\n\ - float b = smoothstep(0.3, 0.32, length(fract(repeat * materialInput.st) - 0.5)); // 0.0 or 1.0\n\ -\n\ - vec4 color = mix(lightColor, darkColor, b);\n\ - material.diffuse = color.rgb;\n\ - material.alpha = color.a;\n\ - \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\ -\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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ - \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\ - \n\ - float u = length(vec2(s, t));\n\ - vec4 color = mix(fadeInColor, fadeOutColor, u);\n\ - \n\ - material.emission = color.rgb;\n\ - material.alpha = color.a;\n\ - \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\ -\n\ -uniform vec4 color;\n\ -uniform float cellAlpha;\n\ -uniform vec2 lineCount;\n\ -uniform vec2 lineThickness;\n\ -uniform vec2 lineOffset;\n\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - vec2 st = materialInput.st;\n\ -\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\ -\n\ - float value;\n\ -#ifdef GL_OES_standard_derivatives\n\ - // Fuzz Factor - Controls blurriness of lines\n\ - const float fuzz = 1.2;\n\ - vec2 thickness = (lineThickness * czm_resolutionScale) - 1.0;\n\ -\n\ - // From \"3D Engine Design for Virtual Globes\" by Cozzi and Ring, Listing 4.13.\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\ - // Fuzz Factor - Controls blurriness of lines\n\ - const float fuzz = 0.05;\n\ -\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\ -\n\ - // Edges taken from RimLightingMaterial.glsl\n\ - // See http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html\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\ -\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\ -\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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ - \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\ - \n\ - material.normal = normalEC;\n\ - \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 "#ifdef GL_OES_standard_derivatives\n\ -#extension GL_OES_standard_derivatives : enable\n\ -#endif\n\ -\n\ -uniform vec4 color;\n\ -\n\ -varying float v_width;\n\ -\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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - vec2 st = materialInput.st;\n\ -\n\ -#ifdef GL_OES_standard_derivatives\n\ - float base = 1.0 - abs(fwidth(st.s)) * 10.0;\n\ -#else\n\ - float base = 0.99; // 1% of the line will be the arrow head\n\ -#endif\n\ -\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\ -\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\ -\n\ - float t = step(base, materialInput.st.s);\n\ - t *= 1.0 - step(ptOnUpperLine, st.t);\n\ - t *= step(ptOnLowerLine, st.t);\n\ -\n\ - // Find the distance from the closest separator (region between two colors)\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\ -\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\ -\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/PolylineDashMaterial',[],function() { - 'use strict'; - return "uniform vec4 color;\n\ -uniform vec4 gapColor;\n\ -uniform float dashLength;\n\ -uniform float dashPattern;\n\ -varying float v_angle;\n\ -\n\ -const float maskLength = 16.0;\n\ -\n\ -mat2 rotate(float rad) {\n\ - float c = cos(rad);\n\ - float s = sin(rad);\n\ - return mat2(\n\ - c, s,\n\ - -s, c\n\ - );\n\ -}\n\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - vec2 pos = rotate(v_angle) * gl_FragCoord.xy;\n\ -\n\ - // Get the relative position within the dash from 0 to 1\n\ - float dashPosition = fract(pos.x / dashLength);\n\ - // Figure out the mask index.\n\ - float maskIndex = floor(dashPosition * maskLength);\n\ - // Test the bit mask.\n\ - float maskTest = floor(dashPattern / pow(2.0, maskIndex));\n\ - vec4 fragColor = (mod(maskTest, 2.0) < 1.0) ? gapColor : color;\n\ - if (fragColor.a < 0.005) { // matches 0/255 and 1/255\n\ - discard;\n\ - }\n\ -\n\ - material.emission = fragColor.rgb;\n\ - material.alpha = fragColor.a;\n\ - return material;\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\ -\n\ -varying float v_width;\n\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - vec2 st = materialInput.st;\n\ - float glow = glowPower / abs(st.t - 0.5) - (glowPower / 0.5);\n\ -\n\ - material.emission = max(vec3(glow - 1.0 + color.rgb), color.rgb);\n\ - material.alpha = clamp(0.0, 1.0, glow) * color.a;\n\ -\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\ -\n\ -varying float v_width;\n\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ - \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\ - \n\ - // Find the distance from the closest separator (region between two colors)\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\ - \n\ - vec4 currentColor = mix(outlineColor, color, b);\n\ - vec4 outColor = czm_antialias(outlineColor, color, currentColor, dist);\n\ - \n\ - material.diffuse = outColor.rgb;\n\ - material.alpha = outColor.a;\n\ - \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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - // See http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html\n\ - float d = 1.0 - dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC));\n\ - float s = smoothstep(1.0 - width, 1.0, d);\n\ -\n\ - material.diffuse = color.rgb;\n\ - material.emission = rimColor.rgb * s; \n\ - material.alpha = mix(color.a, rimColor.a, s);\n\ -\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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - // Based on the Stripes Fragment Shader in the Orange Book (11.1.2)\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\ - \n\ - vec4 currentColor = mix(evenColor, oddColor, step(0.5, value));\n\ - vec4 color = czm_antialias(evenColor, oddColor, currentColor, dist);\n\ - \n\ - material.diffuse = color.rgb;\n\ - material.alpha = color.a;\n\ - \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 "// Thanks for the contribution Jonas\n\ -// http://29a.ch/2012/7/19/webgl-terrain-rendering-water-fog\n\ -\n\ -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\ -\n\ -czm_material czm_getMaterial(czm_materialInput materialInput)\n\ -{\n\ - czm_material material = czm_getDefaultMaterial(materialInput);\n\ -\n\ - float time = czm_frameNumber * animationSpeed;\n\ - \n\ - // fade is a function of the distance from the fragment and the frequency of the waves\n\ - float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency * fadeFactor);\n\ - \n\ - float specularMapValue = texture2D(specularMap, materialInput.st).r;\n\ - \n\ - // note: not using directional motion at this time, just set the angle to 0.0;\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\ - \n\ - // fade out the normal perturbation as we move further from the water surface\n\ - normalTangentSpace.xy /= fade;\n\ - \n\ - // attempt to fade out the normal perturbation as we approach non water areas (low specular map value)\n\ - normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);\n\ - \n\ - normalTangentSpace = normalize(normalTangentSpace);\n\ - \n\ - // get ratios for alignment of the new normal vector with a vector perpendicular to the tangent plane\n\ - float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);\n\ - \n\ - // fade out water effect as specular map value decreases\n\ - material.alpha = specularMapValue;\n\ - \n\ - // base color is a blend of the water and non-water color based on the value from the specular map\n\ - // may need a uniform blend factor to better control this\n\ - material.diffuse = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);\n\ - \n\ - // diffuse highlights are based on how perturbed the normal is\n\ - material.diffuse += (0.1 * tsPerturbationRatio);\n\ - \n\ - material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);\n\ - \n\ - material.specular = specularIntensity;\n\ - material.shininess = 10.0;\n\ - \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/loadCRN', - '../Core/loadImage', - '../Core/loadKTX', - '../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/PolylineDashMaterial', - '../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, - loadCRN, - loadImage, - loadKTX, - Matrix2, - Matrix3, - Matrix4, - CubeMap, - Texture, - BumpMapMaterial, - CheckerboardMaterial, - DotMaterial, - FadeMaterial, - GridMaterial, - NormalMapMaterial, - PolylineArrowMaterial, - PolylineDashMaterial, - PolylineGlowMaterial, - PolylineOutlineMaterial, - RimLightingMaterial, - StripeMaterial, - WaterMaterial, - when) { + +define('DataSources/BillboardGraphics',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + './createPropertyDescriptor' + ], function( + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + createPropertyDescriptor) { '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.
      • - *
      - *
    • PolylineDash
    • - *
        - *
      • color: color for the line.
      • - *
      • gapColor: color for the gaps in the line.
      • - *
      • dashLength: Dash length in pixels.
      • - *
      • dashPattern: The 16 bit stipple pattern for the line..
      • - *
      - *
    • 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.
      • - *
      - *
    + * Describes a two dimensional icon located at the position of the containing {@link Entity}. + *

    + *

    + *
    + * Example billboards *
    + *

    * - * @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. - * + * @alias BillboardGraphics * @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(); + * @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. * - * // 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) - * } - * } - * }); + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo} */ - function Material(options) { + 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._disableDepthTestDistance = undefined; + this._disableDepthTestDistanceSubscription = undefined; + this._definitionChanged = new Event(); + + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } + + defineProperties(BillboardGraphics.prototype, { /** - * 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 + * Gets the event that is raised whenever a property or sub-property is changed or modified. + * @memberof BillboardGraphics.prototype + * + * @type {Event} + * @readonly */ - this.type = undefined; + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, /** - * The glsl shader source for this material. - * @type {String} - * @default undefined + * Gets or sets the Property specifying the Image, URI, or Canvas to use for the billboard. + * @memberof BillboardGraphics.prototype + * @type {Property} */ - this.shaderSource = undefined; + image : createPropertyDescriptor('image'), /** - * Maps sub-material names to Material objects. - * @type {Object} - * @default undefined + * 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} */ - this.materials = undefined; + imageSubRegion : createPropertyDescriptor('imageSubRegion'), /** - * Maps uniform names to their values. - * @type {Object} - * @default undefined + * 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 */ - this.uniforms = undefined; - this._uniforms = undefined; + scale : createPropertyDescriptor('scale'), /** - * When true or a function that returns true, - * the geometry is expected to appear translucent. - * @type {Boolean|Function} - * @default undefined + * Gets or sets the numeric Property specifying the rotation of the image + * counter clockwise from the alignedAxis. + * @memberof BillboardGraphics.prototype + * @type {Property} + * @default 0 */ - this.translucent = undefined; - - this._strict = undefined; - this._template = undefined; - this._count = undefined; + rotation : createPropertyDescriptor('rotation'), - this._texturePaths = {}; - this._loadedImages = []; - this._loadedCubeMaps = []; + /** + * 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'), - this._textures = {}; + /** + * Gets or sets the Property specifying the {@link HorizontalOrigin}. + * @memberof BillboardGraphics.prototype + * @type {Property} + * @default HorizontalOrigin.CENTER + */ + horizontalOrigin : createPropertyDescriptor('horizontalOrigin'), - this._updateFunctions = []; + /** + * Gets or sets the Property specifying the {@link VerticalOrigin}. + * @memberof BillboardGraphics.prototype + * @type {Property} + * @default VerticalOrigin.CENTER + */ + verticalOrigin : createPropertyDescriptor('verticalOrigin'), - this._defaultTexture = undefined; + /** + * 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'), - initializeMaterial(options, this); - defineProperties(this, { - type : { - value : this.type, - writable : false - } - }); + /** + * 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'), - if (!defined(Material._uniformList[this.type])) { - Material._uniformList[this.type] = Object.keys(this._uniforms); - } - } + /** + * Gets or sets the Property specifying the {@link HeightReference}. + * @memberof BillboardGraphics.prototype + * @type {Property} + * @default HeightReference.NONE + */ + heightReference : createPropertyDescriptor('heightReference'), - // Cached list of combined uniform names indexed by type. - // Used to get the list of uniforms in the same order. - Material._uniformList = {}; + /** + * 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'), - /** - * 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) { - - var material = new Material({ - fabric : { - type : type - } - }); + /** + * Gets or sets the boolean Property specifying the visibility of the billboard. + * @memberof BillboardGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - if (defined(uniforms)) { - for (var name in uniforms) { - if (uniforms.hasOwnProperty(name)) { - material.uniforms[name] = uniforms[name]; - } - } - } + /** + * 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'), - return material; - }; + /** + * 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 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(); - } + /** + * 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'), - return this.translucent; - } + /** + * 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'), - 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; - } + /** + * 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'), - if (!translucent) { - break; - } + /** + * 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'), + + /** + * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. + * @memberof BillboardGraphics.prototype + * @type {Property} + */ + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + }); + + /** + * 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); } - return translucent; + 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; + result.disableDepthTestDistance = this._disableDepthTestDistance; + return result; }; /** - * @private + * 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. */ - 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; - if (defined(image.internalFormat)) { - texture = new Texture({ - context : context, - pixelFormat : image.internalFormat, - width : image.width, - height : image.height, - source : { - arrayBufferView : image.bufferView - } - }); - } else { - texture = new Texture({ - context : context, - source : image - }); - } + BillboardGraphics.prototype.merge = function(source) { + + 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); + this.disableDepthTestDistance = defaultValue(this._disableDepthTestDistance, source.disableDepthTestDistance); + }; - this._textures[uniformId] = texture; + return BillboardGraphics; +}); - var uniformDimensionsName = uniformId + 'Dimensions'; - if (this.uniforms.hasOwnProperty(uniformDimensionsName)) { - var uniformDimensions = this.uniforms[uniformDimensionsName]; - uniformDimensions.x = texture._width; - uniformDimensions.y = texture._height; - } - } +define('Scene/HeightReference',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; - loadedImages.length = 0; + /** + * Represents the position relative to the terrain. + * + * @exports HeightReference + */ + var HeightReference = { + /** + * The position is absolute. + * @type {Number} + * @constant + */ + NONE : 0, - var loadedCubeMaps = this._loadedCubeMaps; - length = loadedCubeMaps.length; + /** + * The position is clamped to the terrain. + * @type {Number} + * @constant + */ + CLAMP_TO_GROUND : 1, - for (i = 0; i < length; ++i) { - var loadedCubeMap = loadedCubeMaps[i]; - uniformId = loadedCubeMap.id; - var images = loadedCubeMap.images; + /** + * The position height is the height above the terrain. + * @type {Number} + * @constant + */ + RELATIVE_TO_GROUND : 2 + }; - 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] - } - }); + return freezeObject(HeightReference); +}); - this._textures[uniformId] = cubeMap; - } +define('Scene/HorizontalOrigin',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; - loadedCubeMaps.length = 0; + /** + * 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, - var updateFunctions = this._updateFunctions; - length = updateFunctions.length; - for (i = 0; i < length; ++i) { - updateFunctions[i](this, context); - } + /** + * The origin is on the left side of the object. + * + * @type {Number} + * @constant + */ + LEFT : 1, - var subMaterials = this.materials; - for (var name in subMaterials) { - if (subMaterials.hasOwnProperty(name)) { - subMaterials[name].update(context); - } - } + /** + * The origin is on the right side of the object. + * + * @type {Number} + * @constant + */ + RIGHT : -1 }; - /** - * 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; - }; + return freezeObject(HorizontalOrigin); +}); + +define('Scene/VerticalOrigin',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; /** - * 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. + * 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. *

    - * 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(); + * @exports VerticalOrigin * - * @see Material#isDestroyed + * @see Billboard#verticalOrigin + * @see Label#verticalOrigin */ - 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 VerticalOrigin = { + /** + * The origin is at the vertical center between BASELINE and TOP. + * + * @type {Number} + * @constant + */ + CENTER : 0, - var materials = this.materials; - for ( var material in materials) { - if (materials.hasOwnProperty(material)) { - materials[material].destroy(); - } - } - return destroyObject(this); + /** + * 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 }; - 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)); + return freezeObject(VerticalOrigin); +}); - result.type = defined(result._template.type) ? result._template.type : createGuid(); +define('DataSources/BoundingSphereState',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; - result.shaderSource = ''; - result.materials = {}; - result.uniforms = {}; - result._uniforms = {}; - result._translucentFunctions = []; + /** + * 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 + }; - var translucent; + return freezeObject(BoundingSphereState); +}); - // 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; - } +define('DataSources/Property',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError' + ], function( + defaultValue, + defined, + defineProperties, + DeveloperError) { + 'use strict'; - // Make sure the template has no obvious errors. More error checking happens later. - checkForTemplateErrors(result); + /** + * 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(); + } - // If the material has a new type, add it to the cache. - if (!defined(cachedMaterial)) { - Material._materialCache.addMaterial(result.type, result); + 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 } + }); - createMethodDefinition(result); - createUniforms(result); - createSubMaterials(result); + /** + * 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; - var defaultTranslucent = result._translucentFunctions.length === 0 ? true : undefined; - translucent = defaultValue(translucent, defaultTranslucent); - translucent = defaultValue(options.translucent, translucent); + /** + * 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; - if (defined(translucent)) { - if (typeof translucent === 'function') { - var wrappedTranslucent = function() { - return translucent(result); - }; - result._translucentFunctions.push(wrappedTranslucent); - } else { - result._translucentFunctions.push(translucent); - } + /** + * @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; } - } - - 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); - } - } - } + if ((!defined(left) || !defined(right)) || (left.length !== right.length)) { + return false; } - } - - function invalidNameError(property, properties) { - } - - function duplicateNameError(property, properties) { + var length = left.length; + for (var i = 0; i < length; i++) { + if (!Property.equals(left[i], right[i])) { + return false; } + } + return true; + }; - var templateProperties = ['type', 'materials', 'uniforms', 'components', 'source']; - var componentProperties = ['diffuse', 'specular', 'shininess', 'normal', 'emission', 'alpha']; + /** + * @private + */ + Property.isConstant = function(property) { + return !defined(property) || property.isConstant; + }; - function checkForTemplateErrors(material) { - var template = material._template; - var uniforms = template.uniforms; - var materials = template.materials; - var components = template.components; + /** + * @private + */ + Property.getValueOrUndefined = function(property, time, result) { + return defined(property) ? property.getValue(time, result) : undefined; + }; - // Make sure source and components do not exist in the same template. - - // Make sure all template and components properties are valid. - checkForValidProperties(template, templateProperties, invalidNameError, true); - checkForValidProperties(components, componentProperties, invalidNameError, true); + /** + * @private + */ + Property.getValueOrDefault = function(property, time, valueDefault, result) { + return defined(property) ? defaultValue(property.getValue(time, result), valueDefault) : valueDefault; + }; - // 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); - } + /** + * @private + */ + Property.getValueOrClonedDefault = function(property, time, valueDefault, result) { + var value; + if (defined(property)) { + value = property.getValue(time, result); } - 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'; + if (!defined(value)) { + value = valueDefault.clone(value); } - } - - var matrixMap = { - 'mat2' : Matrix2, - 'mat3' : Matrix3, - 'mat4' : Matrix4 + return value; }; - var ktxRegex = /\.ktx$/i; - var crnRegex = /\.crn$/i; - - 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]; + return Property; +}); - var uniformDimensionsName; - var uniformDimensions; +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'; - 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; - } + 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 defaultDisableDepthTestDistance = 0.0; - if (!defined(texture) || texture === context.defaultTexture) { - texture = new Texture({ - context : context, - source : uniformValue - }); - material._textures[uniformId] = texture; - return; - } + 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(); - texture.copyFrom(uniformValue); - } else if (!defined(texture)) { - material._textures[uniformId] = context.defaultTexture; - } - return; - } + function EntityData(entity) { + this.entity = entity; + this.billboard = undefined; + this.textureValue = undefined; + } - 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; + /** + * 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) { + + entityCollection.collectionChanged.addEventListener(BillboardVisualizer.prototype._onCollectionChanged, this); - uniformDimensionsName = uniformId + 'Dimensions'; - if (uniforms.hasOwnProperty(uniformDimensionsName)) { - uniformDimensions = uniforms[uniformDimensionsName]; - uniformDimensions.x = uniformValue._width; - uniformDimensions.y = uniformValue._height; - } + this._cluster = entityCluster; + this._entityCollection = entityCollection; + this._items = new AssociativeArray(); + this._onCollectionChanged(entityCollection, entityCollection.values, [], []); + } - return; - } + /** + * 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) { + + var items = this._items.values; + var cluster = this._cluster; - if (!defined(texture)) { - material._texturePaths[uniformId] = undefined; - if (!defined(material._defaultTexture)) { - material._defaultTexture = context.defaultTexture; - } - texture = material._textures[uniformId] = material._defaultTexture; + 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); - uniformDimensionsName = uniformId + 'Dimensions'; - if (uniforms.hasOwnProperty(uniformDimensionsName)) { - uniformDimensions = uniforms[uniformDimensionsName]; - uniformDimensions.x = texture._width; - uniformDimensions.y = texture._height; - } + if (show) { + position = Property.getValueOrUndefined(entity._position, time, position); + textureValue = Property.getValueOrUndefined(billboardGraphics._image, time); + show = defined(position) && defined(textureValue); } - if (uniformValue === Material.DefaultImageId) { - return; + if (!show) { + //don't bother creating or updating anything else + returnPrimitive(item, entity, cluster); + continue; } - if (uniformValue !== material._texturePaths[uniformId]) { - if (typeof uniformValue === 'string') { - var promise; - if (ktxRegex.test(uniformValue)) { - promise = loadKTX(uniformValue); - } else if (crnRegex.test(uniformValue)) { - promise = loadCRN(uniformValue); - } else { - promise = loadImage(uniformValue); - } - when(promise, 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; + if (!Property.isConstant(entity._position)) { + cluster._clusterDirty = true; } - }; - } - - 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(billboard)) { + billboard = cluster.getBillboard(entity); + billboard.id = entity; + billboard.image = undefined; + item.billboard = billboard; } - if (!defined(material._textures[uniformId])) { - material._texturePaths[uniformId] = undefined; - material._textures[uniformId] = context.defaultCubeMap; + 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, time, defaultSizeInMeters); + billboard.distanceDisplayCondition = Property.getValueOrUndefined(billboardGraphics._distanceDisplayCondition, time, distanceDisplayCondition); + billboard.disableDepthTestDistance = Property.getValueOrDefault(billboardGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); - if (uniformValue === Material.DefaultCubeMapId) { - return; + var subRegion = Property.getValueOrUndefined(billboardGraphics._imageSubRegion, time, boundingRectangle); + if (defined(subRegion)) { + billboard.setImageSubRegion(billboard._imageId, subRegion); } + } + return true; + }; - 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 - }); - }); + /** + * 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) { + + var item = this._items.get(entity.id); + if (!defined(item) || !defined(item.billboard)) { + return BoundingSphereState.FAILED; + } - material._texturePaths[uniformId] = path; + 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; + }; - function createUniforms(material) { - var uniforms = material._template.uniforms; - for ( var uniformId in uniforms) { - if (uniforms.hasOwnProperty(uniformId)) { - createUniform(material, uniformId); - } + /** + * 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); + }; - // 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); + BillboardVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { + var i; + var entity; + var items = this._items; + var cluster = this._cluster; - - var replacedTokenCount; - if (uniformType === 'channels') { - replacedTokenCount = replaceToken(material, uniformId, uniformValue, false); - } 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); - } + 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)); } + } - // 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; + 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); } + } - var newUniformId = uniformId + '_' + material._count++; - replacedTokenCount = replaceToken(material, uniformId, newUniformId); - - // Set uniform value - material.uniforms[uniformId] = uniformValue; + for (i = removed.length - 1; i > -1; i--) { + entity = removed[i]; + returnPrimitive(items.get(entity.id), entity, cluster); + items.remove(entity.id); + } + }; - 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]; - }; - } + function returnPrimitive(item, entity, cluster) { + if (defined(item)) { + item.billboard = undefined; + cluster.removeBillboard(entity); } } - // 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); - } - } - } - - // 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 < 1.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 < 1.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 < 1.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 < 1.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 < 1.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 < 1.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.PolylineDashType = 'PolylineDash'; - Material._materialCache.addMaterial(Material.PolylineDashType, { - fabric : { - type : Material.PolylineDashType, - uniforms : { - color : new Color(1.0, 0.0, 1.0, 1.0), - gapColor : new Color(0.0, 0.0, 0.0, 0.0), - dashLength : 16.0, - dashPattern : 255.0 - }, - source : PolylineDashMaterial - }, - 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; + return BillboardVisualizer; }); -/*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) { +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Appearances/AllMaterialAppearanceFS',[],function() { '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, - * tangent, and bitangent vertex attributes. - * - * @constant - */ - ALL : freezeObject({ - vertexFormat : VertexFormat.ALL, - vertexShaderSource : AllMaterialAppearanceVS, - fragmentShaderSource : AllMaterialAppearanceFS - }) - }; - - return MaterialAppearance; + return "varying vec3 v_positionEC;\n\ +varying vec3 v_normalEC;\n\ +varying vec3 v_tangentEC;\n\ +varying vec3 v_bitangentEC;\n\ +varying vec2 v_st;\n\ +\n\ +void main()\n\ +{\n\ + vec3 positionToEyeEC = -v_positionEC;\n\ + mat3 tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(v_normalEC, v_tangentEC, v_bitangentEC);\n\ +\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\ +\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\ +\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/PerInstanceColorAppearanceFS',[],function() { +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 bitangent;\n\ +attribute vec2 st;\n\ +attribute float batchId;\n\ +\n\ +varying vec3 v_positionEC;\n\ +varying vec3 v_normalEC;\n\ +varying vec3 v_tangentEC;\n\ +varying vec3 v_bitangentEC;\n\ +varying vec2 v_st;\n\ +\n\ +void main()\n\ +{\n\ + vec4 p = czm_computePosition();\n\ +\n\ + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ + v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ + v_tangentEC = czm_normal * tangent; // tangent in eye coordinates\n\ + v_bitangentEC = czm_normal * bitangent; // bitangent in eye coordinates\n\ + v_st = st;\n\ +\n\ + gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Appearances/BasicMaterialAppearanceFS',[],function() { 'use strict'; return "varying vec3 v_positionEC;\n\ varying vec3 v_normalEC;\n\ -varying vec4 v_color;\n\ \n\ void main()\n\ {\n\ - vec3 positionToEyeEC = -v_positionEC;\n\ - \n\ + vec3 positionToEyeEC = -v_positionEC; \n\ +\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\ - \n\ +\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\ + czm_material material = czm_getMaterial(materialInput);\n\ \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/PerInstanceColorAppearanceVS',[],function() { +define('Shaders/Appearances/BasicMaterialAppearanceVS',[],function() { 'use strict'; return "attribute vec3 position3DHigh;\n\ attribute vec3 position3DLow;\n\ attribute vec3 normal;\n\ -attribute vec4 color;\n\ attribute float batchId;\n\ \n\ varying vec3 v_positionEC;\n\ varying vec3 v_normalEC;\n\ -varying vec4 v_color;\n\ \n\ void main() \n\ {\n\ @@ -79947,346 +79490,350 @@ void main() \n\ \n\ v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ - v_color = color;\n\ \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() { +define('Shaders/Appearances/TexturedMaterialAppearanceFS',[],function() { 'use strict'; - return "varying vec4 v_color;\n\ + return "varying vec3 v_positionEC;\n\ +varying vec3 v_normalEC;\n\ +varying vec2 v_st;\n\ \n\ void main()\n\ {\n\ - gl_FragColor = v_color;\n\ + vec3 positionToEyeEC = -v_positionEC; \n\ +\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\ +\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\ + \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/PerInstanceFlatColorAppearanceVS',[],function() { +define('Shaders/Appearances/TexturedMaterialAppearanceVS',[],function() { 'use strict'; return "attribute vec3 position3DHigh;\n\ attribute vec3 position3DLow;\n\ -attribute vec4 color;\n\ +attribute vec3 normal;\n\ +attribute vec2 st;\n\ attribute float batchId;\n\ \n\ -varying vec4 v_color;\n\ +varying vec3 v_positionEC;\n\ +varying vec3 v_normalEC;\n\ +varying vec2 v_st;\n\ \n\ void main() \n\ {\n\ vec4 p = czm_computePosition();\n\ \n\ - v_color = color;\n\ + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ + v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ + v_st = st;\n\ \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' +define('Scene/BlendEquation',[ + '../Core/freezeObject', + '../Core/WebGLConstants' ], function( - defaultValue, - defineProperties, - VertexFormat, - PerInstanceColorAppearanceFS, - PerInstanceColorAppearanceVS, - PerInstanceFlatColorAppearanceFS, - PerInstanceFlatColorAppearanceVS, - Appearance) { + freezeObject, + WebGLConstants) { '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. + * Determines how two pixels' values are combined. * - * @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() - * }); + * @exports BlendEquation */ - 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; + var BlendEquation = { + /** + * Pixel values are added componentwise. This is used in additive blending for translucency. + * + * @type {Number} + * @constant + */ + ADD : WebGLConstants.FUNC_ADD, /** - * This property is part of the {@link Appearance} interface, but is not - * used by {@link PerInstanceColorAppearance} since a fully custom fragment shader is used. + * Pixel values are subtracted componentwise (source - destination). This is used in alpha blending for translucency. * - * @type Material + * @type {Number} + * @constant + */ + SUBTRACT : WebGLConstants.FUNC_SUBTRACT, + + /** + * Pixel values are subtracted componentwise (destination - source). * - * @default undefined + * @type {Number} + * @constant */ - this.material = undefined; + REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT, /** - * When true, the geometry is expected to appear translucent so - * {@link PerInstanceColorAppearance#renderState} has alpha blending enabled. + * Pixel values are given to the minimum function (min(source, destination)). * - * @type {Boolean} + * This equation operates on each pixel color component. * - * @default true + * @type {Number} + * @constant */ - this.translucent = translucent; + MIN : WebGLConstants.MIN, - this._vertexShaderSource = defaultValue(options.vertexShaderSource, vs); - this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs); - this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState); - this._closed = closed; + /** + * Pixel values are given to the maximum function (max(source, destination)). + * + * This equation operates on each pixel color component. + * + * @type {Number} + * @constant + */ + MAX : WebGLConstants.MAX + }; - // Non-derived members + return freezeObject(BlendEquation); +}); - this._vertexFormat = vertexFormat; - this._flat = flat; - this._faceForward = defaultValue(options.faceForward, !closed); - } +define('Scene/BlendFunction',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; - defineProperties(PerInstanceColorAppearance.prototype, { + /** + * Determines how blending factors are computed. + * + * @exports BlendFunction + */ + var BlendFunction = { /** - * The GLSL source code for the vertex shader. - * - * @memberof PerInstanceColorAppearance.prototype + * The blend factor is zero. * - * @type {String} - * @readonly + * @type {Number} + * @constant */ - vertexShaderSource : { - get : function() { - return this._vertexShaderSource; - } - }, + ZERO : WebGLConstants.ZERO, /** - * The GLSL source code for the fragment shader. - * - * @memberof PerInstanceColorAppearance.prototype + * The blend factor is one. * - * @type {String} - * @readonly + * @type {Number} + * @constant */ - fragmentShaderSource : { - get : function() { - return this._fragmentShaderSource; - } - }, + ONE : WebGLConstants.ONE, /** - * 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}. - *

    + * The blend factor is the source color. * - * @memberof PerInstanceColorAppearance.prototype + * @type {Number} + * @constant + */ + SOURCE_COLOR : WebGLConstants.SRC_COLOR, + + /** + * The blend factor is one minus the source color. * - * @type {Object} - * @readonly + * @type {Number} + * @constant */ - renderState : { - get : function() { - return this._renderState; - } - }, + ONE_MINUS_SOURCE_COLOR : WebGLConstants.ONE_MINUS_SRC_COLOR, /** - * 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. + * The blend factor is the destination color. * - * @memberof PerInstanceColorAppearance.prototype + * @type {Number} + * @constant + */ + DESTINATION_COLOR : WebGLConstants.DST_COLOR, + + /** + * The blend factor is one minus the destination color. * - * @type {Boolean} - * @readonly + * @type {Number} + * @constant + */ + ONE_MINUS_DESTINATION_COLOR : WebGLConstants.ONE_MINUS_DST_COLOR, + + /** + * The blend factor is the source alpha. * - * @default false + * @type {Number} + * @constant */ - closed : { - get : function() { - return this._closed; - } - }, + SOURCE_ALPHA : WebGLConstants.SRC_ALPHA, /** - * 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. + * The blend factor is one minus the source alpha. * - * @memberof PerInstanceColorAppearance.prototype + * @type {Number} + * @constant + */ + ONE_MINUS_SOURCE_ALPHA : WebGLConstants.ONE_MINUS_SRC_ALPHA, + + /** + * The blend factor is the destination alpha. * - * @type VertexFormat - * @readonly + * @type {Number} + * @constant */ - vertexFormat : { - get : function() { - return this._vertexFormat; - } - }, + DESTINATION_ALPHA : WebGLConstants.DST_ALPHA, /** - * When true, flat shading is used in the fragment shader, - * which means lighting is not taking into account. + * The blend factor is one minus the destination alpha. * - * @memberof PerInstanceColorAppearance.prototype + * @type {Number} + * @constant + */ + ONE_MINUS_DESTINATION_ALPHA : WebGLConstants.ONE_MINUS_DST_ALPHA, + + /** + * The blend factor is the constant color. * - * @type {Boolean} - * @readonly + * @type {Number} + * @constant + */ + CONSTANT_COLOR : WebGLConstants.CONSTANT_COLOR, + + /** + * The blend factor is one minus the constant color. * - * @default false + * @type {Number} + * @constant */ - flat : { - get : function() { - return this._flat; - } - }, + ONE_MINUS_CONSTANT_COLOR : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA, /** - * 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}. + * The blend factor is the constant alpha. * - * @memberof PerInstanceColorAppearance.prototype + * @type {Number} + * @constant + */ + CONSTANT_ALPHA : WebGLConstants.CONSTANT_ALPHA, + + /** + * The blend factor is one minus the constant alpha. * - * @type {Boolean} - * @readonly + * @type {Number} + * @constant + */ + ONE_MINUS_CONSTANT_ALPHA : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA, + + /** + * The blend factor is the saturated source alpha. * - * @default true + * @type {Number} + * @constant */ - faceForward : { - get : function() { - return this._faceForward; - } - } - }); + SOURCE_ALPHA_SATURATE : WebGLConstants.SRC_ALPHA_SATURATE + }; - /** - * 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; + return freezeObject(BlendFunction); +}); - /** - * 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; +define('Scene/BlendingState',[ + '../Core/freezeObject', + './BlendEquation', + './BlendFunction' + ], function( + freezeObject, + BlendEquation, + BlendFunction) { + 'use strict'; /** - * 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 + * 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}. + *

    * - * @returns {String} The full GLSL fragment shader source. + * @exports BlendingState */ - PerInstanceColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; + var BlendingState = { + /** + * Blending is disabled. + * + * @type {Object} + * @constant + */ + DISABLED : freezeObject({ + enabled : false + }), - /** - * 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; + /** + * 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 + }), - /** - * 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; + /** + * 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 + }), - return PerInstanceColorAppearance; + /** + * 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('Renderer/BufferUsage',[ +define('Scene/CullFace',[ '../Core/freezeObject', '../Core/WebGLConstants' ], function( @@ -80295,3866 +79842,3641 @@ define('Renderer/BufferUsage',[ 'use strict'; /** - * @private + * Determines which triangles, if any, are culled. + * + * @exports CullFace */ - var BufferUsage = { - STREAM_DRAW : WebGLConstants.STREAM_DRAW, - STATIC_DRAW : WebGLConstants.STATIC_DRAW, - DYNAMIC_DRAW : WebGLConstants.DYNAMIC_DRAW, + var CullFace = { + /** + * Front-facing triangles are culled. + * + * @type {Number} + * @constant + */ + FRONT : WebGLConstants.FRONT, - validate : function(bufferUsage) { - return ((bufferUsage === BufferUsage.STREAM_DRAW) || - (bufferUsage === BufferUsage.STATIC_DRAW) || - (bufferUsage === BufferUsage.DYNAMIC_DRAW)); - } + /** + * 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(BufferUsage); + return freezeObject(CullFace); }); -/*global define*/ -define('Renderer/DrawCommand',[ +define('Scene/Appearance',[ + '../Core/clone', + '../Core/combine', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/PrimitiveType' + './BlendingState', + './CullFace' ], function( + clone, + combine, defaultValue, defined, defineProperties, - PrimitiveType) { + BlendingState, + CullFace) { 'use strict'; /** - * Represents a command to the renderer for drawing. + * 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. * - * @private + * @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 DrawCommand(options) { + function Appearance(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; + /** + * 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; /** - * @private + * When true, the geometry is expected to appear translucent. + * + * @type {Boolean} + * + * @default true */ - this.derivedCommands = {}; + 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(DrawCommand.prototype, { + defineProperties(Appearance.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. - *

    + * The GLSL source code for the vertex shader. * - * @memberof DrawCommand.prototype - * @type {Object} - * @default undefined + * @memberof Appearance.prototype * - * @see DrawCommand#debugShowBoundingVolume + * @type {String} + * @readonly */ - boundingVolume : { + vertexShaderSource : { get : function() { - return this._boundingVolume; - }, - set : function(value) { - if (this._boundingVolume !== value) { - this._boundingVolume = value; - this.dirty = true; - } + return this._vertexShaderSource; } }, /** - * 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. + * 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 DrawCommand.prototype - * @type {OrientedBoundingBox} - * @default undefined + * @memberof Appearance.prototype * - * @see DrawCommand#debugShowBoundingVolume + * @type {String} + * @readonly */ - orientedBoundingBox : { + fragmentShaderSource : { get : function() { - return this._orientedBoundingBox; - }, - set : function(value) { - if (this._orientedBoundingBox !== value) { - this._orientedBoundingBox = value; - this.dirty = true; - } + return this._fragmentShaderSource; } }, /** - * 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. + * The WebGL fixed-function state to use when rendering the geometry. * - * @memberof DrawCommand.prototype - * @type {Boolean} - * @default true + * @memberof Appearance.prototype + * + * @type {Object} + * @readonly */ - cull : { + renderState : { get : function() { - return this._cull; - }, - set : function(value) { - if (this._cull !== value) { - this._cull = value; - this.dirty = true; - } + return this._renderState; } }, /** - * The transformation from the geometry in model space to world space. - *

    - * When undefined, the geometry is assumed to be defined in world space. - *

    + * When true, the geometry is expected to be closed. * - * @memberof DrawCommand.prototype - * @type {Matrix4} - * @default undefined + * @memberof Appearance.prototype + * + * @type {Boolean} + * @readonly + * + * @default false */ - modelMatrix : { + closed : { get : function() { - return this._modelMatrix; - }, - set : function(value) { - if (this._modelMatrix !== value) { - this._modelMatrix = value; - this.dirty = true; - } + 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; +}); + +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 type of geometry in the vertex array. - * - * @memberof DrawCommand.prototype - * @type {PrimitiveType} - * @default PrimitiveType.TRIANGLES + * 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. */ - primitiveType : { - get : function() { - return this._primitiveType; - }, - set : function(value) { - if (this._primitiveType !== value) { - this._primitiveType = value; - this.dirty = true; - } + maximumCombinedTextureImageUnits : { + get: function () { + return ContextLimits._maximumCombinedTextureImageUnits; } }, /** - * The vertex array. - * - * @memberof DrawCommand.prototype - * @type {VertexArray} - * @default undefined + * 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. */ - vertexArray : { - get : function() { - return this._vertexArray; - }, - set : function(value) { - if (this._vertexArray !== value) { - this._vertexArray = value; - this.dirty = true; - } + maximumCubeMapSize : { + get: function () { + return ContextLimits._maximumCubeMapSize; } }, /** - * The number of vertices to draw in the vertex array. - * - * @memberof DrawCommand.prototype + * 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} - * @default undefined + * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_FRAGMENT_UNIFORM_VECTORS. */ - count : { - get : function() { - return this._count; - }, - set : function(value) { - if (this._count !== value) { - this._count = value; - this.dirty = true; - } + maximumFragmentUniformVectors : { + get: function () { + return ContextLimits._maximumFragmentUniformVectors; } }, /** - * The offset to start drawing in the vertex array. - * - * @memberof DrawCommand.prototype + * 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} - * @default 0 + * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_TEXTURE_IMAGE_UNITS. */ - offset : { - get : function() { - return this._offset; - }, - set : function(value) { - if (this._offset !== value) { - this._offset = value; - this.dirty = true; - } + maximumTextureImageUnits : { + get: function () { + return ContextLimits._maximumTextureImageUnits; } }, /** - * The number of instances to draw. - * - * @memberof DrawCommand.prototype + * 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} - * @default 0 + * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_RENDERBUFFER_SIZE. */ - instanceCount : { - get : function() { - return this._instanceCount; - }, - set : function(value) { - if (this._instanceCount !== value) { - this._instanceCount = value; - this.dirty = true; - } + maximumRenderbufferSize : { + get: function () { + return ContextLimits._maximumRenderbufferSize; } }, /** - * The shader program to apply. - * - * @memberof DrawCommand.prototype - * @type {ShaderProgram} - * @default undefined + * 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. */ - shaderProgram : { - get : function() { - return this._shaderProgram; - }, - set : function(value) { - if (this._shaderProgram !== value) { - this._shaderProgram = value; - this.dirty = true; - } + maximumTextureSize : { + get: function () { + return ContextLimits._maximumTextureSize; } }, /** - * Whether this command should cast shadows when shadowing is enabled. - * - * @memberof DrawCommand.prototype - * @type {Boolean} - * @default false + * The maximum number of vec4 varying variables supported by this WebGL implementation. + * The minimum is eight. Matrices and arrays count as multiple vec4s. + * @memberof ContextLimits + * @type {Number} + * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VARYING_VECTORS. */ - castShadows : { - get : function() { - return this._castShadows; - }, - set : function(value) { - if (this._castShadows !== value) { - this._castShadows = value; - this.dirty = true; - } + maximumVaryingVectors : { + get: function () { + return ContextLimits._maximumVaryingVectors; } }, /** - * Whether this command should receive shadows when shadowing is enabled. - * - * @memberof DrawCommand.prototype - * @type {Boolean} - * @default false + * 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. */ - receiveShadows : { - get : function() { - return this._receiveShadows; - }, - set : function(value) { - if (this._receiveShadows !== value) { - this._receiveShadows = value; - this.dirty = true; - } + maximumVertexAttributes : { + get: function () { + return ContextLimits._maximumVertexAttributes; } }, /** - * 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 + * 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. */ - uniformMap : { - get : function() { - return this._uniformMap; - }, - set : function(value) { - if (this._uniformMap !== value) { - this._uniformMap = value; - this.dirty = true; - } + maximumVertexTextureImageUnits : { + get: function () { + return ContextLimits._maximumVertexTextureImageUnits; } }, /** - * The render state. - * - * @memberof DrawCommand.prototype - * @type {RenderState} - * @default undefined + * 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. */ - renderState : { - get : function() { - return this._renderState; - }, - set : function(value) { - if (this._renderState !== value) { - this._renderState = value; - this.dirty = true; - } + maximumVertexUniformVectors : { + get: function () { + return ContextLimits._maximumVertexUniformVectors; } }, /** - * The framebuffer to draw to. - * - * @memberof DrawCommand.prototype - * @type {Framebuffer} - * @default undefined + * 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. */ - framebuffer : { - get : function() { - return this._framebuffer; - }, - set : function(value) { - if (this._framebuffer !== value) { - this._framebuffer = value; - this.dirty = true; - } + minimumAliasedLineWidth : { + get: function () { + return ContextLimits._minimumAliasedLineWidth; } }, /** - * The pass when to render. - * - * @memberof DrawCommand.prototype - * @type {Pass} - * @default undefined + * 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. */ - pass : { - get : function() { - return this._pass; - }, - set : function(value) { - if (this._pass !== value) { - this._pass = value; - this.dirty = true; - } + maximumAliasedLineWidth : { + get: function () { + return ContextLimits._maximumAliasedLineWidth; } }, /** - * 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 + * 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. */ - executeInClosestFrustum : { - get : function() { - return this._executeInClosestFrustum; - }, - set : function(value) { - if (this._executeInClosestFrustum !== value) { - this._executeInClosestFrustum = value; - this.dirty = true; - } + minimumAliasedPointSize : { + get: function () { + return ContextLimits._minimumAliasedPointSize; } }, /** - * 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 + * 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. */ - owner : { - get : function() { - return this._owner; - }, - set : function(value) { - if (this._owner !== value) { - this._owner = value; - this.dirty = true; - } + maximumAliasedPointSize : { + get: function () { + return ContextLimits._maximumAliasedPointSize; } }, /** - * 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 + * 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. */ - debugShowBoundingVolume : { - get : function() { - return this._debugShowBoundingVolume; - }, - set : function(value) { - if (this._debugShowBoundingVolume !== value) { - this._debugShowBoundingVolume = value; - this.dirty = true; - } + maximumViewportWidth : { + get: function () { + return ContextLimits._maximumViewportWidth; } }, /** - * Used to implement Scene.debugShowFrustums. - * @private + * 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. */ - debugOverlappingFrustums : { - get : function() { - return this._debugOverlappingFrustums; - }, - set : function(value) { - if (this._debugOverlappingFrustums !== value) { - this._debugOverlappingFrustums = value; - this.dirty = true; - } + maximumViewportHeight : { + get: function () { + return ContextLimits._maximumViewportHeight; } - } - }); - - /** - * @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; + /** + * The maximum degree of anisotropy for texture filtering + * @memberof ContextLimits + * @type {Number} + */ + maximumTextureFilterAnisotropy : { + get: function () { + return ContextLimits._maximumTextureFilterAnisotropy; + } + }, - return result; - }; + /** + * The maximum number of simultaneous outputs that may be written in a fragment shader. + * @memberof ContextLimits + * @type {Number} + */ + maximumDrawBuffers : { + get: function () { + return ContextLimits._maximumDrawBuffers; + } + }, - /** - * 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); - }; + /** + * The maximum number of color attachments supported. + * @memberof ContextLimits + * @type {Number} + */ + maximumColorAttachments : { + get: function () { + return ContextLimits._maximumColorAttachments; + } + }, - return DrawCommand; -}); + /** + * High precision float supported (highp) in fragment shaders. + * @memberof ContextLimits + * @type {Boolean} + */ + highpFloatSupported : { + get: function () { + return ContextLimits._highpFloatSupported; + } + }, -/*global define*/ -define('Renderer/Pass',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + /** + * High precision int supported (highp) in fragment shaders. + * @memberof ContextLimits + * @type {Boolean} + */ + highpIntSupported : { + get: function () { + return ContextLimits._highpIntSupported; + } + } - /** - * 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); + return ContextLimits; }); -/*global define*/ -define('Renderer/RenderState',[ - '../Core/BoundingRectangle', - '../Core/Color', +define('Renderer/CubeMapFace',[ '../Core/defaultValue', - '../Core/defined', + '../Core/defineProperties', '../Core/DeveloperError', - '../Core/WebGLConstants', - '../Core/WindingOrder', - './ContextLimits' + './PixelDatatype' ], function( - BoundingRectangle, - Color, defaultValue, - defined, + defineProperties, DeveloperError, - WebGLConstants, - WindingOrder, - ContextLimits) { + PixelDatatype) { '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; - - - this.id = 0; - this._applyFunctions = []; + 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; } - var nextRenderStateId = 0; - var renderStateCache = {}; + defineProperties(CubeMapFace.prototype, { + pixelFormat : { + get : function() { + return this._pixelFormat; + } + }, + pixelDatatype : { + get : function() { + return this._pixelDatatype; + } + }, + _target : { + get : function() { + return this._targetFace; + } + } + }); /** - * 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. + * Copies texels from the source to the cubemap's face. * - * @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. + * @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 - * 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 + * // 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]) + * }); */ - RenderState.fromCache = function(renderState) { - var partialKey = JSON.stringify(renderState); - var cachedState = renderStateCache[partialKey]; - if (defined(cachedState)) { - ++cachedState.referenceCount; - return cachedState.state; - } + CubeMapFace.prototype.copyFrom = function(source, xOffset, yOffset) { + xOffset = defaultValue(xOffset, 0); + yOffset = defaultValue(yOffset, 0); - // 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++; + + var gl = this._gl; + var target = this._textureTarget; - cachedState = { - referenceCount : 0, - state : states - }; + // 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); - // Cache full render state. Multiple partially defined render states may map to this. - renderStateCache[fullKey] = cachedState; + 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); } - ++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; + gl.bindTexture(target, null); }; /** - * @private + * 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(); */ - RenderState.removeFromCache = function(renderState) { - var states = new RenderState(renderState); - var fullKey = JSON.stringify(states); - var fullCachedState = renderStateCache[fullKey]; + 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); - // decrement partial key reference count - var partialKey = JSON.stringify(renderState); - var cachedState = renderStateCache[partialKey]; - if (defined(cachedState)) { - --cachedState.referenceCount; + + var gl = this._gl; + var target = this._textureTarget; - if (cachedState.referenceCount === 0) { - // remove partial key - delete renderStateCache[partialKey]; + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(target, this._texture); + gl.copyTexSubImage2D(this._targetFace, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height); + gl.bindTexture(target, null); + }; - // decrement full key reference count - if (defined(fullCachedState)) { - --fullCachedState.referenceCount; - } - } - } + return CubeMapFace; +}); - // remove full key if reference count is zero - if (defined(fullCachedState) && (fullCachedState.referenceCount === 0)) { - delete renderStateCache[fullKey]; - } - }; +define('Renderer/MipmapHint',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; /** - * This function is for testing purposes only. * @private */ - RenderState.getCache = function() { - return renderStateCache; + 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); +}); + +define('Renderer/TextureMagnificationFilter',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; + /** - * This function is for testing purposes only. * @private */ - RenderState.clearCache = function() { - renderStateCache = {}; - }; + var TextureMagnificationFilter = { + NEAREST : WebGLConstants.NEAREST, + LINEAR : WebGLConstants.LINEAR, - function enableOrDisable(gl, glEnum, enable) { - if (enable) { - gl.enable(glEnum); - } else { - gl.disable(glEnum); + validate : function(textureMagnificationFilter) { + return ((textureMagnificationFilter === TextureMagnificationFilter.NEAREST) || + (textureMagnificationFilter === TextureMagnificationFilter.LINEAR)); } - } + }; - function applyFrontFace(gl, renderState) { - gl.frontFace(renderState.frontFace); - } + return freezeObject(TextureMagnificationFilter); +}); - function applyCull(gl, renderState) { - var cull = renderState.cull; - var enabled = cull.enabled; +define('Renderer/TextureMinificationFilter',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; - enableOrDisable(gl, gl.CULL_FACE, enabled); + /** + * @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, - if (enabled) { - gl.cullFace(cull.face); + 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)); } - } - - 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); - } - } + return freezeObject(TextureMinificationFilter); +}); - function applyScissorTest(gl, renderState, passState) { - var scissorTest = renderState.scissorTest; - var enabled = (defined(passState.scissorTest)) ? passState.scissorTest.enabled : scissorTest.enabled; +define('Renderer/TextureWrap',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; - enableOrDisable(gl, gl.SCISSOR_TEST, enabled); + /** + * @private + */ + var TextureWrap = { + CLAMP_TO_EDGE : WebGLConstants.CLAMP_TO_EDGE, + REPEAT : WebGLConstants.REPEAT, + MIRRORED_REPEAT : WebGLConstants.MIRRORED_REPEAT, - if (enabled) { - var rectangle = (defined(passState.scissorTest)) ? passState.scissorTest.rectangle : scissorTest.rectangle; - gl.scissor(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + validate : function(textureWrap) { + return ((textureWrap === TextureWrap.CLAMP_TO_EDGE) || + (textureWrap === TextureWrap.REPEAT) || + (textureWrap === TextureWrap.MIRRORED_REPEAT)); } - } - - 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); - } - } + return freezeObject(TextureWrap); +}); - function applyColorMask(gl, renderState) { - var colorMask = renderState.colorMask; - gl.colorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha); - } +define('Renderer/Sampler',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + './TextureMagnificationFilter', + './TextureMinificationFilter', + './TextureWrap' + ], function( + defaultValue, + defined, + defineProperties, + DeveloperError, + TextureMagnificationFilter, + TextureMinificationFilter, + TextureWrap) { + 'use strict'; - function applyDepthMask(gl, renderState) { - gl.depthMask(renderState.depthMask); - } + /** + * @private + */ + function Sampler(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - function applyStencilMask(gl, renderState) { - gl.stencilMask(renderState.stencilMask); - } + 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; - function applyBlendingColor(gl, color) { - gl.blendColor(color.red, color.green, color.blue, color.alpha); + + this._wrapS = wrapS; + this._wrapT = wrapT; + this._minificationFilter = minificationFilter; + this._magnificationFilter = magnificationFilter; + this._maximumAnisotropy = maximumAnisotropy; } - 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); + 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; + } } - } + }); - function applyStencilTest(gl, renderState) { - var stencilTest = renderState.stencilTest; - var enabled = stencilTest.enabled; + return Sampler; +}); - enableOrDisable(gl, gl.STENCIL_TEST, enabled); +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'; - if (enabled) { - var frontFunction = stencilTest.frontFunction; - var backFunction = stencilTest.backFunction; - var reference = stencilTest.reference; - var mask = stencilTest.mask; + function CubeMap(options) { - // 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); + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var frontOperation = stencilTest.frontOperation; - var frontOperationFail = frontOperation.fail; - var frontOperationZFail = frontOperation.zFail; - var frontOperationZPass = frontOperation.zPass; + + var context = options.context; + var source = options.source; + var width; + var height; - gl.stencilOpSeparate(gl.FRONT, frontOperationFail, frontOperationZFail, frontOperationZPass); + if (defined(source)) { + var faces = [source.positiveX, source.negativeX, source.positiveY, source.negativeY, source.positiveZ, source.negativeZ]; - var backOperation = stencilTest.backOperation; - var backOperationFail = backOperation.fail; - var backOperationZFail = backOperation.zFail; - var backOperationZPass = backOperation.zPass; + + width = faces[0].width; + height = faces[0].height; - gl.stencilOpSeparate(gl.BACK, backOperationFail, backOperationZFail, backOperationZPass); + } else { + width = options.width; + height = options.height; } - } - - 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 size = width; + var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA); + var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE); - 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; - } + + var sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, size, size) * 6; - passState.context.uniformState.viewport = viewport; - gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height); - } + // 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); - 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); - }; + var gl = context._gl; + var textureTarget = gl.TEXTURE_CUBE_MAP; + var texture = gl.createTexture(); - function createFuncs(previousState, nextState) { - var funcs = []; + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(textureTarget, texture); - if (previousState.frontFace !== nextState.frontFace) { - funcs.push(applyFrontFace); + 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 ((previousState.cull.enabled !== nextState.cull.enabled) || (previousState.cull.face !== nextState.cull.face)) { - funcs.push(applyCull); - } + 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 (previousState.lineWidth !== nextState.lineWidth) { - funcs.push(applyLineWidth); + 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); - if ((previousState.polygonOffset.enabled !== nextState.polygonOffset.enabled) || - (previousState.polygonOffset.factor !== nextState.polygonOffset.factor) || - (previousState.polygonOffset.units !== nextState.polygonOffset.units)) { - funcs.push(applyPolygonOffset); - } + this._gl = gl; + this._textureFilterAnisotropic = context._textureFilterAnisotropic; + this._textureTarget = textureTarget; + this._texture = texture; + this._pixelFormat = pixelFormat; + this._pixelDatatype = pixelDatatype; + this._size = size; + this._hasMipmap = false; + this._sizeInBytes = sizeInBytes; + this._preMultiplyAlpha = preMultiplyAlpha; + this._flipY = flipY; + this._sampler = undefined; - 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); - } + 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); - 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); - } + this.sampler = defined(options.sampler) ? options.sampler : new Sampler(); + } - if (previousState.depthMask !== nextState.depthMask) { - funcs.push(applyDepthMask); - } + 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; - if (previousState.stencilMask !== nextState.stencilMask) { - funcs.push(applyStencilMask); - } + var mipmap = + (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) || + (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) || + (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) || + (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); - 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); - } + // 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; + } - if ((previousState.sampleCoverage.enabled !== nextState.sampleCoverage.enabled) || - (previousState.sampleCoverage.value !== nextState.sampleCoverage.value) || - (previousState.sampleCoverage.invert !== nextState.sampleCoverage.invert)) { - funcs.push(applySampleCoverage); - } + var gl = this._gl; + var target = this._textureTarget; - return funcs; - } + 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); - 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; + 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; + } + }, + sizeInBytes : { + get : function() { + if (this._hasMipmap) { + return Math.floor(this._sizeInBytes * 4 / 3); + } + return this._sizeInBytes; + } + }, + preMultiplyAlpha : { + get : function() { + return this._preMultiplyAlpha; + } + }, + flipY : { + get : function() { + return this._flipY; } + }, - var len = funcs.length; - for (var i = 0; i < len; ++i) { - funcs[i](gl, renderState); + _target : { + get : function() { + return this._textureTarget; } } + }); - var previousScissorTest = (defined(previousPassState.scissorTest)) ? previousPassState.scissorTest : previousRenderState.scissorTest; - var scissorTest = (defined(passState.scissorTest)) ? passState.scissorTest : renderState.scissorTest; + /** + * 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); - // 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); - } + + this._hasMipmap = true; - 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); - } + 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); + }; - if (previousRenderState !== renderState || previousPassState !== passState || previousPassState.context !== passState.context) { - applyViewport(gl, renderState, passState); - } + CubeMap.prototype.isDestroyed = function() { + return false; }; - RenderState.getState = function(renderState) { - - 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 - }; + 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 RenderState; + return CubeMap; }); -/*global define*/ -define('Renderer/AutomaticUniforms',[ - '../Core/Cartesian3', - '../Core/Matrix4', - '../Core/WebGLConstants' +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( - Cartesian3, - Matrix4, - WebGLConstants) { + Cartesian2, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + CesiumMath, + PixelFormat, + WebGLConstants, + ContextLimits, + MipmapHint, + PixelDatatype, + Sampler, + TextureMagnificationFilter, + TextureMinificationFilter) { 'use strict'; - /*global WebGLRenderingContext*/ - var viewerPositionWCScratch = new Cartesian3(); + function Texture(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - function AutomaticUniform(options) { - this._size = options.size; - this._datatype = options.datatype; - this.getValue = options.getValue; - } + + var context = options.context; + var width = options.width; + var height = options.height; + var source = options.source; - // this check must use typeof, not defined, because defined doesn't work with undeclared variables. - if (typeof WebGLRenderingContext === 'undefined') { - return {}; - } + if (defined(source)) { + if (!defined(width)) { + width = defaultValue(source.videoWidth, source.width); + } + if (!defined(height)) { + height = defaultValue(source.videoHeight, source.height); + } + } - 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'; + var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA); + var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE); + var internalFormat = pixelFormat; - AutomaticUniform.prototype.getDeclaration = function(name) { - var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name; + var isCompressed = PixelFormat.isCompressedFormat(internalFormat); - var size = this._size; - if (size === 1) { - declaration += ';'; + 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 (pixelDatatype === PixelDatatype.FLOAT) { + switch (pixelFormat) { + case PixelFormat.RGBA: + internalFormat = WebGLConstants.RGBA32F; + break; + case PixelFormat.RGB: + internalFormat = WebGLConstants.RGB32F; + break; + case PixelFormat.RG: + internalFormat = WebGLConstants.RG32F; + break; + case PixelFormat.R: + internalFormat = WebGLConstants.R32F; + break; + } + } + } + + + // 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 + if (isCompressed) { + gl.compressedTexImage2D(textureTarget, 0, internalFormat, width, height, 0, source.arrayBufferView); + } else { + 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 { - declaration += '[' + size.toString() + '];'; + gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, null); } + gl.bindTexture(textureTarget, null); - return declaration; - }; + var sizeInBytes; + if (isCompressed) { + sizeInBytes = PixelFormat.compressedTextureSizeInBytes(pixelFormat, width, height); + } else { + sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, width, height); + } + + 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._hasMipmap = false; + this._sizeInBytes = sizeInBytes; + 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, DEPTH_STENCIL or a compressed format. + * @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 */ - 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; - } - }), + Texture.fromFramebuffer = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - /** - * 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; - } - }), + + var context = options.context; + var gl = context._gl; - /** - * 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; - } - }), + 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; - /** - * 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; + + 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 } - }), + }); - /** - * 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; - } - }), + return texture; + }; + defineProperties(Texture.prototype, { /** - * 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 + * 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} */ - czm_inverseModel : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModel; - } - }), + sampler : { + get : function() { + return this._sampler; + }, + set : function(sampler) { + var minificationFilter = sampler.minificationFilter; + var magnificationFilter = sampler.magnificationFilter; - /** - * 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; - } - }), + var mipmap = + (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) || + (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) || + (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) || + (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); - /** - * 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; - } - }), + // 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; + } - /** - * 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; - } - }), + var gl = this._context._gl; + var target = this._textureTarget; - /** - * 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; - } - }), + 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); - /** - * 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; + this._sampler = sampler; } - }), - - /** - * 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; + }, + pixelFormat : { + get : function() { + return this._pixelFormat; } - }), - - /** - * 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; + }, + pixelDatatype : { + get : function() { + return this._pixelDatatype; } - }), - - /** - * 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; + }, + dimensions : { + get : function() { + return this._dimensions; } - }), - - /** - * 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; + }, + preMultiplyAlpha : { + get : function() { + return this._preMultiplyAlpha; } - }), - - /** - * 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; - } - }), - - /** - * 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; + }, + flipY : { + get : function() { + return this._flipY; } - }), - - /** - * 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; + }, + width : { + get : function() { + return this._width; } - }), - - /** - * 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; + }, + height : { + get : function() { + return this._height; } - }), - - /** - * 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; + }, + sizeInBytes : { + get : function() { + if (this._hasMipmap) { + return Math.floor(this._sizeInBytes * 4 / 3); + } + return this._sizeInBytes; } - }), - - /** - * 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; + }, + _target : { + get : function() { + return this._textureTarget; } - }), + } + }); - /** - * 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; - } - }), + /** + * 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} Cannot call copyFrom with a compressed texture pixel format. + * @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); - /** - * 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; - } - }), + + var gl = this._context._gl; + var target = this._textureTarget; - /** - * 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; - } - }), + // 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); - /** - * 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; - } - }), + 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); + } - /** - * 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; - } - }), + gl.bindTexture(target, null); + }; - /** - * 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; - } - }), + /** + * @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} Cannot call copyFrom with a compressed texture pixel format. + * @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); - /** - * 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; - } - }), + + var gl = this._context._gl; + var target = this._textureTarget; - /** - * 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; - } - }), + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(target, this._texture); + gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height); + gl.bindTexture(target, null); + }; - /** - * 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; - } - }), + /** + * @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} Cannot call generateMipmap when the texture pixel format is a compressed format. + * @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); - /** - * 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; - } - }), + + this._hasMipmap = true; - /** - * 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); - } - }), + var gl = this._context._gl; + var target = this._textureTarget; - /** - * 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; - } - }), + gl.hint(gl.GENERATE_MIPMAP_HINT, hint); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(target, this._texture); + gl.generateMipmap(target); + gl.bindTexture(target, null); + }; - /** - * 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; - } - }), + Texture.prototype.isDestroyed = function() { + return false; + }; - /** - * 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; - } - }), + Texture.prototype.destroy = function() { + this._context._gl.deleteTexture(this._texture); + return destroyObject(this); + }; - /** - * 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; - } - }), + return Texture; +}); - /** - * An automatic GLSL uniform representing the current scene background color. - * - * @alias czm_backgroundColor - * @glslUniform - * - * @example - * // GLSL declaration - * uniform vec4 czm_backgroundColor; - * - * // Example: If the given color's RGB matches the background color, invert it. - * vec4 adjustColorForContrast(vec4 color) - * { - * if (czm_backgroundColor.rgb == color.rgb) - * { - * color.rgb = vec3(1.0) - color.rgb; - * } - * - * return color; - * } - */ - czm_backgroundColor : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.backgroundColor; - } - }), - - /** - * 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; - } - }), - - /** - * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter. - * This will be in pixel coordinates relative to the canvas. - * - * @alias czm_imagerySplitPosition - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform float czm_imagerySplitPosition; - */ - czm_imagerySplitPosition : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.imagerySplitPosition; - } - }), - - /** - * An automatic GLSL uniform scalar representing the geometric tolerance per meter - * - * @alias czm_geometricToleranceOverMeter - * @glslUniform - */ - czm_geometricToleranceOverMeter : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.geometricToleranceOverMeter; - } - }), - - /** - * An automatic GLSL uniform representing the distance from the camera at which to disable the depth test of billboards, labels and points - * to, for example, prevent clipping against terrain. When set to zero, the depth test should always be applied. When less than zero, - * the depth test should never be applied. - * - * @alias czm_minimumDisableDepthTestDistance - * @glslUniform - */ - czm_minimumDisableDepthTestDistance : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.minimumDisableDepthTestDistance; - } - }) - }; - - return AutomaticUniforms; +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/BumpMapMaterial',[],function() { + 'use strict'; + return "uniform sampler2D image;\n\ +uniform float strength;\n\ +uniform vec2 repeat;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + vec2 st = materialInput.st;\n\ + \n\ + vec2 centerPixel = fract(repeat * st);\n\ + float centerBump = texture2D(image, centerPixel).channel;\n\ + \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\ + \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\ + \n\ + vec3 normalTangentSpace = normalize(vec3(centerBump - rightBump, centerBump - topBump, clamp(1.0 - strength, 0.1, 1.0)));\n\ + vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace;\n\ + \n\ + material.normal = normalEC;\n\ + material.diffuse = vec3(0.01);\n\ + \n\ + return material;\n\ +}\n\ +"; }); - -/*global define*/ -define('Renderer/createUniform',[ +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/CheckerboardMaterial',[],function() { + 'use strict'; + return "uniform vec4 lightColor;\n\ +uniform vec4 darkColor;\n\ +uniform vec2 repeat;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + vec2 st = materialInput.st;\n\ + \n\ + // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights\n\ + float b = mod(floor(repeat.s * st.s) + floor(repeat.t * st.t), 2.0); // 0.0 or 1.0\n\ + \n\ + // Find the distance from the closest separator (region between two colors)\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\ + \n\ + vec4 currentColor = mix(lightColor, darkColor, b);\n\ + vec4 color = czm_antialias(lightColor, darkColor, currentColor, value, 0.03);\n\ + \n\ + material.diffuse = color.rgb;\n\ + material.alpha = color.a;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/DotMaterial',[],function() { + 'use strict'; + return "uniform vec4 lightColor;\n\ +uniform vec4 darkColor;\n\ +uniform vec2 repeat;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ + \n\ + // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights\n\ + float b = smoothstep(0.3, 0.32, length(fract(repeat * materialInput.st) - 0.5)); // 0.0 or 1.0\n\ +\n\ + vec4 color = mix(lightColor, darkColor, b);\n\ + material.diffuse = color.rgb;\n\ + material.alpha = color.a;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +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\ +\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\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ + \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\ + \n\ + float u = length(vec2(s, t));\n\ + vec4 color = mix(fadeInColor, fadeOutColor, u);\n\ + \n\ + material.emission = color.rgb;\n\ + material.alpha = color.a;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/GridMaterial',[],function() { + 'use strict'; + return "#ifdef GL_OES_standard_derivatives\n\ + #extension GL_OES_standard_derivatives : enable\n\ +#endif\n\ +\n\ +uniform vec4 color;\n\ +uniform float cellAlpha;\n\ +uniform vec2 lineCount;\n\ +uniform vec2 lineThickness;\n\ +uniform vec2 lineOffset;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + vec2 st = materialInput.st;\n\ +\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\ +\n\ + float value;\n\ +#ifdef GL_OES_standard_derivatives\n\ + // Fuzz Factor - Controls blurriness of lines\n\ + const float fuzz = 1.2;\n\ + vec2 thickness = (lineThickness * czm_resolutionScale) - 1.0;\n\ +\n\ + // From \"3D Engine Design for Virtual Globes\" by Cozzi and Ring, Listing 4.13.\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\ + // Fuzz Factor - Controls blurriness of lines\n\ + const float fuzz = 0.05;\n\ +\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\ +\n\ + // Edges taken from RimLightingMaterial.glsl\n\ + // See http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html\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\ +\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\ +\n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/NormalMapMaterial',[],function() { + 'use strict'; + return "uniform sampler2D image;\n\ +uniform float strength;\n\ +uniform vec2 repeat;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ + \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\ + \n\ + material.normal = normalEC;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/PolylineArrowMaterial',[],function() { + 'use strict'; + return "#ifdef GL_OES_standard_derivatives\n\ +#extension GL_OES_standard_derivatives : enable\n\ +#endif\n\ +\n\ +uniform vec4 color;\n\ +\n\ +varying float v_width;\n\ +\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\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + vec2 st = materialInput.st;\n\ +\n\ +#ifdef GL_OES_standard_derivatives\n\ + float base = 1.0 - abs(fwidth(st.s)) * 10.0;\n\ +#else\n\ + float base = 0.99; // 1% of the line will be the arrow head\n\ +#endif\n\ +\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\ +\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\ +\n\ + float t = step(base, materialInput.st.s);\n\ + t *= 1.0 - step(ptOnUpperLine, st.t);\n\ + t *= step(ptOnLowerLine, st.t);\n\ +\n\ + // Find the distance from the closest separator (region between two colors)\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\ +\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\ +\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. +define('Shaders/Materials/PolylineDashMaterial',[],function() { + 'use strict'; + return "uniform vec4 color;\n\ +uniform vec4 gapColor;\n\ +uniform float dashLength;\n\ +uniform float dashPattern;\n\ +varying float v_polylineAngle;\n\ +\n\ +const float maskLength = 16.0;\n\ +\n\ +mat2 rotate(float rad) {\n\ + float c = cos(rad);\n\ + float s = sin(rad);\n\ + return mat2(\n\ + c, s,\n\ + -s, c\n\ + );\n\ +}\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + vec2 pos = rotate(v_polylineAngle) * gl_FragCoord.xy;\n\ +\n\ + // Get the relative position within the dash from 0 to 1\n\ + float dashPosition = fract(pos.x / dashLength);\n\ + // Figure out the mask index.\n\ + float maskIndex = floor(dashPosition * maskLength);\n\ + // Test the bit mask.\n\ + float maskTest = floor(dashPattern / pow(2.0, maskIndex));\n\ + vec4 fragColor = (mod(maskTest, 2.0) < 1.0) ? gapColor : color;\n\ + if (fragColor.a < 0.005) { // matches 0/255 and 1/255\n\ + discard;\n\ + }\n\ +\n\ + material.emission = fragColor.rgb;\n\ + material.alpha = fragColor.a;\n\ + return material;\n\ +}"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/PolylineGlowMaterial',[],function() { + 'use strict'; + return "uniform vec4 color;\n\ +uniform float glowPower;\n\ +\n\ +varying float v_width;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + vec2 st = materialInput.st;\n\ + float glow = glowPower / abs(st.t - 0.5) - (glowPower / 0.5);\n\ +\n\ + material.emission = max(vec3(glow - 1.0 + color.rgb), color.rgb);\n\ + material.alpha = clamp(0.0, 1.0, glow) * color.a;\n\ +\n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/PolylineOutlineMaterial',[],function() { + 'use strict'; + return "uniform vec4 color;\n\ +uniform vec4 outlineColor;\n\ +uniform float outlineWidth;\n\ +\n\ +varying float v_width;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ + \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\ + \n\ + // Find the distance from the closest separator (region between two colors)\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\ + \n\ + vec4 currentColor = mix(outlineColor, color, b);\n\ + vec4 outColor = czm_antialias(outlineColor, color, currentColor, dist);\n\ + \n\ + material.diffuse = outColor.rgb;\n\ + material.alpha = outColor.a;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/RimLightingMaterial',[],function() { + 'use strict'; + return "uniform vec4 color;\n\ +uniform vec4 rimColor;\n\ +uniform float width;\n\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + // See http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html\n\ + float d = 1.0 - dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC));\n\ + float s = smoothstep(1.0 - width, 1.0, d);\n\ +\n\ + material.diffuse = color.rgb;\n\ + material.emission = rimColor.rgb * s; \n\ + material.alpha = mix(color.a, rimColor.a, s);\n\ +\n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +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\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + // Based on the Stripes Fragment Shader in the Orange Book (11.1.2)\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\ + \n\ + vec4 currentColor = mix(evenColor, oddColor, step(0.5, value));\n\ + vec4 color = czm_antialias(evenColor, oddColor, currentColor, dist);\n\ + \n\ + material.diffuse = color.rgb;\n\ + material.alpha = color.a;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Materials/Water',[],function() { + 'use strict'; + return "// Thanks for the contribution Jonas\n\ +// http://29a.ch/2012/7/19/webgl-terrain-rendering-water-fog\n\ +\n\ +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\ +\n\ +czm_material czm_getMaterial(czm_materialInput materialInput)\n\ +{\n\ + czm_material material = czm_getDefaultMaterial(materialInput);\n\ +\n\ + float time = czm_frameNumber * animationSpeed;\n\ + \n\ + // fade is a function of the distance from the fragment and the frequency of the waves\n\ + float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency * fadeFactor);\n\ + \n\ + float specularMapValue = texture2D(specularMap, materialInput.st).r;\n\ + \n\ + // note: not using directional motion at this time, just set the angle to 0.0;\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\ + \n\ + // fade out the normal perturbation as we move further from the water surface\n\ + normalTangentSpace.xy /= fade;\n\ + \n\ + // attempt to fade out the normal perturbation as we approach non water areas (low specular map value)\n\ + normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);\n\ + \n\ + normalTangentSpace = normalize(normalTangentSpace);\n\ + \n\ + // get ratios for alignment of the new normal vector with a vector perpendicular to the tangent plane\n\ + float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);\n\ + \n\ + // fade out water effect as specular map value decreases\n\ + material.alpha = specularMapValue;\n\ + \n\ + // base color is a blend of the water and non-water color based on the value from the specular map\n\ + // may need a uniform blend factor to better control this\n\ + material.diffuse = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);\n\ + \n\ + // diffuse highlights are based on how perturbed the normal is\n\ + material.diffuse += (0.1 * tsPerturbationRatio);\n\ + \n\ + material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);\n\ + \n\ + material.specular = specularIntensity;\n\ + material.shininess = 10.0;\n\ + \n\ + return material;\n\ +}\n\ +"; +}); +define('Scene/Material',[ '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartesian4', + '../Core/clone', '../Core/Color', + '../Core/combine', + '../Core/createGuid', + '../Core/defaultValue', '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', + '../Core/isArray', + '../Core/loadCRN', + '../Core/loadImage', + '../Core/loadKTX', '../Core/Matrix2', '../Core/Matrix3', '../Core/Matrix4', - '../Core/RuntimeError' + '../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/PolylineDashMaterial', + '../Shaders/Materials/PolylineGlowMaterial', + '../Shaders/Materials/PolylineOutlineMaterial', + '../Shaders/Materials/RimLightingMaterial', + '../Shaders/Materials/StripeMaterial', + '../Shaders/Materials/Water', + '../ThirdParty/when' ], function( Cartesian2, - Cartesian3, - Cartesian4, + clone, Color, + combine, + createGuid, + defaultValue, defined, + defineProperties, + destroyObject, DeveloperError, + isArray, + loadCRN, + loadImage, + loadKTX, Matrix2, Matrix3, Matrix4, - RuntimeError) { + CubeMap, + Texture, + BumpMapMaterial, + CheckerboardMaterial, + DotMaterial, + FadeMaterial, + GridMaterial, + NormalMapMaterial, + PolylineArrowMaterial, + PolylineDashMaterial, + PolylineGlowMaterial, + PolylineOutlineMaterial, + RimLightingMaterial, + StripeMaterial, + WaterMaterial, + when) { 'use strict'; /** - * @private + * 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.
      • + *
      + *
    • PolylineDash
    • + *
        + *
      • color: color for the line.
      • + *
      • gapColor: color for the gaps in the line.
      • + *
      • dashLength: Dash length in pixels.
      • + *
      • dashPattern: The 16 bit stipple pattern for the line..
      • + *
      + *
    • 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 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) { + function Material(options) { /** - * @readonly + * 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.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); - } - }; - - /////////////////////////////////////////////////////////////////////////// + this.type = undefined; - function UniformFloatVec2(gl, activeUniform, uniformName, location) { /** - * @readonly + * The glsl shader source for this material. + * @type {String} + * @default undefined */ - 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); - } - }; + this.shaderSource = undefined; - /////////////////////////////////////////////////////////////////////////// + /** + * Maps sub-material names to Material objects. + * @type {Object} + * @default undefined + */ + this.materials = undefined; - function UniformFloatVec3(gl, activeUniform, uniformName, location) { /** - * @readonly + * Maps uniform names to their values. + * @type {Object} + * @default undefined */ - this.name = uniformName; + this.uniforms = undefined; + this._uniforms = undefined; - this.value = undefined; - this._value = 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._gl = gl; - this._location = location; - } + this._strict = undefined; + this._template = undefined; + this._count = undefined; - UniformFloatVec3.prototype.set = function() { - var v = this.value; + this._texturePaths = {}; + this._loadedImages = []; + this._loadedCubeMaps = []; - 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 { - } - }; + this._textures = {}; - /////////////////////////////////////////////////////////////////////////// + this._updateFunctions = []; - function UniformFloatVec4(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; + this._defaultTexture = undefined; - this.value = undefined; - this._value = undefined; + initializeMaterial(options, this); + defineProperties(this, { + type : { + value : this.type, + writable : false + } + }); - this._gl = gl; - this._location = location; + if (!defined(Material._uniformList[this.type])) { + Material._uniformList[this.type] = Object.keys(this._uniforms); + } } - UniformFloatVec4.prototype.set = function() { - var v = this.value; + // Cached list of combined uniform names indexed by type. + // Used to get the list of uniforms in the same order. + Material._uniformList = {}; - 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); + /** + * 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) { + + var material = new Material({ + fabric : { + type : type } - } else { - } - }; - - /////////////////////////////////////////////////////////////////////////// - - 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); - }; + if (defined(uniforms)) { + for (var name in uniforms) { + if (uniforms.hasOwnProperty(name)) { + material.uniforms[name] = uniforms[name]; + } + } + } - UniformSampler.prototype._setSampler = function(textureUnitIndex) { - this.textureUnitIndex = textureUnitIndex; - this._gl.uniform1i(this._location, textureUnitIndex); - return textureUnitIndex + 1; + return material; }; - /////////////////////////////////////////////////////////////////////////// - - function UniformInt(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; - - this.value = undefined; - this._value = 0.0; - - this._gl = gl; - this._location = location; - } + /** + * 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(); + } - UniformInt.prototype.set = function() { - if (this.value !== this._value) { - this._value = this.value; - this._gl.uniform1i(this._location, this.value); + return this.translucent; } - }; - - /////////////////////////////////////////////////////////////////////////// - - function UniformIntVec2(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; - - this.value = undefined; - this._value = new Cartesian2(); - this._gl = gl; - this._location = location; - } + 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; + } - 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); + if (!translucent) { + break; + } } + return translucent; }; - /////////////////////////////////////////////////////////////////////////// + /** + * @private + */ + Material.prototype.update = function(context) { + var i; + var uniformId; - function UniformIntVec3(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; + var loadedImages = this._loadedImages; + var length = loadedImages.length; - this.value = undefined; - this._value = new Cartesian3(); + for (i = 0; i < length; ++i) { + var loadedImage = loadedImages[i]; + uniformId = loadedImage.id; + var image = loadedImage.image; - this._gl = gl; - this._location = location; - } + var texture; + if (defined(image.internalFormat)) { + texture = new Texture({ + context : context, + pixelFormat : image.internalFormat, + width : image.width, + height : image.height, + source : { + arrayBufferView : image.bufferView + } + }); + } else { + texture = new Texture({ + context : context, + source : image + }); + } - 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); + 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; - function UniformIntVec4(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; + var loadedCubeMaps = this._loadedCubeMaps; + length = loadedCubeMaps.length; - this.value = undefined; - this._value = new Cartesian4(); + for (i = 0; i < length; ++i) { + var loadedCubeMap = loadedCubeMaps[i]; + uniformId = loadedCubeMap.id; + var images = loadedCubeMap.images; - this._gl = gl; - this._location = location; - } + 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] + } + }); - 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); + this._textures[uniformId] = cubeMap; } - }; - - /////////////////////////////////////////////////////////////////////////// - - function UniformMat2(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; - this.value = undefined; - this._value = new Float32Array(4); + loadedCubeMaps.length = 0; - this._gl = gl; - this._location = location; - } + var updateFunctions = this._updateFunctions; + length = updateFunctions.length; + for (i = 0; i < length; ++i) { + updateFunctions[i](this, context); + } - 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); + var subMaterials = this.materials; + for (var name in subMaterials) { + if (subMaterials.hasOwnProperty(name)) { + subMaterials[name].update(context); + } } }; - /////////////////////////////////////////////////////////////////////////// - - function UniformMat3(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; - - this.value = undefined; - this._value = new Float32Array(9); + /** + * 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; + }; - this._gl = gl; - this._location = location; - } + /** + * 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(); + } + } + } - 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); + 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)); - function UniformMat4(gl, activeUniform, uniformName, location) { - /** - * @readonly - */ - this.name = uniformName; + result.type = defined(result._template.type) ? result._template.type : createGuid(); - this.value = undefined; - this._value = new Float32Array(16); + result.shaderSource = ''; + result.materials = {}; + result.uniforms = {}; + result._uniforms = {}; + result._translucentFunctions = []; - this._gl = gl; - this._location = location; - } + var translucent; - 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); + // 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; } - }; - - 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'; + // Make sure the template has no obvious errors. More error checking happens later. + checkForTemplateErrors(result); - /** - * @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 + '".'); + // If the material has a new type, add it to the cache. + if (!defined(cachedMaterial)) { + Material._materialCache.addMaterial(result.type, result); } - } - function UniformArrayFloat(gl, activeUniform, uniformName, locations) { - var length = locations.length; + createMethodDefinition(result); + createUniforms(result); + createSubMaterials(result); - /** - * @readonly - */ - this.name = uniformName; + var defaultTranslucent = result._translucentFunctions.length === 0 ? true : undefined; + translucent = defaultValue(translucent, defaultTranslucent); + translucent = defaultValue(options.translucent, translucent); - this.value = new Array(length); - this._value = new Float32Array(length); + if (defined(translucent)) { + if (typeof translucent === 'function') { + var wrappedTranslucent = function() { + return translucent(result); + }; + result._translucentFunctions.push(wrappedTranslucent); + } else { + result._translucentFunctions.push(translucent); + } - 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; + 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); + } + } } } + } - if (changed) { - this._gl.uniform1fv(this._location, arraybuffer); - } - }; + function invalidNameError(property, properties) { + } - /////////////////////////////////////////////////////////////////////////// + function duplicateNameError(property, properties) { + } - function UniformArrayFloatVec2(gl, activeUniform, uniformName, locations) { - var length = locations.length; + var templateProperties = ['type', 'materials', 'uniforms', 'components', 'source']; + var componentProperties = ['diffuse', 'specular', 'shininess', 'normal', 'emission', 'alpha']; - /** - * @readonly - */ - this.name = uniformName; + function checkForTemplateErrors(material) { + var template = material._template; + var uniforms = template.uniforms; + var materials = template.materials; + var components = template.components; - this.value = new Array(length); - this._value = new Float32Array(length * 2); + // Make sure source and components do not exist in the same template. + + // Make sure all template and components properties are valid. + checkForValidProperties(template, templateProperties, invalidNameError, true); + checkForValidProperties(components, componentProperties, invalidNameError, true); - this._gl = gl; - this._location = locations[0]; + // 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); } - 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; + // 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'; + } + } } - j += 2; + material.shaderSource += 'return material;\n}\n'; } + } - if (changed) { - this._gl.uniform2fv(this._location, arraybuffer); - } + var matrixMap = { + 'mat2' : Matrix2, + 'mat3' : Matrix3, + 'mat4' : Matrix4 }; - /////////////////////////////////////////////////////////////////////////// - - 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); + var ktxRegex = /\.ktx$/i; + var crnRegex = /\.crn$/i; - this._gl = gl; - this._location = locations[0]; - } + 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]; - UniformArrayFloatVec3.prototype.set = function() { - var value = this.value; - var length = value.length; - var arraybuffer = this._value; - var changed = false; - var j = 0; + var uniformDimensionsName; + var uniformDimensions; - for (var i = 0; i < length; ++i) { - var v = value[i]; + 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(v.red)) { - if ((v.red !== arraybuffer[j]) || - (v.green !== arraybuffer[j + 1]) || - (v.blue !== arraybuffer[j + 2])) { + if (!defined(texture) || texture === context.defaultTexture) { + texture = new Texture({ + context : context, + source : uniformValue + }); + material._textures[uniformId] = texture; + return; + } - arraybuffer[j] = v.red; - arraybuffer[j + 1] = v.green; - arraybuffer[j + 2] = v.blue; - changed = true; + texture.copyFrom(uniformValue); + } else if (!defined(texture)) { + material._textures[uniformId] = context.defaultTexture; } - } else if (defined(v.x)) { - if (!Cartesian3.equalsArray(v, arraybuffer, j)) { - Cartesian3.pack(v, arraybuffer, j); - changed = true; + return; + } + + if (uniformValue instanceof Texture && uniformValue !== texture) { + material._texturePaths[uniformId] = undefined; + var tmp = material._textures[uniformId]; + if (tmp !== material._defaultTexture) { + tmp.destroy(); } - } else { - } + material._textures[uniformId] = uniformValue; - j += 3; - } + uniformDimensionsName = uniformId + 'Dimensions'; + if (uniforms.hasOwnProperty(uniformDimensionsName)) { + uniformDimensions = uniforms[uniformDimensionsName]; + uniformDimensions.x = uniformValue._width; + uniformDimensions.y = uniformValue._height; + } - if (changed) { - this._gl.uniform3fv(this._location, arraybuffer); - } - }; + return; + } - /////////////////////////////////////////////////////////////////////////// + if (!defined(texture)) { + material._texturePaths[uniformId] = undefined; + if (!defined(material._defaultTexture)) { + material._defaultTexture = context.defaultTexture; + } + texture = material._textures[uniformId] = material._defaultTexture; - function UniformArrayFloatVec4(gl, activeUniform, uniformName, locations) { - var length = locations.length; + uniformDimensionsName = uniformId + 'Dimensions'; + if (uniforms.hasOwnProperty(uniformDimensionsName)) { + uniformDimensions = uniforms[uniformDimensionsName]; + uniformDimensions.x = texture._width; + uniformDimensions.y = texture._height; + } + } - /** - * @readonly - */ - this.name = uniformName; + if (uniformValue === Material.DefaultImageId) { + return; + } - this.value = new Array(length); - this._value = new Float32Array(length * 4); + if (uniformValue !== material._texturePaths[uniformId]) { + if (typeof uniformValue === 'string') { + var promise; + if (ktxRegex.test(uniformValue)) { + promise = loadKTX(uniformValue); + } else if (crnRegex.test(uniformValue)) { + promise = loadCRN(uniformValue); + } else { + promise = loadImage(uniformValue); + } + when(promise, function(image) { + material._loadedImages.push({ + id : uniformId, + image : image + }); + }); + } else if (uniformValue instanceof HTMLCanvasElement) { + material._loadedImages.push({ + id : uniformId, + image : uniformValue + }); + } - this._gl = gl; - this._location = locations[0]; + material._texturePaths[uniformId] = uniformValue; + } + }; } - 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]; + function createCubeMapUpdateFunction(uniformId) { + return function(material, context) { + var uniformValue = material.uniforms[uniformId]; - 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; + if (uniformValue instanceof CubeMap) { + var tmp = material._textures[uniformId]; + if (tmp !== material._defaultTexture) { + tmp.destroy(); } - } else { - } - - j += 4; - } - - if (changed) { - this._gl.uniform4fv(this._location, arraybuffer); - } - }; + material._texturePaths[uniformId] = undefined; + material._textures[uniformId] = uniformValue; + return; + } - /////////////////////////////////////////////////////////////////////////// + if (!defined(material._textures[uniformId])) { + material._texturePaths[uniformId] = undefined; + material._textures[uniformId] = context.defaultCubeMap; + } - function UniformArraySampler(gl, activeUniform, uniformName, locations) { - var length = locations.length; + if (uniformValue === Material.DefaultCubeMapId) { + return; + } - /** - * @readonly - */ - this.name = uniformName; + var path = + uniformValue.positiveX + uniformValue.negativeX + + uniformValue.positiveY + uniformValue.negativeY + + uniformValue.positiveZ + uniformValue.negativeZ; - this.value = new Array(length); - this._value = new Float32Array(length); + 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) + ]; - this._gl = gl; - this._locations = locations; + when.all(promises).then(function(images) { + material._loadedCubeMaps.push({ + id : uniformId, + images : images + }); + }); - this.textureUnitIndex = undefined; + material._texturePaths[uniformId] = path; + } + }; } - 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); + function createUniforms(material) { + var uniforms = material._template.uniforms; + for ( var uniformId in uniforms) { + if (uniforms.hasOwnProperty(uniformId)) { + createUniform(material, uniformId); + } } + } - 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); + // 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); - this._gl = gl; - this._location = locations[0]; - } + + var replacedTokenCount; + if (uniformType === 'channels') { + replacedTokenCount = replaceToken(material, uniformId, uniformValue, false); + } 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); + } + } - UniformArrayInt.prototype.set = function() { - var value = this.value; - var length = value.length; - var arraybuffer = this._value; - var changed = false; + // 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; + } - for (var i = 0; i < length; ++i) { - var v = value[i]; + var newUniformId = uniformId + '_' + material._count++; + replacedTokenCount = replaceToken(material, uniformId, newUniformId); + + // Set uniform value + material.uniforms[uniformId] = uniformValue; - if (v !== arraybuffer[i]) { - arraybuffer[i] = v; - changed = true; + 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]; + }; } } + } - if (changed) { - this._gl.uniform1iv(this._location, arraybuffer); + // 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; + } - function UniformArrayIntVec2(gl, activeUniform, uniformName, locations) { - var length = locations.length; + // 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 + }); - /** - * @readonly - */ - this.name = uniformName; + material._count = subMaterial._count; + material._uniforms = combine(material._uniforms, subMaterial._uniforms, true); + material.materials[subMaterialId] = subMaterial; + material._translucentFunctions = material._translucentFunctions.concat(subMaterial._translucentFunctions); - this.value = new Array(length); - this._value = new Int32Array(length * 2); + // 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; - this._gl = gl; - this._location = locations[0]; + // Replace each material id with an czm_getMaterial method call. + var materialMethodCall = newMethodName + '(materialInput)'; + var tokensReplacedCount = replaceToken(material, subMaterialId, materialMethodCall); + } + } } - 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; + // 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; } - j += 2; - } + count += 1; + return newToken; + }); + return count; + } - if (changed) { - this._gl.uniform2iv(this._location, arraybuffer); + 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'; - 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]; + /** + * Gets or sets the default cube map texture uniform value. + * @type {String} + */ + Material.DefaultCubeMapId = 'czm_defaultCubeMap'; - if (!Cartesian3.equalsArray(v, arraybuffer, j)) { - Cartesian3.pack(v, arraybuffer, j); - changed = true; + /** + * 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' } - j += 3; + }, + translucent : function(material) { + return material.uniforms.color.alpha < 1.0; } + }); - if (changed) { - this._gl.uniform3iv(this._location, arraybuffer); + /** + * 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; } - }; - - /////////////////////////////////////////////////////////////////////////// + }); - function UniformArrayIntVec4(gl, activeUniform, uniformName, locations) { - var length = locations.length; + /** + * 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 + }); - /** - * @readonly - */ - this.name = uniformName; + /** + * 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 + }); - this.value = new Array(length); - this._value = new Int32Array(length * 4); + /** + * 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 + }); - this._gl = gl; - this._location = locations[0]; - } + /** + * 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 + }); - UniformArrayIntVec4.prototype.set = function() { - var value = this.value; - var length = value.length; - var arraybuffer = this._value; - var changed = false; - var j = 0; + /** + * 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 + }); - for (var i = 0; i < length; ++i) { - var v = value[i]; + /** + * 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 + }); - if (!Cartesian4.equalsArray(v, arraybuffer, j)) { - Cartesian4.pack(v, arraybuffer, j); - changed = true; - } - j += 4; + /** + * 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); } + }); - if (changed) { - this._gl.uniform4iv(this._location, arraybuffer); + /** + * 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 < 1.0); } - }; - - /////////////////////////////////////////////////////////////////////////// - - 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; + /** + * 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 < 1.0); } + }); - if (changed) { - this._gl.uniformMatrix2fv(this._location, false, arraybuffer); + /** + * 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 < 1.0); } - }; - - /////////////////////////////////////////////////////////////////////////// - - 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; + /** + * 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 < 1.0); } + }); - if (changed) { - this._gl.uniformMatrix3fv(this._location, false, arraybuffer); + /** + * 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 < 1.0); } - }; - - /////////////////////////////////////////////////////////////////////////// - - 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]; - } + /** + * 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 < 1.0); + } + }); - UniformArrayMat4.prototype.set = function() { - var value = this.value; - var length = value.length; - var arraybuffer = this._value; - var changed = false; - var j = 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 + }); - for (var i = 0; i < length; ++i) { - var v = value[i]; + /** + * Gets the name of the polyline glow material. + * @type {String} + * @readonly + */ + Material.PolylineDashType = 'PolylineDash'; + Material._materialCache.addMaterial(Material.PolylineDashType, { + fabric : { + type : Material.PolylineDashType, + uniforms : { + color : new Color(1.0, 0.0, 1.0, 1.0), + gapColor : new Color(0.0, 0.0, 0.0, 0.0), + dashLength : 16.0, + dashPattern : 255.0 + }, + source : PolylineDashMaterial + }, + translucent : true + }); - if (!Matrix4.equalsArray(v, arraybuffer, j)) { - Matrix4.pack(v, arraybuffer, j); - changed = true; - } - j += 16; - } + /** + * 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 + }); - if (changed) { - this._gl.uniformMatrix4fv(this._location, false, arraybuffer); + /** + * 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 createUniformArray; + return Material; }); -/*global define*/ -define('Renderer/ShaderProgram',[ +define('Scene/MaterialAppearance',[ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/destroyObject', - '../Core/DeveloperError', - '../Core/RuntimeError', - './AutomaticUniforms', - './ContextLimits', - './createUniform', - './createUniformArray' + '../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, - destroyObject, - DeveloperError, - RuntimeError, - AutomaticUniforms, - ContextLimits, - createUniform, - createUniformArray) { + freezeObject, + VertexFormat, + AllMaterialAppearanceFS, + AllMaterialAppearanceVS, + BasicMaterialAppearanceFS, + BasicMaterialAppearanceVS, + TexturedMaterialAppearanceFS, + TexturedMaterialAppearanceVS, + Appearance, + Material) { 'use strict'; - var nextShaderProgramId = 0; - /** - * @private + * 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 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; + function MaterialAppearance(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - 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 + var translucent = defaultValue(options.translucent, true); + var closed = defaultValue(options.closed, false); + var materialSupport = defaultValue(options.materialSupport, MaterialAppearance.MaterialSupport.TEXTURED); /** - * @private + * 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.maximumTextureUnitIndex = undefined; - - this._vertexShaderSource = options.vertexShaderSource; - this._vertexShaderText = options.vertexShaderText; - this._fragmentShaderSource = options.fragmentShaderSource; - this._fragmentShaderText = modifiedFS.fragmentShaderText; + this.material = (defined(options.material)) ? options.material : Material.fromType(Material.ColorType); /** - * @private + * When true, the geometry is expected to appear translucent. + * + * @type {Boolean} + * + * @default true */ - this.id = nextShaderProgramId++; - } - - ShaderProgram.fromCache = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this.translucent = translucent; - - return options.context.shaderCache.getShaderProgram(options); - }; + 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; - ShaderProgram.replaceCache = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + // Non-derived members - - return options.context.shaderCache.replaceShaderProgram(options); - }; + this._materialSupport = materialSupport; + this._vertexFormat = materialSupport.vertexFormat; + this._flat = defaultValue(options.flat, false); + this._faceForward = defaultValue(options.faceForward, !closed); + } - defineProperties(ShaderProgram.prototype, { + defineProperties(MaterialAppearance.prototype, { /** - * GLSL source for the shader program's vertex shader. - * @memberof ShaderProgram.prototype + * The GLSL source code for the vertex shader. * - * @type {ShaderSource} + * @memberof MaterialAppearance.prototype + * + * @type {String} * @readonly */ vertexShaderSource : { @@ -84162,11 +83484,16 @@ define('Renderer/ShaderProgram',[ return this._vertexShaderSource; } }, + /** - * GLSL source for the shader program's fragment shader. - * @memberof ShaderProgram.prototype + * 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. * - * @type {ShaderSource} + * @memberof MaterialAppearance.prototype + * + * @type {String} * @readonly */ fragmentShaderSource : { @@ -84174,676 +83501,5400 @@ define('Renderer/ShaderProgram',[ return this._fragmentShaderSource; } }, - vertexAttributes : { + + /** + * 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() { - initialize(this); - return this._vertexAttributes; + return this._renderState; } }, - numberOfVertexAttributes : { + + /** + * 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() { - initialize(this); - return this._numberOfVertexAttributes; + return this._closed; } }, - allUniforms : { + + /** + * 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() { - initialize(this); - return this._uniformsByName; + return this._materialSupport; } - } - }); + }, - 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); + /** + * 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; } - } - 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; - } - } + /** + * 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; } - } - - 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); - } + /** + * 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; } } + }); - 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); - } + /** + * 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; - 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)); - } + /** + * 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; - gl.deleteProgram(program); - throw new RuntimeError('Program failed to link. Link log: ' + log); - } + /** + * 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; - var logShaderCompilation = shader._logShaderCompilation; + /** + * 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, + * tangent, and bitangent vertex attributes. + * + * @constant + */ + ALL : freezeObject({ + vertexFormat : VertexFormat.ALL, + vertexShaderSource : AllMaterialAppearanceVS, + fragmentShaderSource : AllMaterialAppearanceFS + }) + }; - if (logShaderCompilation) { - log = gl.getShaderInfoLog(vertexShader); - if (defined(log) && (log.length > 0)) { - console.log(consolePrefix + 'Vertex shader compile log: ' + log); - } - } + return MaterialAppearance; +}); - if (logShaderCompilation) { - log = gl.getShaderInfoLog(fragmentShader); - if (defined(log) && (log.length > 0)) { - console.log(consolePrefix + 'Fragment shader compile log: ' + log); - } - } +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Appearances/PerInstanceColorAppearanceFS',[],function() { + 'use strict'; + return "varying vec3 v_positionEC;\n\ +varying vec3 v_normalEC;\n\ +varying vec4 v_color;\n\ +\n\ +void main()\n\ +{\n\ + vec3 positionToEyeEC = -v_positionEC;\n\ + \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\ + \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\ + \n\ + gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +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\ +\n\ +varying vec3 v_positionEC;\n\ +varying vec3 v_normalEC;\n\ +varying vec4 v_color;\n\ +\n\ +void main() \n\ +{\n\ + vec4 p = czm_computePosition();\n\ +\n\ + v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates\n\ + v_normalEC = czm_normal * normal; // normal in eye coordinates\n\ + v_color = color;\n\ + \n\ + gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Appearances/PerInstanceFlatColorAppearanceFS',[],function() { + 'use strict'; + return "varying vec4 v_color;\n\ +\n\ +void main()\n\ +{\n\ + gl_FragColor = v_color;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Appearances/PerInstanceFlatColorAppearanceVS',[],function() { + 'use strict'; + return "attribute vec3 position3DHigh;\n\ +attribute vec3 position3DLow;\n\ +attribute vec4 color;\n\ +attribute float batchId;\n\ +\n\ +varying vec4 v_color;\n\ +\n\ +void main() \n\ +{\n\ + vec4 p = czm_computePosition();\n\ +\n\ + v_color = color;\n\ + \n\ + gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\ +}\n\ +"; +}); +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'; - if (logShaderCompilation) { - log = gl.getProgramInfoLog(program); - if (defined(log) && (log.length > 0)) { - console.log(consolePrefix + 'Shader program link log: ' + log); - } - } + /** + * 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); - return program; - } + 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; - 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); + /** + * 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; - attributes[attr.name] = { - name : attr.name, - type : attr.type, - index : location - }; - } + /** + * When true, the geometry is expected to appear translucent so + * {@link PerInstanceColorAppearance#renderState} has alpha blending enabled. + * + * @type {Boolean} + * + * @default true + */ + this.translucent = translucent; - return attributes; + 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); } - function findUniforms(gl, program) { - var uniformsByName = {}; - var uniforms = []; - var samplerUniforms = []; + defineProperties(PerInstanceColorAppearance.prototype, { + /** + * The GLSL source code for the vertex shader. + * + * @memberof PerInstanceColorAppearance.prototype + * + * @type {String} + * @readonly + */ + vertexShaderSource : { + get : function() { + return this._vertexShaderSource; + } + }, - var numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + /** + * The GLSL source code for the fragment shader. + * + * @memberof PerInstanceColorAppearance.prototype + * + * @type {String} + * @readonly + */ + fragmentShaderSource : { + get : function() { + return this._fragmentShaderSource; + } + }, - 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; + /** + * 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; + } + }, - // 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); + /** + * 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; + } + }, - // 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); + /** + * 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; + } + }, - uniformsByName[uniformName] = uniform; - uniforms.push(uniform); + /** + * 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; + } + }, - if (uniform._setSampler) { - samplerUniforms.push(uniform); - } - } - } else { - // Uniform array + /** + * 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; + } + } + }); - var uniformArray; - var locations; - var value; - var loc; + /** + * 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; - // 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)]; + /** + * 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; - // 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; - } + /** + * 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; - locations = uniformArray._locations; + /** + * 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; - // 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); + /** + * 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; - // 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 + ']'); + return PerInstanceColorAppearance; +}); - // Workaround for IE 11.0.9. See above. - if (loc !== null) { - locations.push(loc); - } - } - uniformArray = createUniformArray(gl, activeUniform, uniformName, locations); +define('Renderer/BufferUsage',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { + 'use strict'; - uniformsByName[uniformName] = uniformArray; - uniforms.push(uniformArray); + /** + * @private + */ + var BufferUsage = { + STREAM_DRAW : WebGLConstants.STREAM_DRAW, + STATIC_DRAW : WebGLConstants.STATIC_DRAW, + DYNAMIC_DRAW : WebGLConstants.DYNAMIC_DRAW, - if (uniformArray._setSampler) { - samplerUniforms.push(uniformArray); - } - } - } - } + validate : function(bufferUsage) { + return ((bufferUsage === BufferUsage.STREAM_DRAW) || + (bufferUsage === BufferUsage.STATIC_DRAW) || + (bufferUsage === BufferUsage.DYNAMIC_DRAW)); } + }; - return { - uniformsByName : uniformsByName, - uniforms : uniforms, - samplerUniforms : samplerUniforms - }; - } + return freezeObject(BufferUsage); +}); - function partitionUniforms(shader, uniforms) { - var automaticUniforms = []; - var manualUniforms = []; +define('Renderer/DrawCommand',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/PrimitiveType' + ], function( + defaultValue, + defined, + defineProperties, + PrimitiveType) { + 'use strict'; - 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); - } - } - } + /** + * Represents a command to the renderer for drawing. + * + * @private + */ + function DrawCommand(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - return { - automaticUniforms : automaticUniforms, - manualUniforms : manualUniforms - }; + 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 = {}; } - function setSamplerUniforms(gl, program, samplerUniforms) { - gl.useProgram(program); + 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; + } + } + }, - var textureUnitIndex = 0; - var length = samplerUniforms.length; - for (var i = 0; i < length; ++i) { - textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex); - } + /** + * 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; + } + } + }, - gl.useProgram(null); + /** + * 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; + } + } + }, - return textureUnitIndex; - } + /** + * 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; + } + } + }, - function initialize(shader) { - if (defined(shader._program)) { - return; - } + /** + * 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; + } + } + }, - 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); + /** + * 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; + } + } + }, - 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; + /** + * 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; + } + } + }, - shader.maximumTextureUnitIndex = setSamplerUniforms(gl, program, uniforms.samplerUniforms); - } + /** + * 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; + } + } + }, - ShaderProgram.prototype._bind = function() { - initialize(this); - this._gl.useProgram(this._program); - }; + /** + * 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; + } + } + }, - ShaderProgram.prototype._setUniforms = function(uniformMap, uniformState, validate) { - var len; - var i; + /** + * 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; + } + } + }, - 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](); + /** + * 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; + } } - } + }, - var automaticUniforms = this._automaticUniforms; - len = automaticUniforms.length; - for (i = 0; i < len; ++i) { - var au = automaticUniforms[i]; - au.uniform.value = au.automaticUniform.getValue(uniformState); - } + /** + * 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; + } + } + }, - // 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(); - } + /** + * 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; + } + } + }, - if (validate) { - var gl = this._gl; - var program = this._program; + /** + * 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; + } + } + }, - gl.validateProgram(program); - } - }; + /** + * 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; + } + } + }, - ShaderProgram.prototype.isDestroyed = function() { - return false; - }; + /** + * 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; + } + } + }, - ShaderProgram.prototype.destroy = function() { - this._cachedShader.cache.releaseShaderProgram(this); - return undefined; - }; + /** + * 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; + } + } + }, - ShaderProgram.prototype.finalDestroy = function() { - this._gl.deleteProgram(this._program); - return destroyObject(this); - }; + /** + * 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; + } + } + }, - return ShaderProgram; -}); + /** + * 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; + } + } + } + }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Constants/degreesPerRadian',[],function() { - 'use strict'; - return "/**\n\ - * A built-in GLSL floating-point constant for converting radians to degrees.\n\ - *\n\ - * @alias czm_degreesPerRadian\n\ - * @glslConstant\n\ - *\n\ - * @see CesiumMath.DEGREES_PER_RADIAN\n\ - *\n\ - * @example\n\ - * // GLSL declaration\n\ - * const float czm_degreesPerRadian = ...;\n\ - *\n\ - * // Example\n\ - * float deg = czm_degreesPerRadian * rad;\n\ - */\n\ -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 "/**\n\ - * A built-in GLSL vec2 constant for defining the depth range.\n\ - * This is a workaround to a bug where IE11 does not implement gl_DepthRange.\n\ - *\n\ - * @alias czm_depthRange\n\ - * @glslConstant\n\ - *\n\ - * @example\n\ - * // GLSL declaration\n\ - * float depthRangeNear = czm_depthRange.near;\n\ - * float depthRangeFar = czm_depthRange.far;\n\ - *\n\ - */\n\ -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 "/**\n\ - * 0.1\n\ - *\n\ - * @name czm_epsilon1\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * 0.01\n\ - *\n\ - * @name czm_epsilon2\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * 0.001\n\ - *\n\ - * @name czm_epsilon3\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * 0.0001\n\ - *\n\ - * @name czm_epsilon4\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * 0.00001\n\ - *\n\ - * @name czm_epsilon5\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * 0.000001\n\ - *\n\ - * @name czm_epsilon6\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * 0.0000001\n\ - *\n\ - * @name czm_epsilon7\n\ - * @glslConstant\n\ - */\n\ -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 "/**\n\ - * DOC_TBA\n\ - *\n\ - * @name czm_infinity\n\ - * @glslConstant\n\ - */\n\ -const float czm_infinity = 5906376272000.0; // Distance from the Sun to Pluto in meters. TODO: What is best given lowp, mediump, and highp?\n\ -"; -}); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Constants/oneOverPi',[],function() { - 'use strict'; - return "/**\n\ - * A built-in GLSL floating-point constant for 1/pi.\n\ - *\n\ - * @alias czm_oneOverPi\n\ - * @glslConstant\n\ - *\n\ - * @see CesiumMath.ONE_OVER_PI\n\ - *\n\ - * @example\n\ - * // GLSL declaration\n\ - * const float czm_oneOverPi = ...;\n\ - *\n\ - * // Example\n\ - * float pi = 1.0 / czm_oneOverPi;\n\ - */\n\ -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 "/**\n\ - * A built-in GLSL floating-point constant for 1/2pi.\n\ - *\n\ - * @alias czm_oneOverTwoPi\n\ - * @glslConstant\n\ - *\n\ - * @see CesiumMath.ONE_OVER_TWO_PI\n\ - *\n\ - * @example\n\ - * // GLSL declaration\n\ - * const float czm_oneOverTwoPi = ...;\n\ - *\n\ - * // Example\n\ - * float pi = 2.0 * czm_oneOverTwoPi;\n\ - */\n\ -const float czm_oneOverTwoPi = 0.15915494309189535;\n\ -"; + /** + * @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; }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Constants/passCompute',[],function() { + +define('Renderer/Pass',[ + '../Core/freezeObject' + ], function( + freezeObject) { 'use strict'; - return "/**\n\ - * The automatic GLSL constant for {@link Pass#COMPUTE}\n\ - *\n\ - * @name czm_passCompute\n\ - * @glslConstant\n\ - *\n\ - * @see czm_pass\n\ - */\n\ -const float czm_passCompute = 1.0;\n\ -"; + + /** + * 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, + TERRAIN_CLASSIFICATION : 3, + CESIUM_3D_TILE : 4, + CESIUM_3D_TILE_CLASSIFICATION : 5, + OPAQUE : 6, + TRANSLUCENT : 7, + OVERLAY : 8, + NUMBER_OF_PASSES : 9 + }; + + return freezeObject(Pass); }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Constants/passEnvironment',[],function() { + +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'; - return "/**\n\ - * The automatic GLSL constant for {@link Pass#ENVIRONMENT}\n\ - *\n\ - * @name czm_passEnvironment\n\ - * @glslConstant\n\ - *\n\ - * @see czm_pass\n\ - */\n\ -const float czm_passEnvironment = 0.0;\n\ -"; + + function validateBlendEquation(blendEquation) { + return ((blendEquation === WebGLConstants.FUNC_ADD) || + (blendEquation === WebGLConstants.FUNC_SUBTRACT) || + (blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT) || + (blendEquation === WebGLConstants.MIN) || + (blendEquation === WebGLConstants.MAX)); + } + + 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; + + + 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) { + + 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; }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Constants/passGlobe',[],function() { + +define('Renderer/AutomaticUniforms',[ + '../Core/Cartesian3', + '../Core/Matrix4', + '../Core/WebGLConstants' + ], function( + Cartesian3, + Matrix4, + WebGLConstants) { 'use strict'; - return "/**\n\ - * The automatic GLSL constant for {@link Pass#GLOBE}\n\ - *\n\ - * @name czm_passGlobe\n\ - * @glslConstant\n\ - *\n\ - * @see czm_pass\n\ - */\n\ -const float czm_passGlobe = 2.0;\n\ + /*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; + } + }), + + /** + * 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 the current scene background color. + * + * @alias czm_backgroundColor + * @glslUniform + * + * @example + * // GLSL declaration + * uniform vec4 czm_backgroundColor; + * + * // Example: If the given color's RGB matches the background color, invert it. + * vec4 adjustColorForContrast(vec4 color) + * { + * if (czm_backgroundColor.rgb == color.rgb) + * { + * color.rgb = vec3(1.0) - color.rgb; + * } + * + * return color; + * } + */ + czm_backgroundColor : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.backgroundColor; + } + }), + + /** + * An automatic GLSL uniform containing the BRDF look up texture used for image-based lighting computations. + * + * @alias czm_brdfLut + * @glslUniform + * + * @example + * // GLSL declaration + * uniform sampler2D czm_brdfLut; + * + * // Example: For a given roughness and NdotV value, find the material's BRDF information in the red and green channels + * float roughness = 0.5; + * float NdotV = dot(normal, view); + * vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg; + */ + czm_brdfLut : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_2D, + getValue : function(uniformState) { + return uniformState.brdfLut; + } + }), + + /** + * An automatic GLSL uniform containing the environment map used within the scene. + * + * @alias czm_environmentMap + * @glslUniform + * + * @example + * // GLSL declaration + * uniform samplerCube czm_environmentMap; + * + * // Example: Create a perfect reflection of the environment map on a model + * float reflected = reflect(view, normal); + * vec4 reflectedColor = textureCube(czm_environmentMap, reflected); + */ + czm_environmentMap : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_CUBE, + getValue : function(uniformState) { + return uniformState.environmentMap; + } + }), + + /** + * 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; + } + }), + + /** + * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter. + * This will be in pixel coordinates relative to the canvas. + * + * @alias czm_imagerySplitPosition + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform float czm_imagerySplitPosition; + */ + czm_imagerySplitPosition : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.imagerySplitPosition; + } + }), + + /** + * An automatic GLSL uniform scalar representing the geometric tolerance per meter + * + * @alias czm_geometricToleranceOverMeter + * @glslUniform + */ + czm_geometricToleranceOverMeter : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.geometricToleranceOverMeter; + } + }), + + /** + * An automatic GLSL uniform representing the distance from the camera at which to disable the depth test of billboards, labels and points + * to, for example, prevent clipping against terrain. When set to zero, the depth test should always be applied. When less than zero, + * the depth test should never be applied. + * + * @alias czm_minimumDisableDepthTestDistance + * @glslUniform + */ + czm_minimumDisableDepthTestDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.minimumDisableDepthTestDistance; + } + }) + }; + + return AutomaticUniforms; +}); + +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 { + } + }; + + /////////////////////////////////////////////////////////////////////////// + + 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 { + } + }; + + /////////////////////////////////////////////////////////////////////////// + + 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; +}); + +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 { + } + + 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 { + } + + 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; +}); + +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); + + + return options.context.shaderCache.getShaderProgram(options); + }; + + ShaderProgram.replaceCache = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + + 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); + } + }; + + 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; +}); + +define('Renderer/modernizeShader',[ + '../Core/defined', + '../Core/DeveloperError', + ], function( + defined, + DeveloperError) { + 'use strict'; + + /** + * A function to port GLSL shaders from GLSL ES 1.00 to GLSL ES 3.00 + * + * This function is nowhere near comprehensive or complete. It just + * handles some common cases. + * + * Note that this function requires the presence of the + * "#define OUTPUT_DECLARATION" line that is appended + * by ShaderSource. + * + * @private + */ + function modernizeShader(source, isFragmentShader) { + var outputDeclarationRegex = /#define OUTPUT_DECLARATION/; + var splitSource = source.split('\n'); + + if (/#version 300 es/g.test(source)) { + return source; + } + + var outputDeclarationLine = -1; + var i, line; + for (i = 0; i < splitSource.length; ++i) { + line = splitSource[i]; + if (outputDeclarationRegex.test(line)) { + outputDeclarationLine = i; + break; + } + } + + if (outputDeclarationLine === -1) { + throw new DeveloperError('Could not find a #define OUTPUT_DECLARATION!'); + } + + var outputVariables = []; + + for (i = 0; i < 10; i++) { + var fragDataString = 'gl_FragData\\[' + i + '\\]'; + var newOutput = 'czm_out' + i; + var regex = new RegExp(fragDataString, 'g'); + if (regex.test(source)) { + setAdd(newOutput, outputVariables); + replaceInSourceString(fragDataString, newOutput, splitSource); + splitSource.splice(outputDeclarationLine, 0, 'layout(location = ' + i + ') out vec4 ' + newOutput + ';'); + outputDeclarationLine += 1; + } + } + + var czmFragColor = 'czm_fragColor'; + if (findInSource('gl_FragColor', splitSource)) { + setAdd(czmFragColor, outputVariables); + replaceInSourceString('gl_FragColor', czmFragColor, splitSource); + splitSource.splice(outputDeclarationLine, 0, 'layout(location = 0) out vec4 czm_fragColor;'); + outputDeclarationLine += 1; + } + + var variableMap = getVariablePreprocessorBranch(outputVariables, splitSource); + var lineAdds = {}; + for (i = 0; i < splitSource.length; i++) { + line = splitSource[i]; + for (var variable in variableMap) { + if (variableMap.hasOwnProperty(variable)) { + var matchVar = new RegExp('(layout)[^]+(out)[^]+(' + variable + ')[^]+', 'g'); + if (matchVar.test(line)) { + lineAdds[line] = variable; + } + } + } + } + + for (var layoutDeclaration in lineAdds) { + if (lineAdds.hasOwnProperty(layoutDeclaration)) { + var variableName = lineAdds[layoutDeclaration]; + var lineNumber = splitSource.indexOf(layoutDeclaration); + var entry = variableMap[variableName]; + var depth = entry.length; + var d; + for (d = 0; d < depth; d++) { + splitSource.splice(lineNumber, 0, entry[d]); + } + lineNumber += depth + 1; + for (d = depth - 1; d >= 0; d--) { + splitSource.splice(lineNumber, 0, '#endif //' + entry[d]); + } + } + } + + var versionThree = '#version 300 es'; + var foundVersion = false; + for (i = 0; i < splitSource.length; i++) { + if (/#version/.test(splitSource[i])) { + splitSource[i] = versionThree; + foundVersion = true; + } + } + + if (!foundVersion) { + splitSource.splice(0, 0, versionThree); + } + + removeExtension('EXT_draw_buffers', splitSource); + removeExtension('EXT_frag_depth', splitSource); + + replaceInSourceString('texture2D', 'texture', splitSource); + replaceInSourceString('texture3D', 'texture', splitSource); + replaceInSourceString('textureCube', 'texture', splitSource); + replaceInSourceString('gl_FragDepthEXT', 'gl_FragDepth', splitSource); + + if (isFragmentShader) { + replaceInSourceString('varying', 'in', splitSource); + } else { + replaceInSourceString('attribute', 'in', splitSource); + replaceInSourceString('varying', 'out', splitSource); + } + + return compileSource(splitSource); + } + + // Note that this fails if your string looks like + // searchString[singleCharacter]searchString + function replaceInSourceString(str, replacement, splitSource) { + var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])'; + var regex = new RegExp(regexStr, 'g'); + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + splitSource[i] = line.replace(regex, '$1' + replacement + '$3'); + } + } + + function replaceInSourceRegex(regex, replacement, splitSource) { + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + splitSource[i] = line.replace(regex, replacement); + } + } + + function findInSource(str, splitSource) { + var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])'; + var regex = new RegExp(regexStr, 'g'); + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + if (regex.test(line)) { + return true; + } + } + return false; + } + + function compileSource(splitSource) { + var wholeSource = ''; + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + wholeSource += splitSource[i] + '\n'; + } + return wholeSource; + } + + function setAdd(variable, set) { + if (set.indexOf(variable) === -1) { + set.push(variable); + } + } + + function getVariablePreprocessorBranch(layoutVariables, splitSource) { + var variableMap = {}; + + var numLayoutVariables = layoutVariables.length; + + var stack = []; + for (var i = 0; i < splitSource.length; ++i) { + var line = splitSource[i]; + var hasIF = /(#ifdef|#if)/g.test(line); + var hasELSE = /#else/g.test(line); + var hasENDIF = /#endif/g.test(line); + + if (hasIF) { + stack.push(line); + } else if (hasELSE) { + var top = stack[stack.length - 1]; + var op = top.replace('ifdef', 'ifndef'); + if (/if/g.test(op)) { + op = op.replace(/(#if\s+)(\S*)([^]*)/, '$1!($2)$3'); + } + stack.pop(); + stack.push(op); + } else if (hasENDIF) { + stack.pop(); + } else if (!/layout/g.test(line)) { + for (var varIndex = 0; varIndex < numLayoutVariables; ++varIndex) { + var varName = layoutVariables[varIndex]; + if (line.indexOf(varName) !== -1) { + if (!defined(variableMap[varName])) { + variableMap[varName] = stack.slice(); + } else { + variableMap[varName] = variableMap[varName].filter(function(x) { + return stack.indexOf(x) >= 0; + }); + } + } + } + } + } + + return variableMap; + } + + function removeExtension(name, splitSource) { + var regex = '#extension\\s+GL_' + name + '\\s+:\\s+[a-zA-Z0-9]+\\s*$'; + replaceInSourceRegex(new RegExp(regex, 'g'), '', splitSource); + } + + return modernizeShader; +}); + +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/degreesPerRadian',[],function() { + 'use strict'; + return "/**\n\ + * A built-in GLSL floating-point constant for converting radians to degrees.\n\ + *\n\ + * @alias czm_degreesPerRadian\n\ + * @glslConstant\n\ + *\n\ + * @see CesiumMath.DEGREES_PER_RADIAN\n\ + *\n\ + * @example\n\ + * // GLSL declaration\n\ + * const float czm_degreesPerRadian = ...;\n\ + *\n\ + * // Example\n\ + * float deg = czm_degreesPerRadian * rad;\n\ + */\n\ +const float czm_degreesPerRadian = 57.29577951308232;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/depthRange',[],function() { + 'use strict'; + return "/**\n\ + * A built-in GLSL vec2 constant for defining the depth range.\n\ + * This is a workaround to a bug where IE11 does not implement gl_DepthRange.\n\ + *\n\ + * @alias czm_depthRange\n\ + * @glslConstant\n\ + *\n\ + * @example\n\ + * // GLSL declaration\n\ + * float depthRangeNear = czm_depthRange.near;\n\ + * float depthRangeFar = czm_depthRange.far;\n\ + *\n\ + */\n\ +const czm_depthRangeStruct czm_depthRange = czm_depthRangeStruct(0.0, 1.0);\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon1',[],function() { + 'use strict'; + return "/**\n\ + * 0.1\n\ + *\n\ + * @name czm_epsilon1\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon1 = 0.1;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon2',[],function() { + 'use strict'; + return "/**\n\ + * 0.01\n\ + *\n\ + * @name czm_epsilon2\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon2 = 0.01;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon3',[],function() { + 'use strict'; + return "/**\n\ + * 0.001\n\ + *\n\ + * @name czm_epsilon3\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon3 = 0.001;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon4',[],function() { + 'use strict'; + return "/**\n\ + * 0.0001\n\ + *\n\ + * @name czm_epsilon4\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon4 = 0.0001;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon5',[],function() { + 'use strict'; + return "/**\n\ + * 0.00001\n\ + *\n\ + * @name czm_epsilon5\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon5 = 0.00001;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon6',[],function() { + 'use strict'; + return "/**\n\ + * 0.000001\n\ + *\n\ + * @name czm_epsilon6\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon6 = 0.000001;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/epsilon7',[],function() { + 'use strict'; + return "/**\n\ + * 0.0000001\n\ + *\n\ + * @name czm_epsilon7\n\ + * @glslConstant\n\ + */\n\ +const float czm_epsilon7 = 0.0000001;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/infinity',[],function() { + 'use strict'; + return "/**\n\ + * DOC_TBA\n\ + *\n\ + * @name czm_infinity\n\ + * @glslConstant\n\ + */\n\ +const float czm_infinity = 5906376272000.0; // Distance from the Sun to Pluto in meters. TODO: What is best given lowp, mediump, and highp?\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/oneOverPi',[],function() { + 'use strict'; + return "/**\n\ + * A built-in GLSL floating-point constant for 1/pi.\n\ + *\n\ + * @alias czm_oneOverPi\n\ + * @glslConstant\n\ + *\n\ + * @see CesiumMath.ONE_OVER_PI\n\ + *\n\ + * @example\n\ + * // GLSL declaration\n\ + * const float czm_oneOverPi = ...;\n\ + *\n\ + * // Example\n\ + * float pi = 1.0 / czm_oneOverPi;\n\ + */\n\ +const float czm_oneOverPi = 0.3183098861837907;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/oneOverTwoPi',[],function() { + 'use strict'; + return "/**\n\ + * A built-in GLSL floating-point constant for 1/2pi.\n\ + *\n\ + * @alias czm_oneOverTwoPi\n\ + * @glslConstant\n\ + *\n\ + * @see CesiumMath.ONE_OVER_TWO_PI\n\ + *\n\ + * @example\n\ + * // GLSL declaration\n\ + * const float czm_oneOverTwoPi = ...;\n\ + *\n\ + * // Example\n\ + * float pi = 2.0 * czm_oneOverTwoPi;\n\ + */\n\ +const float czm_oneOverTwoPi = 0.15915494309189535;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/passCesium3DTile',[],function() { + 'use strict'; + return "/**\n\ + * The automatic GLSL constant for {@link Pass#CESIUM_3D_TILE}\n\ + *\n\ + * @name czm_passCesium3DTile\n\ + * @glslConstant\n\ + *\n\ + * @see czm_pass\n\ + */\n\ +const float czm_passCesium3DTile = 4.0;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/passCesium3DTileClassification',[],function() { + 'use strict'; + return "/**\n\ + * The automatic GLSL constant for {@link Pass#CESIUM_3D_TILE_CLASSIFICATION}\n\ + *\n\ + * @name czm_passCesium3DTileClassification\n\ + * @glslConstant\n\ + *\n\ + * @see czm_pass\n\ + */\n\ +const float czm_passCesium3DTileClassification = 5.0;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/passCompute',[],function() { + 'use strict'; + return "/**\n\ + * The automatic GLSL constant for {@link Pass#COMPUTE}\n\ + *\n\ + * @name czm_passCompute\n\ + * @glslConstant\n\ + *\n\ + * @see czm_pass\n\ + */\n\ +const float czm_passCompute = 1.0;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/passEnvironment',[],function() { + 'use strict'; + return "/**\n\ + * The automatic GLSL constant for {@link Pass#ENVIRONMENT}\n\ + *\n\ + * @name czm_passEnvironment\n\ + * @glslConstant\n\ + *\n\ + * @see czm_pass\n\ + */\n\ +const float czm_passEnvironment = 0.0;\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Constants/passGround',[],function() { +define('Shaders/Builtin/Constants/passGlobe',[],function() { 'use strict'; return "/**\n\ - * The automatic GLSL constant for {@link Pass#GROUND}\n\ + * The automatic GLSL constant for {@link Pass#GLOBE}\n\ *\n\ - * @name czm_passGround\n\ + * @name czm_passGlobe\n\ * @glslConstant\n\ *\n\ * @see czm_pass\n\ */\n\ -const float czm_passGround = 3.0;\n\ +const float czm_passGlobe = 2.0;\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Constants/passOpaque',[],function() { 'use strict'; return "/**\n\ @@ -84854,11 +88905,10 @@ define('Shaders/Builtin/Constants/passOpaque',[],function() { *\n\ * @see czm_pass\n\ */\n\ -const float czm_passOpaque = 4.0;\n\ +const float czm_passOpaque = 6.0;\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Constants/passOverlay',[],function() { 'use strict'; return "/**\n\ @@ -84869,11 +88919,24 @@ define('Shaders/Builtin/Constants/passOverlay',[],function() { *\n\ * @see czm_pass\n\ */\n\ -const float czm_passOverlay = 6.0;\n\ +const float czm_passOverlay = 8.0;\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Constants/passTerrainClassification',[],function() { + 'use strict'; + return "/**\n\ + * The automatic GLSL constant for {@link Pass#TERRAIN_CLASSIFICATION}\n\ + *\n\ + * @name czm_passTerrainClassification\n\ + * @glslConstant\n\ + *\n\ + * @see czm_pass\n\ + */\n\ +const float czm_passTerrainClassification = 3.0;\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Constants/passTranslucent',[],function() { 'use strict'; return "/**\n\ @@ -84884,11 +88947,10 @@ define('Shaders/Builtin/Constants/passTranslucent',[],function() { *\n\ * @see czm_pass\n\ */\n\ -const float czm_passTranslucent = 5.0;\n\ +const float czm_passTranslucent = 7.0;\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Constants/pi',[],function() { 'use strict'; return "/**\n\ @@ -84910,7 +88972,6 @@ 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 "/**\n\ @@ -84932,7 +88993,6 @@ 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 "/**\n\ @@ -84954,7 +89014,6 @@ 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 "/**\n\ @@ -84976,7 +89035,6 @@ 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 "/**\n\ @@ -84998,7 +89056,6 @@ 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 "/**\n\ @@ -85020,7 +89077,6 @@ 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 "/**\n\ @@ -85037,7 +89093,6 @@ 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 "/**\n\ @@ -85054,7 +89109,6 @@ 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 "/**\n\ @@ -85071,7 +89125,6 @@ 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 "/**\n\ @@ -85088,7 +89141,6 @@ 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 "/**\n\ @@ -85107,7 +89159,6 @@ 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 "/**\n\ @@ -85129,7 +89180,6 @@ 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 "/**\n\ @@ -85151,7 +89201,6 @@ 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 "/**\n\ @@ -85173,7 +89222,6 @@ 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 "/**\n\ @@ -85188,7 +89236,6 @@ struct czm_depthRangeStruct\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Structs/ellipsoid',[],function() { 'use strict'; return "/** DOC_TBA\n\ @@ -85206,7 +89253,6 @@ struct czm_ellipsoid\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Structs/material',[],function() { 'use strict'; return "/**\n\ @@ -85234,7 +89280,6 @@ struct czm_material\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Structs/materialInput',[],function() { 'use strict'; return "/**\n\ @@ -85262,7 +89307,6 @@ struct czm_materialInput\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Structs/ray',[],function() { 'use strict'; return "/**\n\ @@ -85279,7 +89323,6 @@ struct czm_ray\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Structs/raySegment',[],function() { 'use strict'; return "/**\n\ @@ -85312,7 +89355,6 @@ 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\ @@ -85333,7 +89375,6 @@ define('Shaders/Builtin/Structs/shadowParameters',[],function() { "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/alphaWeight',[],function() { 'use strict'; return "/**\n\ @@ -85371,7 +89412,6 @@ float czm_alphaWeight(float a)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/antialias',[],function() { 'use strict'; return "/**\n\ @@ -85416,7 +89456,6 @@ vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/cascadeColor',[],function() { 'use strict'; return "\n\ @@ -85430,7 +89469,6 @@ vec4 czm_cascadeColor(vec4 weights)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/cascadeDistance',[],function() { 'use strict'; return "\n\ @@ -85443,7 +89481,6 @@ float czm_cascadeDistance(vec4 weights)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/cascadeMatrix',[],function() { 'use strict'; return "\n\ @@ -85459,7 +89496,6 @@ mat4 czm_cascadeMatrix(vec4 weights)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/cascadeWeights',[],function() { 'use strict'; return "\n\ @@ -85475,7 +89511,6 @@ vec4 czm_cascadeWeights(float depthEye)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/columbusViewMorph',[],function() { 'use strict'; return "/**\n\ @@ -85493,7 +89528,6 @@ vec4 czm_columbusViewMorph(vec4 position2D, vec4 position3D, float time)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/computePosition',[],function() { 'use strict'; return "/**\n\ @@ -85521,7 +89555,6 @@ 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 "/**\n\ @@ -85738,7 +89771,6 @@ vec2 czm_cosineAndSine(float angle)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/decompressTextureCoordinates',[],function() { 'use strict'; return "/**\n\ @@ -85761,7 +89793,6 @@ define('Shaders/Builtin/Functions/decompressTextureCoordinates',[],function() { "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/eastNorthUpToEyeCoordinates',[],function() { 'use strict'; return "/**\n\ @@ -85800,7 +89831,6 @@ mat3 czm_eastNorthUpToEyeCoordinates(vec3 positionMC, vec3 normalEC)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/ellipsoidContainsPoint',[],function() { 'use strict'; return "/**\n\ @@ -85818,7 +89848,6 @@ bool czm_ellipsoidContainsPoint(czm_ellipsoid ellipsoid, vec3 point)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/ellipsoidNew',[],function() { 'use strict'; return "/**\n\ @@ -85838,7 +89867,6 @@ czm_ellipsoid czm_ellipsoidNew(vec3 center, vec3 radii)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/ellipsoidWgs84TextureCoordinates',[],function() { 'use strict'; return "/**\n\ @@ -85854,7 +89882,6 @@ vec2 czm_ellipsoidWgs84TextureCoordinates(vec3 normal)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/equalsEpsilon',[],function() { 'use strict'; return "/**\n\ @@ -85896,7 +89923,6 @@ bool czm_equalsEpsilon(float left, float right, float epsilon) {\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/eyeOffset',[],function() { 'use strict'; return "/**\n\ @@ -85922,7 +89948,6 @@ vec4 czm_eyeOffset(vec4 positionEC, vec3 eyeOffset)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/eyeToWindowCoordinates',[],function() { 'use strict'; return "/**\n\ @@ -85960,7 +89985,6 @@ vec4 czm_eyeToWindowCoordinates(vec4 positionEC)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/fog',[],function() { 'use strict'; return "/**\n\ @@ -85985,7 +90009,6 @@ vec3 czm_fog(float distanceToCamera, vec3 color, vec3 fogColor)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/geodeticSurfaceNormal',[],function() { 'use strict'; return "/**\n\ @@ -86007,7 +90030,6 @@ vec3 czm_geodeticSurfaceNormal(vec3 positionOnEllipsoid, vec3 ellipsoidCenter, v "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/getDefaultMaterial',[],function() { 'use strict'; return "/**\n\ @@ -86040,7 +90062,6 @@ czm_material czm_getDefaultMaterial(czm_materialInput materialInput)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/getLambertDiffuse',[],function() { 'use strict'; return "/**\n\ @@ -86068,7 +90089,6 @@ float czm_getLambertDiffuse(vec3 lightDirectionEC, vec3 normalEC)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/getSpecular',[],function() { 'use strict'; return "/**\n\ @@ -86103,7 +90123,6 @@ float czm_getSpecular(vec3 lightDirectionEC, vec3 toEyeEC, vec3 normalEC, float "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/getWaterNoise',[],function() { 'use strict'; return "/**\n\ @@ -86146,7 +90165,6 @@ vec4 czm_getWaterNoise(sampler2D normalMap, vec2 uv, float time, float angleInRa "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/getWgs84EllipsoidEC',[],function() { 'use strict'; return "/**\n\ @@ -86173,7 +90191,6 @@ czm_ellipsoid czm_getWgs84EllipsoidEC()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/HSBToRGB',[],function() { 'use strict'; return "/**\n\ @@ -86203,7 +90220,6 @@ vec3 czm_HSBToRGB(vec3 hsb)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/HSLToRGB',[],function() { 'use strict'; return "/**\n\ @@ -86240,7 +90256,6 @@ vec3 czm_HSLToRGB(vec3 hsl)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/hue',[],function() { 'use strict'; return "/**\n\ @@ -86276,7 +90291,6 @@ vec3 czm_hue(vec3 rgb, float adjustment)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/isEmpty',[],function() { 'use strict'; return "/**\n\ @@ -86301,7 +90315,6 @@ bool czm_isEmpty(czm_raySegment interval)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/isFull',[],function() { 'use strict'; return "/**\n\ @@ -86326,7 +90339,6 @@ bool czm_isFull(czm_raySegment interval)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/latitudeToWebMercatorFraction',[],function() { 'use strict'; return "/**\n\ @@ -86353,7 +90365,6 @@ float czm_latitudeToWebMercatorFraction(float latitude, float southMercatorY, fl "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/luminance',[],function() { 'use strict'; return "/**\n\ @@ -86379,7 +90390,6 @@ float czm_luminance(vec3 rgb)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/metersPerPixel',[],function() { 'use strict'; return "/**\n\ @@ -86426,7 +90436,6 @@ float czm_metersPerPixel(vec4 positionEC)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/modelToWindowCoordinates',[],function() { 'use strict'; return "/**\n\ @@ -86469,7 +90478,6 @@ vec4 czm_modelToWindowCoordinates(vec4 position)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/multiplyWithColorBalance',[],function() { 'use strict'; return "/**\n\ @@ -86493,7 +90501,6 @@ vec3 czm_multiplyWithColorBalance(vec3 left, vec3 right)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/nearFarScalar',[],function() { 'use strict'; return "/**\n\ @@ -86525,7 +90532,6 @@ float czm_nearFarScalar(vec4 nearFarScalar, float cameraDistSq)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/octDecode',[],function() { 'use strict'; return " /**\n\ @@ -86614,7 +90620,6 @@ define('Shaders/Builtin/Functions/octDecode',[],function() { "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/packDepth',[],function() { 'use strict'; return "/**\n\ @@ -86638,7 +90643,6 @@ vec4 czm_packDepth(float depth)\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\ @@ -86707,7 +90711,6 @@ vec4 czm_private_phong(vec3 toEye, czm_material material)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/pointAlongRay',[],function() { 'use strict'; return "/**\n\ @@ -86732,7 +90735,6 @@ vec3 czm_pointAlongRay(czm_ray ray, float time)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/rayEllipsoidIntersectionInterval',[],function() { 'use strict'; return "/**\n\ @@ -86821,7 +90823,6 @@ czm_raySegment czm_rayEllipsoidIntersectionInterval(czm_ray ray, czm_ellipsoid e "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/RGBToHSB',[],function() { 'use strict'; return "/**\n\ @@ -86854,7 +90855,6 @@ vec3 czm_RGBToHSB(vec3 rgb)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/RGBToHSL',[],function() { 'use strict'; return "/**\n\ @@ -86894,7 +90894,6 @@ vec3 czm_RGBToHSL(vec3 rgb)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/RGBToXYZ',[],function() { 'use strict'; return "/**\n\ @@ -86930,7 +90929,6 @@ vec3 czm_RGBToXYZ(vec3 rgb)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/saturation',[],function() { 'use strict'; return "/**\n\ @@ -86958,7 +90956,6 @@ vec3 czm_saturation(vec3 rgb, float adjustment)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/shadowDepthCompare',[],function() { 'use strict'; return "\n\ @@ -86988,7 +90985,6 @@ float czm_shadowDepthCompare(sampler2D shadowMap, vec2 uv, float depth)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/shadowVisibility',[],function() { 'use strict'; return "\n\ @@ -87060,7 +91056,6 @@ float czm_shadowVisibility(sampler2D shadowMap, czm_shadowParameters shadowParam "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/signNotZero',[],function() { 'use strict'; return "/**\n\ @@ -87095,7 +91090,6 @@ vec4 czm_signNotZero(vec4 value)\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/tangentToEyeSpaceMatrix',[],function() { 'use strict'; return "/**\n\ @@ -87126,7 +91120,6 @@ mat3 czm_tangentToEyeSpaceMatrix(vec3 normalEC, vec3 tangentEC, vec3 bitangentEC "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/Builtin/Functions/translateRelativeToEye',[],function() { 'use strict'; return "/**\n\ @@ -87143,5935 +91136,10855 @@ define('Shaders/Builtin/Functions/translateRelativeToEye',[],function() { * @name czm_translateRelativeToEye\n\ * @glslFunction\n\ *\n\ - * @param {vec3} high The position's high bits.\n\ - * @param {vec3} low The position's low bits.\n\ - * @returns {vec3} The position translated to be relative to the camera's position.\n\ + * @param {vec3} high The position's high bits.\n\ + * @param {vec3} low The position's low bits.\n\ + * @returns {vec3} The position translated to be relative to the camera's position.\n\ + *\n\ + * @example\n\ + * attribute vec3 positionHigh;\n\ + * attribute vec3 positionLow;\n\ + * \n\ + * void main() \n\ + * {\n\ + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\ + * gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\ + * }\n\ + *\n\ + * @see czm_modelViewRelativeToEye\n\ + * @see czm_modelViewProjectionRelativeToEye\n\ + * @see czm_computePosition\n\ + * @see EncodedCartesian3\n\ + */\n\ +vec4 czm_translateRelativeToEye(vec3 high, vec3 low)\n\ +{\n\ + vec3 highDifference = high - czm_encodedCameraPositionMCHigh;\n\ + vec3 lowDifference = low - czm_encodedCameraPositionMCLow;\n\ +\n\ + return vec4(highDifference + lowDifference, 1.0);\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Functions/translucentPhong',[],function() { + 'use strict'; + return "/**\n\ + * @private\n\ + */\n\ +vec4 czm_translucentPhong(vec3 toEye, czm_material material)\n\ +{\n\ + // Diffuse from directional light sources at eye (for top-down and horizon views)\n\ + float diffuse = czm_getLambertDiffuse(vec3(0.0, 0.0, 1.0), material.normal);\n\ + \n\ + if (czm_sceneMode == czm_sceneMode3D) {\n\ + // (and horizon views in 3D)\n\ + diffuse += czm_getLambertDiffuse(vec3(0.0, 1.0, 0.0), material.normal);\n\ + }\n\ + \n\ + diffuse = clamp(diffuse, 0.0, 1.0);\n\ +\n\ + // Specular from sun and pseudo-moon\n\ + float specular = czm_getSpecular(czm_sunDirectionEC, toEye, material.normal, material.shininess);\n\ + specular += czm_getSpecular(czm_moonDirectionEC, toEye, material.normal, material.shininess);\n\ +\n\ + // Temporary workaround for adding ambient.\n\ + vec3 materialDiffuse = material.diffuse * 0.5;\n\ +\n\ + vec3 ambient = materialDiffuse;\n\ + vec3 color = ambient + material.emission;\n\ + color += materialDiffuse * diffuse;\n\ + color += material.specular * specular;\n\ +\n\ + return vec4(color, material.alpha);\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Functions/transpose',[],function() { + 'use strict'; + return "/**\n\ + * Returns the transpose of the matrix. The input matrix can be\n\ + * a mat2, mat3, or mat4.\n\ + *\n\ + * @name czm_transpose\n\ + * @glslFunction\n\ + *\n\ + * @param {} matrix The matrix to transpose.\n\ + *\n\ + * @returns {} The transposed matrix.\n\ + *\n\ + * @example\n\ + * // GLSL declarations\n\ + * mat2 czm_transpose(mat2 matrix);\n\ + * mat3 czm_transpose(mat3 matrix);\n\ + * mat4 czm_transpose(mat4 matrix);\n\ + *\n\ + * // Transpose a 3x3 rotation matrix to find its inverse.\n\ + * mat3 eastNorthUpToEye = czm_eastNorthUpToEyeCoordinates(\n\ + * positionMC, normalEC);\n\ + * mat3 eyeToEastNorthUp = czm_transpose(eastNorthUpToEye);\n\ + */\n\ +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\ +\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\ +\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. +define('Shaders/Builtin/Functions/unpackDepth',[],function() { + 'use strict'; + return "/**\n\ + * Unpacks a vec3 depth depth value to a float.\n\ + *\n\ + * @name czm_unpackDepth\n\ + * @glslFunction\n\ + *\n\ + * @param {vec3} packedDepth The packed depth.\n\ + *\n\ + * @returns {float} The floating-point depth.\n\ + */\n\ + float czm_unpackDepth(vec4 packedDepth)\n\ + {\n\ + // See Aras Pranckevičius' post Encoding Floats to RGBA\n\ + // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/\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. +define('Shaders/Builtin/Functions/windowToEyeCoordinates',[],function() { + 'use strict'; + return "/**\n\ + * Transforms a position from window to eye coordinates.\n\ + * The transform from window to normalized device coordinates is done using components\n\ + * of (@link czm_viewport} and {@link czm_viewportTransformation} instead of calculating\n\ + * the inverse of czm_viewportTransformation. The transformation from\n\ + * normalized device coordinates to clip coordinates is done using positionWC.w,\n\ + * which is expected to be the scalar used in the perspective divide. The transformation\n\ + * from clip to eye coordinates is done using {@link czm_inverseProjection}.\n\ + *\n\ + * @name czm_windowToEyeCoordinates\n\ + * @glslFunction\n\ + *\n\ + * @param {vec4} fragmentCoordinate The position in window coordinates to transform.\n\ + *\n\ + * @returns {vec4} The transformed position in eye coordinates.\n\ + *\n\ + * @see czm_modelToWindowCoordinates\n\ + * @see czm_eyeToWindowCoordinates\n\ + * @see czm_inverseProjection\n\ + * @see czm_viewport\n\ + * @see czm_viewportTransformation\n\ + *\n\ + * @example\n\ + * vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord);\n\ + */\n\ +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\ +\n\ + if (czm_inverseProjection != mat4(0.0)) {\n\ + q = czm_inverseProjection * q;\n\ + } else {\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\ +\n\ + float near = czm_currentFrustum.x;\n\ + float far = czm_currentFrustum.y;\n\ +\n\ + q.x = (q.x * (right - left) + left + right) * 0.5;\n\ + q.y = (q.y * (top - bottom) + bottom + top) * 0.5;\n\ + q.z = (q.z * (near - far) - near - far) * 0.5;\n\ + q.w = 1.0;\n\ + }\n\ +\n\ + return q;\n\ +}\n\ +"; +}); +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/Builtin/Functions/XYZToRGB',[],function() { + 'use strict'; + return "/**\n\ + * Converts a CIE Yxy color to RGB.\n\ + *

    The conversion is described in\n\ + * {@link http://content.gpwiki.org/index.php/D3DBook:High-Dynamic_Range_Rendering#Luminance_Transform|Luminance Transform}\n\ + *

    \n\ + * \n\ + * @name czm_XYZToRGB\n\ + * @glslFunction\n\ + * \n\ + * @param {vec3} Yxy The color in CIE Yxy.\n\ + *\n\ + * @returns {vec3} The color in RGB.\n\ *\n\ * @example\n\ - * attribute vec3 positionHigh;\n\ - * attribute vec3 positionLow;\n\ - * \n\ - * void main() \n\ - * {\n\ - * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\ - * gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\ - * }\n\ - *\n\ - * @see czm_modelViewRelativeToEye\n\ - * @see czm_modelViewProjectionRelativeToEye\n\ - * @see czm_computePosition\n\ - * @see EncodedCartesian3\n\ + * vec3 xyz = czm_RGBToXYZ(rgb);\n\ + * xyz.x = max(xyz.x - luminanceThreshold, 0.0);\n\ + * rgb = czm_XYZToRGB(xyz);\n\ */\n\ -vec4 czm_translateRelativeToEye(vec3 high, vec3 low)\n\ +vec3 czm_XYZToRGB(vec3 Yxy)\n\ {\n\ - vec3 highDifference = high - czm_encodedCameraPositionMCHigh;\n\ - vec3 lowDifference = low - czm_encodedCameraPositionMCLow;\n\ -\n\ - return vec4(highDifference + lowDifference, 1.0);\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\ + \n\ + return XYZ2RGB * xyz;\n\ }\n\ "; }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Functions/translucentPhong',[],function() { +//This file is automatically rebuilt by the Cesium build process. +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/passCesium3DTile', + './Constants/passCesium3DTileClassification', + './Constants/passCompute', + './Constants/passEnvironment', + './Constants/passGlobe', + './Constants/passOpaque', + './Constants/passOverlay', + './Constants/passTerrainClassification', + './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/HSBToRGB', + './Functions/HSLToRGB', + './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/RGBToHSB', + './Functions/RGBToHSL', + './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_passCesium3DTile, + czm_passCesium3DTileClassification, + czm_passCompute, + czm_passEnvironment, + czm_passGlobe, + czm_passOpaque, + czm_passOverlay, + czm_passTerrainClassification, + 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_HSBToRGB, + czm_HSLToRGB, + 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_RGBToHSB, + czm_RGBToHSL, + 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_passCesium3DTile : czm_passCesium3DTile, + czm_passCesium3DTileClassification : czm_passCesium3DTileClassification, + czm_passCompute : czm_passCompute, + czm_passEnvironment : czm_passEnvironment, + czm_passGlobe : czm_passGlobe, + czm_passOpaque : czm_passOpaque, + czm_passOverlay : czm_passOverlay, + czm_passTerrainClassification : czm_passTerrainClassification, + 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_HSBToRGB : czm_HSBToRGB, + czm_HSLToRGB : czm_HSLToRGB, + 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_RGBToHSB : czm_RGBToHSB, + czm_RGBToHSL : czm_RGBToHSL, + 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}; +}); +define('Renderer/ShaderSource',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/DeveloperError', + '../Renderer/modernizeShader', + '../Shaders/Builtin/CzmBuiltins', + './AutomaticUniforms' + ], function( + defaultValue, + defined, + DeveloperError, + modernizeShader, + 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]); + } + } + + } + + 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, context) { + 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) { + + // 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'; + }); + + // Extract shader extensions from sources + var extensions = []; + combinedSources = combinedSources.replace(/#extension.*\n/gm, function(match) { + // Extract extension to put at the top + extensions.push(match); + + // Replace original #extension directive with a new line so the line numbers + // are not off by one. + 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 + '\n'; + } + + var extensionsLength = extensions.length; + for (i = 0; i < extensionsLength; i++) { + result += extensions[i]; + } + + 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'; + } + } + } + + // GLSLModernizer inserts its own layout qualifiers + // at this position in the source + if (context.webgl2) { + result += '#define OUTPUT_DECLARATION\n\n'; + } + + // append built-ins + if (shaderSource.includeBuiltIns) { + result += getBuiltinsAndAutomaticUniforms(combinedSources); + } + + // reset line number + result += '\n#line 0\n'; + + // append actual source + result += combinedSources; + + // modernize the source + if (context.webgl2) { + result = modernizeShader(result, isFragmentShader, true); + } + + 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; + + + 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. + * + * @param {Context} context The current rendering context + * + * @returns {String} The combined shader string. + */ + ShaderSource.prototype.createCombinedVertexShader = function(context) { + return combineShader(this, false, context); + }; + + /** + * Create a single string containing the full, combined fragment shader with all dependencies and defines. + * + * @param {Context} context The current rendering context + * + * @returns {String} The combined shader string. + */ + ShaderSource.prototype.createCombinedFragmentShader = function(context) { + return combineShader(this, true, context); + }; + + /** + * 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; +}); + +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); + + + 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; + } + + + 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) { + + 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} options.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) { + + 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); + + + 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; +}); + +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; + + + // 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); + + + 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 hasConstantAttributes = 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; + } + if (defined(vaAttributes[i].value)) { + hasConstantAttributes = 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._hasConstantAttributes = hasConstantAttributes; + 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); + + + 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) { + + 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; + } + } + } + } + + // Vertex attributes backed by a constant value go through vertexAttrib[1234]f[v] + // which is part of context state rather than VAO state. + function setConstantAttributes(vertexArray, gl) { + var attributes = vertexArray._attributes; + var length = attributes.length; + for (var i = 0; i < length; ++i) { + var attribute = attributes[i]; + if (attribute.enabled && defined(attribute.value)) { + attribute.vertexAttrib(gl); + } + } + } + + VertexArray.prototype._bind = function() { + if (defined(this._vao)) { + this._context.glBindVertexArray(this._vao); + if (this._context.instancedArrays) { + setVertexAttribDivisor(this); + } + if (this._hasConstantAttributes) { + setConstantAttributes(this, this._gl); + } + } 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; +}); + +define('Scene/BatchTable',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/FeatureDetection', + '../Core/Math', + '../Core/PixelFormat', + '../Renderer/ContextLimits', + '../Renderer/PixelDatatype', + '../Renderer/Sampler', + '../Renderer/Texture', + '../Renderer/TextureMagnificationFilter', + '../Renderer/TextureMinificationFilter' + ], function( + Cartesian2, + Cartesian3, + Cartesian4, + combine, + ComponentDatatype, + defined, + defineProperties, + destroyObject, + DeveloperError, + FeatureDetection, + CesiumMath, + PixelFormat, + ContextLimits, + PixelDatatype, + Sampler, + Texture, + TextureMagnificationFilter, + TextureMinificationFilter) { 'use strict'; - return "/**\n\ - * @private\n\ - */\n\ -vec4 czm_translucentPhong(vec3 toEye, czm_material material)\n\ -{\n\ - // Diffuse from directional light sources at eye (for top-down and horizon views)\n\ - float diffuse = czm_getLambertDiffuse(vec3(0.0, 0.0, 1.0), material.normal);\n\ - \n\ - if (czm_sceneMode == czm_sceneMode3D) {\n\ - // (and horizon views in 3D)\n\ - diffuse += czm_getLambertDiffuse(vec3(0.0, 1.0, 0.0), material.normal);\n\ - }\n\ - \n\ - diffuse = clamp(diffuse, 0.0, 1.0);\n\ -\n\ - // Specular from sun and pseudo-moon\n\ - float specular = czm_getSpecular(czm_sunDirectionEC, toEye, material.normal, material.shininess);\n\ - specular += czm_getSpecular(czm_moonDirectionEC, toEye, material.normal, material.shininess);\n\ -\n\ - // Temporary workaround for adding ambient.\n\ - vec3 materialDiffuse = material.diffuse * 0.5;\n\ -\n\ - vec3 ambient = materialDiffuse;\n\ - vec3 color = ambient + material.emission;\n\ - color += materialDiffuse * diffuse;\n\ - color += material.specular * specular;\n\ -\n\ - return vec4(color, material.alpha);\n\ -}\n\ -"; + + /** + * 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) { + + 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); + } + + if (!FeatureDetection.supportsTypedArrays()) { + return; + } + 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) { + + 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) { + + 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; }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Functions/transpose',[],function() { + +define('Scene/DepthFunction',[ + '../Core/freezeObject', + '../Core/WebGLConstants' + ], function( + freezeObject, + WebGLConstants) { 'use strict'; - return "/**\n\ - * Returns the transpose of the matrix. The input matrix can be\n\ - * a mat2, mat3, or mat4.\n\ - *\n\ - * @name czm_transpose\n\ - * @glslFunction\n\ - *\n\ - * @param {} matrix The matrix to transpose.\n\ - *\n\ - * @returns {} The transposed matrix.\n\ - *\n\ - * @example\n\ - * // GLSL declarations\n\ - * mat2 czm_transpose(mat2 matrix);\n\ - * mat3 czm_transpose(mat3 matrix);\n\ - * mat4 czm_transpose(mat4 matrix);\n\ - *\n\ - * // Transpose a 3x3 rotation matrix to find its inverse.\n\ - * mat3 eastNorthUpToEye = czm_eastNorthUpToEyeCoordinates(\n\ - * positionMC, normalEC);\n\ - * mat3 eyeToEastNorthUp = czm_transpose(eastNorthUpToEye);\n\ - */\n\ -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\ -\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\ -\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\ -"; + + /** + * 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); }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Functions/unpackDepth',[],function() { + +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'; - return "/**\n\ - * Unpacks a vec3 depth depth value to a float.\n\ - *\n\ - * @name czm_unpackDepth\n\ - * @glslFunction\n\ - *\n\ - * @param {vec3} packedDepth The packed depth.\n\ - *\n\ - * @returns {float} The floating-point depth.\n\ - */\n\ - float czm_unpackDepth(vec4 packedDepth)\n\ - {\n\ - // See Aras Pranckevičius' post Encoding Floats to RGBA\n\ - // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/\n\ - return dot(packedDepth, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0));\n\ - }\n\ -"; + + // 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; + } + } + + + // 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; + //acevedo check for undefined instance + if (instance) + { + 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; }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Functions/windowToEyeCoordinates',[],function() { + +define('Scene/PrimitiveState',[ + '../Core/freezeObject' + ], function( + freezeObject) { 'use strict'; - return "/**\n\ - * Transforms a position from window to eye coordinates.\n\ - * The transform from window to normalized device coordinates is done using components\n\ - * of (@link czm_viewport} and {@link czm_viewportTransformation} instead of calculating\n\ - * the inverse of czm_viewportTransformation. The transformation from\n\ - * normalized device coordinates to clip coordinates is done using positionWC.w,\n\ - * which is expected to be the scalar used in the perspective divide. The transformation\n\ - * from clip to eye coordinates is done using {@link czm_inverseProjection}.\n\ - *\n\ - * @name czm_windowToEyeCoordinates\n\ - * @glslFunction\n\ - *\n\ - * @param {vec4} fragmentCoordinate The position in window coordinates to transform.\n\ - *\n\ - * @returns {vec4} The transformed position in eye coordinates.\n\ - *\n\ - * @see czm_modelToWindowCoordinates\n\ - * @see czm_eyeToWindowCoordinates\n\ - * @see czm_inverseProjection\n\ - * @see czm_viewport\n\ - * @see czm_viewportTransformation\n\ - *\n\ - * @example\n\ - * vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord);\n\ - */\n\ -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\ -\n\ - if (czm_inverseProjection != mat4(0.0)) {\n\ - q = czm_inverseProjection * q;\n\ - } else {\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\ -\n\ - float near = czm_currentFrustum.x;\n\ - float far = czm_currentFrustum.y;\n\ -\n\ - q.x = (q.x * (right - left) + left + right) * 0.5;\n\ - q.y = (q.y * (top - bottom) + bottom + top) * 0.5;\n\ - q.z = (q.z * (near - far) - near - far) * 0.5;\n\ - q.w = 1.0;\n\ - }\n\ -\n\ - return q;\n\ -}\n\ -"; + + /** + * @private + */ + var PrimitiveState = { + READY : 0, + CREATING : 1, + CREATED : 2, + COMBINING : 3, + COMBINED : 4, + COMPLETE : 5, + FAILED : 6 + }; + + return freezeObject(PrimitiveState); }); -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/Builtin/Functions/XYZToRGB',[],function() { + +define('Scene/SceneMode',[ + '../Core/freezeObject' + ], function( + freezeObject) { 'use strict'; - return "/**\n\ - * Converts a CIE Yxy color to RGB.\n\ - *

    The conversion is described in\n\ - * {@link http://content.gpwiki.org/index.php/D3DBook:High-Dynamic_Range_Rendering#Luminance_Transform|Luminance Transform}\n\ - *

    \n\ - * \n\ - * @name czm_XYZToRGB\n\ - * @glslFunction\n\ - * \n\ - * @param {vec3} Yxy The color in CIE Yxy.\n\ - *\n\ - * @returns {vec3} The color in RGB.\n\ - *\n\ - * @example\n\ - * vec3 xyz = czm_RGBToXYZ(rgb);\n\ - * xyz.x = max(xyz.x - luminanceThreshold, 0.0);\n\ - * rgb = czm_XYZToRGB(xyz);\n\ - */\n\ -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\ - \n\ - return XYZ2RGB * xyz;\n\ -}\n\ -"; + + /** + * 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); }); -//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/HSBToRGB', - './Functions/HSLToRGB', - './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/RGBToHSB', - './Functions/RGBToHSL', - './Functions/RGBToXYZ', - './Functions/saturation', - './Functions/shadowDepthCompare', - './Functions/shadowVisibility', - './Functions/signNotZero', - './Functions/tangentToEyeSpaceMatrix', - './Functions/translateRelativeToEye', - './Functions/translucentPhong', - './Functions/transpose', - './Functions/unpackDepth', - './Functions/windowToEyeCoordinates', - './Functions/XYZToRGB' + +define('Scene/ShadowMode',[ + '../Core/freezeObject' ], 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_HSBToRGB, - czm_HSLToRGB, - 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_RGBToHSB, - czm_RGBToHSL, - 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_HSBToRGB : czm_HSBToRGB, - czm_HSLToRGB : czm_HSLToRGB, - 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_RGBToHSB : czm_RGBToHSB, - czm_RGBToHSL : czm_RGBToHSL, - 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}; + 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; + } + return ShadowMode.DISABLED; + }; + + return freezeObject(ShadowMode); }); -/*global define*/ -define('Renderer/ShaderSource',[ + +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', - '../Shaders/Builtin/CzmBuiltins', - './AutomaticUniforms' + '../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', + './DepthFunction', + './PrimitivePipeline', + './PrimitiveState', + './SceneMode', + './ShadowMode' ], function( + BoundingSphere, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, + clone, + Color, + combine, + ComponentDatatype, defaultValue, defined, + defineProperties, + destroyObject, DeveloperError, - CzmBuiltins, - AutomaticUniforms) { + EncodedCartesian3, + FeatureDetection, + Geometry, + GeometryAttribute, + GeometryAttributes, + isArray, + Matrix4, + RuntimeError, + subdivideArray, + TaskProcessor, + BufferUsage, + ContextLimits, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + VertexArray, + when, + BatchTable, + CullFace, + DepthFunction, + PrimitivePipeline, + PrimitiveState, + SceneMode, + ShadowMode) { '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; - }); + /** + * 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 + * @see ClassificationPrimitive + * @see GroundPrimitive + */ + 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. + *

    + * + * @readonly + * @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 {@link Appearance} used to shade this primitive when it fails the depth test. Each geometry + * instance is shaded with the same appearance. Some appearances, like + * {@link PerInstanceColorAppearance} allow giving each instance unique + * properties. + * + *

    + * When using an appearance that requires a color attribute, like PerInstanceColorAppearance, + * add a depthFailColor per-instance attribute instead. + *

    + * + *

    + * Requires the EXT_frag_depth WebGL extension to render properly. If the extension is not supported, + * there may be artifacts. + *

    + * @type Appearance + * + * @default undefined + */ + this.depthFailAppearance = options.depthFailAppearance; + this._depthFailAppearance = undefined; + this._depthFailMaterial = 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; + + + /** + * 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._depthFailAppearance = undefined; + this._spDepthFail = undefined; + this._frontFaceDepthFailRS = undefined; + this._backFaceDepthFailRS = 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; } - function getDependencyNode(name, glslSource, nodes) { - var dependencyNode; + 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; + } + }, - // check if already loaded - for (var i = 0; i < nodes.length; ++i) { - if (nodes[i].name === name) { - dependencyNode = nodes[i]; + /** + * 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; } } + }); - 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); + function getCommonPerInstanceAttributeNames(instances) { + var length = instances.length; - // create new node - dependencyNode = { - name : name, - glslSource : glslSource, - dependsOn : [], - requiredBy : [], - evaluated : false - }; - nodes.push(dependencyNode); + 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 dependencyNode; + return attributesInAllInstances; } - function generateDependencies(currentNode, dependencyNodes) { - if (currentNode.evaluated) { + 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; } - currentNode.evaluated = true; + var names = getCommonPerInstanceAttributeNames(instances); + var length = names.length; - // 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; + 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 }); + } - 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); + 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; + } - // recursive call to find any dependencies of the new node - generateDependencies(referencedNode, dependencyNodes); - } + 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 sortDependencies(dependencyNodes) { - var nodesWithoutIncomingEdges = []; - var allNodes = []; + 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 + }); + } - while (dependencyNodes.length > 0) { - var node = dependencyNodes.pop(); - allNodes.push(node); + 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]); + } + } - if (node.requiredBy.length === 0) { - nodesWithoutIncomingEdges.push(node); + var indices; + if (defined(geometry.indices)) { + var sourceValues = geometry.indices; + if (isArray(sourceValues)) { + indices = sourceValues.slice(0); + } else { + indices = new sourceValues.constructor(sourceValues); } } - while (nodesWithoutIncomingEdges.length > 0) { - var currentNode = nodesWithoutIncomingEdges.shift(); + return new Geometry({ + attributes : newAttributes, + indices : indices, + primitiveType : geometry.primitiveType, + boundingSphere : BoundingSphere.clone(geometry.boundingSphere) + }); + } - dependencyNodes.push(currentNode); + function cloneInstance(instance, geometry) { + return { + geometry : geometry, + modelMatrix : Matrix4.clone(instance.modelMatrix), + pickPrimitive : instance.pickPrimitive, + id : instance.id + }; + } - 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); + var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g; - // if referenced node has no more incoming edges, add to list - if (referencedNode.requiredBy.length === 0) { - nodesWithoutIncomingEdges.push(referencedNode); + 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'); } } - // 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]); - } + 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' + + '}'; - function getBuiltinsAndAutomaticUniforms(shaderSource) { - // generate a dependency graph for builtin functions - var dependencyNodes = []; - var root = getDependencyNode('main', shaderSource, dependencyNodes); - generateDependencies(root, dependencyNodes); - sortDependencies(dependencyNodes); + return renamedVS + '\n' + showMain; + }; - // 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'; + Primitive._updateColorAttribute = function(primitive, vertexShaderSource, isDepthFail) { + // some appearances have a color attribute for per vertex color. + // only remove if color is a per instance attribute. + if (!defined(primitive._batchTableAttributeIndices.color) && !defined(primitive._batchTableAttributeIndices.depthFailColor)) { + return vertexShaderSource; } - return builtinsSource.replace(root.glslSource, ''); - } + if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) { + return vertexShaderSource; + } - function combineShader(shaderSource, isFragmentShader) { - var i; - var length; + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, ''); + if (!isDepthFail) { + modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2'); + } else { + modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_depthFailColor(batchId)$2'); + } + return modifiedVS; + }; - // 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]; - } + 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; } - combinedSources = removeComments(combinedSources); + 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'; - // Extract existing shader version from sources - var version; - combinedSources = combinedSources.replace(/#version\s+(.*?)\n/gm, function(match, group1) { - - // Extract #version to put at the top - version = group1; + 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'; + } - // 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'; - }); + 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; + }; - // Remove precision qualifier - combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, ''); + function modifyForEncodedNormals(primitive, vertexShaderSource) { + if (!primitive.compressVertices) { + return vertexShaderSource; + } - // Replace main() for picked if desired. - var pickColorQualifier = shaderSource.pickColorQualifier; - if (defined(pickColorQualifier)) { - combinedSources = ShaderSource.createPickFragmentShaderSource(combinedSources, pickColorQualifier); + 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; } - // combine into single string - var result = ''; + var containsTangent = vertexShaderSource.search(/attribute\s+vec3\s+tangent;/g) !== -1; + var containsBitangent = vertexShaderSource.search(/attribute\s+vec3\s+bitangent;/g) !== -1; - // #version must be first - // defaults to #version 100 if not specified - if (defined(version)) { - result = '#version ' + version; + var numComponents = containsSt && containsNormal ? 2.0 : 1.0; + numComponents += containsTangent || containsBitangent ? 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 (isFragmentShader) { - result += '\ -#ifdef GL_FRAGMENT_PRECISION_HIGH\n\ - precision highp float;\n\ -#else\n\ - precision mediump float;\n\ -#endif\n\n'; + if (containsNormal && containsTangent && containsBitangent) { + globalDecl += + 'vec3 normal;\n' + + 'vec3 tangent;\n' + + 'vec3 bitangent;\n'; + decode += ' czm_octDecode(' + attributeName + '.' + (containsSt ? 'yz' : 'xy') + ', normal, tangent, bitangent);\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 (containsBitangent) { + globalDecl += 'vec3 bitangent;\n'; + decode += ' bitangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\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'; + 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+bitangent;/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 depthClampVS(vertexShaderSource) { + var modifiedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_depth_clamp_main'); + // The varying should be surround by #ifdef GL_EXT_frag_depth as an optimization. + // It is not to workaround an issue with Edge: + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12120362/ + modifiedVS += + 'varying float v_WindowZ;\n' + + 'void main() {\n' + + ' czm_non_depth_clamp_main();\n' + + ' vec4 position = gl_Position;\n' + + ' v_WindowZ = (0.5 * (position.z / position.w) + 0.5) * position.w;\n' + + ' position.z = min(position.z, position.w);\n' + + ' gl_Position = position;' + + '}\n'; + return modifiedVS; + } + + function depthClampFS(fragmentShaderSource) { + var modifiedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_non_depth_clamp_main'); + modifiedFS += + 'varying float v_WindowZ;\n' + + 'void main() {\n' + + ' czm_non_depth_clamp_main();\n' + + '#ifdef GL_EXT_frag_depth\n' + + ' gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n' + + '#endif\n' + + '}\n'; + modifiedFS = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n' + + modifiedFS; + return modifiedFS; + } + + 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; + + } + + 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); + + + 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); + }); } + } - // append built-ins - if (shaderSource.includeBuiltIns) { - result += getBuiltinsAndAutomaticUniforms(combinedSources); + 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); } - // reset line number - result += '\n#line 0\n'; + clonedInstances.length = geometryIndex; - // append actual source - result += combinedSources; + var scene3DOnly = frameState.scene3DOnly; + var projection = frameState.mapProjection; - return result; - } + 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 + }); - /** - * 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; + 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; - - 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); + if (defined(primitive._geometries) && primitive._geometries.length > 0) { + primitive._state = PrimitiveState.COMBINED; + } else { + setReady(primitive, frameState, PrimitiveState.FAILED, undefined); + } } - ShaderSource.prototype.clone = function() { - return new ShaderSource({ - sources : this.sources, - defines : this.defines, - pickColorQuantifier : this.pickColorQualifier, - includeBuiltIns : this.includeBuiltIns - }); - }; + var scratchBoundingSphereCenterEncoded = new EncodedCartesian3(); + var scratchBoundingSphereCartographic = new Cartographic(); + var scratchBoundingSphereCenter2D = new Cartesian3(); + var scratchBoundingSphere = new BoundingSphere(); - ShaderSource.replaceMain = function(source, renamedMain) { - renamedMain = 'void ' + renamedMain + '()'; - return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain); - }; + function updateBatchTableBoundingSpheres(primitive, frameState) { + var hasDistanceDisplayCondition = defined(primitive._batchTableAttributeIndices.distanceDisplayCondition); + if (!hasDistanceDisplayCondition || primitive._batchTableBoundingSpheresUpdated) { + return; + } - /** - * 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); - }; + var indices = primitive._batchTableBoundingSphereAttributeIndices; + var center3DHighIndex = indices.center3DHigh; + var center3DLowIndex = indices.center3DLow; + var center2DHighIndex = indices.center2DHigh; + var center2DLowIndex = indices.center2DLow; + var radiusIndex = indices.radius; - /** - * 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); - }; + var projection = frameState.mapProjection; + var ellipsoid = projection.ellipsoid; + + var batchTable = primitive._batchTable; + var boundingSpheres = primitive._instanceBoundingSpheres; + var length = boundingSpheres.length; + //acevedo + //check for undefined boundingSphere. Return if any + for (var i = 0; i < length; ++i) { + var boundingSphere = boundingSpheres[i]; + if (!defined(boundingSphere)) { + primitive._batchTableBoundingSpheresUpdated = false; + continue; + } + + var modelMatrix = primitive.modelMatrix; + if (defined(modelMatrix)) { + boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, scratchBoundingSphere); + } + + var center = boundingSphere.center; + var radius = boundingSphere.radius; - /** - * For ShaderProgram testing - * @private - */ - ShaderSource._czmBuiltinsAndUniforms = {}; + var encodedCenter = EncodedCartesian3.fromCartesian(center, scratchBoundingSphereCenterEncoded); + batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high); + batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low); - // 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); - } + 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); } - } - 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' + - '}'; + primitive._batchTableBoundingSpheresUpdated = true; + } - return renamedVS + '\n' + pickMain; - }; + function createVertexArray(primitive, frameState) { + var attributeLocations = primitive._attributeLocations; + var geometries = primitive._geometries; + var scene3DOnly = frameState.scene3DOnly; + var context = frameState.context; - 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' + - '}'; + var va = []; + var length = geometries.length; + for (var i = 0; i < length; ++i) { + var geometry = geometries[i]; - return renamedFS + '\n' + pickMain; - }; + va.push(VertexArray.fromGeometry({ + context : context, + geometry : geometry, + attributeLocations : attributeLocations, + bufferUsage : BufferUsage.STATIC_DRAW, + interleave : primitive._interleave + })); - ShaderSource.findVarying = function(shaderSource, names) { - var sources = shaderSource.sources; + if (defined(primitive._createBoundingVolumeFunction)) { + primitive._createBoundingVolumeFunction(frameState, geometry); + } else { + primitive._boundingSpheres.push(BoundingSphere.clone(geometry.boundingSphere)); + primitive._boundingSphereWC.push(new BoundingSphere()); - var namesLength = names.length; - for (var i = 0; i < namesLength; ++i) { - var name = names[i]; + 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; - var sourcesLength = sources.length; - for (var j = 0; j < sourcesLength; ++j) { - if (sources[j].indexOf(name) !== -1) { - return name; + primitive._boundingSphereCV.push(BoundingSphere.clone(geometry.boundingSphereCV)); + primitive._boundingSphere2D.push(new BoundingSphere()); + primitive._boundingSphereMorph.push(new BoundingSphere()); } } } - return undefined; - }; - - var normalVaryingNames = ['v_normalEC', 'v_normal']; + primitive._va = va; + primitive._primitiveType = geometries[0].primitiveType; - ShaderSource.findNormalVarying = function(shaderSource) { - return ShaderSource.findVarying(shaderSource, normalVaryingNames); - }; + if (primitive.releaseGeometryInstances) { + primitive.geometryInstances = undefined; + } - var positionVaryingNames = ['v_positionEC']; + primitive._geometries = undefined; + setReady(primitive, frameState, PrimitiveState.COMPLETE, undefined); + } - ShaderSource.findPositionVarying = function(shaderSource) { - return ShaderSource.findVarying(shaderSource, positionVaryingNames); - }; + function createRenderStates(primitive, context, appearance, twoPasses) { + var renderState = appearance.getRenderState(); + var rs; - return ShaderSource; -}); + if (twoPasses) { + rs = clone(renderState, false); + rs.cull = { + enabled : true, + face : CullFace.BACK + }; + primitive._frontFaceRS = RenderState.fromCache(rs); -/*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'; + rs.cull.face = CullFace.FRONT; + primitive._backFaceRS = RenderState.fromCache(rs); + } else { + primitive._frontFaceRS = RenderState.fromCache(renderState); + primitive._backFaceRS = primitive._frontFaceRS; + } - /** - * @private - */ - function Buffer(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + rs = clone(renderState, false); + if (defined(primitive._depthFailAppearance)) { + rs.depthTest.enabled = false; + } - - 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 (primitive.allowPicking) { + if (twoPasses) { + rs.cull = { + enabled : false + }; + primitive._pickRS = RenderState.fromCache(rs); + } else { + primitive._pickRS = RenderState.fromCache(rs); + } + } else { + rs.colorMask = { + red : false, + green : false, + blue : false, + alpha : false + }; - if (hasArray) { - sizeInBytes = typedArray.byteLength; + if (twoPasses) { + rs.cull = { + enabled : false + }; + primitive._pickRS = RenderState.fromCache(rs); + } else { + primitive._pickRS = RenderState.fromCache(rs); + } } - - var buffer = gl.createBuffer(); - gl.bindBuffer(bufferTarget, buffer); - gl.bufferData(bufferTarget, hasArray ? typedArray : sizeInBytes, usage); - gl.bindBuffer(bufferTarget, null); + if (defined(primitive._depthFailAppearance)) { + renderState = primitive._depthFailAppearance.getRenderState(); + rs = clone(renderState, false); + rs.depthTest.func = DepthFunction.GREATER; + if (twoPasses) { + rs.cull = { + enabled : true, + face : CullFace.BACK + }; + primitive._frontFaceDepthFailRS = RenderState.fromCache(rs); - this._gl = gl; - this._bufferTarget = bufferTarget; - this._sizeInBytes = sizeInBytes; - this._usage = usage; - this._buffer = buffer; - this.vertexArrayDestroyable = true; + rs.cull.face = CullFace.FRONT; + primitive._backFaceDepthFailRS = RenderState.fromCache(rs); + } else { + primitive._frontFaceDepthFailRS = RenderState.fromCache(rs); + primitive._backFaceDepthFailRS = primitive._frontFaceRS; + } + } } - /** - * 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) { - - return new Buffer({ - context: options.context, - bufferTarget: WebGLConstants.ARRAY_BUFFER, - typedArray: options.typedArray, - sizeInBytes: options.sizeInBytes, - usage: options.usage - }); - }; + function createShaderProgram(primitive, frameState, appearance) { + var context = frameState.context; - /** - * 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} options.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) { - - var context = options.context; - var indexDatatype = options.indexDatatype; + var attributeLocations = primitive._attributeLocations; - var bytesPerIndex = IndexDatatype.getSizeInBytes(indexDatatype); - var buffer = new Buffer({ + var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource); + vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive, vs, false); + 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, - bufferTarget : WebGLConstants.ELEMENT_ARRAY_BUFFER, - typedArray : options.typedArray, - sizeInBytes : options.sizeInBytes, - usage : options.usage + shaderProgram : primitive._sp, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations }); + validateShaderMatching(primitive._sp, attributeLocations); - var numberOfIndices = buffer.sizeInBytes / bytesPerIndex; + if (defined(primitive._depthFailAppearance)) { + vs = primitive._batchTable.getVertexShaderCallback()(primitive._depthFailAppearance.vertexShaderSource); + vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive, vs, true); + vs = modifyForEncodedNormals(primitive, vs); + vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); + vs = depthClampVS(vs); - defineProperties(buffer, { - indexDatatype: { - get : function() { - return indexDatatype; - } - }, - bytesPerIndex : { - get : function() { - return bytesPerIndex; - } - }, - numberOfIndices : { - get : function() { - return numberOfIndices; + fs = depthClampFS(primitive._depthFailAppearance.getFragmentShaderSource()); + + primitive._spDepthFail = ShaderProgram.replaceCache({ + context : context, + shaderProgram : primitive._spDepthFail, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + validateShaderMatching(primitive._spDepthFail, attributeLocations); + } + } + + var modifiedModelViewScratch = new Matrix4(); + var rtcScratch = new Cartesian3(); + + function getUniforms(primitive, appearance, material, 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)) { + + appearanceUniformMap[name] = getUniformFunction(appearanceUniforms, name); } } - }); + } + var uniforms = combine(appearanceUniformMap, materialUniformMap); + uniforms = primitive._batchTable.getUniformMapCallback()(uniforms); - return buffer; - }; + 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; + }; + } - defineProperties(Buffer.prototype, { - sizeInBytes : { - get : function() { - return this._sizeInBytes; - } - }, + return uniforms; + } - usage: { - get : function() { - return this._usage; - } + function createCommands(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands, frameState) { + var uniforms = getUniforms(primitive, appearance, material, frameState); + + var depthFailUniforms; + if (defined(primitive._depthFailAppearance)) { + depthFailUniforms = getUniforms(primitive, primitive._depthFailAppearance, primitive._depthFailAppearance.material, frameState); } - }); - Buffer.prototype._getBuffer = function() { - return this._buffer; - }; + var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; - Buffer.prototype.copyFromArrayView = function(arrayView, offsetInBytes) { - offsetInBytes = defaultValue(offsetInBytes, 0); + var multiplier = twoPasses ? 2 : 1; + multiplier *= defined(primitive._depthFailAppearance) ? 2 : 1; - - var gl = this._gl; - var target = this._bufferTarget; - gl.bindBuffer(target, this._buffer); - gl.bufferSubData(target, offsetInBytes, arrayView); - gl.bindBuffer(target, null); - }; + colorCommands.length = primitive._va.length * multiplier; + pickCommands.length = primitive._va.length; - Buffer.prototype.isDestroyed = function() { - return false; - }; + var length = colorCommands.length; + var m = 0; + var vaIndex = 0; + for (var i = 0; i < length; ++i) { + var colorCommand; - Buffer.prototype.destroy = function() { - this._gl.deleteBuffer(this._buffer); - return destroyObject(this); - }; + 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; - return Buffer; -}); + ++i; + } -/*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'; + 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; - 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 (defined(primitive._depthFailAppearance)) { + if (twoPasses) { + ++i; - - // 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) - }; + colorCommand = colorCommands[i]; + if (!defined(colorCommand)) { + colorCommand = colorCommands[i] = new DrawCommand({ + owner : primitive, + primitiveType : primitive._primitiveType + }); + } + colorCommand.vertexArray = primitive._va[vaIndex]; + colorCommand.renderState = primitive._backFaceDepthFailRS; + colorCommand.shaderProgram = primitive._spDepthFail; + colorCommand.uniformMap = depthFailUniforms; + colorCommand.pass = pass; + } - 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; + ++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._frontFaceDepthFailRS; + colorCommand.shaderProgram = primitive._spDepthFail; + colorCommand.uniformMap = depthFailUniforms; + colorCommand.pass = pass; + } - attr.disableVertexAttribArray = function(gl) { - gl.disableVertexAttribArray(this.index); - if (this.instanceDivisor > 0) { - context.glVertexAttribDivisor(index, 0); + 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; + } + } + + Primitive._updateBoundingVolumes = function(primitive, frameState, modelMatrix) { + var i; + var length; + var boundingSphere; + + // 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)) { + length = primitive._boundingSpheres.length; + for (i = 0; i < length; ++i) { + 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; + } + } + + if (!Matrix4.equals(modelMatrix, primitive._modelMatrix)) { + Matrix4.clone(modelMatrix, primitive._modelMatrix); + length = primitive._boundingSpheres.length; + for (i = 0; i < length; ++i) { + 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]); + } } - }; - } 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) { - }; + function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); + + 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; } - attributes.push(attr); - } + 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; - function bind(gl, attributes, indexBuffer) { - for ( var i = 0; i < attributes.length; ++i) { - var attribute = attributes[i]; - if (attribute.enabled) { - attribute.vertexAttrib(gl); + var factor = twoPasses ? 2 : 1; + factor *= defined(primitive._depthFailAppearance) ? 2 : 1; + + for (var j = 0; j < colorLength; ++j) { + var sphereIndex = Math.floor(j / factor); + 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 (defined(indexBuffer)) { - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._getBuffer()); + 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); + } } } /** - * 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 + * 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: + *

    * - * @private + * @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. */ - function VertexArray(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - - 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 hasConstantAttributes = false; + 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; + } - var length = attributes.length; - for (i = 0; i < length; ++i) { - addAttribute(vaAttributes, attributes[i], i, context); + if (defined(this._error)) { + throw this._error; } - length = vaAttributes.length; - for (i = 0; i < length; ++i) { - var attribute = vaAttributes[i]; + + if (this._state === PrimitiveState.FAILED) { + return; + } - 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; + 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); } - for (i = 0; i < length; ++i) { - if (vaAttributes[i].instanceDivisor > 0) { - hasInstancedAttributes = true; - } - if (defined(vaAttributes[i].value)) { - hasConstantAttributes = true; + if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) { + if (this.asynchronous) { + loadAsynchronous(this, frameState); + } else { + loadSynchronous(this, frameState); } } - - var vao; + if (this._state === PrimitiveState.COMBINED) { + updateBatchTableBoundingSpheres(this, frameState); + createVertexArray(this, frameState); + } - // Setup VAO if supported - if (context.vertexArrayObject) { - vao = context.glCreateVertexArray(); - context.glBindVertexArray(vao); - bind(gl, vaAttributes, indexBuffer); - context.glBindVertexArray(null); + if (!this.show || this._state !== PrimitiveState.COMPLETE) { + return; } - this._numberOfVertices = numberOfVertices; - this._hasInstancedAttributes = hasInstancedAttributes; - this._hasConstantAttributes = hasConstantAttributes; - this._context = context; - this._gl = gl; - this._vao = vao; - this._attributes = vaAttributes; - this._indexBuffer = indexBuffer; - } + // 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; - function computeNumberOfVertices(attribute) { - return attribute.values.length / attribute.componentsPerAttribute; - } + if (this._appearance !== appearance) { + this._appearance = appearance; + this._material = material; + createRS = true; + createSP = true; + } else if (this._material !== material ) { + this._material = material; + createSP = true; + } - function computeAttributeSizeInBytes(attribute) { - return ComponentDatatype.getSizeInBytes(attribute.componentDatatype) * attribute.componentsPerAttribute; - } + var depthFailAppearance = this.depthFailAppearance; + var depthFailMaterial = defined(depthFailAppearance) ? depthFailAppearance.material : undefined; - function interleaveAttributes(attributes) { - var j; - var name; - var attribute; + if (this._depthFailAppearance !== depthFailAppearance) { + this._depthFailAppearance = depthFailAppearance; + this._depthFailMaterial = depthFailMaterial; + createRS = true; + createSP = true; + } else if (this._depthFailMaterial !== depthFailMaterial) { + this._depthFailMaterial = depthFailMaterial; + createSP = true; + } - // 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); + var translucent = this._appearance.isTranslucent(); + if (this._translucent !== translucent) { + this._translucent = translucent; + createRS = true; + } - if (attributes[name].componentDatatype === ComponentDatatype.DOUBLE) { - attributes[name].componentDatatype = ComponentDatatype.FLOAT; - attributes[name].values = ComponentDatatype.createTypedArray(ComponentDatatype.FLOAT, attributes[name].values); - } - } + if (defined(this._material)) { + this._material.update(context); } - // Validation. Compute number of vertices. - var numberOfVertices; - var namesLength = names.length; + var twoPasses = appearance.closed && translucent; - if (namesLength > 0) { - numberOfVertices = computeNumberOfVertices(attributes[names[0]]); + if (createRS) { + var rsFunc = defaultValue(this._createRenderStatesFunction, createRenderStates); + rsFunc(this, context, appearance, twoPasses); + } - for (j = 1; j < namesLength; ++j) { - var currentNumberOfVertices = computeNumberOfVertices(attributes[names[j]]); + if (createSP) { + var spFunc = defaultValue(this._createShaderProgramFunction, createShaderProgram); + spFunc(this, frameState, appearance); + } - 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() + ').'); - } - } + if (createRS || createSP) { + var commandFunc = defaultValue(this._createCommandsFunction, createCommands); + commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState); } - // 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); - }); + var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands); + updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses); + }; - // Compute sizes and strides. - var vertexSizeInBytes = 0; - var offsetsInBytes = {}; + 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; + }; + } - for (j = 0; j < namesLength; ++j) { - name = names[j]; - attribute = attributes[name]; + function createSetFunction(batchTable, instanceIndex, attributeIndex) { + return function(value) { + var attributeValue = getAttributeValue(value); + batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue); + }; + } - offsetsInBytes[name] = vertexSizeInBytes; - vertexSizeInBytes += computeAttributeSizeInBytes(attribute); - } + function createBoundingSphereProperties(primitive, properties, index) { + properties.boundingSphere = { + get : function() { + var boundingSphere = primitive._instanceBoundingSpheres[index]; + var modelMatrix = primitive.modelMatrix; + if (defined(modelMatrix) && defined(boundingSphere)) { + boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix); + } + return boundingSphere; + } + }; + properties.boundingSphereCV = { + get : function() { + return primitive._instanceBoundingSpheresCV[index]; + } + }; + } - 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); + /** + * 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) { + + 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; } + } - // Total vertex buffer size in bytes, including per-vertex padding. - var vertexBufferSizeInBytes = numberOfVertices * vertexSizeInBytes; + if (index === -1) { + return undefined; + } - // Create array for interleaved vertices. Each attribute has a different view (pointer) into the array. - var buffer = new ArrayBuffer(vertexBufferSizeInBytes); - var views = {}; + var attributes = this._perInstanceAttributeCache[index]; + if (defined(attributes)) { + return attributes; + } - for (j = 0; j < namesLength; ++j) { - name = names[j]; - var sizeInBytes = ComponentDatatype.getSizeInBytes(attributes[name].componentDatatype); + var batchTable = this._batchTable; + var perInstanceAttributeIndices = this._batchTableAttributeIndices; + attributes = {}; + var properties = {}; - views[name] = { - pointer : ComponentDatatype.createTypedArray(attributes[name].componentDatatype, buffer), - index : offsetsInBytes[name] / sizeInBytes, // Offset in ComponentType - strideInComponentType : vertexSizeInBytes / sizeInBytes + for (var name in perInstanceAttributeIndices) { + if (perInstanceAttributeIndices.hasOwnProperty(name)) { + var attributeIndex = perInstanceAttributeIndices[name]; + properties[name] = { + get : createGetFunction(batchTable, index, attributeIndex) }; - } - // 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]; + 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; + } } + } - view.index += view.strideInComponentType; + if (createSetter) { + properties[name].set = createSetFunction(batchTable, index, attributeIndex); } } - - return { - buffer : buffer, - offsetsInBytes : offsetsInBytes, - vertexSizeInBytes : vertexSizeInBytes - }; } - // No attributes to interleave. - return undefined; - } + createBoundingSphereProperties(this, properties, index); + defineProperties(attributes, properties); + + this._lastPerInstanceAttributeIndex = index; + this._perInstanceAttributeCache[index] = attributes; + return attributes; + }; /** - * 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. + * 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. + *

    * - * @param {Object} options An object defining the geometry, attribute indices, buffer usage, and vertex layout used to create the vertex array. + * @returns {Boolean} true if this object was destroyed; otherwise, false. * - * @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. + * @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} * - * @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), - * }); + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * - * @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(); + * e = e && e.destroy(); * - * @see Buffer#createVertexBuffer - * @see Buffer#createIndexBuffer - * @see GeometryPipeline.createAttributeLocations - * @see ShaderProgram + * @see Primitive#isDestroyed */ - VertexArray.fromGeometry = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + Primitive.prototype.destroy = function() { + var length; + var i; - - var context = options.context; - var geometry = defaultValue(options.geometry, defaultValue.EMPTY_OBJECT); + this._sp = this._sp && this._sp.destroy(); + this._pickSP = this._pickSP && this._pickSP.destroy(); - var bufferUsage = defaultValue(options.bufferUsage, BufferUsage.DYNAMIC_DRAW); + var va = this._va; + length = va.length; + for (i = 0; i < length; ++i) { + va[i].destroy(); + } + this._va = undefined; - var attributeLocations = defaultValue(options.attributeLocations, defaultValue.EMPTY_OBJECT); - var interleave = defaultValue(options.interleave, false); - var createdVAAttributes = options.vertexArrayAttributes; + var pickIds = this._pickIds; + length = pickIds.length; + for (i = 0; i < length; ++i) { + pickIds[i].destroy(); + } + this._pickIds = undefined; - var name; - var attribute; - var vertexBuffer; - var vaAttributes = (defined(createdVAAttributes)) ? createdVAAttributes : []; - var attributes = geometry.attributes; + this._batchTable = this._batchTable && this._batchTable.destroy(); - 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; + //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; - for (name in attributes) { - if (attributes.hasOwnProperty(name) && defined(attributes[name])) { - attribute = attributes[name]; + return destroyObject(this); + }; - 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 - }); - } - } - } + 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); } - } 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 - }); - } - } - } + return Primitive; +}); - 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 - }); - } - } +define('DataSources/ColorMaterialProperty',[ + '../Core/Color', + '../Core/defined', + '../Core/defineProperties', + '../Core/Event', + './createPropertyDescriptor', + './Property' + ], function( + Color, + defined, + defineProperties, + Event, + createPropertyDescriptor, + Property) { + 'use strict'; - return new VertexArray({ - context : context, - attributes : vaAttributes, - indexBuffer : indexBuffer - }); - }; + /** + * 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(VertexArray.prototype, { - numberOfAttributes : { + 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 this._attributes.length; + return Property.isConstant(this._color); } }, - numberOfVertices : { + /** + * 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._numberOfVertices; + return this._definitionChanged; } }, - indexBuffer : { - get : function() { - return this._indexBuffer; - } - } + /** + * Gets or sets the {@link Color} {@link Property}. + * @memberof ColorMaterialProperty.prototype + * @type {Property} + * @default Color.WHITE + */ + color : createPropertyDescriptor('color') }); /** - * index is the location in the array of attributes, not the index property of an attribute. + * 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. */ - VertexArray.prototype.getAttribute = function(index) { - - return this._attributes[index]; + ColorMaterialProperty.prototype.getType = function(time) { + return 'Color'; }; - // 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; - } - } - } - } - - // Vertex attributes backed by a constant value go through vertexAttrib[1234]f[v] - // which is part of context state rather than VAO state. - function setConstantAttributes(vertexArray, gl) { - var attributes = vertexArray._attributes; - var length = attributes.length; - for (var i = 0; i < length; ++i) { - var attribute = attributes[i]; - if (attribute.enabled && defined(attribute.value)) { - attribute.vertexAttrib(gl); - } + /** + * 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; + }; - VertexArray.prototype._bind = function() { - if (defined(this._vao)) { - this._context.glBindVertexArray(this._vao); - if (this._context.instancedArrays) { - setVertexAttribDivisor(this); - } - if (this._hasConstantAttributes) { - setConstantAttributes(this, this._gl); - } - } else { - bind(this._gl, this._attributes, this._indexBuffer); - } + /** + * 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)); }; - VertexArray.prototype._unBind = function() { - if (defined(this._vao)) { - this._context.glBindVertexArray(null); - } else { - var attributes = this._attributes; - var gl = this._gl; + return ColorMaterialProperty; +}); - 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); - } - } - }; +define('DataSources/dynamicGeometryGetBoundingSphere',[ + '../Core/BoundingSphere', + '../Core/defined', + '../Core/DeveloperError', + './BoundingSphereState' + ], function( + BoundingSphere, + defined, + DeveloperError, + BoundingSphereState) { + 'use strict'; - VertexArray.prototype.isDestroyed = function() { - return false; - }; + /** + * @private + */ + function dynamicGeometryGetBoundingSphere(entity, primitive, outlinePrimitive, result) { + + var attributes; - 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(); + //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)) { + BoundingSphere.clone(attributes.boundingSphere, result); + return BoundingSphereState.DONE; } } - var indexBuffer = this._indexBuffer; - if (defined(indexBuffer) && !indexBuffer.isDestroyed() && indexBuffer.vertexArrayDestroyable) { - indexBuffer.destroy(); + if (defined(outlinePrimitive) && outlinePrimitive.show && outlinePrimitive.ready) { + attributes = outlinePrimitive.getGeometryInstanceAttributes(entity); + if (defined(attributes) && defined(attributes.boundingSphere)) { + BoundingSphere.clone(attributes.boundingSphere, result); + return BoundingSphereState.DONE; + } } - if (defined(this._vao)) { - this._context.glDeleteVertexArray(this._vao); + if ((defined(primitive) && !primitive.ready) || (defined(outlinePrimitive) && !outlinePrimitive.ready)) { + return BoundingSphereState.PENDING; } - return destroyObject(this); - }; + return BoundingSphereState.FAILED; + } - return VertexArray; + return dynamicGeometryGetBoundingSphere; }); -/*global define*/ -define('Scene/BatchTable',[ - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/combine', - '../Core/ComponentDatatype', +define('DataSources/MaterialProperty',[ + '../Core/Color', '../Core/defined', '../Core/defineProperties', - '../Core/destroyObject', '../Core/DeveloperError', - '../Core/FeatureDetection', - '../Core/Math', - '../Core/PixelFormat', - '../Renderer/ContextLimits', - '../Renderer/PixelDatatype', - '../Renderer/Sampler', - '../Renderer/Texture', - '../Renderer/TextureMagnificationFilter', - '../Renderer/TextureMinificationFilter' + '../Scene/Material' ], function( - Cartesian2, - Cartesian3, - Cartesian4, - combine, - ComponentDatatype, + Color, defined, defineProperties, - destroyObject, DeveloperError, - FeatureDetection, - CesiumMath, - PixelFormat, - ContextLimits, - PixelDatatype, - Sampler, - Texture, - TextureMagnificationFilter, - TextureMinificationFilter) { + Material) { '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. + * The interface for all {@link Property} objects that represent {@link Material} uniforms. + * This type defines an interface and cannot be instantiated directly. * - * @alias BatchTable + * @alias MaterialProperty * @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, - * }); + * @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 * - * drawCommand.shaderProgram = shaderProgram; - * drawCommand.uniformMap = batchTable.getUniformMapCallback()(uniformMap); + * @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 * - * // use the attribute function names in the shader to retrieve the instance values - * // ... - * attribute float batchId; + * @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 * - * void main() { - * // ... - * float show = getShow(batchId); - * vec3 pickColor = getPickColor(batchId); - * // ... - * } + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. */ - function BatchTable(context, attributes, numberOfInstances) { - - this._attributes = attributes; - this._numberOfInstances = numberOfInstances; + MaterialProperty.prototype.equals = DeveloperError.throwInstantiationError; - if (attributes.length === 0) { - return; + /** + * @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; + } } - // 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); + if (!defined(material) || (material.type !== Material.ColorType)) { + material = Material.fromType(Material.ColorType); + } + Color.clone(Color.WHITE, material.uniforms.color); - var stride = getStride(offsets, attributes, packFloats); - var maxNumberOfInstancesPerRow = Math.floor(ContextLimits.maximumTextureSize / stride); + return material; + }; - var instancesPerWidth = Math.min(numberOfInstances, maxNumberOfInstancesPerRow); - var width = stride * instancesPerWidth; - var height = Math.ceil(numberOfInstances / instancesPerWidth); + return MaterialProperty; +}); - var stepX = 1.0 / width; - var centerX = stepX * 0.5; - var stepY = 1.0 / height; - var centerY = stepY * 0.5; +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'; - 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 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(); - var batchLength = 4 * width * height; - this._batchValues = pixelDatatype === PixelDatatype.FLOAT && !packFloats ? new Float32Array(batchLength) : new Uint8Array(batchLength); - this._batchValuesDirty = false; + function GeometryOptions(entity) { + this.id = entity; + this.vertexFormat = undefined; + this.dimensions = undefined; } - defineProperties(BatchTable.prototype, { + /** + * 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) { + + 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; + } + }, /** - * The attribute descriptions. - * @memberOf BatchTable.prototype - * @type {Object[]} + * 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 */ - attributes : { + isDynamic : { get : function() { - return this._attributes; + return this._dynamic; } }, /** - * The number of instances. - * @memberOf BatchTable.prototype - * @type {Number} + * Gets a value indicating if the geometry is closed. + * This property is only valid for static geometry. + * @memberof BoxGeometryUpdater.prototype + * + * @type {Boolean} * @readonly */ - numberOfInstances : { - get : function () { - return this._numberOfInstances; + 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; } } }); - 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; - } + /** + * 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); + }; - function createOffsets(attributes, packFloats) { - var offsets = new Array(attributes.length); + /** + * 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); + }; - var currentOffset = 0; - var attributesLength = attributes.length; - for (var i = 0; i < attributesLength; ++i) { - var attribute = attributes[i]; - var componentDatatype = attribute.componentDatatype; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); - offsets[i] = currentOffset; + var attributes; - if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) { - currentOffset += 4; - } else { - ++currentOffset; + 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 offsets; - } + return new GeometryInstance({ + id : entity, + geometry : BoxGeometry.fromDimensions(this._options), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), + attributes : attributes + }); + }; - function getStride(offsets, attributes, packFloats) { - var length = offsets.length; - var lastOffset = offsets[length - 1]; - var lastAttribute = attributes[length - 1]; - var componentDatatype = lastAttribute.componentDatatype; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) { - return lastOffset + 4; - } - return lastOffset + 1; - } + return new GeometryInstance({ + id : entity, + geometry : BoxOutlineGeometry.fromDimensions(this._options), + modelMatrix : entity.computeModelMatrix(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) + } + }); + }; - var scratchPackedFloatCartesian4 = new Cartesian4(); + /** + * 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; + }; - var SHIFT_LEFT_8 = 256.0; - var SHIFT_LEFT_16 = 65536.0; - var SHIFT_LEFT_24 = 16777216.0; + /** + * 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); + }; - 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; + BoxGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'box')) { + return; + } - var BIAS = 38.0; + var box = this._entity.box; - function unpackFloat(value) { - var temp = value.w / 2.0; - var exponent = Math.floor(temp); - var sign = (temp - exponent) * 2.0; - exponent = exponent - BIAS; + if (!defined(box)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } - sign = sign * 2.0 - 1.0; - sign = -sign; + var fillProperty = box.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - if (exponent >= BIAS) { - return sign < 0.0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY; + var outlineProperty = box.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); } - var unpacked = sign * value.x * SHIFT_RIGHT_8; - unpacked += sign * value.y * SHIFT_RIGHT_16; - unpacked += sign * value.z * SHIFT_RIGHT_24; + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } - return unpacked * Math.pow(10.0, exponent); - } + var dimensions = box.dimensions; + var position = entity.position; - function getPackedFloat(array, index, result) { - var packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4); - var x = unpackFloat(packed); + 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; + } - packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4); - var y = unpackFloat(packed); + 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); - packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4); - var z = unpackFloat(packed); + var outlineWidth = box.outlineWidth; - packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4); - var w = unpackFloat(packed); + this._fillEnabled = fillEnabled; + this._outlineEnabled = outlineEnabled; - return Cartesian4.fromElements(x, y, z, w, result); - } + 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); + } + }; - if (!FeatureDetection.supportsTypedArrays()) { - return; - } - var scratchFloatArray = new Float32Array(1); + /** + * 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) { + + return new DynamicGeometryUpdater(primitives, this); + }; - function packFloat(value, result) { - scratchFloatArray[0] = value; - value = scratchFloatArray[0]; + /** + * @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) { + + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + this._primitive = undefined; + this._outlinePrimitive = undefined; - if (value === 0.0) { - return Cartesian4.clone(Cartesian4.ZERO, result); + 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 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 options = this._options; + var modelMatrix = entity.computeModelMatrix(time); + var dimensions = Property.getValueOrUndefined(box.dimensions, time, options.dimensions); + if (!defined(modelMatrix) || !defined(dimensions)) { + return; } - 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; - } + options.dimensions = dimensions; - function setPackedAttribute(value, array, index) { - var packed = packFloat(value.x, scratchPackedFloatCartesian4); - Cartesian4.pack(packed, array, index); + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - packed = packFloat(value.y, packed); - Cartesian4.pack(packed, array, index + 4); + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); - packed = packFloat(value.z, packed); - Cartesian4.pack(packed, array, index + 8); + if (Property.getValueOrDefault(box.fill, time, true)) { + var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); + this._material = material; - packed = packFloat(value.w, packed); - Cartesian4.pack(packed, array, index + 12); - } + var appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : true + }); + options.vertexFormat = appearance.vertexFormat; - var scratchGetAttributeCartesian4 = new Cartesian4(); + 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 + })); + } - /** - * 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) { - - var attributes = this._attributes; - var offset = this._offsets[attributeIndex]; - var stride = this._stride; + if (Property.getValueOrDefault(box.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - var index = 4 * stride * instanceIndex + 4 * offset; - var value; + 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; - if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) { - value = getPackedFloat(this._batchValues, index, scratchGetAttributeCartesian4); - } else { - value = Cartesian4.unpack(this._batchValues, index, scratchGetAttributeCartesian4); + 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 + })); } + }; - var attributeType = getAttributeType(attributes, attributeIndex); - if (defined(attributeType.fromCartesian4)) { - return attributeType.fromCartesian4(value, result); - } else if (defined(attributeType.clone)) { - return attributeType.clone(value, result); - } + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + }; - return value.x; + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; }; - var setAttributeScratchValues = [undefined, undefined, new Cartesian2(), new Cartesian3(), new Cartesian4()]; - var setAttributeScratchCartesian4 = new Cartesian4(); + DynamicGeometryUpdater.prototype.destroy = function() { + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + destroyObject(this); + }; + + return BoxGeometryUpdater; +}); + +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; /** - * 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. + * A {@link MaterialProperty} that maps to image {@link Material} uniforms. + * @alias ImageMaterialProperty + * @constructor * - * @exception {DeveloperError} instanceIndex is out of range. - * @exception {DeveloperError} attributeIndex is out of range. + * @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) */ - BatchTable.prototype.setBatchedAttribute = function(instanceIndex, attributeIndex, value) { - - 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; + function ImageMaterialProperty(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var offset = this._offsets[attributeIndex]; - var stride = this._stride; - var index = 4 * stride * instanceIndex + 4 * offset; + 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; + } - if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) { - setPackedAttribute(attributeValue, this._batchValues, index); - } else { - Cartesian4.pack(attributeValue, this._batchValues, index); - } + 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') + }); - this._batchValuesDirty = true; + /** + * 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'; }; - 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. + * Gets the value of the property at the provided time. * - * @exception {RuntimeError} The floating point texture extension is required but not supported. + * @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. */ - BatchTable.prototype.update = function(frameState) { - if ((defined(this._texture) && !this._batchValuesDirty) || this._attributes.length === 0) { - return; + ImageMaterialProperty.prototype.getValue = function(time, result) { + if (!defined(result)) { + result = {}; } - this._batchValuesDirty = false; - - if (!defined(this._texture)) { - createTexture(this, frameState.context); + 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); } - updateTexture(this); + + return result; }; /** - * Gets a function that will update a uniform map to contain values for looking up values in the batch table. + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. * - * @returns {BatchTable~updateUniformMapCallback} A callback for updating uniform maps. + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. */ - 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); - }; + 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)); }; - 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 ImageMaterialProperty; +}); - 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'; - } +define('DataSources/createMaterialPropertyDescriptor',[ + '../Core/Color', + '../Core/DeveloperError', + './ColorMaterialProperty', + './createPropertyDescriptor', + './ImageMaterialProperty' + ], function( + Color, + DeveloperError, + ColorMaterialProperty, + createPropertyDescriptor, + ImageMaterialProperty) { + 'use strict'; - function getGlslUnpackFloat(batchTable) { - if (!batchTable._packFloats) { - return ''; + function createMaterialProperty(value) { + if (value instanceof Color) { + return new ColorMaterialProperty(value); } - 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'; + if (typeof value === 'string' || value instanceof HTMLCanvasElement || value instanceof HTMLVideoElement) { + var result = new ImageMaterialProperty(); + result.image = value; + return result; } - return 'vec' + componentsPerAttribute; - } - function getComponentSwizzle(componentsPerAttribute) { - if (componentsPerAttribute === 1) { - return '.x'; - } else if (componentsPerAttribute === 2) { - return '.xy'; - } else if (componentsPerAttribute === 3) { - return '.xyz'; - } - return ''; + } + + /** + * @private + */ + function createMaterialPropertyDescriptor(name, configurable) { + return createPropertyDescriptor(name, configurable, createMaterialProperty); } - 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); + return createMaterialPropertyDescriptor; +}); - var offset = batchTable._offsets[attributeIndex]; +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'; - var glslFunction = - functionReturnType + ' ' + functionName + '(float batchId) \n' + - '{ \n' + - ' vec2 st = computeSt(batchId); \n' + - ' st.x += batchTextureStep.x * float(' + offset + '); \n'; + /** + * 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(); - 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'; + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - } else { - glslFunction += ' vec4 textureValue = texture2D(batchTexture, st); \n'; - } + 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; + } + }, - glslFunction += ' ' + functionReturnType + ' value = textureValue' + functionReturnValue + '; \n'; + /** + * Gets or sets the boolean Property specifying the visibility of the box. + * @memberof BoxGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - 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'; - } + /** + * Gets or sets {@link Cartesian3} Property property specifying the length, width, and height of the box. + * @memberof BoxGraphics.prototype + * @type {Property} + */ + dimensions : createPropertyDescriptor('dimensions'), - glslFunction += - ' return value; \n' + - '} \n'; - return glslFunction; - } + /** + * Gets or sets the material used to fill the box. + * @memberof BoxGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - /** - * 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; - }; - } + /** + * 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'), - var batchTableShader = 'uniform sampler2D batchTexture; \n'; - batchTableShader += getGlslComputeSt(this) + '\n'; - batchTableShader += getGlslUnpackFloat(this) + '\n'; + /** + * Gets or sets the Property specifying whether the box is outlined. + * @memberof BoxGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), - var length = attributes.length; - for (var i = 0; i < length; ++i) { - batchTableShader += getGlslAttributeFunction(this, i); - } + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof BoxGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), - 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; - }; - }; + /** + * Gets or sets the numeric Property specifying the width of the outline. + * @memberof BoxGraphics.prototype + * @type {Property} + * @default 1.0 + */ + outlineWidth : createPropertyDescriptor('outlineWidth'), - /** - * 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; - }; + /** + * 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'), - /** - * 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); - }; + /** + * 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') + }); /** - * A callback for updating uniform maps. - * @callback BatchTable~updateUniformMapCallback + * Duplicates this instance. * - * @param {Object} uniformMap The uniform map. - * @returns {Object} The new uniform map with properties for retrieving values from the batch table. + * @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; + }; /** - * A callback for updating a vertex shader source. - * @callback BatchTable~updateVertexShaderSourceCallback + * Assigns each unassigned property on this object to the value + * of the same property on the provided source object. * - * @param {String} vertexShaderSource The vertex shader source. - * @returns {String} The new vertex shader source with the functions for retrieving batch table values injected. + * @param {BoxGraphics} source The object to be merged into this object. */ + BoxGraphics.prototype.merge = function(source) { + + 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 BatchTable; + return BoxGraphics; }); -/*global define*/ -define('Scene/DepthFunction',[ - '../Core/freezeObject', - '../Core/WebGLConstants' +define('DataSources/CallbackProperty',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event' ], function( - freezeObject, - WebGLConstants) { + defined, + defineProperties, + DeveloperError, + Event) { 'use strict'; /** - * Determines the function used to compare two depths for the depth test. + * A {@link Property} whose value is lazily evaluated by a callback function. * - * @exports DepthFunction + * @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. */ - var DepthFunction = { - /** - * The depth test never passes. - * - * @type {Number} - * @constant - */ - NEVER : WebGLConstants.NEVER, + function CallbackProperty(callback, isConstant) { + this._callback = undefined; + this._isConstant = undefined; + this._definitionChanged = new Event(); + this.setCallback(callback, isConstant); + } + defineProperties(CallbackProperty.prototype, { /** - * The depth test passes if the incoming depth is less than the stored depth. + * Gets a value indicating if this property is constant. + * @memberof CallbackProperty.prototype * - * @type {Number} - * @constant + * @type {Boolean} + * @readonly */ - LESS : WebGLConstants.LESS, - + isConstant : { + get : function() { + return this._isConstant; + } + }, /** - * The depth test passes if the incoming depth is equal to the stored depth. + * 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 {Number} - * @constant + * @type {Event} + * @readonly */ - EQUAL : WebGLConstants.EQUAL, + definitionChanged : { + get : function() { + return this._definitionChanged; + } + } + }); - /** - * 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, + /** + * 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); + }; - /** - * The depth test passes if the incoming depth is greater than the stored depth. - * - * @type {Number} - * @constant - */ - GREATER : WebGLConstants.GREATER, + /** + * 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) { + + var changed = this._callback !== callback || this._isConstant !== isConstant; - /** - * The depth test passes if the incoming depth is not equal to the stored depth. - * - * @type {Number} - * @constant - */ - NOT_EQUAL : WebGLConstants.NOTEQUAL, + this._callback = callback; + this._isConstant = isConstant; - /** - * 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, + if (changed) { + this._definitionChanged.raiseEvent(this); + } + }; - /** - * The depth test always passes. - * - * @type {Number} - * @constant - */ - ALWAYS : WebGLConstants.ALWAYS + /** + * 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); }; - return freezeObject(DepthFunction); -}); + /** + * 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. + */ -//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\ -\n\ -varying float v_width;\n\ -varying vec2 v_st;\n\ -varying float v_angle;\n\ -\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\ -\n\ - vec4 p = czm_computePosition();\n\ - vec4 prev = czm_computePrevPosition();\n\ - vec4 next = czm_computeNextPosition();\n\ -\n\ - v_width = width;\n\ - v_st = st;\n\ -\n\ - vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev, v_angle);\n\ - gl_Position = czm_viewportOrthographic * positionWC;\n\ -}\n\ -"; -}); -//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\ -\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\ -\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\ - // t = (-plane distance - dot(plane normal, ray origin)) / dot(plane normal, ray direction)\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\ -\n\ - positionWC = czm_eyeToWindowCoordinates(vec4(p0, 1.0));\n\ -}\n\ -\n\ -vec4 getPolylineWindowCoordinates(vec4 position, vec4 previous, vec4 next, float expandDirection, float width, bool usePrevious, out float angle) {\n\ - vec4 endPointWC, p0, p1;\n\ - bool culledByNearPlane, clipped;\n\ -\n\ - vec4 positionEC = czm_modelViewRelativeToEye * position;\n\ - vec4 prevEC = czm_modelViewRelativeToEye * previous;\n\ - vec4 nextEC = czm_modelViewRelativeToEye * next;\n\ -\n\ - // Compute the window coordinates of the points.\n\ - vec4 positionWindow = czm_eyeToWindowCoordinates(positionEC);\n\ - vec4 previousWindow = czm_eyeToWindowCoordinates(prevEC);\n\ - vec4 nextWindow = czm_eyeToWindowCoordinates(nextEC);\n\ -\n\ -#ifdef POLYLINE_DASH\n\ - // Determine the relative screen space direction of the line.\n\ - vec2 lineDir;\n\ - if (usePrevious) {\n\ - lineDir = normalize(positionWindow.xy - previousWindow.xy);\n\ - }\n\ - else {\n\ - lineDir = normalize(nextWindow.xy - positionWindow.xy);\n\ - }\n\ - angle = atan(lineDir.x, lineDir.y) - 1.570796327; // precomputed atan(1,0)\n\ -\n\ - // Quantize the angle so it doesn't change rapidly between segments.\n\ - angle = floor(angle / czm_piOverFour + 0.5) * czm_piOverFour;\n\ -#endif\n\ -\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\ -\n\ - if (culledByNearPlane)\n\ - {\n\ - return vec4(0.0, 0.0, 0.0, 1.0);\n\ - }\n\ -\n\ - vec2 prevWC = normalize(p0.xy - endPointWC.xy);\n\ - vec2 nextWC = normalize(p1.xy - endPointWC.xy);\n\ -\n\ - float expandWidth = width * 0.5;\n\ - vec2 direction;\n\ -\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\ -\n\ - // The sine of the angle between the two vectors is given by the formula\n\ - // |a x b| = |a||b|sin(theta)\n\ - // which is\n\ - // float sinAngle = length(cross(vec3(direction, 0.0), vec3(nextWC, 0.0)));\n\ - // Because the z components of both vectors are zero, the x and y coordinate will be zero.\n\ - // Therefore, the sine of the angle is just the z component of the cross product.\n\ - float sinAngle = abs(direction.x * nextWC.y - direction.y * nextWC.x);\n\ - expandWidth = clamp(expandWidth / sinAngle, 0.0, width * 2.0);\n\ - }\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\ -\n\ -void main()\n\ -{\n\ - czm_materialInput materialInput;\n\ - \n\ - materialInput.s = v_st.s;\n\ - materialInput.st = v_st;\n\ - materialInput.str = vec3(v_st, 0.0);\n\ - \n\ - czm_material material = czm_getMaterial(materialInput);\n\ - gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\ -}\n\ -"; + return CallbackProperty; }); -/*global define*/ -define('Scene/PolylineMaterialAppearance',[ + +define('DataSources/CheckerboardMaterialProperty',[ + '../Core/Cartesian2', + '../Core/Color', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/VertexFormat', - '../Shaders/Appearances/PolylineMaterialAppearanceVS', - '../Shaders/PolylineCommon', - '../Shaders/PolylineFS', - './Appearance', - './Material' + '../Core/Event', + './createPropertyDescriptor', + './Property' ], function( + Cartesian2, + Color, defaultValue, defined, defineProperties, - VertexFormat, - PolylineMaterialAppearanceVS, - PolylineCommon, - PolylineFS, - Appearance, - Material) { + Event, + createPropertyDescriptor, + Property) { 'use strict'; - var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineMaterialAppearanceVS; - var defaultFragmentShaderSource = PolylineFS; + var defaultEvenColor = Color.WHITE; + var defaultOddColor = Color.BLACK; + var defaultRepeat = new Cartesian2(2.0, 2.0); /** - * An appearance for {@link PolylineGeometry} that supports shading with materials. - * - * @alias PolylineMaterialAppearance + * A {@link MaterialProperty} that maps to checkerboard {@link Material} uniforms. + * @alias CheckerboardMaterialProperty * @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') - * }) - * }); + * @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 PolylineMaterialAppearance(options) { + function CheckerboardMaterialProperty(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); + this._definitionChanged = new Event(); - /** - * 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._evenColor = undefined; + this._evenColorSubscription = undefined; - this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource); - this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource); - this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState); - this._closed = closed; + this._oddColor = undefined; + this._oddColorSubscription = undefined; - // Non-derived members + this._repeat = undefined; + this._repeatSubscription = undefined; - this._vertexFormat = vertexFormat; + this.evenColor = options.evenColor; + this.oddColor = options.oddColor; + this.repeat = options.repeat; } - defineProperties(PolylineMaterialAppearance.prototype, { + defineProperties(CheckerboardMaterialProperty.prototype, { /** - * The GLSL source code for the vertex shader. - * - * @memberof PolylineMaterialAppearance.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 {String} + * @type {Boolean} * @readonly */ - vertexShaderSource : { + isConstant : { get : function() { - var vs = this._vertexShaderSource; - if (this.material.shaderSource.search(/varying\s+float\s+v_angle;/g) !== -1) { - vs = '#define POLYLINE_DASH\n' + vs; - } - return vs; + return Property.isConstant(this._evenColor) && // + Property.isConstant(this._oddColor) && // + Property.isConstant(this._repeat); } }, - /** - * The GLSL source code for the fragment shader. - * - * @memberof PolylineMaterialAppearance.prototype + * 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 {String} + * @type {Event} * @readonly */ - fragmentShaderSource : { + definitionChanged : { get : function() { - return this._fragmentShaderSource; + return this._definitionChanged; } }, - /** - * 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 + * Gets or sets the Property specifying the first {@link Color}. + * @memberof CheckerboardMaterialProperty.prototype + * @type {Property} + * @default Color.WHITE */ - renderState : { - get : function() { - return this._renderState; - } - }, - + evenColor : createPropertyDescriptor('evenColor'), /** - * 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 + * Gets or sets the Property specifying the second {@link Color}. + * @memberof CheckerboardMaterialProperty.prototype + * @type {Property} + * @default Color.BLACK */ - closed : { - get : function() { - return this._closed; - } - }, - + oddColor : createPropertyDescriptor('oddColor'), /** - * 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} + * 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) */ - vertexFormat : { - get : function() { - return this._vertexFormat; - } - } + repeat : createPropertyDescriptor('repeat') }); /** - * 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 + * Gets the {@link Material} type at the provided time. * - * @returns {String} The full GLSL fragment shader source. + * @param {JulianDate} time The time for which to retrieve the type. + * @returns {String} The type of material. */ - PolylineMaterialAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; + CheckerboardMaterialProperty.prototype.getType = function(time) { + return 'Checkerboard'; + }; /** - * Determines if the geometry is translucent based on {@link PolylineMaterialAppearance#translucent} and {@link Material#isTranslucent}. - * - * @function + * Gets the value of the property at the provided time. * - * @returns {Boolean} true if the appearance is translucent. + * @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. */ - PolylineMaterialAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent; + 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; + }; /** - * 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 + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. * - * @returns {Object} The render state. + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. */ - PolylineMaterialAppearance.prototype.getRenderState = Appearance.prototype.getRenderState; + 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 PolylineMaterialAppearance; + return CheckerboardMaterialProperty; }); -/*global define*/ -define('Scene/PrimitivePipeline',[ - '../Core/BoundingSphere', - '../Core/ComponentDatatype', +define('DataSources/PositionProperty',[ + '../Core/Cartesian3', '../Core/defined', + '../Core/defineProperties', '../Core/DeveloperError', - '../Core/Ellipsoid', - '../Core/FeatureDetection', - '../Core/GeographicProjection', - '../Core/Geometry', - '../Core/GeometryAttribute', - '../Core/GeometryAttributes', - '../Core/GeometryPipeline', - '../Core/IndexDatatype', - '../Core/Matrix4', - '../Core/WebMercatorProjection' + '../Core/Matrix3', + '../Core/ReferenceFrame', + '../Core/Transforms' ], function( - BoundingSphere, - ComponentDatatype, + Cartesian3, defined, + defineProperties, DeveloperError, - Ellipsoid, - FeatureDetection, - GeographicProjection, - Geometry, - GeometryAttribute, - GeometryAttributes, - GeometryPipeline, - IndexDatatype, - Matrix4, - WebMercatorProjection) { + Matrix3, + ReferenceFrame, + Transforms) { '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 {}; + /** + * 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(); } - 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); + 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 } - } + }); - function addGeometryBatchId(geometry, batchId) { - var attributes = geometry.attributes; - var positionAttr = attributes.position; - var numberOfComponents = positionAttr.values.length / positionAttr.componentsPerAttribute; + /** + * 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; - attributes.batchId = new GeometryAttribute({ - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 1, - values : new Float32Array(numberOfComponents) - }); + /** + * 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; - var values = attributes.batchId.values; - for (var j = 0; j < numberOfComponents; ++j) { - values[j] = batchId; - } - } + /** + * 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; - function addBatchIds(instances) { - var length = instances.length; + var scratchMatrix3 = new Matrix3(); - 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); - } + /** + * @private + */ + PositionProperty.convertToReferenceFrame = function(time, value, inputFrame, outputFrame, result) { + if (!defined(value)) { + return value; } - } - - 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; - } + if (!defined(result)){ + result = new Cartesian3(); } - - // 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]); - } - } + if (inputFrame === outputFrame) { + return Cartesian3.clone(value, result); } - 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); - } - } + 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); + } + }; - // 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]; + return PositionProperty; +}); - // 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'; +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'; - // Compute 2D positions - GeometryPipeline.projectTo2D(geometry, name, name3D, name2D, projection); - if (defined(geometry.boundingSphere) && name === 'position') { - geometry.boundingSphereCV = BoundingSphere.fromVertices(geometry.attributes.position2D.values); - } + /** + * 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); + } - 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'); - } - } + 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; } - - // oct encode and pack normals, compress texture coordinates - if (compressVertices) { - GeometryPipeline.compressVertices(geometry); + }, + /** + * 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; } - } - - 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)); + }, + /** + * Gets the reference frame in which the position is defined. + * @memberof ConstantPositionProperty.prototype + * @type {ReferenceFrame} + * @default ReferenceFrame.FIXED; + */ + referenceFrame : { + get : function() { + return this._referenceFrame; } - - geometries = splitGeometries; } + }); - return geometries; - } - - function createPickOffsets(instances, geometryName, geometries, pickOffsets) { - var offset; - var indexCount; - var geometryIndex; + /** + * 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); + }; - 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; + /** + * 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); } - - 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; + if (defined(referenceFrame) && this._referenceFrame !== referenceFrame) { + definitionChanged = true; + this._referenceFrame = referenceFrame; } - } - - function createInstancePickOffsets(instances, geometries) { - var pickOffsets = []; - createPickOffsets(instances, 'geometry', geometries, pickOffsets); - createPickOffsets(instances, 'westHemisphereGeometry', geometries, pickOffsets); - createPickOffsets(instances, 'eastHemisphereGeometry', geometries, pickOffsets); - return pickOffsets; - } + if (definitionChanged) { + this._definitionChanged.raiseEvent(this); + } + }; /** - * @private + * 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. */ - var PrimitivePipeline = {}; + ConstantPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { + + return PositionProperty.convertToReferenceFrame(time, this._value, this._referenceFrame, referenceFrame, result); + }; /** - * @private + * 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. */ - 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 - }; + ConstantPositionProperty.prototype.equals = function(other) { + return this === other || + (other instanceof ConstantPositionProperty && + Cartesian3.equals(this._value, other._value) && + this._referenceFrame === other._referenceFrame); }; - function transferGeometry(geometry, transferableObjects) { - var attributes = geometry.attributes; - for ( var name in attributes) { - if (attributes.hasOwnProperty(name)) { - var attribute = attributes[name]; + return ConstantPositionProperty; +}); - if (defined(attribute) && defined(attribute.values)) { - transferableObjects.push(attribute.values.buffer); - } - } - } +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'; - if (defined(geometry.indices)) { - transferableObjects.push(geometry.indices.buffer); - } - } + /** + * 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(); - function transferGeometries(geometries, transferableObjects) { - var length = geometries.length; - for (var i = 0; i < length; ++i) { - transferGeometry(geometries[i], transferableObjects); - } + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } - // 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; + 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; } + }, - var attributes = geometry.attributes; + /** + * Gets or sets the boolean Property specifying the visibility of the corridor. + * @memberof CorridorGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - count += 6 + 2 * BoundingSphere.packedLength + (defined(geometry.indices) ? geometry.indices.length : 0); + /** + * Gets or sets the Property specifying the material used to fill the corridor. + * @memberof CorridorGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - for ( var property in attributes) { - if (attributes.hasOwnProperty(property) && defined(attributes[property])) { - var attribute = attributes[property]; - count += 5 + attribute.values.length; - } - } - } + /** + * 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'), - return count; - } + /** + * Gets or sets the numeric Property specifying the altitude of the corridor. + * @memberof CorridorGraphics.prototype + * @type {Property} + * @default 0.0 + */ + height : createPropertyDescriptor('height'), - /** - * @private - */ - PrimitivePipeline.packCreateGeometryResults = function(items, transferableObjects) { - var packedData = new Float64Array(countCreateGeometryResults(items)); - var stringTable = []; - var stringHash = {}; + /** + * 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'), - var length = items.length; - var count = 0; - packedData[count++] = length; - for (var i = 0; i < length; i++) { - var geometry = items[i]; + /** + * 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'), - var validGeometry = defined(geometry); - packedData[count++] = validGeometry ? 1.0 : 0.0; + /** + * Gets or sets the numeric Property specifying the width of the corridor. + * @memberof CorridorGraphics.prototype + * @type {Property} + */ + width : createPropertyDescriptor('width'), - if (!validGeometry) { - continue; - } + /** + * 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'), - packedData[count++] = geometry.primitiveType; - packedData[count++] = geometry.geometryType; + /** + * Gets or sets the Property specifying whether the corridor is outlined. + * @memberof CorridorGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), - var validBoundingSphere = defined(geometry.boundingSphere) ? 1.0 : 0.0; - packedData[count++] = validBoundingSphere; - if (validBoundingSphere) { - BoundingSphere.pack(geometry.boundingSphere, packedData, count); - } + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof CorridorGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), - count += BoundingSphere.packedLength; + /** + * Gets or sets the numeric Property specifying the width of the outline. + * @memberof CorridorGraphics.prototype + * @type {Property} + * @default 1.0 + */ + outlineWidth : createPropertyDescriptor('outlineWidth'), - var validBoundingSphereCV = defined(geometry.boundingSphereCV) ? 1.0 : 0.0; - packedData[count++] = validBoundingSphereCV; - if (validBoundingSphereCV) { - BoundingSphere.pack(geometry.boundingSphereCV, packedData, count); - } + /** + * Gets or sets the {@link CornerType} Property specifying how corners are styled. + * @memberof CorridorGraphics.prototype + * @type {Property} + * @default CornerType.ROUNDED + */ + cornerType : createPropertyDescriptor('cornerType'), - count += BoundingSphere.packedLength; + /** + * 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'), - 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); - } - } - } + /** + * 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') + }); - 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; - } + /** + * 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; + }; - var indicesLength = defined(geometry.indices) ? geometry.indices.length : 0; - packedData[count++] = indicesLength; + /** + * 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) { + + 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); + }; - if (indicesLength > 0) { - packedData.set(geometry.indices, count); - count += indicesLength; - } - } + return CorridorGraphics; +}); - transferableObjects.push(packedData.buffer); +define('DataSources/createRawPropertyDescriptor',[ + './createPropertyDescriptor' + ], function( + createPropertyDescriptor) { + 'use strict'; - return { - stringTable : stringTable, - packedData : packedData - }; - }; + function createRawProperty(value) { + return value; + } /** * @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; - } + function createRawPropertyDescriptor(name, configurable) { + return createPropertyDescriptor(name, configurable, createRawProperty); + } - var primitiveType = packedGeometry[packedGeometryIndex++]; - var geometryType = packedGeometry[packedGeometryIndex++]; + return createRawPropertyDescriptor; +}); - var boundingSphere; - var boundingSphereCV; +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'; - var validBoundingSphere = packedGeometry[packedGeometryIndex++] === 1.0; - if (validBoundingSphere) { - boundingSphere = BoundingSphere.unpack(packedGeometry, packedGeometryIndex); - } + /** + * 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(); - packedGeometryIndex += BoundingSphere.packedLength; + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - var validBoundingSphereCV = packedGeometry[packedGeometryIndex++] === 1.0; - if (validBoundingSphereCV) { - boundingSphereCV = BoundingSphere.unpack(packedGeometry, packedGeometryIndex); + 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; } + }, - packedGeometryIndex += BoundingSphere.packedLength; + /** + * Gets or sets the numeric Property specifying the length of the cylinder. + * @memberof CylinderGraphics.prototype + * @type {Property} + */ + length : createPropertyDescriptor('length'), - 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; + /** + * Gets or sets the numeric Property specifying the radius of the top of the cylinder. + * @memberof CylinderGraphics.prototype + * @type {Property} + */ + topRadius : createPropertyDescriptor('topRadius'), - length = packedGeometry[packedGeometryIndex++]; - values = ComponentDatatype.createTypedArray(componentDatatype, length); - for (var valuesIndex = 0; valuesIndex < length; valuesIndex++) { - values[valuesIndex] = packedGeometry[packedGeometryIndex++]; - } + /** + * Gets or sets the numeric Property specifying the radius of the bottom of the cylinder. + * @memberof CylinderGraphics.prototype + * @type {Property} + */ + bottomRadius : createPropertyDescriptor('bottomRadius'), - attributes[name] = new GeometryAttribute({ - componentDatatype : componentDatatype, - componentsPerAttribute : componentsPerAttribute, - normalize : normalize, - values : values - }); - } + /** + * 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'), - var indices; - length = packedGeometry[packedGeometryIndex++]; + /** + * Gets or sets the Property specifying the number of edges around the perimeter of the cylinder. + * @memberof CylinderGraphics.prototype + * @type {Property} + * @default 128 + */ + slices : createPropertyDescriptor('slices'), - if (length > 0) { - var numberOfVertices = values.length / componentsPerAttribute; - indices = IndexDatatype.createTypedArray(numberOfVertices, length); - for (i = 0; i < length; i++) { - indices[i] = packedGeometry[packedGeometryIndex++]; - } - } + /** + * Gets or sets the boolean Property specifying the visibility of the cylinder. + * @memberof CylinderGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - result[resultIndex++] = new Geometry({ - primitiveType : primitiveType, - geometryType : geometryType, - boundingSphere : boundingSphere, - boundingSphereCV : boundingSphereCV, - indices : indices, - attributes : attributes - }); - } + /** + * Gets or sets the Property specifying the material used to fill the cylinder. + * @memberof CylinderGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - return result; - }; + /** + * 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'), - 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]; + /** + * Gets or sets the boolean Property specifying whether the cylinder is outlined. + * @memberof CylinderGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), - Matrix4.pack(instance.modelMatrix, packedData, count); - count += Matrix4.packedLength; - } - transferableObjects.push(packedData.buffer); + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof CylinderGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), - return packedData; - } + /** + * Gets or sets the numeric Property specifying the width of the outline. + * @memberof CylinderGraphics.prototype + * @type {Property} + * @default 1.0 + */ + outlineWidth : createPropertyDescriptor('outlineWidth'), - function unpackInstancesForCombine(data) { - var packedInstances = data; - var result = new Array(packedInstances[0]); - var count = 0; + /** + * 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'), - var i = 1; - while (i < packedInstances.length) { - var modelMatrix = Matrix4.unpack(packedInstances, i); - i += Matrix4.packedLength; + /** + * 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') + }); - result[count++] = { - modelMatrix : modelMatrix - }; + /** + * 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; - } + }; /** - * @private + * 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. */ - PrimitivePipeline.packCombineGeometryParameters = function(parameters, transferableObjects) { - var createGeometryResults = parameters.createGeometryResults; - var length = createGeometryResults.length; + CylinderGraphics.prototype.merge = function(source) { + + 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); + }; - for (var i = 0; i < length; i++) { - transferableObjects.push(createGeometryResults[i].packedData.buffer); - } + return CylinderGraphics; +}); - 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 - }; - }; +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'; /** - * @private + * 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} */ - PrimitivePipeline.unpackCombineGeometryParameters = function(packedParameters) { - var instances = unpackInstancesForCombine(packedParameters.packedInstances); - var createGeometryResults = packedParameters.createGeometryResults; - var length = createGeometryResults.length; - var instanceIndex = 0; + 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(); - 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; - //acevedo check for undefined instance - if (instance) - { - instance.geometry = geometry; - } - ++instanceIndex; - } - } + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - var ellipsoid = Ellipsoid.clone(packedParameters.ellipsoid); - var projection = packedParameters.isGeographic ? new GeographicProjection(ellipsoid) : new WebMercatorProjection(ellipsoid); + 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; + } + }, - 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 - }; - }; + /** + * Gets or sets the numeric Property specifying the semi-major axis. + * @memberof EllipseGraphics.prototype + * @type {Property} + */ + semiMajorAxis : createPropertyDescriptor('semiMajorAxis'), - function packBoundingSpheres(boundingSpheres) { - var length = boundingSpheres.length; - var bufferLength = 1 + (BoundingSphere.packedLength + 1) * length; - var buffer = new Float32Array(bufferLength); + /** + * Gets or sets the numeric Property specifying the semi-minor axis. + * @memberof EllipseGraphics.prototype + * @type {Property} + */ + semiMinorAxis : createPropertyDescriptor('semiMinorAxis'), - var bufferIndex = 0; - buffer[bufferIndex++] = length; + /** + * 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'), - 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; - } + /** + * Gets or sets the boolean Property specifying the visibility of the ellipse. + * @memberof EllipseGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - return buffer; - } + /** + * Gets or sets the Property specifying the material used to fill the ellipse. + * @memberof EllipseGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - function unpackBoundingSpheres(buffer) { - var result = new Array(buffer[0]); - var count = 0; + /** + * Gets or sets the numeric Property specifying the altitude of the ellipse. + * @memberof EllipseGraphics.prototype + * @type {Property} + * @default 0.0 + */ + height : createPropertyDescriptor('height'), - var i = 1; - while (i < buffer.length) { - if (buffer[i++] === 1.0) { - result[count] = BoundingSphere.unpack(buffer, i); - } - ++count; - i += BoundingSphere.packedLength; - } + /** + * 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'), - return result; - } + /** + * 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'), - /** - * @private - */ - PrimitivePipeline.packCombineGeometryResults = function(results, transferableObjects) { - if (defined(results.geometries)) { - transferGeometries(results.geometries, transferableObjects); - } + /** + * 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'), - var packedBoundingSpheres = packBoundingSpheres(results.boundingSpheres); - var packedBoundingSpheresCV = packBoundingSpheres(results.boundingSpheresCV); - transferableObjects.push(packedBoundingSpheres.buffer, packedBoundingSpheresCV.buffer); + /** + * 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'), - return { - geometries : results.geometries, - attributeLocations : results.attributeLocations, - modelMatrix : results.modelMatrix, - pickOffsets : results.pickOffsets, - boundingSpheres : packedBoundingSpheres, - boundingSpheresCV : packedBoundingSpheresCV - }; - }; + /** + * 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') + }); /** - * @private + * 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. */ - 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) - }; + 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; }; - return PrimitivePipeline; -}); - -/*global define*/ -define('Scene/PrimitiveState',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; - /** - * @private + * 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. */ - var PrimitiveState = { - READY : 0, - CREATING : 1, - CREATED : 2, - COMBINING : 3, - COMBINED : 4, - COMPLETE : 5, - FAILED : 6 + EllipseGraphics.prototype.merge = function(source) { + + 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 freezeObject(PrimitiveState); + return EllipseGraphics; }); -/*global define*/ -define('Scene/SceneMode',[ - '../Core/freezeObject' +define('DataSources/EllipsoidGraphics',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + './createMaterialPropertyDescriptor', + './createPropertyDescriptor' ], function( - freezeObject) { + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + createMaterialPropertyDescriptor, + createPropertyDescriptor) { 'use strict'; /** - * Indicates if the scene is viewed in 3D, 2D, or 2.5D Columbus view. + * Describe an ellipsoid or sphere. The center position and orientation are determined by the containing {@link Entity}. * - * @exports SceneMode + * @alias EllipsoidGraphics + * @constructor * - * @see Scene#mode + * @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} */ - var SceneMode = { + 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, { /** - * Morphing between mode, e.g., 3D to 2D. + * Gets the event that is raised whenever a property or sub-property is changed or modified. + * @memberof EllipsoidGraphics.prototype * - * @type {Number} - * @constant + * @type {Event} + * @readonly */ - MORPHING : 0, + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, /** - * 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 + * Gets or sets the boolean Property specifying the visibility of the ellipsoid. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + * @default true */ - COLUMBUS_VIEW : 1, + show : createPropertyDescriptor('show'), /** - * 2D mode. The map is viewed top-down with an orthographic projection. - * - * @type {Number} - * @constant + * Gets or sets the {@link Cartesian3} {@link Property} specifying the radii of the ellipsoid. + * @memberof EllipsoidGraphics.prototype + * @type {Property} */ - SCENE2D : 2, + radii : createPropertyDescriptor('radii'), /** - * 3D mode. A traditional 3D perspective view of the globe. - * - * @type {Number} - * @constant + * Gets or sets the Property specifying the material used to fill the ellipsoid. + * @memberof EllipsoidGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE */ - SCENE3D : 3 - }; + material : createMaterialPropertyDescriptor('material'), - /** - * 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; - }; + /** + * 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'), - return freezeObject(SceneMode); -}); + /** + * Gets or sets the Property specifying whether the ellipsoid is outlined. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), -/*global define*/ -define('Scene/ShadowMode',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), - /** - * 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 + * Gets or sets the numeric Property specifying the width of the outline. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + * @default 1.0 */ - DISABLED : 0, + outlineWidth : createPropertyDescriptor('outlineWidth'), /** - * The object casts and receives shadows. - * - * @type {Number} - * @constant + * Gets or sets the Property specifying the number of stacks. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + * @default 64 */ - ENABLED : 1, + stackPartitions : createPropertyDescriptor('stackPartitions'), /** - * The object casts shadows only. - * - * @type {Number} - * @constant + * Gets or sets the Property specifying the number of radial slices. + * @memberof EllipsoidGraphics.prototype + * @type {Property} + * @default 64 */ - CAST_ONLY : 2, + slicePartitions : createPropertyDescriptor('slicePartitions'), /** - * The object receives shadows only. - * - * @type {Number} - * @constant + * 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 */ - RECEIVE_ONLY : 3, + subdivisions : createPropertyDescriptor('subdivisions'), /** - * @private + * 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 */ - NUMBER_OF_SHADOW_MODES : 4 - }; + shadows : createPropertyDescriptor('shadows'), - /** - * @private - */ - ShadowMode.castShadows = function(shadowMode) { - return (shadowMode === ShadowMode.ENABLED) || (shadowMode === ShadowMode.CAST_ONLY); - }; + /** + * 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') + }); /** - * @private + * 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. */ - ShadowMode.receiveShadows = function(shadowMode) { - return (shadowMode === ShadowMode.ENABLED) || (shadowMode === ShadowMode.RECEIVE_ONLY); + 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; }; /** - * @private + * 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. */ - 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; - } + EllipsoidGraphics.prototype.merge = function(source) { + + 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 freezeObject(ShadowMode); + return EllipsoidGraphics; }); -/*global define*/ -define('Scene/Primitive',[ - '../Core/BoundingSphere', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/Cartographic', - '../Core/clone', - '../Core/Color', - '../Core/combine', - '../Core/ComponentDatatype', +define('DataSources/LabelGraphics',[ '../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', - './DepthFunction', - './Material', - './PolylineMaterialAppearance', - './PrimitivePipeline', - './PrimitiveState', - './SceneMode', - './ShadowMode' + '../Core/Event', + './createPropertyDescriptor' ], 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, - DepthFunction, - Material, - PolylineMaterialAppearance, - PrimitivePipeline, - PrimitiveState, - SceneMode, - ShadowMode) { + Event, + createPropertyDescriptor) { '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. - *

    + * Describes a two dimensional label located at the position of the containing {@link Entity}. *

    - * {@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. + *

    + *
    + * Example labels + *
    *

    * - * @alias Primitive + * @alias LabelGraphics * @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() - * })); + * @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.scaleByDistance] A {@link NearFarScalar} Property used to set scale 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. * - * @see GeometryInstance - * @see Appearance + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} */ - function Primitive(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + 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._scaleByDistance = undefined; + this._scaleByDistanceSubscription = undefined; + this._distanceDisplayCondition = undefined; + this._distanceDisplayConditionSubscription = undefined; + this._disableDepthTestDistance = undefined; + this._disableDepthTestDistanceSubscription = undefined; + this._definitionChanged = new Event(); + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } + + defineProperties(LabelGraphics.prototype, { /** - * 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 + * Gets the event that is raised whenever a property or sub-property is changed or modified. + * @memberof LabelGraphics.prototype * - * @default undefined + * @type {Event} + * @readonly */ - this.geometryInstances = options.geometryInstances; + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, /** - * 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 + * Gets or sets the string Property specifying the text of the label. + * Explicit newlines '\n' are supported. + * @memberof LabelGraphics.prototype + * @type {Property} */ - this.appearance = options.appearance; - this._appearance = undefined; - this._material = undefined; + text : createPropertyDescriptor('text'), /** - * The {@link Appearance} used to shade this primitive when it fails the depth test. Each geometry - * instance is shaded with the same appearance. Some appearances, like - * {@link PerInstanceColorAppearance} allow giving each instance unique - * properties. - * + * 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. *

    - * When using an appearance that requires a color attribute, like PerInstanceColorAppearance, - * add a depthFailColor per-instance attribute instead. + * 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. *

    - * Requires the EXT_frag_depth WebGL extension to render properly. If the extension is not supported, - * there may be artifacts. + *

    + * + * + * + *
    + * l.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);

    + *
    *

    - * @type Appearance - * - * @default undefined + * @memberof LabelGraphics.prototype + * @type {Property} + * @default Cartesian3.ZERO */ - this.depthFailAppearance = options.depthFailAppearance; - this._depthFailAppearance = undefined; - this._depthFailMaterial = undefined; + eyeOffset : createPropertyDescriptor('eyeOffset'), /** - * 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}. - * + * 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. *

    - * This property is only supported in 3D mode. + *

    + * + * + * + *
    default
    l.pixeloffset = new Cartesian2(25, 75);
    + * The label's origin is indicated by the yellow point. + *
    *

    - * - * @type Matrix4 - * - * @default Matrix4.IDENTITY - * - * @example - * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0); - * p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin); + * @memberof LabelGraphics.prototype + * @type {Property} + * @default Cartesian2.ZERO */ - this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); - this._modelMatrix = new Matrix4(); + pixelOffset : createPropertyDescriptor('pixelOffset'), /** - * Determines if the primitive will be shown. This affects all geometry - * instances in the primitive. - * - * @type Boolean - * - * @default true + * 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 */ - 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); + scale : createPropertyDescriptor('scale'), /** - * 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 + * Gets or sets the boolean Property specifying the visibility of the label. + * @memberof LabelGraphics.prototype + * @type {Property} */ - this.cull = defaultValue(options.cull, true); + show : createPropertyDescriptor('show'), /** - * 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} - * + * Gets or sets the boolean Property specifying the visibility of the background behind the label. + * @memberof LabelGraphics.prototype + * @type {Property} * @default false */ - this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + showBackground : createPropertyDescriptor('showBackground'), /** - * @private + * 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) */ - this.rtcCenter = options.rtcCenter; + backgroundColor : createPropertyDescriptor('backgroundColor'), - /** - * Determines whether this primitive casts or receives shadows from each light source. - * - * @type {ShadowMode} - * - * @default ShadowMode.DISABLED + * 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) */ - this.shadows = defaultValue(options.shadows, ShadowMode.DISABLED); + backgroundPadding : createPropertyDescriptor('backgroundPadding'), - this._translucent = undefined; + /** + * 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'), - this._state = PrimitiveState.READY; - this._geometries = []; - this._error = undefined; - this._numberOfInstances = 0; + /** + * 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'), - this._boundingSpheres = []; - this._boundingSphereWC = []; - this._boundingSphereCV = []; - this._boundingSphere2D = []; - this._boundingSphereMorph = []; - this._perInstanceAttributeCache = []; - this._instanceIds = []; - this._lastPerInstanceAttributeIndex = 0; + /** + * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera. + * A label'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 label's scale remains clamped to the nearest bound. If undefined, + * scaleByDistance will be disabled. + * @memberof LabelGraphics.prototype + * @type {Property} + */ + scaleByDistance : createPropertyDescriptor('scaleByDistance'), - this._va = []; - this._attributeLocations = undefined; - this._primitiveType = undefined; + /** + * 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'), - this._frontFaceRS = undefined; - this._backFaceRS = undefined; - this._sp = undefined; + /** + * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. + * @memberof LabelGraphics.prototype + * @type {Property} + */ + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + }); - this._depthFailAppearance = undefined; - this._spDepthFail = undefined; - this._frontFaceDepthFailRS = undefined; - this._backFaceDepthFailRS = undefined; + /** + * 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.scaleByDistance = this.scaleByDistance; + result.distanceDisplayCondition = this.distanceDisplayCondition; + result.disableDepthTestDistance = this.disableDepthTestDistance; + return result; + }; - this._pickRS = undefined; - this._pickSP = undefined; - this._pickIds = []; + /** + * 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) { + + 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.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); + this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); + this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + }; - this._colorCommands = []; - this._pickCommands = []; + return LabelGraphics; +}); - this._readOnlyInstanceAttributes = options._readOnlyInstanceAttributes; +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'; - this._createBoundingVolumeFunction = options._createBoundingVolumeFunction; - this._createRenderStatesFunction = options._createRenderStatesFunction; - this._createShaderProgramFunction = options._createShaderProgramFunction; - this._createCommandsFunction = options._createCommandsFunction; - this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction; + var defaultNodeTransformation = new TranslationRotationScale(); - this._createPickOffsets = options._createPickOffsets; - this._pickOffsets = undefined; + /** + * 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._createGeometryResults = undefined; - this._ready = false; - this._readyPromise = when.defer(); + this._definitionChanged = new Event(); + this._translation = undefined; + this._translationSubscription = undefined; + this._rotation = undefined; + this._rotationSubscription = undefined; + this._scale = undefined; + this._scaleSubscription = undefined; - this._batchTable = undefined; - this._batchTableAttributeIndices = undefined; - this._instanceBoundingSpheres = undefined; - this._instanceBoundingSpheresCV = undefined; - this._batchTableBoundingSpheresUpdated = false; - this._batchTableBoundingSphereAttributeIndices = undefined; - } + this.translation = options.translation; + this.rotation = options.rotation; + this.scale = options.scale; + }; - defineProperties(Primitive.prototype, { + defineProperties(NodeTransformationProperty.prototype, { /** - * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. - * - * @memberof Primitive.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 - * - * @default true */ - vertexCacheOptimize : { + isConstant : { get : function() { - return this._vertexCacheOptimize; + return Property.isConstant(this._translation) && + Property.isConstant(this._rotation) && + Property.isConstant(this._scale); } }, /** - * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. - * - * @memberof Primitive.prototype + * 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 {Boolean} + * @type {Event} * @readonly - * - * @default false */ - interleave : { + definitionChanged : { get : function() { - return this._interleave; + return this._definitionChanged; } }, /** - * When true, the primitive does not keep a reference to the input geometryInstances to save memory. - * - * @memberof Primitive.prototype - * - * @type {Boolean} - * @readonly - * - * @default true + * 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 */ - releaseGeometryInstances : { - get : function() { - return this._releaseGeometryInstances; - } - }, + translation : createPropertyDescriptor('translation'), /** - * 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 + * 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 */ - allowPicking : { - get : function() { - return this._allowPicking; - } - }, + rotation : createPropertyDescriptor('rotation'), /** - * Determines if the geometry instances will be created and batched on a web worker. - * - * @memberof Primitive.prototype - * - * @type {Boolean} - * @readonly - * - * @default true + * 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) */ - asynchronous : { - get : function() { - return this._asynchronous; - } - }, + 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; +}); + +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, { /** - * When true, geometry vertices are compressed, which will save memory. - * - * @memberof Primitive.prototype - * - * @type {Boolean} - * @readonly - * - * @default true + * Gets the names of all properties registered on this instance. + * @memberof PropertyBag.prototype + * @type {Array} */ - compressVertices : { + propertyNames : { get : function() { - return this._compressVertices; + return this._propertyNames; } }, - /** - * 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 + * 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 */ - ready : { + isConstant : { get : function() { - return this._ready; + 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 a promise that resolves when the primitive is ready to render. - * @memberof Primitive.prototype - * @type {Promise.} + * 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 */ - readyPromise : { + definitionChanged : { get : function() { - return this._readyPromise.promise; + return this._definitionChanged; } } }); - 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); - } - } - } + /** + * 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; + }; - return attributesInAllInstances; + function createConstantProperty(value) { + return new ConstantProperty(value); } - var scratchGetAttributeCartesian2 = new Cartesian2(); - var scratchGetAttributeCartesian3 = new Cartesian3(); - var scratchGetAttributeCartesian4 = new Cartesian4(); + /** + * Adds a property to this object. + * + * @param {String} propertyName The name of the property to add. + * @param {*} [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; - 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); - } - } + + propertyNames.push(propertyName); + Object.defineProperty(this, propertyName, createPropertyDescriptor(propertyName, true, defaultValue(createPropertyCallback, createConstantProperty))); - function createBatchTable(primitive, context) { - var geometryInstances = primitive.geometryInstances; - var instances = (isArray(geometryInstances)) ? geometryInstances : [geometryInstances]; - var numberOfInstances = instances.length; - if (numberOfInstances === 0) { - return; + if (defined(value)) { + this[propertyName] = value; } - 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; + this._definitionChanged.raiseEvent(this); + }; - var i; - var name; - var attribute; + /** + * 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); - for (i = 0; i < length; ++i) { - name = names[i]; - attribute = instanceAttributes[name]; + + this._propertyNames.splice(index, 1); + delete this[propertyName]; - attributeIndices[name] = i; - attributes.push({ - functionName : 'czm_batchTable_' + name, - componentDatatype : attribute.componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - normalize : attribute.normalize - }); - } + this._definitionChanged.raiseEvent(this); + }; - 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; + /** + * 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(result)) { + result = {}; } - if (allowPicking) { - attributes.push({ - functionName : 'czm_batchTable_pickColor', - componentDatatype : ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 4, - normalize : true - }); + 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; + }; - var attributesLength = attributes.length; - var batchTable = new BatchTable(context, attributes, numberOfInstances); + /** + * 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) { + + 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]; - for (i = 0; i < numberOfInstances; ++i) { - var instance = instances[i]; - instanceAttributes = instance.attributes; + var targetProperty = this[name]; + var sourceProperty = source[name]; - 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); + //Custom properties that are registered on the source must also be added to this. + if (targetProperty === undefined && propertyNames.indexOf(name) === -1) { + this.addProperty(name, undefined, createPropertyCallback); } - if (allowPicking) { - var pickObject = { - primitive : defaultValue(instance.pickPrimitive, primitive) - }; - - if (defined(instance.id)) { - pickObject.id = instance.id; + if (sourceProperty !== undefined) { + if (targetProperty !== undefined) { + if (defined(targetProperty) && defined(targetProperty.merge)) { + targetProperty.merge(sourceProperty); + } + } else if (defined(sourceProperty) && defined(sourceProperty.merge) && defined(sourceProperty.clone)) { + this[name] = sourceProperty.clone(); + } else { + this[name] = sourceProperty; } - - 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 propertiesEqual(a, b) { + var aPropertyNames = a._propertyNames; + var bPropertyNames = b._propertyNames; - function cloneAttribute(attribute) { - var clonedValues; - if (isArray(attribute.values)) { - clonedValues = attribute.values.slice(0); - } else { - clonedValues = new attribute.values.constructor(attribute.values); + var len = aPropertyNames.length; + if (len !== bPropertyNames.length) { + return false; } - 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]); + for (var aIndex = 0; aIndex < len; ++aIndex) { + var name = aPropertyNames[aIndex]; + var bIndex = bPropertyNames.indexOf(name); + if (bIndex === -1) { + return false; } - } - - var indices; - if (defined(geometry.indices)) { - var sourceValues = geometry.indices; - if (isArray(sourceValues)) { - indices = sourceValues.slice(0); - } else { - indices = new sourceValues.constructor(sourceValues); + if (!Property.equals(a[name], b[name])) { + return false; } } - - 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 - }; + return true; } - 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, isDepthFail) { - // some appearances have a color attribute for per vertex color. - // only remove if color is a per instance attribute. - if (!defined(primitive._batchTableAttributeIndices.color) && !defined(primitive._batchTableAttributeIndices.depthFailColor)) { - 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, ''); - if (!isDepthFail) { - modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2'); - } else { - modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_depthFailColor(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; + /** + * 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)); }; - 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 containsBitangent = vertexShaderSource.search(/attribute\s+vec3\s+bitangent;/g) !== -1; - - var numComponents = containsSt && containsNormal ? 2.0 : 1.0; - numComponents += containsTangent || containsBitangent ? 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 && containsBitangent) { - globalDecl += - 'vec3 normal;\n' + - 'vec3 tangent;\n' + - 'vec3 bitangent;\n'; - decode += ' czm_octDecode(' + attributeName + '.' + (containsSt ? 'yz' : 'xy') + ', normal, tangent, bitangent);\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 (containsBitangent) { - globalDecl += 'vec3 bitangent;\n'; - decode += ' bitangent = 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+bitangent;/g, ''); - modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); - var compressedMain = - 'void main() \n' + - '{ \n' + - decode + - ' czm_non_compressed_main(); \n' + - '}'; + return PropertyBag; +}); - return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); - } +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 depthClampVS(vertexShaderSource) { - var modifiedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_depth_clamp_main'); - // The varying should be surround by #ifdef GL_EXT_frag_depth as an optimization. - // It is not to workaround an issue with Edge: - // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12120362/ - modifiedVS += - 'varying float v_WindowZ;\n' + - 'void main() {\n' + - ' czm_non_depth_clamp_main();\n' + - ' vec4 position = gl_Position;\n' + - ' v_WindowZ = (0.5 * (position.z / position.w) + 0.5) * position.w;\n' + - ' position.z = min(position.z, position.w);\n' + - ' gl_Position = position;' + - '}\n'; - return modifiedVS; + function createNodeTransformationProperty(value) { + return new NodeTransformationProperty(value); } - function depthClampFS(fragmentShaderSource) { - var modifiedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_non_depth_clamp_main'); - modifiedFS += - 'varying float v_WindowZ;\n' + - 'void main() {\n' + - ' czm_non_depth_clamp_main();\n' + - '#ifdef GL_EXT_frag_depth\n' + - ' gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n' + - '#endif\n' + - '}\n'; - modifiedFS = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n' + - modifiedFS; - return modifiedFS; + function createNodeTransformationPropertyBag(value) { + return new PropertyBag(value, createNodeTransformationProperty); } - 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; - - } + /** + * 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(); - function getUniformFunction(uniforms, name) { - return function() { - return uniforms[name]; - }; + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } - 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; + 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; + } + }, - if (primitive._state === PrimitiveState.READY) { - instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; - var length = primitive._numberOfInstances = instances.length; + /** + * Gets or sets the boolean Property specifying the visibility of the model. + * @memberof ModelGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - var promises = []; - var subTasks = []; - for (i = 0; i < length; ++i) { - geometry = instances[i].geometry; - instanceIds.push(instances[i].id); + /** + * 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'), - - subTasks.push({ - moduleName : geometry._workerName, - geometry : geometry - }); - } + /** + * 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'), - if (!defined(createGeometryTaskProcessors)) { - createGeometryTaskProcessors = new Array(numberOfCreationWorkers); - for (i = 0; i < numberOfCreationWorkers; i++) { - createGeometryTaskProcessors[i] = new TaskProcessor('createGeometry', Number.POSITIVE_INFINITY); - } - } + /** + * 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'), - var subTask; - subTasks = subdivideArray(subTasks, numberOfCreationWorkers); + /** + * 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'), - 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); - } - } + /** + * 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'), - var subTaskTransferableObjects; + /** + * Gets or sets the string Property specifying the URI of the glTF asset. + * @memberof ModelGraphics.prototype + * @type {Property} + */ + uri : createPropertyDescriptor('uri'), - if (packedLength > 0) { - var array = new Float64Array(packedLength); - subTaskTransferableObjects = [array.buffer]; + /** + * Gets or sets the boolean Property specifying if glTF animations should be run. + * @memberof ModelGraphics.prototype + * @type {Property} + * @default true + */ + runAnimations : createPropertyDescriptor('runAnimations'), - 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; - } - } - } + /** + * 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), - promises.push(createGeometryTaskProcessors[i].scheduleTask({ - subTasks : subTasks[i] - }, subTaskTransferableObjects)); - } + /** + * Gets or sets the Property specifying the {@link HeightReference}. + * @memberof ModelGraphics.prototype + * @type {Property} + * @default HeightReference.NONE + */ + heightReference : createPropertyDescriptor('heightReference'), - primitive._state = PrimitiveState.CREATING; + /** + * 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'), - 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]; + /** + * Gets or sets the Property specifying the {@link Color} of the silhouette. + * @memberof ModelGraphics.prototype + * @type {Property} + * @default Color.RED + */ + silhouetteColor: createPropertyDescriptor('silhouetteColor'), - var scene3DOnly = frameState.scene3DOnly; - var projection = frameState.mapProjection; + /** + * 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'), - 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); + /** + * 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'), - primitive._createGeometryResults = undefined; - primitive._state = PrimitiveState.COMBINING; + /** + * 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'), - 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; + /** + * 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') + }); - 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); - }); + /** + * 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); } - } - - 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; + 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; - var instance; - var i; + return result; + }; - var geometryIndex = 0; - for (i = 0; i < length; i++) { - instance = instances[i]; - var geometry = instance.geometry; + /** + * 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) { + + 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 createdGeometry; - if (defined(geometry.attributes) && defined(geometry.primitiveType)) { - createdGeometry = cloneGeometry(geometry); + var sourceNodeTransformations = source.nodeTransformations; + if (defined(sourceNodeTransformations)) { + var targetNodeTransformations = this.nodeTransformations; + if (defined(targetNodeTransformations)) { + targetNodeTransformations.merge(sourceNodeTransformations); } else { - createdGeometry = geometry.constructor.createGeometry(geometry); + this.nodeTransformations = new PropertyBag(sourceNodeTransformations, createNodeTransformationProperty); } - - clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry); - instanceIds.push(instance.id); } + }; - clonedInstances.length = geometryIndex; - - var scene3DOnly = frameState.scene3DOnly; - var projection = frameState.mapProjection; + return ModelGraphics; +}); - 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 - }); +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'; - 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; + /** + * 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(); - if (defined(primitive._geometries) && primitive._geometries.length > 0) { - primitive._state = PrimitiveState.COMBINED; - } else { - setReady(primitive, frameState, PrimitiveState.FAILED, undefined); - } + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } - var scratchBoundingSphereCenterEncoded = new EncodedCartesian3(); - var scratchBoundingSphereCartographic = new Cartographic(); - var scratchBoundingSphereCenter2D = new Cartesian3(); - var scratchBoundingSphere = new BoundingSphere(); + 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; + } + }, - function updateBatchTableBoundingSpheres(primitive, frameState) { - var hasDistanceDisplayCondition = defined(primitive._batchTableAttributeIndices.distanceDisplayCondition); - if (!hasDistanceDisplayCondition || primitive._batchTableBoundingSpheresUpdated) { - return; - } + /** + * Gets or sets the boolean Property specifying the visibility of the path. + * @memberof PathGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - var indices = primitive._batchTableBoundingSphereAttributeIndices; - var center3DHighIndex = indices.center3DHigh; - var center3DLowIndex = indices.center3DLow; - var center2DHighIndex = indices.center2DHigh; - var center2DLowIndex = indices.center2DLow; - var radiusIndex = indices.radius; + /** + * Gets or sets the Property specifying the material used to draw the path. + * @memberof PathGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - var projection = frameState.mapProjection; - var ellipsoid = projection.ellipsoid; + /** + * Gets or sets the numeric Property specifying the width in pixels. + * @memberof PathGraphics.prototype + * @type {Property} + * @default 1.0 + */ + width : createPropertyDescriptor('width'), - var batchTable = primitive._batchTable; - var boundingSpheres = primitive._instanceBoundingSpheres; - var length = boundingSpheres.length; - //acevedo - //check for undefined boundingSphere. Return if any - for (var i = 0; i < length; ++i) { - var boundingSphere = boundingSpheres[i]; - if (!defined(boundingSphere)) { - primitive._batchTableBoundingSpheresUpdated = false; - continue; - } + /** + * 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'), - var modelMatrix = primitive.modelMatrix; - if (defined(modelMatrix)) { - boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, scratchBoundingSphere); - } + /** + * 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'), - var center = boundingSphere.center; - var radius = boundingSphere.radius; + /** + * Gets or sets the Property specifying the number of seconds behind the object to show. + * @memberof PathGraphics.prototype + * @type {Property} + */ + trailTime : createPropertyDescriptor('trailTime'), - var encodedCenter = EncodedCartesian3.fromCartesian(center, scratchBoundingSphereCenterEncoded); - batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high); - batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low); + /** + * 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') + }); - 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); + /** + * 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; + }; - primitive._batchTableBoundingSpheresUpdated = true; - } - - function createVertexArray(primitive, frameState) { - var attributeLocations = primitive._attributeLocations; - var geometries = primitive._geometries; - var scene3DOnly = frameState.scene3DOnly; - var context = frameState.context; + /** + * 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) { + + 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); + }; - var va = []; - var length = geometries.length; - for (var i = 0; i < length; ++i) { - var geometry = geometries[i]; + return PathGraphics; +}); - va.push(VertexArray.fromGeometry({ - context : context, - geometry : geometry, - attributeLocations : attributeLocations, - bufferUsage : BufferUsage.STATIC_DRAW, - interleave : primitive._interleave - })); +define('DataSources/PointGraphics',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + './createPropertyDescriptor' + ], function( + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + createPropertyDescriptor) { + 'use strict'; - if (defined(primitive._createBoundingVolumeFunction)) { - primitive._createBoundingVolumeFunction(frameState, geometry); - } else { - primitive._boundingSpheres.push(BoundingSphere.clone(geometry.boundingSphere)); - primitive._boundingSphereWC.push(new BoundingSphere()); + /** + * 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._disableDepthTestDistance = undefined; + this._disableDepthTestDistanceSubscription = undefined; + this._definitionChanged = new Event(); - 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; + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - primitive._boundingSphereCV.push(BoundingSphere.clone(geometry.boundingSphereCV)); - primitive._boundingSphere2D.push(new BoundingSphere()); - primitive._boundingSphereMorph.push(new BoundingSphere()); - } + 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; } - } + }, - primitive._va = va; - primitive._primitiveType = geometries[0].primitiveType; + /** + * Gets or sets the Property specifying the {@link Color} of the point. + * @memberof PointGraphics.prototype + * @type {Property} + * @default Color.WHITE + */ + color : createPropertyDescriptor('color'), - if (primitive.releaseGeometryInstances) { - primitive.geometryInstances = undefined; - } + /** + * Gets or sets the numeric Property specifying the size in pixels. + * @memberof PointGraphics.prototype + * @type {Property} + * @default 1 + */ + pixelSize : createPropertyDescriptor('pixelSize'), - primitive._geometries = undefined; - setReady(primitive, frameState, PrimitiveState.COMPLETE, undefined); - } + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof PointGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), - function createRenderStates(primitive, context, appearance, twoPasses) { - var renderState = appearance.getRenderState(); - var rs; + /** + * Gets or sets the numeric Property specifying the the outline width in pixels. + * @memberof PointGraphics.prototype + * @type {Property} + * @default 0 + */ + outlineWidth : createPropertyDescriptor('outlineWidth'), - if (twoPasses) { - rs = clone(renderState, false); - rs.cull = { - enabled : true, - face : CullFace.BACK - }; - primitive._frontFaceRS = RenderState.fromCache(rs); + /** + * Gets or sets the boolean Property specifying the visibility of the point. + * @memberof PointGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - rs.cull.face = CullFace.FRONT; - primitive._backFaceRS = RenderState.fromCache(rs); - } else { - primitive._frontFaceRS = RenderState.fromCache(renderState); - primitive._backFaceRS = primitive._frontFaceRS; - } + /** + * 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'), - rs = clone(renderState, false); - if (defined(primitive._depthFailAppearance)) { - rs.depthTest.enabled = false; - } + /** + * 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'), - if (primitive.allowPicking) { - if (twoPasses) { - rs.cull = { - enabled : false - }; - primitive._pickRS = RenderState.fromCache(rs); - } else { - primitive._pickRS = RenderState.fromCache(rs); - } - } else { - rs.colorMask = { - red : false, - green : false, - blue : false, - alpha : false - }; + /** + * Gets or sets the Property specifying the {@link HeightReference}. + * @memberof PointGraphics.prototype + * @type {Property} + * @default HeightReference.NONE + */ + heightReference : createPropertyDescriptor('heightReference'), - if (twoPasses) { - rs.cull = { - enabled : false - }; - primitive._pickRS = RenderState.fromCache(rs); - } else { - primitive._pickRS = RenderState.fromCache(rs); - } - } + /** + * 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'), - if (defined(primitive._depthFailAppearance)) { - renderState = primitive._depthFailAppearance.getRenderState(); - rs = clone(renderState, false); - rs.depthTest.func = DepthFunction.GREATER; - if (twoPasses) { - rs.cull = { - enabled : true, - face : CullFace.BACK - }; - primitive._frontFaceDepthFailRS = RenderState.fromCache(rs); + /** + * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. + * @memberof PointGraphics.prototype + * @type {Property} + */ + disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + }); - rs.cull.face = CullFace.FRONT; - primitive._backFaceDepthFailRS = RenderState.fromCache(rs); - } else { - primitive._frontFaceDepthFailRS = RenderState.fromCache(rs); - primitive._backFaceDepthFailRS = primitive._frontFaceRS; - } + /** + * 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; + result.disableDepthTestDistance = this.disableDepthTestDistance; + return result; + }; - function createShaderProgram(primitive, frameState, appearance) { - var context = frameState.context; + /** + * 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) { + + 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); + this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + }; - var attributeLocations = primitive._attributeLocations; + return PointGraphics; +}); - var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource); - vs = Primitive._appendShowToShader(primitive, vs); - vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly); - vs = Primitive._updateColorAttribute(primitive, vs, false); - vs = modifyForEncodedNormals(primitive, vs); - vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); - var fs = appearance.getFragmentShaderSource(); +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'; - // Create pick program - if (primitive.allowPicking) { - var vsPick = ShaderSource.createPickVertexShaderSource(vs); - vsPick = Primitive._updatePickColorAttribute(vsPick); + /** + * 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; - 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); + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - primitive._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : primitive._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - validateShaderMatching(primitive._sp, attributeLocations); + 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; + } + }, - if (defined(primitive._depthFailAppearance)) { - vs = primitive._batchTable.getVertexShaderCallback()(primitive._depthFailAppearance.vertexShaderSource); - vs = Primitive._appendShowToShader(primitive, vs); - vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly); - vs = Primitive._updateColorAttribute(primitive, vs, true); - vs = modifyForEncodedNormals(primitive, vs); - vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); - vs = depthClampVS(vs); + /** + * Gets or sets the boolean Property specifying the visibility of the polygon. + * @memberof PolygonGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - fs = depthClampFS(primitive._depthFailAppearance.getFragmentShaderSource()); + /** + * Gets or sets the Property specifying the material used to fill the polygon. + * @memberof PolygonGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - primitive._spDepthFail = ShaderProgram.replaceCache({ - context : context, - shaderProgram : primitive._spDepthFail, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - validateShaderMatching(primitive._spDepthFail, attributeLocations); - } - } + /** + * Gets or sets the Property specifying the {@link PolygonHierarchy}. + * @memberof PolygonGraphics.prototype + * @type {Property} + */ + hierarchy : createPropertyDescriptor('hierarchy'), - function getUniforms(primitive, appearance, material, 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)) { - - appearanceUniformMap[name] = getUniformFunction(appearanceUniforms, name); - } - } - } - var uniforms = combine(appearanceUniformMap, materialUniformMap); - uniforms = primitive._batchTable.getUniformMapCallback()(uniforms); + /** + * Gets or sets the numeric Property specifying the constant altitude of the polygon. + * @memberof PolygonGraphics.prototype + * @type {Property} + * @default 0.0 + */ + height : createPropertyDescriptor('height'), - 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; - }; - } + /** + * 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'), - return uniforms; - } + /** + * 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'), - var modifiedModelViewScratch = new Matrix4(); - var rtcScratch = new Cartesian3(); + /** + * 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'), - function createCommands(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands, frameState) { - var uniforms = getUniforms(primitive, appearance, material, frameState); + /** + * 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'), - var depthFailUniforms; - if (defined(primitive._depthFailAppearance)) { - depthFailUniforms = getUniforms(primitive, primitive._depthFailAppearance, primitive._depthFailAppearance.material, frameState); - } + /** + * Gets or sets the Property specifying whether the polygon is outlined. + * @memberof PolygonGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), - var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; + /** + * 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'), - var multiplier = twoPasses ? 2 : 1; - multiplier *= defined(primitive._depthFailAppearance) ? 2 : 1; + /** + * 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'), - colorCommands.length = primitive._va.length * multiplier; - pickCommands.length = primitive._va.length; + /** + * 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'), - var length = colorCommands.length; - var m = 0; - var vaIndex = 0; - for (var i = 0; i < length; ++i) { - var colorCommand; + /** + * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polygon will be displayed. + * @memberof PolygonGraphics.prototype + * @type {Property} + */ + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') + }); - 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; + /** + * 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; + }; - ++i; - } + /** + * 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) { + + 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); + }; - 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; + return PolygonGraphics; +}); - if (defined(primitive._depthFailAppearance)) { - if (twoPasses) { - ++i; +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'; - colorCommand = colorCommands[i]; - if (!defined(colorCommand)) { - colorCommand = colorCommands[i] = new DrawCommand({ - owner : primitive, - primitiveType : primitive._primitiveType - }); - } - colorCommand.vertexArray = primitive._va[vaIndex]; - colorCommand.renderState = primitive._backFaceDepthFailRS; - colorCommand.shaderProgram = primitive._spDepthFail; - colorCommand.uniformMap = depthFailUniforms; - colorCommand.pass = pass; - } + /** + * 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._depthFailMaterial = undefined; + this._depthFailMaterialSubscription = 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(); - ++i; + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - colorCommand = colorCommands[i]; - if (!defined(colorCommand)) { - colorCommand = colorCommands[i] = new DrawCommand({ - owner : primitive, - primitiveType : primitive._primitiveType - }); - } - colorCommand.vertexArray = primitive._va[vaIndex]; - colorCommand.renderState = primitive._frontFaceDepthFailRS; - colorCommand.shaderProgram = primitive._spDepthFail; - colorCommand.uniformMap = depthFailUniforms; - colorCommand.pass = pass; + 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; } + }, - 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; + /** + * Gets or sets the boolean Property specifying the visibility of the polyline. + * @memberof PolylineGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - ++vaIndex; - } - } + /** + * Gets or sets the Property specifying the material used to draw the polyline. + * @memberof PolylineGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - 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; - } - } - } + /** + * Gets or sets the Property specifying the material used to draw the polyline when it fails the depth test. + *

    + * Requires the EXT_frag_depth WebGL extension to render properly. If the extension is not supported, + * there may be artifacts. + *

    + * @type {MaterialProperty} + * @default undefined + */ + depthFailMaterial : createMaterialPropertyDescriptor('depthFailMaterial'), - function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { - - updateBoundingVolumes(primitive, frameState); + /** + * 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'), - 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]); - } - } - } - } + /** + * Gets or sets the numeric Property specifying the width in pixels. + * @memberof PolylineGraphics.prototype + * @type {Property} + * @default 1.0 + */ + width : createPropertyDescriptor('width'), - 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; - } + /** + * 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'), - 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; + /** + * 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'), - var factor = twoPasses ? 2 : 1; - factor *= defined(primitive._depthFailAppearance) ? 2 : 1; + /** + * 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'), - for (var j = 0; j < colorLength; ++j) { - var sphereIndex = Math.floor(j / factor); - var colorCommand = colorCommands[j]; - colorCommand.modelMatrix = modelMatrix; - colorCommand.boundingVolume = boundingSpheres[sphereIndex]; - colorCommand.cull = cull; - colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; - colorCommand.castShadows = castShadows; - colorCommand.receiveShadows = receiveShadows; + /** + * 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') + }); - commandList.push(colorCommand); - } + /** + * 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.depthFailMaterial = this.depthFailMaterial; + 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; + }; - 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; + /** + * 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) { + + this.show = defaultValue(this.show, source.show); + this.material = defaultValue(this.material, source.material); + this.depthFailMaterial = defaultValue(this.depthFailMaterial, source.depthFailMaterial); + 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); + }; - commandList.push(pickCommand); - } - } - } + return PolylineGraphics; +}); + +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'; /** - * 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: - *

    + * 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. * - * @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. + * @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} */ - 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; - } + 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(); - - if (this._state === PrimitiveState.FAILED) { - return; - } + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + } - 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.'); + 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; } - this._batchTable.update(frameState); - } + }, - if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) { - if (this.asynchronous) { - loadAsynchronous(this, frameState); - } else { - loadSynchronous(this, frameState); - } - } + /** + * Gets or sets the boolean Property specifying the visibility of the volume. + * @memberof PolylineVolumeGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - if (this._state === PrimitiveState.COMBINED) { - updateBatchTableBoundingSpheres(this, frameState); - createVertexArray(this, frameState); - } + /** + * Gets or sets the Property specifying the material used to fill the volume. + * @memberof PolylineVolumeGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - if (!this.show || this._state !== PrimitiveState.COMPLETE) { - return; - } + /** + * 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'), - // 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; + /** + * 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'), - if (this._appearance !== appearance) { - this._appearance = appearance; - this._material = material; - createRS = true; - createSP = true; - } else if (this._material !== material ) { - this._material = material; - createSP = true; - } + /** + * 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'), - var depthFailAppearance = this.depthFailAppearance; - var depthFailMaterial = defined(depthFailAppearance) ? depthFailAppearance.material : undefined; + /** + * 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'), - if (this._depthFailAppearance !== depthFailAppearance) { - this._depthFailAppearance = depthFailAppearance; - this._depthFailMaterial = depthFailMaterial; - createRS = true; - createSP = true; - } else if (this._depthFailMaterial !== depthFailMaterial) { - this._depthFailMaterial = depthFailMaterial; - createSP = true; - } + /** + * Gets or sets the Property specifying whether the volume is outlined. + * @memberof PolylineVolumeGraphics.prototype + * @type {Property} + * @default false + */ + outline : createPropertyDescriptor('outline'), - var translucent = this._appearance.isTranslucent(); - if (this._translucent !== translucent) { - this._translucent = translucent; - createRS = true; - } + /** + * Gets or sets the Property specifying the {@link Color} of the outline. + * @memberof PolylineVolumeGraphics.prototype + * @type {Property} + * @default Color.BLACK + */ + outlineColor : createPropertyDescriptor('outlineColor'), - if (defined(this._material)) { - this._material.update(context); - } + /** + * Gets or sets the numeric Property specifying the width of the outline. + * @memberof PolylineVolumeGraphics.prototype + * @type {Property} + * @default 1.0 + */ + outlineWidth : createPropertyDescriptor('outlineWidth'), - var twoPasses = appearance.closed && translucent; + /** + * Gets or sets the {@link CornerType} Property specifying the style of the corners. + * @memberof PolylineVolumeGraphics.prototype + * @type {Property} + * @default CornerType.ROUNDED + */ + cornerType : createPropertyDescriptor('cornerType'), - if (createRS) { - var rsFunc = defaultValue(this._createRenderStatesFunction, createRenderStates); - rsFunc(this, context, appearance, twoPasses); - } + /** + * 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'), - if (createSP) { - var spFunc = defaultValue(this._createShaderProgramFunction, createShaderProgram); - spFunc(this, frameState, appearance); - } + /** + * 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') + }); - if (createRS || createSP) { - var commandFunc = defaultValue(this._createCommandsFunction, createCommands); - commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState); + /** + * 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); } - - var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands); - updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses); + 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; }; - 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; - }; - } + /** + * 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) { + + 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); + }; - function createSetFunction(batchTable, instanceIndex, attributeIndex) { - return function(value) { - var attributeValue = getAttributeValue(value); - batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue); - }; - } + return PolylineVolumeGraphics; +}); - function createBoundingSphereProperties(primitive, properties, index) { - properties.boundingSphere = { - get : function() { - var boundingSphere = primitive._instanceBoundingSpheres[index]; - var modelMatrix = primitive.modelMatrix; - if (defined(modelMatrix) && defined(boundingSphere)) { - boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix); - } - return boundingSphere; - } - }; - properties.boundingSphereCV = { - get : function() { - return primitive._instanceBoundingSpheresCV[index]; - } - }; - } +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'; /** - * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. + * 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. * - * @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. + * @alias RectangleGraphics + * @constructor * - * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes. + * @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. * - * @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); + * @see Entity + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo} */ - Primitive.prototype.getGeometryInstanceAttributes = function(id) { - - 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; + 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; } - } + }, - if (index === -1) { - return undefined; - } + /** + * Gets or sets the boolean Property specifying the visibility of the rectangle. + * @memberof RectangleGraphics.prototype + * @type {Property} + * @default true + */ + show : createPropertyDescriptor('show'), - var attributes = this._perInstanceAttributeCache[index]; - if (defined(attributes)) { - return attributes; - } + /** + * Gets or sets the Property specifying the {@link Rectangle}. + * @memberof RectangleGraphics.prototype + * @type {Property} + */ + coordinates : createPropertyDescriptor('coordinates'), - var batchTable = this._batchTable; - var perInstanceAttributeIndices = this._batchTableAttributeIndices; - attributes = {}; - var properties = {}; + /** + * Gets or sets the Property specifying the material used to fill the rectangle. + * @memberof RectangleGraphics.prototype + * @type {MaterialProperty} + * @default Color.WHITE + */ + material : createMaterialPropertyDescriptor('material'), - for (var name in perInstanceAttributeIndices) { - if (perInstanceAttributeIndices.hasOwnProperty(name)) { - var attributeIndex = perInstanceAttributeIndices[name]; - properties[name] = { - get : createGetFunction(batchTable, index, attributeIndex) - }; + /** + * Gets or sets the numeric Property specifying the altitude of the rectangle. + * @memberof RectangleGraphics.prototype + * @type {Property} + * @default 0.0 + */ + height : createPropertyDescriptor('height'), - 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; - } - } - } + /** + * 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'), - if (createSetter) { - properties[name].set = createSetFunction(batchTable, index, attributeIndex); - } - } - } + /** + * 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'), - createBoundingSphereProperties(this, properties, index); - defineProperties(attributes, properties); + /** + * 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'), - this._lastPerInstanceAttributeIndex = index; - this._perInstanceAttributeCache[index] = attributes; - return attributes; - }; + /** + * 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'), - /** - * 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. + /** + * 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. * - * @see Primitive#destroy + * @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. */ - Primitive.prototype.isDestroyed = function() { - return false; + 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; }; /** - * 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(); + * Assigns each unassigned property on this object to the value + * of the same property on the provided source object. * - * @see Primitive#isDestroyed + * @param {RectangleGraphics} source The object to be merged into this object. */ - 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); + RectangleGraphics.prototype.merge = function(source) { + + 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); }; - 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; + return RectangleGraphics; }); -/*global define*/ -define('DataSources/ColorMaterialProperty',[ - '../Core/Color', +define('DataSources/WallGraphics',[ + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/DeveloperError', '../Core/Event', - './createPropertyDescriptor', - './Property' + './createMaterialPropertyDescriptor', + './createPropertyDescriptor' ], function( - Color, + defaultValue, defined, defineProperties, + DeveloperError, Event, - createPropertyDescriptor, - Property) { + createMaterialPropertyDescriptor, + createPropertyDescriptor) { '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. + * 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 ColorMaterialProperty + * @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 ColorMaterialProperty(color) { + 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._color = undefined; - this._colorSubscription = undefined; - this.color = color; + + this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } - 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); - } - }, + defineProperties(WallGraphics.prototype, { /** - * 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 + * Gets the event that is raised whenever a property or sub-property is changed or modified. + * @memberof WallGraphics.prototype * * @type {Event} * @readonly @@ -93081,1401 +101994,1759 @@ define('DataSources/ColorMaterialProperty',[ return this._definitionChanged; } }, + /** - * Gets or sets the {@link Color} {@link Property}. - * @memberof ColorMaterialProperty.prototype + * 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 */ - color : createPropertyDescriptor('color') - }); + material : createMaterialPropertyDescriptor('material'), - /** - * 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 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') + }); /** - * Gets the value of the property at the provided time. + * Duplicates this instance. * - * @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. + * @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. */ - ColorMaterialProperty.prototype.getValue = function(time, result) { + WallGraphics.prototype.clone = function(result) { if (!defined(result)) { - result = {}; + return new WallGraphics(this); } - result.color = Property.getValueOrClonedDefault(this._color, time, Color.WHITE, result.color); + 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; }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * Assigns each unassigned property on this object to the value + * of the same property on the provided source object. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @param {WallGraphics} source The object to be merged into this object. */ - ColorMaterialProperty.prototype.equals = function(other) { - return this === other || // - (other instanceof ColorMaterialProperty && // - Property.equals(this._color, other._color)); + WallGraphics.prototype.merge = function(source) { + + 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 ColorMaterialProperty; + return WallGraphics; }); -/*global define*/ -define('DataSources/dynamicGeometryGetBoundingSphere',[ - '../Core/BoundingSphere', +define('DataSources/Entity',[ + '../Core/Cartesian3', + '../Core/Check', + '../Core/createGuid', + '../Core/defaultValue', '../Core/defined', + '../Core/defineProperties', '../Core/DeveloperError', - './BoundingSphereState' + '../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', + './PropertyBag', + './RectangleGraphics', + './WallGraphics' ], function( - BoundingSphere, + Cartesian3, + Check, + createGuid, + defaultValue, defined, + defineProperties, DeveloperError, - BoundingSphereState) { + Event, + Matrix3, + Matrix4, + Quaternion, + Transforms, + BillboardGraphics, + BoxGraphics, + ConstantPositionProperty, + CorridorGraphics, + createPropertyDescriptor, + createRawPropertyDescriptor, + CylinderGraphics, + EllipseGraphics, + EllipsoidGraphics, + LabelGraphics, + ModelGraphics, + PathGraphics, + PointGraphics, + PolygonGraphics, + PolylineGraphics, + PolylineVolumeGraphics, + Property, + PropertyBag, + RectangleGraphics, + WallGraphics) { 'use strict'; - /** - * @private - */ - function dynamicGeometryGetBoundingSphere(entity, primitive, outlinePrimitive, result) { - - var attributes; + function createConstantPositionProperty(value) { + return new ConstantPositionProperty(value); + } - //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)) { - BoundingSphere.clone(attributes.boundingSphere, result); - return BoundingSphereState.DONE; - } - } + function createPositionPropertyDescriptor(name) { + return createPropertyDescriptor(name, undefined, createConstantPositionProperty); + } - if (defined(outlinePrimitive) && outlinePrimitive.show && outlinePrimitive.ready) { - attributes = outlinePrimitive.getGeometryInstanceAttributes(entity); - if (defined(attributes) && defined(attributes.boundingSphere)) { - BoundingSphere.clone(attributes.boundingSphere, result); - return BoundingSphereState.DONE; + function createPropertyTypeDescriptor(name, Type) { + return createPropertyDescriptor(name, undefined, function(value) { + if (value instanceof Type) { + return value; } - } - - if ((defined(primitive) && !primitive.ready) || (defined(outlinePrimitive) && !outlinePrimitive.ready)) { - return BoundingSphereState.PENDING; - } - - return BoundingSphereState.FAILED; + return new Type(value); + }); } - 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 + * 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 * - * @see ColorMaterialProperty - * @see CompositeMaterialProperty - * @see GridMaterialProperty - * @see ImageMaterialProperty - * @see PolylineGlowMaterialProperty - * @see PolylineOutlineMaterialProperty - * @see StripeMaterialProperty + * @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 {PropertyBag} [options.properties] Arbitrary properties 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 MaterialProperty() { - DeveloperError.throwInstantiationError(); + 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', 'properties', '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._properties = undefined; + this._propertiesSubscription = 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); } - defineProperties(MaterialProperty.prototype, { + 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, { /** - * 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 + * 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} */ - isConstant : { - get : DeveloperError.throwInstantiationError + 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 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 + * Gets the event that is raised whenever a property or sub-property is changed or modified. + * @memberof Entity.prototype * * @type {Event} * @readonly */ definitionChanged : { - get : DeveloperError.throwInstantiationError - } + 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 (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 bag of arbitrary properties associated with this entity. + * @memberof Entity.prototype + * @type {PropertyBag} + */ + properties : createPropertyTypeDescriptor('properties', PropertyBag), + /** + * 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) }); /** - * Gets the {@link Material} type at the provided time. - * @function + * Given a time, returns true if this object should have data during that time. * - * @param {JulianDate} time The time for which to retrieve the type. - * @returns {String} The type of material. + * @param {JulianDate} time The time to check availability for. + * @returns {Boolean} true if the object should have data during the provided time, false otherwise. */ - MaterialProperty.prototype.getType = DeveloperError.throwInstantiationError; + Entity.prototype.isAvailable = function(time) { + + var availability = this._availability; + return !defined(availability) || availability.contains(time); + }; /** - * Gets the value of the property at the provided time. - * @function + * 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 {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. + * @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. */ - MaterialProperty.prototype.getValue = DeveloperError.throwInstantiationError; + Entity.prototype.addProperty = function(propertyName) { + var propertyNames = this._propertyNames; + + + propertyNames.push(propertyName); + Object.defineProperty(this, propertyName, createRawPropertyDescriptor(propertyName, true)); + }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. - * @function + * Removed a property previously added with addProperty. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @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. */ - MaterialProperty.prototype.equals = DeveloperError.throwInstantiationError; + Entity.prototype.removeProperty = function(propertyName) { + var propertyNames = this._propertyNames; + var index = propertyNames.indexOf(propertyName); + + + this._propertyNames.splice(index, 1); + delete this[propertyName]; + }; /** - * @private + * 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. */ - MaterialProperty.getValue = function(time, materialProperty, material) { - var type; + Entity.prototype.merge = function(source) { + + //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); - if (defined(materialProperty)) { - type = materialProperty.getType(time); - if (defined(type)) { - if (!defined(material) || (material.type !== type)) { - material = Material.fromType(type); + 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; } - 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); + var matrix3Scratch = new Matrix3(); + var positionScratch = new Cartesian3(); + var orientationScratch = new Quaternion(); - return material; + /** + * Computes the model matrix for the entity's transform at specified time. Returns undefined if orientation or position + * are undefined. + * + * @param {JulianDate} time The time to retrieve model matrix for. + * @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. Result is undefined if position or orientation are undefined. + */ + Entity.prototype.computeModelMatrix = function(time, result) { + Check.typeOf.object('time', time); + 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 MaterialProperty; + return Entity; }); -/*global define*/ -define('DataSources/BoxGeometryUpdater',[ - '../Core/BoxGeometry', - '../Core/BoxOutlineGeometry', - '../Core/Color', - '../Core/ColorGeometryInstanceAttribute', - '../Core/defaultValue', +define('DataSources/EntityCollection',[ + '../Core/AssociativeArray', + '../Core/createGuid', '../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' + '../Core/JulianDate', + '../Core/RuntimeError', + '../Core/TimeInterval', + './Entity' ], function( - BoxGeometry, - BoxOutlineGeometry, - Color, - ColorGeometryInstanceAttribute, - defaultValue, + AssociativeArray, + createGuid, defined, defineProperties, - destroyObject, - DeveloperError, - DistanceDisplayCondition, - DistanceDisplayConditionGeometryInstanceAttribute, + DeveloperError, Event, - GeometryInstance, Iso8601, - ShowGeometryInstanceAttribute, - MaterialAppearance, - PerInstanceColorAppearance, - Primitive, - ShadowMode, - ColorMaterialProperty, - ConstantProperty, - dynamicGeometryGetBoundingSphere, - MaterialProperty, - Property) { + JulianDate, + RuntimeError, + TimeInterval, + Entity) { '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(); + var entityOptionsScratch = { + id : undefined + }; - function GeometryOptions(entity) { - this.id = entity; - this.vertexFormat = undefined; - this.dimensions = 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; + } + } } /** - * A {@link GeometryUpdater} for boxes. - * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}. - * @alias BoxGeometryUpdater + * An observable collection of {@link Entity} instances where each entity has a unique id. + * @alias EntityCollection * @constructor * - * @param {Entity} entity The entity containing the geometry to be visualized. - * @param {Scene} scene The scene where visualization is taking place. + * @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection. */ - function BoxGeometryUpdater(entity, scene) { - - 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); + 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; } - 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 - } - }); + /** + * 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++; + }; - 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; - } - }, + /** + * 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() { + + 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 constant with of the geometry outline, in pixels. - * This value is only valid if isDynamic is false. - * @memberof BoxGeometryUpdater.prototype - * - * @type {Number} + * 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} */ - outlineWidth : { + collectionChanged : { get : function() { - return this._outlineWidth; + return this._collectionChanged; } }, /** - * Gets the property specifying whether the geometry - * casts or receives shadows from each light source. - * @memberof BoxGeometryUpdater.prototype - * - * @type {Property} + * Gets a globally unique identifier for this collection. + * @memberof EntityCollection.prototype * @readonly + * @type {String} */ - shadowsProperty : { + id : { get : function() { - return this._shadowsProperty; + return this._id; } }, /** - * 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} + * Gets the array of Entity instances in the collection. + * This array should not be modified directly. + * @memberof EntityCollection.prototype * @readonly + * @type {Entity[]} */ - distanceDisplayConditionProperty : { + values : { get : function() { - return this._distanceDisplayConditionProperty; + return this._entities.values; } }, /** - * 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 - * + * 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} - * @readonly */ - isDynamic : { + show : { get : function() { - return this._dynamic; + return this._show; + }, + set : function(value) { + + 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 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} + * 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} */ - geometryChanged : { + owner : { get : function() { - return this._geometryChanged; + return this._owner; } } }); /** - * Checks if the geometry is outlined at the provided time. + * 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. * - * @param {JulianDate} time The time for which to retrieve visibility. - * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise. + * @returns {TimeInterval} The availability of entities in the collection. */ - BoxGeometryUpdater.prototype.isOutlineVisible = function(time) { - var entity = this._entity; - return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); - }; + 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; + } + } + } - /** - * 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); + 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 + }); }; /** - * 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. + * Add an entity to the collection. * - * @exception {DeveloperError} This instance does not represent a filled geometry. + * @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. */ - BoxGeometryUpdater.prototype.createFillGeometryInstance = function(time) { + EntityCollection.prototype.add = function(entity) { - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + if (!(entity instanceof Entity)) { + entity = new Entity(entity); + } - var attributes; + 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.'); + } - 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 - }; + entity.entityCollection = this; + entities.set(id, entity); + + if (!this._removedEntities.remove(id)) { + this._addedEntities.set(id, entity); } + entity.definitionChanged.addEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this); - return new GeometryInstance({ - id : entity, - geometry : BoxGeometry.fromDimensions(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), - attributes : attributes - }); + fireChangedEvent(this); + return entity; }; /** - * 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. + * Removes an entity from the collection. * - * @exception {DeveloperError} This instance does not represent an outlined geometry. + * @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. */ - BoxGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { - - 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) - } - }); + EntityCollection.prototype.remove = function(entity) { + if (!defined(entity)) { + return false; + } + return this.removeById(entity.id); }; /** - * Returns true if this object was destroyed; otherwise, false. + * Returns true if the provided entity is in this collection, false otherwise. * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * @param {Entity} entity The entity. + * @returns {Boolean} true if the provided entity is in this collection, false otherwise. */ - BoxGeometryUpdater.prototype.isDestroyed = function() { - return false; + EntityCollection.prototype.contains = function(entity) { + return this._entities.get(entity.id) === entity; }; /** - * Destroys and resources used by the object. Once an object is destroyed, it should not be used. + * Removes an entity with the provided id from the collection. * - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * @param {String} 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. */ - 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); + EntityCollection.prototype.removeById = function(id) { + if (!defined(id)) { + return false; } - if (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; + var entities = this._entities; + var entity = entities.get(id); + if (!this._entities.remove(id)) { + return false; } - 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; + 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); - 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); + return true; + }; - var outlineWidth = box.outlineWidth; + /** + * 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; - this._fillEnabled = fillEnabled; - this._outlineEnabled = outlineEnabled; + var addedEntities = this._addedEntities; + var removed = this._removedEntities; - if (!position.isConstant || // - !Property.isConstant(entity.orientation) || // - !dimensions.isConstant || // - !Property.isConstant(outlineWidth)) { - if (!this._dynamic) { - this._dynamic = true; - this._geometryChanged.raiseEvent(this); + 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); } - } 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); } + + entities.removeAll(); + addedEntities.removeAll(); + this._changedEntities.removeAll(); + fireChangedEvent(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. + * Gets an entity with the specified id. * - * @exception {DeveloperError} This instance does not represent dynamic geometry. + * @param {String} 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. */ - BoxGeometryUpdater.prototype.createDynamicUpdater = function(primitives) { + EntityCollection.prototype.getById = function(id) { - return new DynamicGeometryUpdater(primitives, this); + return this._entities.get(id); }; /** - * @private + * Gets an entity with the specified id or creates it and adds it to the collection if it does not exist. + * + * @param {String} id The id of the entity to retrieve or create. + * @returns {Entity} The new or existing object. */ - 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) { + EntityCollection.prototype.getOrCreateEntity = function(id) { - 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 entity = this._entities.get(id); + if (!defined(entity)) { + entityOptionsScratch.id = id; + entity = new Entity(entityOptionsScratch); + this.add(entity); } + return entity; + }; - var options = this._options; - var modelMatrix = entity._getModelMatrix(time); - var dimensions = Property.getValueOrUndefined(box.dimensions, time, options.dimensions); - if (!defined(modelMatrix) || !defined(dimensions)) { - return; + EntityCollection.prototype._onEntityDefinitionChanged = function(entity) { + var id = entity.id; + if (!this._addedEntities.contains(id)) { + this._changedEntities.set(id, entity); } + fireChangedEvent(this); + }; - 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); + return EntityCollection; +}); - if (Property.getValueOrDefault(box.fill, time, true)) { - var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); - this._material = material; +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 appearance = new MaterialAppearance({ - material : material, - translucent : material.isTranslucent(), - closed : true - }); - options.vertexFormat = appearance.vertexFormat; + var entityOptionsScratch = { + id : undefined + }; + var entityIdScratch = new Array(2); - 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 - })); + function clean(entity) { + var propertyNames = entity.propertyNames; + var propertyNamesLength = propertyNames.length; + for (var i = 0; i < propertyNamesLength; i++) { + entity[propertyNames[i]] = undefined; } + } - if (Property.getValueOrDefault(box.outline, time, false)) { - options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + function subscribeToEntity(that, eventHash, collectionId, entity) { + entityIdScratch[0] = collectionId; + entityIdScratch[1] = entity.id; + eventHash[JSON.stringify(entityIdScratch)] = entity.definitionChanged.addEventListener(CompositeEntityCollection.prototype._onDefinitionChanged, that); + } - 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; + function unsubscribeFromEntity(that, eventHash, collectionId, entity) { + entityIdScratch[0] = collectionId; + entityIdScratch[1] = entity.id; + var id = JSON.stringify(entityIdScratch); + eventHash[id](); + eventHash[id] = undefined; + } - 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 - })); + function recomposite(that) { + that._shouldRecomposite = true; + if (that._suspendCount !== 0) { + return; } - }; - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); - }; + var collections = that._collections; + var collectionsLength = collections.length; - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; - }; + var collectionsCopy = that._collectionsCopy; + var collectionsCopyLength = collectionsCopy.length; - DynamicGeometryUpdater.prototype.destroy = function() { - var primitives = this._primitives; - primitives.removeAndDestroy(this._primitive); - primitives.removeAndDestroy(this._outlinePrimitive); - destroyObject(this); - }; + 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; - return BoxGeometryUpdater; -}); + 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); + } + } -/*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'; + for (i = collectionsLength - 1; i >= 0; i--) { + collection = collections[i]; + collection.collectionChanged.addEventListener(CompositeEntityCollection.prototype._onCollectionChanged, that); - var defaultRepeat = new Cartesian2(1, 1); - var defaultTransparent = false; - var defaultColor = Color.WHITE; + //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(); + } /** - * A {@link MaterialProperty} that maps to image {@link Material} uniforms. - * @alias ImageMaterialProperty + * 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 {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) + * @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 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; + 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(ImageMaterialProperty.prototype, { + defineProperties(CompositeEntityCollection.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} + * 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} */ - isConstant : { + collectionChanged : { get : function() { - return Property.isConstant(this._image) && Property.isConstant(this._repeat); + return this._composite._collectionChanged; } }, /** - * 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} + * Gets a globally unique identifier for this collection. + * @memberof CompositeEntityCollection.prototype * @readonly + * @type {String} */ - definitionChanged : { + id : { get : function() { - return this._definitionChanged; + return this._id; } }, /** - * 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 + * Gets the array of Entity instances in the collection. + * This array should not be modified directly. + * @memberof CompositeEntityCollection.prototype + * @readonly + * @type {Entity[]} */ - color : createPropertyDescriptor('color'), + values : { + get : function() { + return this._composite.values; + } + }, /** - * Gets or sets the Boolean Property specifying whether the image has transparency - * @memberof ImageMaterialProperty.prototype - * @type {Property} - * @default 1.0 + * 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} */ - transparent : createPropertyDescriptor('transparent') + owner : { + get : function() { + return this._owner; + } + } }); /** - * Gets the {@link Material} type at the provided time. + * 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 (!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 {JulianDate} time The time for which to retrieve the type. - * @returns {String} The type of material. + * @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. */ - ImageMaterialProperty.prototype.getType = function(time) { - return 'Image'; + CompositeEntityCollection.prototype.indexOfCollection = function(collection) { + return this._collections.indexOf(collection); }; /** - * Gets the value of the property at the provided time. + * Gets a collection by index from the composite. * - * @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. + * @param {Number} index the index to retrieve. */ - 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; + CompositeEntityCollection.prototype.getCollection = function(index) { + + return this._collections[index]; }; /** - * 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. + * Gets the number of collections in this composite. */ - 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)); + CompositeEntityCollection.prototype.getCollectionsLength = function() { + return this._collections.length; }; - return ImageMaterialProperty; -}); + function getCollectionIndex(collections, collection) { + + var index = collections.indexOf(collection); -/*global define*/ -define('DataSources/createMaterialPropertyDescriptor',[ - '../Core/Color', - '../Core/DeveloperError', - './ColorMaterialProperty', - './createPropertyDescriptor', - './ImageMaterialProperty' - ], function( - Color, - DeveloperError, - ColorMaterialProperty, - createPropertyDescriptor, - ImageMaterialProperty) { - 'use strict'; + + return index; + } - function createMaterialProperty(value) { - if (value instanceof Color) { - return new ColorMaterialProperty(value); - } + 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 (typeof value === 'string' || value instanceof HTMLCanvasElement || value instanceof HTMLVideoElement) { - var result = new ImageMaterialProperty(); - result.image = value; - return result; + if (i === j) { + return; } - } + var temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; - /** - * @private - */ - function createMaterialPropertyDescriptor(name, configurable) { - return createPropertyDescriptor(name, configurable, createMaterialProperty); + recomposite(composite); } - 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 + * Raises a collection up one position in the composite. * - * @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. + * @param {EntityCollection} collection the collection to move. * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo} + * @exception {DeveloperError} collection is not in this composite. */ - 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') - }); + CompositeEntityCollection.prototype.raiseCollection = function(collection) { + var index = getCollectionIndex(this._collections, collection); + swapCollections(this, index, index + 1); + }; /** - * Duplicates this instance. + * Lowers a collection down one position in the composite. * - * @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. + * @param {EntityCollection} collection the collection to move. + * + * @exception {DeveloperError} collection is not in this composite. */ - 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; + CompositeEntityCollection.prototype.lowerCollection = function(collection) { + var index = getCollectionIndex(this._collections, collection); + swapCollections(this, index, index - 1); }; /** - * Assigns each unassigned property on this object to the value - * of the same property on the provided source object. + * Raises a collection to the top of the composite. * - * @param {BoxGraphics} source The object to be merged into this object. + * @param {EntityCollection} collection the collection to move. + * + * @exception {DeveloperError} collection is not in this composite. */ - BoxGraphics.prototype.merge = function(source) { - - 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; -}); + 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); -/*global define*/ -define('DataSources/CallbackProperty',[ - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event' - ], function( - defined, - defineProperties, - DeveloperError, - Event) { - 'use strict'; + recomposite(this); + }; /** - * A {@link Property} whose value is lazily evaluated by a callback function. + * Lowers a collection to the bottom of the composite. * - * @alias CallbackProperty - * @constructor + * @param {EntityCollection} collection the collection to move. * - * @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. + * @exception {DeveloperError} collection is not in this composite. */ - 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; - } + 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); + }; /** - * 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. + * 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}. */ - CallbackProperty.prototype.getValue = function(time, result) { - return this._callback(time, result); + CompositeEntityCollection.prototype.suspendEvents = function() { + this._suspendCount++; + this._composite.suspendEvents(); }; /** - * Sets the callback to be used. + * 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}. * - * @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. + * @exception {DeveloperError} resumeEvents can not be called before suspendEvents. */ - CallbackProperty.prototype.setCallback = function(callback, isConstant) { + CompositeEntityCollection.prototype.resumeEvents = function() { - var changed = this._callback !== callback || this._isConstant !== isConstant; - - this._callback = callback; - this._isConstant = isConstant; - - if (changed) { - this._definitionChanged.raiseEvent(this); + 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(); }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * 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. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @returns {TimeInterval} The availability of entities in the collection. */ - CallbackProperty.prototype.equals = function(other) { - return this === other || (other instanceof CallbackProperty && this._callback === other._callback && this._isConstant === other._isConstant); + CompositeEntityCollection.prototype.computeAvailability = function() { + return this._composite.computeAvailability(); }; /** - * A function that returns the value of the property. - * @callback CallbackProperty~Callback + * Gets an entity with the specified id. * - * @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. + * @param {String} 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); + }; - return CallbackProperty; + 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/CheckerboardMaterialProperty',[ - '../Core/Cartesian2', - '../Core/Color', - '../Core/defaultValue', +define('DataSources/CompositeProperty',[ '../Core/defined', '../Core/defineProperties', + '../Core/DeveloperError', '../Core/Event', - './createPropertyDescriptor', + '../Core/EventHelper', + '../Core/TimeIntervalCollection', './Property' ], function( - Cartesian2, - Color, - defaultValue, defined, defineProperties, + DeveloperError, Event, - createPropertyDescriptor, + EventHelper, + TimeIntervalCollection, Property) { 'use strict'; - var defaultEvenColor = Color.WHITE; - var defaultOddColor = Color.BLACK; - var defaultRepeat = new Cartesian2(2.0, 2.0); + 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 MaterialProperty} that maps to checkerboard {@link Material} uniforms. - * @alias CheckerboardMaterialProperty + * 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 * - * @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. + * + * @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 CheckerboardMaterialProperty(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - + function CompositeProperty() { + this._eventHelper = new EventHelper(); 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; + this._intervals = new TimeIntervalCollection(); + this._intervals.changedEvent.addEventListener(CompositeProperty.prototype._intervalsChanged, this); } - defineProperties(CheckerboardMaterialProperty.prototype, { + 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 CheckerboardMaterialProperty.prototype + * @memberof CompositeProperty.prototype * * @type {Boolean} * @readonly */ isConstant : { get : function() { - return Property.isConstant(this._evenColor) && // - Property.isConstant(this._oddColor) && // - Property.isConstant(this._repeat); + 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 CheckerboardMaterialProperty.prototype + * The definition is changed whenever setValue is called with data different + * than the current value. + * @memberof CompositeProperty.prototype * * @type {Event} * @readonly @@ -94486,38 +103757,18 @@ define('DataSources/CheckerboardMaterialProperty',[ } }, /** - * 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) + * Gets the interval collection. + * @memberof CompositeProperty.prototype + * + * @type {TimeIntervalCollection} */ - repeat : createPropertyDescriptor('repeat') + intervals : { + get : function() { + return this._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. - */ - CheckerboardMaterialProperty.prototype.getType = function(time) { - return 'Checkerboard'; - }; - /** * Gets the value of the property at the provided time. * @@ -94525,14 +103776,13 @@ define('DataSources/CheckerboardMaterialProperty',[ * @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 = {}; + CompositeProperty.prototype.getValue = function(time, result) { + + var innerProperty = this._intervals.findDataForIntervalContainingDate(time); + if (defined(innerProperty)) { + return innerProperty.getValue(time, 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; + return undefined; }; /** @@ -94542,206 +103792,200 @@ define('DataSources/CheckerboardMaterialProperty',[ * @param {Property} [other] The other property. * @returns {Boolean} true if left and right are equal, false otherwise. */ - CheckerboardMaterialProperty.prototype.equals = function(other) { + CompositeProperty.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)); + (other instanceof CompositeProperty && // + this._intervals.equals(other._intervals, Property.equals)); }; - return CheckerboardMaterialProperty; + /** + * @private + */ + CompositeProperty.prototype._intervalsChanged = function() { + subscribeAll(this, this._eventHelper, this._definitionChanged, this._intervals); + this._definitionChanged.raiseEvent(this); + }; + + return CompositeProperty; }); -/*global define*/ -define('DataSources/PositionProperty',[ - '../Core/Cartesian3', +define('DataSources/CompositeMaterialProperty',[ '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', - '../Core/Matrix3', - '../Core/ReferenceFrame', - '../Core/Transforms' + '../Core/Event', + './CompositeProperty', + './Property' ], function( - Cartesian3, defined, defineProperties, DeveloperError, - Matrix3, - ReferenceFrame, - Transforms) { + Event, + CompositeProperty, + Property) { '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. + * A {@link CompositeProperty} which is also a {@link MaterialProperty}. * - * @alias PositionProperty + * @alias CompositeMaterialProperty * @constructor - * - * @see CompositePositionProperty - * @see ConstantPositionProperty - * @see SampledPositionProperty - * @see TimeIntervalCollectionPositionProperty */ - function PositionProperty() { - DeveloperError.throwInstantiationError(); + function CompositeMaterialProperty() { + this._definitionChanged = new Event(); + this._composite = new CompositeProperty(); + this._composite.definitionChanged.addEventListener(CompositeMaterialProperty.prototype._raiseDefinitionChanged, this); } - defineProperties(PositionProperty.prototype, { + 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 PositionProperty.prototype + * @memberof CompositeMaterialProperty.prototype * * @type {Boolean} * @readonly */ isConstant : { - get : DeveloperError.throwInstantiationError + get : function() { + return this._composite.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 PositionProperty.prototype + * The definition is changed whenever setValue is called with data different + * than the current value. + * @memberof CompositeMaterialProperty.prototype * * @type {Event} * @readonly */ definitionChanged : { - get : DeveloperError.throwInstantiationError + get : function() { + return this._definitionChanged; + } }, /** - * Gets the reference frame that the position is defined in. - * @memberof PositionProperty.prototype - * @type {ReferenceFrame} + * Gets the interval collection. + * @memberof CompositeMaterialProperty.prototype + * + * @type {TimeIntervalCollection} */ - referenceFrame : { - get : DeveloperError.throwInstantiationError + intervals : { + get : function() { + return this._composite._intervals; + } } }); /** - * Gets the value of the property at the provided time in the fixed frame. - * @function + * Gets the {@link Material} type 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. + * @param {JulianDate} time The time for which to retrieve the type. + * @returns {String} The type of material. */ - PositionProperty.prototype.getValue = DeveloperError.throwInstantiationError; + CompositeMaterialProperty.prototype.getType = function(time) { + + 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 and in the provided reference frame. - * @function + * Gets the value of the property at the provided time. * * @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. + * @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. */ - PositionProperty.prototype.getValueInReferenceFrame = DeveloperError.throwInstantiationError; + CompositeMaterialProperty.prototype.getValue = function(time, result) { + + 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. - * @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(); + CompositeMaterialProperty.prototype.equals = function(other) { + return this === other || // + (other instanceof CompositeMaterialProperty && // + this._composite.equals(other._composite, Property.equals)); + }; /** * @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); - } + CompositeMaterialProperty.prototype._raiseDefinitionChanged = function() { + this._definitionChanged.raiseEvent(this); }; - return PositionProperty; + return CompositeMaterialProperty; }); -/*global define*/ -define('DataSources/ConstantPositionProperty',[ - '../Core/Cartesian3', +define('DataSources/CompositePositionProperty',[ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', '../Core/Event', '../Core/ReferenceFrame', - './PositionProperty' + './CompositeProperty', + './Property' ], function( - Cartesian3, defaultValue, defined, defineProperties, DeveloperError, Event, ReferenceFrame, - PositionProperty) { + CompositeProperty, + Property) { 'use strict'; /** - * A {@link PositionProperty} whose value does not change in respect to the - * {@link ReferenceFrame} in which is it defined. + * A {@link CompositeProperty} which is also a {@link PositionProperty}. * - * @alias ConstantPositionProperty + * @alias CompositePositionProperty * @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); + 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(ConstantPositionProperty.prototype, { + 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 ConstantPositionProperty.prototype + * @memberof CompositePositionProperty.prototype * * @type {Boolean} * @readonly */ isConstant : { get : function() { - return !defined(this._value) || this._referenceFrame === ReferenceFrame.FIXED; + return this._composite.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 ConstantPositionProperty.prototype + * The definition is changed whenever setValue is called with data different + * than the current value. + * @memberof CompositePositionProperty.prototype * * @type {Event} * @readonly @@ -94752,14 +103996,31 @@ define('DataSources/ConstantPositionProperty',[ } }, /** - * Gets the reference frame in which the position is defined. - * @memberof ConstantPositionProperty.prototype + * 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} - * @default ReferenceFrame.FIXED; */ referenceFrame : { get : function() { return this._referenceFrame; + }, + set : function(value) { + this._referenceFrame = value; } } }); @@ -94771,31 +104032,10 @@ define('DataSources/ConstantPositionProperty',[ * @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) { + CompositePositionProperty.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. * @@ -94804,9002 +104044,10014 @@ define('DataSources/ConstantPositionProperty',[ * @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) { + CompositePositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { - return PositionProperty.convertToReferenceFrame(time, this._value, this._referenceFrame, referenceFrame, result); + 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. +define('Shaders/ShadowVolumeFS',[],function() { + 'use strict'; + return "#ifdef GL_EXT_frag_depth\n\ +#extension GL_EXT_frag_depth : enable\n\ +#endif\n\ +\n\ +// emulated noperspective\n\ +varying float v_WindowZ;\n\ +varying vec4 v_color;\n\ +\n\ +void writeDepthClampedToFarPlane()\n\ +{\n\ +#ifdef GL_EXT_frag_depth\n\ + gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n\ +#endif\n\ +}\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. +define('Shaders/ShadowVolumeVS',[],function() { + 'use strict'; + return "attribute vec3 position3DHigh;\n\ +attribute vec3 position3DLow;\n\ +attribute vec4 color;\n\ +attribute float batchId;\n\ +\n\ +#ifdef EXTRUDED_GEOMETRY\n\ +attribute vec3 extrudeDirection;\n\ +\n\ +uniform float u_globeMinimumAltitude;\n\ +#endif\n\ +\n\ +// emulated noperspective\n\ +varying float v_WindowZ;\n\ +varying vec4 v_color;\n\ +\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\ +\n\ +void main()\n\ +{\n\ + v_color = color;\n\ +\n\ + vec4 position = czm_computePosition();\n\ +\n\ +#ifdef EXTRUDED_GEOMETRY\n\ + float delta = min(u_globeMinimumAltitude, czm_geometricToleranceOverMeter * length(position.xyz));\n\ + delta *= czm_sceneMode == czm_sceneMode3D ? 1.0 : 0.0;\n\ +\n\ + //extrudeDirection is zero for the top layer\n\ + position = position + vec4(extrudeDirection * delta, 0.0);\n\ +#endif\n\ +\n\ + gl_Position = depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position);\n\ +}\n\ +"; +}); +define('Scene/ClassificationType',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * Whether a classification affects terrain, 3D Tiles or both. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @exports ClassificationOption */ - ConstantPositionProperty.prototype.equals = function(other) { - return this === other || - (other instanceof ConstantPositionProperty && - Cartesian3.equals(this._value, other._value) && - this._referenceFrame === other._referenceFrame); + var ClassificationType = { + /** + * Only terrain will be classified. + * + * @type {Number} + * @constant + */ + TERRAIN : 0, + /** + * Only 3D Tiles will be classified. + * + * @type {Number} + * @constant + */ + CESIUM_3D_TILE : 1, + /** + * Both terrain and 3D Tiles will be classified. + * + * @type {Number} + * @constant + */ + BOTH : 2 }; - return ConstantPositionProperty; + return freezeObject(ClassificationType); }); -/*global define*/ -define('DataSources/CorridorGraphics',[ - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - './createMaterialPropertyDescriptor', - './createPropertyDescriptor' +define('Scene/StencilFunction',[ + '../Core/freezeObject', + '../Core/WebGLConstants' ], function( - defaultValue, - defined, - defineProperties, - DeveloperError, - Event, - createMaterialPropertyDescriptor, - createPropertyDescriptor) { + freezeObject, + WebGLConstants) { '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. + * Determines the function used to compare stencil values for the stencil test. * - * @see Entity - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo} + * @exports StencilFunction */ - 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, { + var StencilFunction = { /** - * Gets the event that is raised whenever a property or sub-property is changed or modified. - * @memberof CorridorGraphics.prototype - * @type {Event} - * @readonly + * The stencil test never passes. + * + * @type {Number} + * @constant */ - definitionChanged : { - get : function() { - return this._definitionChanged; - } - }, + NEVER : WebGLConstants.NEVER, /** - * Gets or sets the boolean Property specifying the visibility of the corridor. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default true + * The stencil test passes when the masked reference value is less than the masked stencil value. + * + * @type {Number} + * @constant */ - show : createPropertyDescriptor('show'), + LESS : WebGLConstants.LESS, /** - * Gets or sets the Property specifying the material used to fill the corridor. - * @memberof CorridorGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE + * The stencil test passes when the masked reference value is equal to the masked stencil value. + * + * @type {Number} + * @constant */ - material : createMaterialPropertyDescriptor('material'), + EQUAL : WebGLConstants.EQUAL, /** - * Gets or sets a Property specifying the array of {@link Cartesian3} positions that define the centerline of the corridor. - * @memberof CorridorGraphics.prototype - * @type {Property} + * The stencil test passes when the masked reference value is less than or equal to the masked stencil value. + * + * @type {Number} + * @constant */ - positions : createPropertyDescriptor('positions'), + LESS_OR_EQUAL : WebGLConstants.LEQUAL, /** - * Gets or sets the numeric Property specifying the altitude of the corridor. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default 0.0 + * The stencil test passes when the masked reference value is greater than the masked stencil value. + * + * @type {Number} + * @constant */ - height : createPropertyDescriptor('height'), + GREATER : WebGLConstants.GREATER, /** - * 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} + * The stencil test passes when the masked reference value is not equal to the masked stencil value. + * + * @type {Number} + * @constant */ - extrudedHeight : createPropertyDescriptor('extrudedHeight'), + NOT_EQUAL : WebGLConstants.NOTEQUAL, /** - * 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} + * The stencil test passes when the masked reference value is greater than or equal to the masked stencil value. + * + * @type {Number} + * @constant */ - granularity : createPropertyDescriptor('granularity'), + GREATER_OR_EQUAL : WebGLConstants.GEQUAL, /** - * Gets or sets the numeric Property specifying the width of the corridor. - * @memberof CorridorGraphics.prototype - * @type {Property} + * The stencil test always passes. + * + * @type {Number} + * @constant */ - width : createPropertyDescriptor('width'), + ALWAYS : WebGLConstants.ALWAYS + }; + + return freezeObject(StencilFunction); +}); + +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 = { /** - * Gets or sets the boolean Property specifying whether the corridor is filled with the provided material. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default true + * Sets the stencil buffer value to zero. + * + * @type {Number} + * @constant */ - fill : createPropertyDescriptor('fill'), + ZERO : WebGLConstants.ZERO, /** - * Gets or sets the Property specifying whether the corridor is outlined. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default false + * Does not change the stencil buffer. + * + * @type {Number} + * @constant */ - outline : createPropertyDescriptor('outline'), + KEEP : WebGLConstants.KEEP, /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default Color.BLACK + * Replaces the stencil buffer value with the reference value. + * + * @type {Number} + * @constant */ - outlineColor : createPropertyDescriptor('outlineColor'), + REPLACE : WebGLConstants.REPLACE, /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default 1.0 + * Increments the stencil buffer value, clamping to unsigned byte. + * + * @type {Number} + * @constant */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + INCREMENT : WebGLConstants.INCR, /** - * Gets or sets the {@link CornerType} Property specifying how corners are styled. - * @memberof CorridorGraphics.prototype - * @type {Property} - * @default CornerType.ROUNDED + * Decrements the stencil buffer value, clamping to zero. + * + * @type {Number} + * @constant */ - cornerType : createPropertyDescriptor('cornerType'), - + DECREMENT : WebGLConstants.DECR, + /** - * 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 + * Bitwise inverts the existing stencil buffer value. + * + * @type {Number} + * @constant */ - shadows : createPropertyDescriptor('shadows'), + INVERT : WebGLConstants.INVERT, /** - * 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} + * Increments the stencil buffer value, wrapping to zero when exceeding the unsigned byte range. + * + * @type {Number} + * @constant */ - 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; - }; + INCREMENT_WRAP : WebGLConstants.INCR_WRAP, - /** - * 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) { - - 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); + /** + * 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 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; + return freezeObject(StencilOperation); }); -/*global define*/ -define('DataSources/CylinderGraphics',[ +define('Scene/ClassificationPrimitive',[ + '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Event', - './createMaterialPropertyDescriptor', - './createPropertyDescriptor' + '../Core/GeometryInstance', + '../Core/isArray', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', + '../Shaders/ShadowVolumeFS', + '../Shaders/ShadowVolumeVS', + '../ThirdParty/when', + './BlendingState', + './ClassificationType', + './DepthFunction', + './PerInstanceColorAppearance', + './Primitive', + './SceneMode', + './StencilFunction', + './StencilOperation' ], function( + ColorGeometryInstanceAttribute, defaultValue, defined, defineProperties, + destroyObject, DeveloperError, - Event, - createMaterialPropertyDescriptor, - createPropertyDescriptor) { + GeometryInstance, + isArray, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + ShadowVolumeFS, + ShadowVolumeVS, + when, + BlendingState, + ClassificationType, + DepthFunction, + PerInstanceColorAppearance, + Primitive, + SceneMode, + StencilFunction, + StencilOperation) { 'use strict'; + var ClassificationPrimitiveReadOnlyInstanceAttributes = ['color']; + /** - * 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}. + * A classification primitive represents a volume enclosing geometry in the {@link Scene} to be highlighted. 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. + *

    + *

    + * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there + * will be rendering artifacts for some viewing angles. + *

    + *

    + * Valid geometries are {@link BoxGeometry}, {@link CylinderGeometry}, {@link EllipsoidGeometry}, {@link PolylineVolumeGeometry}, and {@link SphereGeometry}. + *

    + *

    + * Geometries that follow the surface of the ellipsoid, such as {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}, + * are also valid if they are extruded volumes; otherwise, they will not be rendered. + *

    * - * @alias CylinderGraphics + * @alias ClassificationPrimitive * @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. + * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. This can either be a single instance or an array of length one. + * @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 {ClassificationType} [options.classificationType=ClassificationType.BOTH] Determines whether terrain, 3D Tiles or both will be classified. + * @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. + * + * @see Primitive + * @see GroundPrimitive + * @see GeometryInstance + * @see Appearance */ - 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)); - } + function ClassificationPrimitive(options) { + options = 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 + * 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 {Event} * @readonly + * @type {Array|GeometryInstance} + * + * @default undefined */ - definitionChanged : { - get : function() { - return this._definitionChanged; - } - }, - + this.geometryInstances = options.geometryInstances; /** - * Gets or sets the numeric Property specifying the length of the cylinder. - * @memberof CylinderGraphics.prototype - * @type {Property} + * Determines if the primitive will be shown. This affects all geometry + * instances in the primitive. + * + * @type {Boolean} + * + * @default true */ - length : createPropertyDescriptor('length'), - + this.show = defaultValue(options.show, true); /** - * Gets or sets the numeric Property specifying the radius of the top of the cylinder. - * @memberof CylinderGraphics.prototype - * @type {Property} + * Determines whether terrain, 3D Tiles or both will be classified. + * + * @type {ClassificationType} + * + * @default ClassificationType.BOTH */ - topRadius : createPropertyDescriptor('topRadius'), - + this.classificationType = defaultValue(options.classificationType, ClassificationType.BOTH); /** - * Gets or sets the numeric Property specifying the radius of the bottom of the cylinder. - * @memberof CylinderGraphics.prototype - * @type {Property} + * 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 */ - bottomRadius : createPropertyDescriptor('bottomRadius'), - + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); /** - * 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 + * 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. + *

    + * + * @type {Boolean} + * + * @default false */ - numberOfVerticalLines : createPropertyDescriptor('numberOfVerticalLines'), + this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); + this._debugShowShadowVolume = false; - /** - * 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'), + // These are used by GroundPrimitive to augment the shader and uniform map. + this._extruded = defaultValue(options._extruded, false); + this._uniformMap = options._uniformMap; + + this._sp = undefined; + this._spPick = undefined; + + this._rsStencilPreloadPass = undefined; + this._rsStencilDepthPass = undefined; + this._rsColorPass = undefined; + this._rsPickPass = undefined; + + this._ready = false; + this._readyPromise = when.defer(); + + this._primitive = undefined; + this._pickPrimitive = options._pickPrimitive; + + var appearance = new PerInstanceColorAppearance({ + flat : true + }); + + var readOnlyAttributes; + if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { + readOnlyAttributes = ClassificationPrimitiveReadOnlyInstanceAttributes; + } + + this._createBoundingVolumeFunction = options._createBoundingVolumeFunction; + this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction; + + 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, + _createBoundingVolumeFunction : undefined, + _createRenderStatesFunction : undefined, + _createShaderProgramFunction : undefined, + _createCommandsFunction : undefined, + _updateAndQueueCommandsFunction : undefined, + _createPickOffsets : true + }; + } + defineProperties(ClassificationPrimitive.prototype, { /** - * Gets or sets the boolean Property specifying the visibility of the cylinder. - * @memberof CylinderGraphics.prototype - * @type {Property} + * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * * @default true */ - show : createPropertyDescriptor('show'), + vertexCacheOptimize : { + get : function() { + return this._primitiveOptions.vertexCacheOptimize; + } + }, /** - * Gets or sets the Property specifying the material used to fill the cylinder. - * @memberof CylinderGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE + * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default false */ - material : createMaterialPropertyDescriptor('material'), + interleave : { + get : function() { + return this._primitiveOptions.interleave; + } + }, /** - * Gets or sets the boolean Property specifying whether the cylinder is filled with the provided material. - * @memberof CylinderGraphics.prototype - * @type {Property} + * When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * * @default true */ - fill : createPropertyDescriptor('fill'), + releaseGeometryInstances : { + get : function() { + return this._primitiveOptions.releaseGeometryInstances; + } + }, /** - * Gets or sets the boolean Property specifying whether the cylinder is outlined. - * @memberof CylinderGraphics.prototype - * @type {Property} - * @default false + * When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true */ - outline : createPropertyDescriptor('outline'), + allowPicking : { + get : function() { + return this._primitiveOptions.allowPicking; + } + }, /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof CylinderGraphics.prototype - * @type {Property} - * @default Color.BLACK + * Determines if the geometry instances will be created and batched on a web worker. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true */ - outlineColor : createPropertyDescriptor('outlineColor'), + asynchronous : { + get : function() { + return this._primitiveOptions.asynchronous; + } + }, /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof CylinderGraphics.prototype - * @type {Property} - * @default 1.0 + * When true, geometry vertices are compressed, which will save memory. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + compressVertices : { + get : function() { + return this._primitiveOptions.compressVertices; + } + }, /** - * 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 + * 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 ClassificationPrimitive#update} + * is called. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly */ - shadows : createPropertyDescriptor('shadows'), + ready : { + get : function() { + return this._ready; + } + }, /** - * 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} + * Gets a promise that resolves when the primitive is ready to render. + * @memberof ClassificationPrimitive.prototype + * @type {Promise.} + * @readonly */ - distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + } }); /** - * Duplicates this instance. + * Determines if ClassificationPrimitive rendering is supported. * - * @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. + * @param {Scene} scene The scene. + * @returns {Boolean} true if ClassificationPrimitives are supported; otherwise, returns false */ - 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; + ClassificationPrimitive.isSupported = function(scene) { + return scene.context.stencilBuffer; }; - /** - * 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) { - - 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); + function getStencilPreloadRenderState(enableStencil) { + return { + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : enableStencil, + 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 + }; + } + + function getStencilDepthRenderState(enableStencil) { + return { + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : enableStencil, + 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 + }; + } + + + function getColorRenderState(enableStencil) { + return { + stencilTest : { + enabled : enableStencil, + 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 }; - return CylinderGraphics; -}); + function createRenderStates(classificationPrimitive, context, appearance, twoPasses) { + if (defined(classificationPrimitive._rsStencilPreloadPass)) { + return; + } + var stencilEnabled = !classificationPrimitive.debugShowShadowVolume; -/*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'; + classificationPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled)); + classificationPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled)); + classificationPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled)); + classificationPrimitive._rsPickPass = RenderState.fromCache(pickRenderState); + } - /** - * 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(); + function modifyForEncodedNormals(primitive, vertexShaderSource) { + if (!primitive.compressVertices) { + return vertexShaderSource; + } + + if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) { + var attributeName = 'compressedAttributes'; + + //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes + var attributeDecl = 'attribute vec2 ' + attributeName + ';'; + + var globalDecl = 'vec3 extrudeDirection;\n'; + var decode = ' extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n'; + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/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 createShaderProgram(classificationPrimitive, frameState, appearance) { + if (defined(classificationPrimitive._sp)) { + return; + } + + var context = frameState.context; + var primitive = classificationPrimitive._primitive; + var vs = ShadowVolumeVS; + vs = classificationPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); + vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); + vs = Primitive._modifyShaderPosition(classificationPrimitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive, vs); + + if (classificationPrimitive._extruded) { + vs = modifyForEncodedNormals(primitive, vs); + } + + var extrudedDefine = classificationPrimitive._extruded ? 'EXTRUDED_GEOMETRY' : ''; + + var vsSource = new ShaderSource({ + defines : [extrudedDefine], + sources : [vs] + }); + var fsSource = new ShaderSource({ + sources : [ShadowVolumeFS] + }); + var attributeLocations = classificationPrimitive._primitive._attributeLocations; + + classificationPrimitive._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._sp, + vertexShaderSource : vsSource, + fragmentShaderSource : fsSource, + attributeLocations : attributeLocations + }); - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + if (classificationPrimitive._primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + + var pickVS = new ShaderSource({ + defines : [extrudedDefine], + sources : [vsPick] + }); + + var pickFS = new ShaderSource({ + sources : [ShadowVolumeFS], + pickColorQualifier : 'varying' + }); + + classificationPrimitive._spPick = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._spPick, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations + }); + } else { + classificationPrimitive._spPick = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vsSource, + fragmentShaderSource : fsSource, + attributeLocations : attributeLocations + }); + } } - 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; + function createColorCommands(classificationPrimitive, colorCommands) { + var primitive = classificationPrimitive._primitive; + var length = primitive._va.length * 3; + colorCommands.length = length * 2; + + var i; + var command; + var vaIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + + for (i = 0; i < length; i += 3) { + var vertexArray = primitive._va[vaIndex++]; + + // stencil preload command + command = colorCommands[i]; + if (!defined(command)) { + command = colorCommands[i] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); } - }, - /** - * Gets or sets the numeric Property specifying the semi-major axis. - * @memberof EllipseGraphics.prototype - * @type {Property} - */ - semiMajorAxis : createPropertyDescriptor('semiMajorAxis'), + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsStencilPreloadPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.TERRAIN_CLASSIFICATION; - /** - * Gets or sets the numeric Property specifying the semi-minor axis. - * @memberof EllipseGraphics.prototype - * @type {Property} - */ - semiMinorAxis : createPropertyDescriptor('semiMinorAxis'), + // stencil depth command + command = colorCommands[i + 1]; + if (!defined(command)) { + command = colorCommands[i + 1] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } - /** - * 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'), + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsStencilDepthPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.TERRAIN_CLASSIFICATION; - /** - * Gets or sets the boolean Property specifying the visibility of the ellipse. - * @memberof EllipseGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + // color command + command = colorCommands[i + 2]; + if (!defined(command)) { + command = colorCommands[i + 2] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } - /** - * Gets or sets the Property specifying the material used to fill the ellipse. - * @memberof EllipseGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE - */ - material : createMaterialPropertyDescriptor('material'), + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsColorPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.TERRAIN_CLASSIFICATION; + } - /** - * Gets or sets the numeric Property specifying the altitude of the ellipse. - * @memberof EllipseGraphics.prototype - * @type {Property} - * @default 0.0 - */ - height : createPropertyDescriptor('height'), + for (i = 0; i < length; ++i) { + command = colorCommands[length + i] = DrawCommand.shallowClone(colorCommands[i], colorCommands[length + i]); + command.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION; + } + } - /** - * 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'), + function createPickCommands(classificationPrimitive, pickCommands) { + var primitive = classificationPrimitive._primitive; + var pickOffsets = primitive._pickOffsets; + var length = pickOffsets.length * 3; + pickCommands.length = length * 2; - /** - * 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'), + var j; + var command; + var pickIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); - /** - * 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'), + for (j = 0; j < length; j += 3) { + var pickOffset = pickOffsets[pickIndex++]; - /** - * 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'), + var offset = pickOffset.offset; + var count = pickOffset.count; + var vertexArray = primitive._va[pickOffset.index]; - /** - * Gets or sets the Property specifying whether the ellipse is outlined. - * @memberof EllipseGraphics.prototype - * @type {Property} - * @default false - */ - outline : createPropertyDescriptor('outline'), + // stencil preload command + command = pickCommands[j]; + if (!defined(command)) { + command = pickCommands[j] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } - /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof EllipseGraphics.prototype - * @type {Property} - * @default Color.BLACK - */ - outlineColor : createPropertyDescriptor('outlineColor'), + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsStencilPreloadPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.TERRAIN_CLASSIFICATION; - /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof EllipseGraphics.prototype - * @type {Property} - * @default 1.0 - */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + // stencil depth command + command = pickCommands[j + 1]; + if (!defined(command)) { + command = pickCommands[j + 1] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } - /** - * 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'), + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsStencilDepthPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.TERRAIN_CLASSIFICATION; - /** - * 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') - }); + // color command + command = pickCommands[j + 2]; + if (!defined(command)) { + command = pickCommands[j + 2] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } - /** - * 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); + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsPickPass; + command.shaderProgram = classificationPrimitive._spPick; + command.uniformMap = uniformMap; + command.pass = Pass.TERRAIN_CLASSIFICATION; } - 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) { - - 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); - }; + for (j = 0; j < length; ++j) { + command = pickCommands[length + j] = DrawCommand.shallowClone(pickCommands[j], pickCommands[length + j]); + command.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION; + } + } - return EllipseGraphics; -}); + function createCommands(classificationPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { + createColorCommands(classificationPrimitive, colorCommands); + createPickCommands(classificationPrimitive, pickCommands); + } -/*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'; + function boundingVolumeIndex(commandIndex, length) { + return Math.floor((commandIndex % (length / 2)) / 3); + } - /** - * 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(); + var scratchCommandIndices = { + start : 0, + end : 0 + }; - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + function getCommandIndices(classificationType, length) { + var startIndex; + var endIndex; + if (classificationType === ClassificationType.TERRAIN) { + startIndex = 0; + endIndex = length / 2; + } else if (classificationType === ClassificationType.CESIUM_3D_TILE) { + startIndex = length / 2; + endIndex = length; + } else { + startIndex = 0; + endIndex = length; + } + + scratchCommandIndices.start = startIndex; + scratchCommandIndices.end = endIndex; + return scratchCommandIndices; } - 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; + function updateAndQueueCommands(classificationPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + var primitive = classificationPrimitive._primitive; + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); + + var boundingVolumes; + if (frameState.mode === SceneMode.SCENE3D) { + boundingVolumes = primitive._boundingSphereWC; + } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + boundingVolumes = primitive._boundingSphereCV; + } else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) { + boundingVolumes = primitive._boundingSphere2D; + } else if (defined(primitive._boundingSphereMorph)) { + boundingVolumes = primitive._boundingSphereMorph; + } + + var commandList = frameState.commandList; + var passes = frameState.passes; + + var indices; + var startIndex; + var endIndex; + var classificationType = classificationPrimitive.classificationType; + + if (passes.render) { + var colorLength = colorCommands.length; + indices = getCommandIndices(classificationType, colorLength); + startIndex = indices.start; + endIndex = indices.end; + + for (var i = startIndex; i < endIndex; ++i) { + var colorCommand = colorCommands[i]; + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(i, colorLength)]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); } - }, + } - /** - * Gets or sets the boolean Property specifying the visibility of the ellipsoid. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + if (passes.pick) { + var pickLength = pickCommands.length; + indices = getCommandIndices(classificationType, pickLength); + startIndex = indices.start; + endIndex = indices.end; - /** - * Gets or sets the {@link Cartesian3} {@link Property} specifying the radii of the ellipsoid. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - */ - radii : createPropertyDescriptor('radii'), + var pickOffsets = primitive._pickOffsets; + for (var j = startIndex; j < endIndex; ++j) { + var pickOffset = pickOffsets[boundingVolumeIndex(j, pickLength)]; + var pickCommand = pickCommands[j]; + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = boundingVolumes[pickOffset.index]; + pickCommand.cull = cull; - /** - * Gets or sets the Property specifying the material used to fill the ellipsoid. - * @memberof EllipsoidGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE - */ - material : createMaterialPropertyDescriptor('material'), + commandList.push(pickCommand); + } + } + } - /** - * 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'), + /** + * 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. + */ + ClassificationPrimitive.prototype.update = function(frameState) { + if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) { + return; + } - /** - * Gets or sets the Property specifying whether the ellipsoid is outlined. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - * @default false - */ - outline : createPropertyDescriptor('outline'), + var that = this; + var primitiveOptions = this._primitiveOptions; - /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - * @default Color.BLACK - */ - outlineColor : createPropertyDescriptor('outlineColor'), + if (!defined(this._primitive)) { + var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; + var length = instances.length; - /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - * @default 1.0 - */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + var i; + var instance; + + var geometryInstances = new Array(length); + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometryInstances[i] = new GeometryInstance({ + geometry : instance.geometry, + attributes : instance.attributes, + modelMatrix : instance.modelMatrix, + id : instance.id, + pickPrimitive : defaultValue(this._pickPrimitive, that) + }); + } - /** - * Gets or sets the Property specifying the number of stacks. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - * @default 64 - */ - stackPartitions : createPropertyDescriptor('stackPartitions'), + primitiveOptions.geometryInstances = geometryInstances; - /** - * Gets or sets the Property specifying the number of radial slices. - * @memberof EllipsoidGraphics.prototype - * @type {Property} - * @default 64 - */ - slicePartitions : createPropertyDescriptor('slicePartitions'), + if (defined(this._createBoundingVolumeFunction)) { + primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { + that._createBoundingVolumeFunction(frameState, geometry); + }; + } - /** - * 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'), + 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); + }; - /** - * 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'), + if (defined(this._updateAndQueueCommandsFunction)) { + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + that._updateAndQueueCommandsFunction(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; + } else { + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; + } - /** - * 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') - }); + 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); + } + }); + } + + if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) { + this._debugShowShadowVolume = true; + this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false)); + this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false)); + this._rsColorPass = RenderState.fromCache(getColorRenderState(false)); + } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) { + this._debugShowShadowVolume = false; + this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true)); + this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); + this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); + } + + this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; + this._primitive.update(frameState); + }; /** - * Duplicates this instance. + * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. * - * @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. + * @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); */ - 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; + ClassificationPrimitive.prototype.getGeometryInstanceAttributes = function(id) { + return this._primitive.getGeometryInstanceAttributes(id); + }; - return result; + /** + * 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 ClassificationPrimitive#destroy + */ + ClassificationPrimitive.prototype.isDestroyed = function() { + return false; }; /** - * Assigns each unassigned property on this object to the value - * of the same property on the provided source object. + * 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. + *

    * - * @param {EllipsoidGraphics} source The object to be merged into this object. + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * e = e && e.destroy(); + * + * @see ClassificationPrimitive#isDestroyed */ - EllipsoidGraphics.prototype.merge = function(source) { - - 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); + ClassificationPrimitive.prototype.destroy = function() { + this._primitive = this._primitive && this._primitive.destroy(); + this._sp = this._sp && this._sp.destroy(); + this._spPick = this._spPick && this._spPick.destroy(); + return destroyObject(this); }; - return EllipsoidGraphics; + return ClassificationPrimitive; }); -/*global define*/ -define('DataSources/LabelGraphics',[ +define('Scene/GroundPrimitive',[ + '../Core/BoundingSphere', + '../Core/buildModuleUrl', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Event', - './createPropertyDescriptor' + '../Core/GeographicTilingScheme', + '../Core/GeometryInstance', + '../Core/isArray', + '../Core/loadJson', + '../Core/Math', + '../Core/OrientedBoundingBox', + '../Core/Rectangle', + '../ThirdParty/when', + './ClassificationPrimitive', + './ClassificationType', + './SceneMode' ], function( + BoundingSphere, + buildModuleUrl, + Cartesian2, + Cartesian3, + Cartographic, defaultValue, defined, defineProperties, + destroyObject, DeveloperError, - Event, - createPropertyDescriptor) { + GeographicTilingScheme, + GeometryInstance, + isArray, + loadJson, + CesiumMath, + OrientedBoundingBox, + Rectangle, + when, + ClassificationPrimitive, + ClassificationType, + SceneMode) { 'use strict'; + var GroundPrimitiveUniformMap = { + u_globeMinimumAltitude: function() { + return 55000.0; + } + }; + /** - * Describes a two dimensional label located at the position of the containing {@link Entity}. + * 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. *

    - *

    - *
    - * Example labels - *
    + * 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. + *

    + *

    + * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there + * will be rendering artifacts for some viewing angles. + *

    + *

    + * Valid geometries are {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}. *

    * - * @alias LabelGraphics + * @alias GroundPrimitive * @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.scaleByDistance] A {@link NearFarScalar} Property used to set scale 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. + * @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 {ClassificationType} [options.classificationType=ClassificationType.BOTH] Determines whether terrain, 3D Tiles or both will be classified. + * @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. * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} + * @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 ClassificationPrimitive + * @see GeometryInstance + * @see Appearance */ - 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._scaleByDistance = undefined; - this._scaleByDistanceSubscription = undefined; - this._distanceDisplayCondition = undefined; - this._distanceDisplayConditionSubscription = undefined; - this._disableDepthTestDistance = undefined; - this._disableDepthTestDistanceSubscription = 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'), + function GroundPrimitive(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); /** - * 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. + * The geometry instance rendered with this primitive. This may + * be undefined if options.releaseGeometryInstances + * is true when the primitive is constructed. *

    - *

    - * - * - * - *
    default
    l.pixeloffset = new Cartesian2(25, 75);
    - * The label's origin is indicated by the yellow point. - *
    + * Changing this property after the primitive is rendered has no effect. *

    - * @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. - *
    + * 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. *

    - * @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} + * + * @readonly + * @type {Array|GeometryInstance} + * + * @default undefined */ - translucencyByDistance : createPropertyDescriptor('translucencyByDistance'), - + this.geometryInstances = options.geometryInstances; /** - * 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} + * Determines if the primitive will be shown. This affects all geometry + * instances in the primitive. + * + * @type {Boolean} + * + * @default true */ - pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance'), - + this.show = defaultValue(options.show, true); /** - * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera. - * A label'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 label's scale remains clamped to the nearest bound. If undefined, - * scaleByDistance will be disabled. - * @memberof LabelGraphics.prototype - * @type {Property} + * Determines whether terrain, 3D Tiles or both will be classified. + * + * @type {ClassificationType} + * + * @default ClassificationType.BOTH */ - scaleByDistance : createPropertyDescriptor('scaleByDistance'), - + this.classificationType = defaultValue(options.classificationType, ClassificationType.BOTH); /** - * 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} + * 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 */ - distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition'), + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); /** - * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. - * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. - * @memberof LabelGraphics.prototype - * @type {Property} + * 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. + *

    + * + * @type {Boolean} + * + * @default false */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') - }); - - /** - * 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.scaleByDistance = this.scaleByDistance; - result.distanceDisplayCondition = this.distanceDisplayCondition; - result.disableDepthTestDistance = this.disableDepthTestDistance; - return result; - }; + this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); - /** - * 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) { - - 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.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance); - this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); - this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); - }; + this._boundingVolumes = []; + this._boundingVolumes2D = []; - return LabelGraphics; -}); + this._ready = false; + this._readyPromise = when.defer(); -/*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'; + this._primitive = undefined; - var defaultNodeTransformation = new TranslationRotationScale(); + this._maxHeight = undefined; + this._minHeight = undefined; - /** - * 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._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; + this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; - this._definitionChanged = new Event(); - this._translation = undefined; - this._translationSubscription = undefined; - this._rotation = undefined; - this._rotationSubscription = undefined; - this._scale = undefined; - this._scaleSubscription = undefined; + this._boundingSpheresKeys = []; + this._boundingSpheres = []; - this.translation = options.translation; - this.rotation = options.rotation; - this.scale = options.scale; - }; + var that = this; + this._primitiveOptions = { + geometryInstances : undefined, + 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), + _createBoundingVolumeFunction : undefined, + _updateAndQueueCommandsFunction : undefined, + _pickPrimitive : that, + _extruded : true, + _uniformMap : GroundPrimitiveUniformMap + }; + } - defineProperties(NodeTransformationProperty.prototype, { + defineProperties(GroundPrimitive.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 + * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * + * @memberof GroundPrimitive.prototype * * @type {Boolean} * @readonly + * + * @default true */ - isConstant : { + vertexCacheOptimize : { get : function() { - return Property.isConstant(this._translation) && - Property.isConstant(this._rotation) && - Property.isConstant(this._scale); + return this._primitiveOptions.vertexCacheOptimize; } }, /** - * 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 + * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. * - * @type {Event} + * @memberof GroundPrimitive.prototype + * + * @type {Boolean} * @readonly + * + * @default false */ - definitionChanged : { + interleave : { get : function() { - return this._definitionChanged; + return this._primitiveOptions.interleave; } }, /** - * 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 + * When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * + * @memberof GroundPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true */ - translation : createPropertyDescriptor('translation'), + releaseGeometryInstances : { + get : function() { + return this._primitiveOptions.releaseGeometryInstances; + } + }, /** - * 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 + * 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 */ - rotation : createPropertyDescriptor('rotation'), + allowPicking : { + get : function() { + return this._primitiveOptions.allowPicking; + } + }, /** - * 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) + * Determines if the geometry instances will be created and batched on a web worker. + * + * @memberof GroundPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true */ - 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); - } - }; + asynchronous : { + get : function() { + return this._primitiveOptions.asynchronous; + } + }, - defineProperties(PropertyBag.prototype, { /** - * Gets the names of all properties registered on this instance. - * @memberof PropertyBag.prototype - * @type {Array} + * When true, geometry vertices are compressed, which will save memory. + * + * @memberof GroundPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true */ - propertyNames : { + compressVertices : { get : function() { - return this._propertyNames; + return this._primitiveOptions.compressVertices; } }, + /** - * 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 + * 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 */ - isConstant : { + ready : { 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; + return this._ready; } }, + /** - * 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} + * Gets a promise that resolves when the primitive is ready to render. + * @memberof GroundPrimitive.prototype + * @type {Promise.} * @readonly */ - definitionChanged : { + readyPromise : { get : function() { - return this._definitionChanged; + return this._readyPromise.promise; } } }); /** - * Determines if this object has defined a property with the given name. - * - * @param {String} propertyName The name of the property to check for. + * Determines if GroundPrimitive rendering is supported. * - * @returns {Boolean} True if this object has defined a property with the given name, false otherwise. + * @param {Scene} scene The scene. + * @returns {Boolean} true if GroundPrimitives are supported; otherwise, returns false */ - PropertyBag.prototype.hasProperty = function(propertyName) { - return this._propertyNames.indexOf(propertyName) !== -1; - }; + GroundPrimitive.isSupported = ClassificationPrimitive.isSupported; - function createConstantProperty(value) { - return new ConstantProperty(value); + 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; + }; } - /** - * Adds a property to this object. - * - * @param {String} propertyName The name of the property to add. - * @param {*} [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; + function getComputeMinimumHeightFunction(primitive) { + return function(granularity, ellipsoid) { + return primitive._minHeight; + }; + } - - propertyNames.push(propertyName); - Object.defineProperty(this, propertyName, createPropertyDescriptor(propertyName, true, defaultValue(createPropertyCallback, createConstantProperty))); + 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(); - if (defined(value)) { - this[propertyName] = value; + 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; } - this._definitionChanged.raiseEvent(this); - }; + var highPositions = geometry.attributes.position3DHigh.values; + var lowPositions = geometry.attributes.position3DLow.values; + var length = highPositions.length; - /** - * 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); + var minLat = Number.POSITIVE_INFINITY; + var minLon = Number.POSITIVE_INFINITY; + var maxLat = Number.NEGATIVE_INFINITY; + var maxLon = Number.NEGATIVE_INFINITY; - - this._propertyNames.splice(index, 1); - delete this[propertyName]; + for (var i = 0; i < length; i +=3) { + var highPosition = Cartesian3.unpack(highPositions, i, scratchBVCartesianHigh); + var lowPosition = Cartesian3.unpack(lowPositions, i, scratchBVCartesianLow); - this._definitionChanged.raiseEvent(this); - }; + var position = Cartesian3.add(highPosition, lowPosition, scratchBVCartesian); + var cartographic = ellipsoid.cartesianToCartographic(position, scratchBVCartographic); - /** - * 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(result)) { - result = {}; - } + var latitude = cartographic.latitude; + var longitude = cartographic.longitude; - 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]); + minLat = Math.min(minLat, latitude); + minLon = Math.min(minLon, longitude); + maxLat = Math.max(maxLat, latitude); + maxLon = Math.max(maxLon, longitude); } - 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) { - - 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 rectangle = scratchBVRectangle; + rectangle.north = maxLat; + rectangle.south = minLat; + rectangle.east = maxLon; + rectangle.west = minLon; - var targetProperty = this[name]; - var sourceProperty = source[name]; + return rectangle; + } - //Custom properties that are registered on the source must also be added to this. - if (targetProperty === undefined && propertyNames.indexOf(name) === -1) { - this.addProperty(name, undefined, createPropertyCallback); - } + var scratchDiagonalCartesianNE = new Cartesian3(); + var scratchDiagonalCartesianSW = new Cartesian3(); + var scratchDiagonalCartographic = new Cartographic(); + var scratchCenterCartesian = new Cartesian3(); + var scratchSurfaceCartesian = new Cartesian3(); - if (sourceProperty !== undefined) { - if (targetProperty !== undefined) { - if (defined(targetProperty) && defined(targetProperty.merge)) { - targetProperty.merge(sourceProperty); - } - } else if (defined(sourceProperty) && defined(sourceProperty.merge) && defined(sourceProperty.clone)) { - this[name] = sourceProperty.clone(); - } else { - this[name] = sourceProperty; + 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; + var i; + for(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; } } - } - }; - function propertiesEqual(a, b) { - var aPropertyNames = a._propertyNames; - var bPropertyNames = b._propertyNames; + if (failed) { + break; + } - var len = aPropertyNames.length; - if (len !== bPropertyNames.length) { - return false; + lastLevelX = currentX; + lastLevelY = currentY; } - for (var aIndex = 0; aIndex < len; ++aIndex) { - var name = aPropertyNames[aIndex]; - var bIndex = bPropertyNames.indexOf(name); - if (bIndex === -1) { - return false; + 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]; } - if (!Property.equals(a[name], b[name])) { - return false; + + // 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; } } - return true; + + primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight); + primitive._maxTerrainHeight = maxTerrainHeight; } - /** - * 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)); - }; + var scratchBoundingSphere = new BoundingSphere(); + function getInstanceBoundingSphere(rectangle, ellipsoid) { + var xyLevel = getTileXYLevel(rectangle); - return PropertyBag; -}); + // 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]; + } + } -/*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'; + var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0); + BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere); - function createNodeTransformationProperty(value) { - return new NodeTransformationProperty(value); + return BoundingSphere.union(result, scratchBoundingSphere, result); } - function createNodeTransformationPropertyBag(value) { - return new PropertyBag(value, createNodeTransformationProperty); + function createBoundingVolume(groundPrimitive, 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, groundPrimitive._maxHeight, groundPrimitive._minHeight, ellipsoid); + groundPrimitive._boundingVolumes.push(obb); + } else { + var highPositions = geometry.attributes.position3DHigh.values; + var lowPositions = geometry.attributes.position3DLow.values; + groundPrimitive._boundingVolumes.push(BoundingSphere.fromEncodedCartesianVertices(highPositions, lowPositions)); + } + + if (!frameState.scene3DOnly) { + var projection = frameState.mapProjection; + var boundingVolume = BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, groundPrimitive._maxHeight, groundPrimitive._minHeight); + Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center); + + groundPrimitive._boundingVolumes2D.push(boundingVolume); + } + } + + function boundingVolumeIndex(commandIndex, length) { + return Math.floor((commandIndex % (length / 2)) / 3); + } + + var scratchCommandIndices = { + start : 0, + end : 0 + }; + + function getCommandIndices(classificationType, length) { + var startIndex; + var endIndex; + if (classificationType === ClassificationType.TERRAIN) { + startIndex = 0; + endIndex = length / 2; + } else if (classificationType === ClassificationType.CESIUM_3D_TILE) { + startIndex = length / 2; + endIndex = length; + } else { + startIndex = 0; + endIndex = length; + } + + scratchCommandIndices.start = startIndex; + scratchCommandIndices.end = endIndex; + return scratchCommandIndices; + } + + 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 indices; + var startIndex; + var endIndex; + var classificationType = groundPrimitive.classificationType; + + var commandList = frameState.commandList; + var passes = frameState.passes; + if (passes.render) { + var colorLength = colorCommands.length; + indices = getCommandIndices(classificationType, colorLength); + startIndex = indices.start; + endIndex = indices.end; + + for (var i = startIndex; i < endIndex; ++i) { + var colorCommand = colorCommands[i]; + colorCommand.owner = groundPrimitive; + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(i, colorLength)]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); + } + } + + if (passes.pick) { + var pickLength = pickCommands.length; + indices = getCommandIndices(classificationType, pickLength); + startIndex = indices.start; + endIndex = indices.end; + + var primitive = groundPrimitive._primitive._primitive; + var pickOffsets = primitive._pickOffsets; + for (var j = startIndex; j < endIndex; ++j) { + var pickOffset = pickOffsets[boundingVolumeIndex(j, pickLength)]; + var bv = boundingVolumes[pickOffset.index]; + + var pickCommand = pickCommands[j]; + pickCommand.owner = groundPrimitive; + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = bv; + pickCommand.cull = cull; + + commandList.push(pickCommand); + } + } } + GroundPrimitive._initialized = false; + GroundPrimitive._initPromise = undefined; + /** - * 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 + * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the + * GroundPrimitive synchronously. * - * @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. + * @returns {Promise} A promise that will resolve once the terrain heights have been loaded. * - * @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)); - } + GroundPrimitive.initializeTerrainHeights = function() { + var initPromise = GroundPrimitive._initPromise; + if (defined(initPromise)) { + return initPromise; + } - 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; - } - }, + GroundPrimitive._initPromise = loadJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) { + GroundPrimitive._initialized = true; + GroundPrimitive._terrainHeights = json; + }); - /** - * Gets or sets the boolean Property specifying the visibility of the model. - * @memberof ModelGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + return GroundPrimitive._initPromise; + }; - /** - * 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'), + /** + * 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) { + if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) { + return; + } - /** - * 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'), + if (!GroundPrimitive._initialized) { + + GroundPrimitive.initializeTerrainHeights(); + return; + } - /** - * 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'), + var that = this; + var primitiveOptions = this._primitiveOptions; - /** - * 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'), + if (!defined(this._primitive)) { + var ellipsoid = frameState.mapProjection.ellipsoid; - /** - * 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'), + var instance; + var geometry; + var instanceType; - /** - * Gets or sets the string Property specifying the URI of the glTF asset. - * @memberof ModelGraphics.prototype - * @type {Property} - */ - uri : createPropertyDescriptor('uri'), + var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; + var length = instances.length; + var groundInstances = new Array(length); - /** - * Gets or sets the boolean Property specifying if glTF animations should be run. - * @memberof ModelGraphics.prototype - * @type {Property} - * @default true - */ - runAnimations : createPropertyDescriptor('runAnimations'), + var i; + 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); + } - /** - * 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), + var id = instance.id; + if (defined(id) && defined(instanceRectangle)) { + var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid); + this._boundingSpheresKeys.push(id); + this._boundingSpheres.push(boundingSphere); + } - /** - * Gets or sets the Property specifying the {@link HeightReference}. - * @memberof ModelGraphics.prototype - * @type {Property} - * @default HeightReference.NONE - */ - heightReference : createPropertyDescriptor('heightReference'), + instanceType = geometry.constructor; + if (!defined(instanceType) || !defined(instanceType.createShadowVolume)) { + } + } - /** - * 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'), + // 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; - /** - * Gets or sets the Property specifying the {@link Color} of the silhouette. - * @memberof ModelGraphics.prototype - * @type {Property} - * @default Color.RED - */ - silhouetteColor: createPropertyDescriptor('silhouetteColor'), + 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 + }); + } - /** - * 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'), + primitiveOptions.geometryInstances = groundInstances; - /** - * 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'), + primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { + createBoundingVolume(that, frameState, geometry); + }; + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; - /** - * 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'), + this._primitive = new ClassificationPrimitive(primitiveOptions); + this._primitive.readyPromise.then(function(primitive) { + that._ready = true; - /** - * 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') - }); + if (that.releaseGeometryInstances) { + that.geometryInstances = undefined; + } - /** - * 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); + var error = primitive._error; + if (!defined(error)) { + that._readyPromise.resolve(that); + } else { + that._readyPromise.reject(error); + } + }); } - 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; + this._primitive.debugShowShadowVolume = this.debugShowShadowVolume; + this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; + this._primitive.update(frameState); }; /** - * 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. + * @private */ - ModelGraphics.prototype.merge = function(source) { - - 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); - } + GroundPrimitive.prototype.getBoundingSphere = function(id) { + var index = this._boundingSpheresKeys.indexOf(id); + if (index !== -1) { + return this._boundingSpheres[index]; } - }; - 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'; + return undefined; + }; /** - * Describes a polyline defined as the path made by an {@link Entity} as it moves over time. + * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. * - * @alias PathGraphics - * @constructor + * @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. * - * @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. + * @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); */ - 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') - }); + GroundPrimitive.prototype.getGeometryInstanceAttributes = function(id) { + return this._primitive.getGeometryInstanceAttributes(id); + }; /** - * Duplicates this instance. + * 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. + *

    * - * @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. + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see GroundPrimitive#destroy */ - 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; + GroundPrimitive.prototype.isDestroyed = function() { + return false; }; /** - * Assigns each unassigned property on this object to the value - * of the same property on the provided source object. + * 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. + *

    * - * @param {PathGraphics} source The object to be merged into this object. + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * e = e && e.destroy(); + * + * @see GroundPrimitive#isDestroyed */ - PathGraphics.prototype.merge = function(source) { - - 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); + GroundPrimitive.prototype.destroy = function() { + this._primitive = this._primitive && this._primitive.destroy(); + return destroyObject(this); }; - return PathGraphics; + return GroundPrimitive; }); -/*global define*/ -define('DataSources/PointGraphics',[ +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', - './createPropertyDescriptor' + '../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, - createPropertyDescriptor) { + 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; + } + /** - * Describes a graphical point located at the position of the containing {@link Entity}. - * - * @alias PointGraphics + * A {@link GeometryUpdater} for corridors. + * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}. + * @alias CorridorGeometryUpdater * @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. + * @param {Entity} entity The entity containing the geometry to be visualized. + * @param {Scene} scene The scene where visualization is taking place. */ - 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._disableDepthTestDistance = undefined; - this._disableDepthTestDistanceSubscription = undefined; - this._definitionChanged = new Event(); + function CorridorGeometryUpdater(entity, scene) { + + 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.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + this._onEntityPropertyChanged(entity, 'corridor', entity.corridor, undefined); } - defineProperties(PointGraphics.prototype, { + defineProperties(CorridorGeometryUpdater, { /** - * Gets the event that is raised whenever a property or sub-property is changed or modified. - * @memberof PointGraphics.prototype + * 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 {Event} + * @type {Entity} * @readonly */ - definitionChanged : { + entity : { get : function() { - return this._definitionChanged; + return this._entity; } }, - /** - * Gets or sets the Property specifying the {@link Color} of the point. - * @memberof PointGraphics.prototype - * @type {Property} - * @default Color.WHITE + * Gets a value indicating if the geometry has a fill component. + * @memberof CorridorGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly */ - color : createPropertyDescriptor('color'), - + fillEnabled : { + get : function() { + return this._fillEnabled; + } + }, /** - * Gets or sets the numeric Property specifying the size in pixels. - * @memberof PointGraphics.prototype - * @type {Property} - * @default 1 + * Gets a value indicating if fill visibility varies with simulation time. + * @memberof CorridorGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly */ - pixelSize : createPropertyDescriptor('pixelSize'), - + hasConstantFill : { + get : function() { + return !this._fillEnabled || + (!defined(this._entity.availability) && + Property.isConstant(this._showProperty) && + Property.isConstant(this._fillProperty)); + } + }, /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof PointGraphics.prototype - * @type {Property} - * @default Color.BLACK + * Gets the material property used to fill the geometry. + * @memberof CorridorGeometryUpdater.prototype + * + * @type {MaterialProperty} + * @readonly */ - outlineColor : createPropertyDescriptor('outlineColor'), - + fillMaterialProperty : { + get : function() { + return this._materialProperty; + } + }, /** - * Gets or sets the numeric Property specifying the the outline width in pixels. - * @memberof PointGraphics.prototype - * @type {Property} - * @default 0 + * Gets a value indicating if the geometry has an outline component. + * @memberof CorridorGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly */ - outlineWidth : createPropertyDescriptor('outlineWidth'), - + outlineEnabled : { + get : function() { + return this._outlineEnabled; + } + }, /** - * Gets or sets the boolean Property specifying the visibility of the point. - * @memberof PointGraphics.prototype - * @type {Property} - * @default true + * Gets a value indicating if the geometry has an outline component. + * @memberof CorridorGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly */ - show : createPropertyDescriptor('show'), - + hasConstantOutline : { + get : function() { + return !this._outlineEnabled || + (!defined(this._entity.availability) && + Property.isConstant(this._showProperty) && + Property.isConstant(this._showOutlineProperty)); + } + }, /** - * 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 + * Gets the {@link Color} property for the geometry outline. + * @memberof CorridorGeometryUpdater.prototype + * * @type {Property} + * @readonly */ - scaleByDistance : createPropertyDescriptor('scaleByDistance'), - + outlineColorProperty : { + get : function() { + return this._outlineColorProperty; + } + }, /** - * 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} + * 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 */ - translucencyByDistance : createPropertyDescriptor('translucencyByDistance'), - + outlineWidth : { + get : function() { + return this._outlineWidth; + } + }, /** - * Gets or sets the Property specifying the {@link HeightReference}. - * @memberof PointGraphics.prototype + * Gets the property specifying whether the geometry + * casts or receives shadows from each light source. + * @memberof CorridorGeometryUpdater.prototype + * * @type {Property} - * @default HeightReference.NONE + * @readonly */ - heightReference : createPropertyDescriptor('heightReference'), - + shadowsProperty : { + get : function() { + return this._shadowsProperty; + } + }, /** - * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this point will be displayed. - * @memberof PointGraphics.prototype + * 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 */ - distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition'), - + distanceDisplayConditionProperty : { + get : function() { + return this._distanceDisplayCondition; + } + }, /** - * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. - * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. - * @memberof PointGraphics.prototype - * @type {Property} + * 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 */ - disableDepthTestDistance : createPropertyDescriptor('disableDepthTestDistance') + 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; + } + } }); /** - * Duplicates this instance. + * Checks if the geometry is outlined at the provided time. * - * @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. + * @param {JulianDate} time The time for which to retrieve visibility. + * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise. */ - 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; - result.disableDepthTestDistance = this.disableDepthTestDistance; - return result; + CorridorGeometryUpdater.prototype.isOutlineVisible = function(time) { + var entity = this._entity; + return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); }; /** - * Assigns each unassigned property on this object to the value - * of the same property on the provided source object. + * Checks if the geometry is filled at the provided time. * - * @param {PointGraphics} source The object to be merged into this object. + * @param {JulianDate} time The time for which to retrieve visibility. + * @returns {Boolean} true if geometry is filled at the provided time, false otherwise. */ - PointGraphics.prototype.merge = function(source) { - - 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); - this.disableDepthTestDistance = defaultValue(this.disableDepthTestDistance, source.disableDepthTestDistance); + CorridorGeometryUpdater.prototype.isFilled = function(time) { + var entity = this._entity; + return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time); }; - return PointGraphics; -}); + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); -/*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'; + 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 + }); + }; /** - * 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. + * Creates the geometry instance which represents the outline of the geometry. * - * @alias PolygonGraphics - * @constructor + * @param {JulianDate} time The time to use when retrieving initial attribute values. + * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry. * - * @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. + * @exception {DeveloperError} This instance does not represent an outlined geometry. + */ + CorridorGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { + + 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. * - * @see Entity - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon Demo} + * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - 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; + CorridorGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); - } + /** + * 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); + }; - 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; + 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; + } - /** - * Gets or sets the boolean Property specifying the visibility of the polygon. - * @memberof PolygonGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + var fillProperty = corridor.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - /** - * Gets or sets the Property specifying the material used to fill the polygon. - * @memberof PolygonGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE - */ - material : createMaterialPropertyDescriptor('material'), + var outlineProperty = corridor.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); + } - /** - * Gets or sets the Property specifying the {@link PolygonHierarchy}. - * @memberof PolygonGraphics.prototype - * @type {Property} - */ - hierarchy : createPropertyDescriptor('hierarchy'), + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } - /** - * Gets or sets the numeric Property specifying the constant altitude of the polygon. - * @memberof PolygonGraphics.prototype - * @type {Property} - * @default 0.0 - */ - height : createPropertyDescriptor('height'), + var positions = corridor.positions; - /** - * 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'), + 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; + } - /** - * 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'), + 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. + * @param {PrimitiveCollection} groundPrimitives The ground primitives 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) { + + 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) { + + 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; - /** - * 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'), + var entity = geometryUpdater._entity; + var corridor = entity.corridor; + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(corridor.show, time, true)) { + return; + } - /** - * 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'), + 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; + } - /** - * Gets or sets the Property specifying whether the polygon is outlined. - * @memberof PolygonGraphics.prototype - * @type {Property} - * @default false - */ - outline : createPropertyDescriptor('outline'), + 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); - /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof PolygonGraphics.prototype - * @type {Property} - * @default Color.BLACK - */ - outlineColor : createPropertyDescriptor('outlineColor'), + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + var distanceDisplayCondition = this._geometryUpdater.distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); - /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof PolygonGraphics.prototype - * @type {Property} - * @default 1.0 - */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + if (!defined(corridor.fill) || corridor.fill.getValue(time)) { + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); + this._material = material; - /** - * 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'), + if (onTerrain) { + var currentColor = Color.WHITE; + if (defined(fillMaterialProperty.color)) { + currentColor = fillMaterialProperty.color.getValue(time); + } - /** - * 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'), + 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; - /** - * 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'), + this._primitive = primitives.add(new Primitive({ + geometryInstances : new GeometryInstance({ + id : entity, + geometry : new CorridorGeometry(options), + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + } + }), + appearance : appearance, + asynchronous : false, + shadows : shadows + })); + } + } - /** - * 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'), + if (!onTerrain && defined(corridor.outline) && corridor.outline.getValue(time)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - /** - * 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') - }); + 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; - /** - * 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); + 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 + })); } - 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) { - - 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); + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); }; - return PolygonGraphics; + 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/PolylineGraphics',[ - '../Core/defaultValue', - '../Core/defined', +define('DataSources/DataSource',[ '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - './createMaterialPropertyDescriptor', - './createPropertyDescriptor' + '../Core/DeveloperError' ], function( - defaultValue, - defined, defineProperties, - DeveloperError, - Event, - createMaterialPropertyDescriptor, - createPropertyDescriptor) { + DeveloperError) { '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 + * 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 * - * @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} + * @see DataSourceDisplay */ - function PolylineGraphics(options) { - this._show = undefined; - this._showSubscription = undefined; - this._material = undefined; - this._materialSubscription = undefined; - this._depthFailMaterial = undefined; - this._depthFailMaterialSubscription = 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)); + function DataSource() { + DeveloperError.throwInstantiationError(); } - defineProperties(PolylineGraphics.prototype, { + defineProperties(DataSource.prototype, { /** - * Gets the event that is raised whenever a property or sub-property is changed or modified. - * @memberof PolylineGraphics.prototype - * - * @type {Event} - * @readonly + * Gets a human-readable name for this instance. + * @memberof DataSource.prototype + * @type {String} */ - definitionChanged : { - get : function() { - return this._definitionChanged; - } + name : { + get : DeveloperError.throwInstantiationError }, - - /** - * 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 + * Gets the preferred clock settings for this data source. + * @memberof DataSource.prototype + * @type {DataSourceClock} */ - material : createMaterialPropertyDescriptor('material'), - + clock : { + get : DeveloperError.throwInstantiationError + }, /** - * Gets or sets the Property specifying the material used to draw the polyline when it fails the depth test. - *

    - * Requires the EXT_frag_depth WebGL extension to render properly. If the extension is not supported, - * there may be artifacts. - *

    - * @type {MaterialProperty} - * @default undefined + * Gets the collection of {@link Entity} instances. + * @memberof DataSource.prototype + * @type {EntityCollection} */ - depthFailMaterial : createMaterialPropertyDescriptor('depthFailMaterial'), - + entities : { + get : DeveloperError.throwInstantiationError + }, /** - * Gets or sets the Property specifying the array of {@link Cartesian3} - * positions that define the line strip. - * @memberof PolylineGraphics.prototype - * @type {Property} + * Gets a value indicating if the data source is currently loading data. + * @memberof DataSource.prototype + * @type {Boolean} */ - positions : createPropertyDescriptor('positions'), - + isLoading : { + get : DeveloperError.throwInstantiationError + }, /** - * Gets or sets the numeric Property specifying the width in pixels. - * @memberof PolylineGraphics.prototype - * @type {Property} - * @default 1.0 + * Gets an event that will be raised when the underlying data changes. + * @memberof DataSource.prototype + * @type {Event} */ - width : createPropertyDescriptor('width'), - + changedEvent : { + get : DeveloperError.throwInstantiationError + }, /** - * 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 + * Gets an event that will be raised if an error is encountered during processing. + * @memberof DataSource.prototype + * @type {Event} */ - followSurface : createPropertyDescriptor('followSurface'), - + errorEvent : { + get : DeveloperError.throwInstantiationError + }, /** - * 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 + * Gets an event that will be raised when the value of isLoading changes. + * @memberof DataSource.prototype + * @type {Event} */ - granularity : createPropertyDescriptor('granularity'), - + loadingEvent : { + get : DeveloperError.throwInstantiationError + }, /** - * 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 + * Gets whether or not this data source should be displayed. + * @memberof DataSource.prototype + * @type {Boolean} */ - shadows : createPropertyDescriptor('shadows'), + show : { + get : DeveloperError.throwInstantiationError + }, /** - * 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} + * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * + * @memberof DataSource.prototype + * @type {EntityCluster} */ - distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') + clustering : { + get : DeveloperError.throwInstantiationError + } }); /** - * Duplicates this instance. + * 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 {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. + * @param {JulianDate} time The simulation time. + * @returns {Boolean} True if this data source is ready to be displayed at the provided time, false otherwise. */ - PolylineGraphics.prototype.clone = function(result) { - if (!defined(result)) { - return new PolylineGraphics(this); - } - result.show = this.show; - result.material = this.material; - result.depthFailMaterial = this.depthFailMaterial; - 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; - }; + DataSource.prototype.update = DeveloperError.throwInstantiationError; /** - * 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. + * @private */ - PolylineGraphics.prototype.merge = function(source) { - - this.show = defaultValue(this.show, source.show); - this.material = defaultValue(this.material, source.material); - this.depthFailMaterial = defaultValue(this.depthFailMaterial, source.depthFailMaterial); - 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); + 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 PolylineGraphics; + return DataSource; }); -/*global define*/ -define('DataSources/PolylineVolumeGraphics',[ - '../Core/defaultValue', +define('Scene/SceneTransforms',[ + '../Core/BoundingRectangle', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Cartographic', '../Core/defined', - '../Core/defineProperties', '../Core/DeveloperError', - '../Core/Event', - './createMaterialPropertyDescriptor', - './createPropertyDescriptor' + '../Core/Math', + '../Core/Matrix4', + '../Core/OrthographicFrustum', + '../Core/OrthographicOffCenterFrustum', + '../Core/Transforms', + './SceneMode' ], function( - defaultValue, + BoundingRectangle, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, defined, - defineProperties, DeveloperError, - Event, - createMaterialPropertyDescriptor, - createPropertyDescriptor) { + CesiumMath, + Matrix4, + OrthographicFrustum, + OrthographicOffCenterFrustum, + Transforms, + SceneMode) { '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. + * Functions that do scene-dependent transforms between rendering-related coordinate systems. * - * @alias PolylineVolumeGraphics - * @constructor + * @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 {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. + * @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. * - * @see Entity - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo} + * @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); */ - 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(); + SceneTransforms.wgs84ToWindowCoordinates = function(scene, position, result) { + return SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(scene, position, Cartesian3.ZERO, result); + }; - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + 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); } - 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; - } - }, + var scratchMaxCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO); + var scratchProjectedCartesian = new Cartesian3(); + var scratchCameraPosition = new Cartesian3(); - /** - * Gets or sets the boolean Property specifying the visibility of the volume. - * @memberof PolylineVolumeGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + /** + * @private + */ + SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates = function(scene, position, eyeOffset, result) { + + // Transform for 3D, 2D, or Columbus view + var frameState = scene.frameState; + var actualPosition = SceneTransforms.computeActualWgs84Position(frameState, position, actualPositionScratch); - /** - * Gets or sets the Property specifying the material used to fill the volume. - * @memberof PolylineVolumeGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE - */ - material : createMaterialPropertyDescriptor('material'), + if (!defined(actualPosition)) { + return undefined; + } - /** - * 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'), + // 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; - /** - * 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'), + var camera = scene.camera; + var cameraCentered = false; - /** - * 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'), + if (frameState.mode === SceneMode.SCENE2D) { + var projection = scene.mapProjection; + var maxCartographic = scratchMaxCartographic; + var maxCoord = projection.project(maxCartographic, scratchProjectedCartesian); - /** - * 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'), + var cameraPosition = Cartesian3.clone(camera.position, scratchCameraPosition); + var frustum = camera.frustum.clone(); - /** - * Gets or sets the Property specifying whether the volume is outlined. - * @memberof PolylineVolumeGraphics.prototype - * @type {Property} - * @default false - */ - outline : createPropertyDescriptor('outline'), + var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, new Matrix4()); + var projectionMatrix = camera.frustum.projectionMatrix; - /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof PolylineVolumeGraphics.prototype - * @type {Property} - * @default Color.BLACK - */ - outlineColor : createPropertyDescriptor('outlineColor'), + 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); - /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof PolylineVolumeGraphics.prototype - * @type {Property} - * @default 1.0 - */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + 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; - /** - * Gets or sets the {@link CornerType} Property specifying the style of the corners. - * @memberof PolylineVolumeGraphics.prototype - * @type {Property} - * @default CornerType.ROUNDED - */ - cornerType : createPropertyDescriptor('cornerType'), + camera.frustum.right = maxCoord.x - x; - /** - * 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'), + positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); + SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord0); - /** - * 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') - }); + viewport.x += windowCoordinates.x; - /** - * 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; - }; + camera.position.x = -camera.position.x; - /** - * 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) { - - 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); - }; + var right = camera.frustum.right; + camera.frustum.right = -camera.frustum.left; + camera.frustum.left = -right; - return PolylineVolumeGraphics; -}); + positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); + SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord1); + } else { + viewport.x += windowCoordinates.x; + viewport.width -= windowCoordinates.x; -/*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'; + camera.frustum.left = -maxCoord.x - x; - /** - * 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(); + positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); + SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord0); - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); - } + viewport.x = viewport.x - viewport.width; - 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; - } - }, + camera.position.x = -camera.position.x; - /** - * Gets or sets the boolean Property specifying the visibility of the rectangle. - * @memberof RectangleGraphics.prototype - * @type {Property} - * @default true - */ - show : createPropertyDescriptor('show'), + var left = camera.frustum.left; + camera.frustum.left = -camera.frustum.right; + camera.frustum.right = -left; - /** - * Gets or sets the Property specifying the {@link Rectangle}. - * @memberof RectangleGraphics.prototype - * @type {Property} - */ - coordinates : createPropertyDescriptor('coordinates'), + positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC); + SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord1); + } - /** - * Gets or sets the Property specifying the material used to fill the rectangle. - * @memberof RectangleGraphics.prototype - * @type {MaterialProperty} - * @default Color.WHITE - */ - material : createMaterialPropertyDescriptor('material'), + Cartesian3.clone(cameraPosition, camera.position); + camera.frustum = frustum.clone(); - /** - * Gets or sets the numeric Property specifying the altitude of the rectangle. - * @memberof RectangleGraphics.prototype - * @type {Property} - * @default 0.0 - */ - height : createPropertyDescriptor('height'), + result = Cartesian2.clone(scratchWindowCoord0, result); + if (result.x < 0.0 || result.x > canvas.clientWidth) { + result.x = scratchWindowCoord1.x; + } + } + } - /** - * 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'), + 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 && !(camera.frustum instanceof OrthographicFrustum) && !(camera.frustum instanceof OrthographicOffCenterFrustum)) { + return undefined; + } - /** - * 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'), + result = SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, result); + } - /** - * 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'), + result.y = canvas.clientHeight - result.y; + return result; + }; - /** - * 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'), + /** + * 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; + } - /** - * 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'), + return SceneTransforms.transformWindowToDrawingBuffer(scene, result, result); + }; - /** - * Gets or sets the Property specifying whether the rectangle is outlined. - * @memberof RectangleGraphics.prototype - * @type {Property} - * @default false - */ - outline : createPropertyDescriptor('outline'), + 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; + } - /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof RectangleGraphics.prototype - * @type {Property} - * @default Color.BLACK - */ - outlineColor : createPropertyDescriptor('outlineColor'), + projection.project(cartographic, projectedPosition); - /** - * Gets or sets the numeric Property specifying the width of the outline. - * @memberof RectangleGraphics.prototype - * @type {Property} - * @default 1.0 - */ - outlineWidth : createPropertyDescriptor('outlineWidth'), + if (mode === SceneMode.COLUMBUS_VIEW) { + return Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, result); + } - /** - * 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'), + if (mode === SceneMode.SCENE2D) { + return Cartesian3.fromElements(0.0, projectedPosition.x, projectedPosition.y, result); + } - /** - * 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'), + // 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); + }; - /** - * 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'), + var positionNDC = new Cartesian3(); + var positionWC = new Cartesian3(); + var viewportTransform = new Matrix4(); - /** - * 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') - }); + /** + * @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); + }; /** - * 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. + * @private */ - 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; + 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(); + /** - * 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. + * @private */ - RectangleGraphics.prototype.merge = function(source) { - - 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); + 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; + var frustum = scene.camera.frustum; + if (!defined(frustum.fovy)) { + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } + var currentFrustum = uniformState.currentFrustum; + worldCoords = scratchWorldCoords; + worldCoords.x = (ndc.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; + worldCoords.y = (ndc.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; + worldCoords.z = (ndc.z * (currentFrustum.x - currentFrustum.y) - currentFrustum.x - currentFrustum.y) * 0.5; + worldCoords.w = 1.0; + + worldCoords = Matrix4.multiplyByVector(uniformState.inverseView, worldCoords, worldCoords); + } else { + 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 RectangleGraphics; + return SceneTransforms; }); -/*global define*/ -define('DataSources/WallGraphics',[ +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/Event', - './createMaterialPropertyDescriptor', - './createPropertyDescriptor' + '../Core/DistanceDisplayCondition', + '../Core/Matrix4', + '../Core/NearFarScalar', + './HeightReference', + './HorizontalOrigin', + './SceneMode', + './SceneTransforms', + './VerticalOrigin' ], function( + BoundingRectangle, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, + Color, + createGuid, defaultValue, defined, defineProperties, DeveloperError, - Event, - createMaterialPropertyDescriptor, - createPropertyDescriptor) { + DistanceDisplayCondition, + Matrix4, + NearFarScalar, + HeightReference, + HorizontalOrigin, + SceneMode, + SceneTransforms, + VerticalOrigin) { '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. + * 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 WallGraphics - * @constructor + * @alias Billboard * - * @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. + * @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. * - * @see Entity - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo} + * @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 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(); + function Billboard(options, billboardCollection) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + + var translucencyByDistance = options.translucencyByDistance; + var pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; + var scaleByDistance = options.scaleByDistance; + var distanceDisplayCondition = options.distanceDisplayCondition; + if (defined(translucencyByDistance)) { + translucencyByDistance = NearFarScalar.clone(translucencyByDistance); + } + if (defined(pixelOffsetScaleByDistance)) { + pixelOffsetScaleByDistance = NearFarScalar.clone(pixelOffsetScaleByDistance); + } + if (defined(scaleByDistance)) { + scaleByDistance = NearFarScalar.clone(scaleByDistance); + } + if (defined(distanceDisplayCondition)) { + distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); + } + + 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 = scaleByDistance; + this._translucencyByDistance = translucencyByDistance; + this._pixelOffsetScaleByDistance = pixelOffsetScaleByDistance; + this._sizeInMeters = defaultValue(options.sizeInMeters, false); + this._distanceDisplayCondition = distanceDisplayCondition; + this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); + 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(); } - defineProperties(WallGraphics.prototype, { + 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; + var DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE = 15; + Billboard.NUMBER_OF_PROPERTIES = 16; + + function makeDirty(billboard, propertyChanged) { + var billboardCollection = billboard._billboardCollection; + if (defined(billboardCollection)) { + billboardCollection._updateBillboard(billboard, propertyChanged); + billboard._dirty = true; + } + } + + defineProperties(Billboard.prototype, { /** - * Gets the event that is raised whenever a property or sub-property is changed or modified. - * @memberof WallGraphics.prototype - * - * @type {Event} - * @readonly + * 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 */ - definitionChanged : { + show : { get : function() { - return this._definitionChanged; + return this._show; + }, + set : function(value) { + + if (this._show !== value) { + this._show = value; + makeDirty(this, SHOW_INDEX); + } } }, /** - * 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 Cartesian position of this billboard. + * @memberof Billboard.prototype + * @type {Cartesian3} + */ + position : { + get : function() { + return this._position; + }, + set : function(value) { + + 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 Property specifying the array of {@link Cartesian3} positions which define the top of the wall. - * @memberof WallGraphics.prototype - * @type {Property} + * Gets or sets the height reference of this billboard. + * @memberof Billboard.prototype + * @type {HeightReference} + * @default HeightReference.NONE */ - positions : createPropertyDescriptor('positions'), + heightReference : { + get : function() { + return this._heightReference; + }, + set : function(value) { + + var heightReference = this._heightReference; + if (value !== heightReference) { + this._heightReference = value; + this._updateClamping(); + makeDirty(this, POSITION_INDEX); + } + } + }, /** - * 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} + * 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} */ - minimumHeights : createPropertyDescriptor('minimumHeights'), + pixelOffset : { + get : function() { + return this._pixelOffset; + }, + set : function(value) { + + var pixelOffset = this._pixelOffset; + if (!Cartesian2.equals(pixelOffset, value)) { + Cartesian2.clone(value, pixelOffset); + makeDirty(this, PIXEL_OFFSET_INDEX); + } + } + }, /** - * 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} + * 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; */ - maximumHeights : createPropertyDescriptor('maximumHeights'), + scaleByDistance : { + get : function() { + return this._scaleByDistance; + }, + set : function(value) { + + var scaleByDistance = this._scaleByDistance; + if (!NearFarScalar.equals(scaleByDistance, value)) { + this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance); + makeDirty(this, SCALE_BY_DISTANCE_INDEX); + } + } + }, /** - * 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} + * 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; */ - granularity : createPropertyDescriptor('granularity'), + translucencyByDistance : { + get : function() { + return this._translucencyByDistance; + }, + set : function(value) { + + 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 boolean Property specifying whether the wall is filled with the provided material. - * @memberof WallGraphics.prototype - * @type {Property} - * @default true + * 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; */ - fill : createPropertyDescriptor('fill'), + pixelOffsetScaleByDistance : { + get : function() { + return this._pixelOffsetScaleByDistance; + }, + set : function(value) { + + 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 Property specifying whether the wall is outlined. - * @memberof WallGraphics.prototype - * @type {Property} - * @default false + * 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} */ - outline : createPropertyDescriptor('outline'), + eyeOffset : { + get : function() { + return this._eyeOffset; + }, + set : function(value) { + + var eyeOffset = this._eyeOffset; + if (!Cartesian3.equals(eyeOffset, value)) { + Cartesian3.clone(value, eyeOffset); + makeDirty(this, EYE_OFFSET_INDEX); + } + } + }, /** - * Gets or sets the Property specifying the {@link Color} of the outline. - * @memberof WallGraphics.prototype - * @type {Property} - * @default Color.BLACK + * 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; */ - outlineColor : createPropertyDescriptor('outlineColor'), + horizontalOrigin : { + get : function() { + return this._horizontalOrigin; + }, + set : function(value) { + + if (this._horizontalOrigin !== value) { + this._horizontalOrigin = value; + makeDirty(this, HORIZONTAL_ORIGIN_INDEX); + } + } + }, /** - * 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 + * 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; */ - shadows : createPropertyDescriptor('shadows'), + verticalOrigin : { + get : function() { + return this._verticalOrigin; + }, + set : function(value) { + + if (this._verticalOrigin !== value) { + this._verticalOrigin = value; + makeDirty(this, VERTICAL_ORIGIN_INDEX); + } + } + }, /** - * 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} + * 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} */ - 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) { - - 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', - './PropertyBag', - './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, - PropertyBag, - 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; + scale : { + get : function() { + return this._scale; + }, + set : function(value) { + + if (this._scale !== value) { + this._scale = value; + makeDirty(this, SCALE_INDEX); + } } - 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 {PropertyBag} [options.properties] Arbitrary properties 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', 'properties', '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._properties = undefined; - this._propertiesSubscription = 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} + * 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); */ - 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); - } + color : { + get : function() { + return this._color; + }, + set : function(value) { + + var color = this._color; + if (!Color.equals(color, value)) { + Color.clone(value, color); + makeDirty(this, COLOR_INDEX); + } + } + }, - 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} + * Gets or sets the rotation angle in radians. + * @memberof Billboard.prototype + * @type {Number} */ - id : { + rotation : { get : function() { - return this._id; + return this._rotation; + }, + set : function(value) { + + if (this._rotation !== value) { + this._rotation = value; + makeDirty(this, ROTATION_INDEX); + } } }, + /** - * Gets the event that is raised whenever a property or sub-property is changed or modified. - * @memberof Entity.prototype + * 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; * - * @type {Event} - * @readonly + * @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; */ - definitionChanged : { + alignedAxis : { get : function() { - return this._definitionChanged; + return this._alignedAxis; + }, + set : function(value) { + + var alignedAxis = this._alignedAxis; + if (!Cartesian3.equals(alignedAxis, value)) { + Cartesian3.clone(value, alignedAxis); + makeDirty(this, ALIGNED_AXIS_INDEX); + } } }, + /** - * 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} + * Gets or sets a width for the billboard. If undefined, the image width will be used. + * @memberof Billboard.prototype + * @type {Number} */ - show : { + width : { get : function() { - return this._show; + return defaultValue(this._width, this._imageWidth); }, set : function(value) { - - if (value === this._show) { - return; + if (this._width !== value) { + this._width = value; + makeDirty(this, IMAGE_INDEX_INDEX); } + } + }, - var wasShowing = this.isShowing; - this._show = value; - var isShowing = this.isShowing; - - if (wasShowing !== isShowing) { - updateShow(this, this._children, isShowing); + /** + * 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); } - - 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 + * 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 */ - isShowing : { + sizeInMeters : { get : function() { - return this._show && (!defined(this.entityCollection) || this.entityCollection.show) && (!defined(this._parent) || this._parent.isShowing); + return this._sizeInMeters; + }, + set : function(value) { + if (this._sizeInMeters !== value) { + this._sizeInMeters = value; + makeDirty(this, COLOR_INDEX); + } } }, + /** - * Gets or sets the parent object. - * @memberof Entity.prototype - * @type {Entity} + * 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 */ - parent : { + distanceDisplayCondition : { get : function() { - return this._parent; + return this._distanceDisplayCondition; }, 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); + if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + makeDirty(this, DISTANCE_DISPLAY_CONDITION); } - - this._definitionChanged.raiseEvent(this, 'parent', value, oldValue); } }, + /** - * Gets the names of all properties registered on this instance. - * @memberof Entity.prototype - * @type {Array} + * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. + * @memberof Billboard.prototype + * @type {Number} + * @default 0.0 */ - propertyNames : { + disableDepthTestDistance : { get : function() { - return this._propertyNames; + return this._disableDepthTestDistance; + }, + set : function(value) { + if (this._disableDepthTestDistance !== value) { + this._disableDepthTestDistance = value; + makeDirty(this, DISABLE_DEPTH_DISTANCE); + } } }, + /** - * 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} + * Gets or sets the user-defined object returned when the billboard is picked. + * @memberof Billboard.prototype + * @type {Object} */ - polylineVolume : createPropertyTypeDescriptor('polylineVolume', PolylineVolumeGraphics), + 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 bag of arbitrary properties associated with this entity. - * @memberof Entity.prototype - * @type {PropertyBag} + * The primitive to return when picking this billboard. + * @memberof Billboard.prototype + * @private */ - properties : createPropertyTypeDescriptor('properties', PropertyBag), + 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 position. - * @memberof Entity.prototype - * @type {PositionProperty} + *

    + * 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; */ - position : createPositionPropertyDescriptor('position'), + 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); + } + } + }, + /** - * Gets or sets the rectangle. - * @memberof Entity.prototype - * @type {RectangleGraphics} + * 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 */ - rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics), + ready : { + get : function() { + return this._imageIndex !== -1; + } + }, + /** - * 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} + * Keeps track of the position of the billboard based on the height reference. + * @memberof Billboard.prototype + * @type {Cartesian3} + * @private */ - viewFrom : createPropertyDescriptor('viewFrom'), + _clampedPosition : { + get : function() { + return this._actualClampedPosition; + }, + set : function(value) { + this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition); + makeDirty(this, POSITION_INDEX); + } + }, + /** - * Gets or sets the wall. - * @memberof Entity.prototype - * @type {WallGraphics} + * Determines whether or not this billboard will be shown or hidden because it was clustered. + * @memberof Billboard.prototype + * @type {Boolean} + * @private */ - wall : createPropertyTypeDescriptor('wall', WallGraphics) + 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)) { + 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)) { + owner._actualClampedPosition = undefined; + 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); + }; + + // acevedo - handle default emp icon and dynamic icons + Billboard.prototype._loadImage = function() { + + var atlas = this._billboardCollection._textureAtlas; + var isDynamic = false; + if (this._id && this.isDynamic) + { + this._imageId = this._id; + isDynamic = true; + } + var imageId = this._imageId; + var image = this._image; + var imageSubRegion = this._imageSubRegion; + var imageIndexPromise; + if (defined(image) && isDynamic) + { + imageIndexPromise = atlas.updateImage(imageId, image); + } + else 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; + that._id = this._id; + 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; + } +// else if (that._imageId === emp.urlProxy + "?" + emp.utilities.getDefaultIcon().iconUrl) +// { +// 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) { + /*global console*/ + var atlas = that._billboardCollection._textureAtlas; + var imageId = that._imageId; + var image = that._image; + var imageSubRegion = that._imageSubRegion; + var imageIndexPromise2; + //acevedo - next flag bilboard that failed to load image. + that.imageLoaded = false; + //stat acevedo edit + //add default icon when Cesium failed to load billboard icon + //if (!(that._imageId === emp.urlProxy + "?url=" + emp.utilities.getDefaultIcon().iconUrl) && emp.util.config.getUseProxySetting() ) + // { + // using proxy + //storeUrlNotAccessible(that._imageId); // store original imageId +// that._imageId = emp.urlProxy + "?url=" + emp.utilities.getDefaultIcon().iconUrl; +// that._image = emp.urlProxy + "?url=" + emp.utilities.getDefaultIcon().iconUrl; +// //that._image = new Cesium.ConstantProperty(empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl); +// that._imageWidth = emp.utilities.getDefaultIcon().offset.width; +// that._imageHeight = emp.utilities.getDefaultIcon().offset.height; +// that.pixelOffset = new Cesium.Cartesian2(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y); +// //that._pixelOffset = new Cesium.Cartesian2(isNaN(that._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000)); +// that._alignedAxis = Cesium.Cartesian3.ZERO; +// that._verticalOrigin = Cesium.VerticalOrigin.BOTTOM; +// that._imageIndexPromise = undefined; +// //that._loadImage(); + // } +// else if (!(that._imageId === emp.utilities.getDefaultIcon().iconUrl) && !emp.util.config.getUseProxySetting()) +// { + // not using proxy + //storeUrlNotAccessible(that._imageId); // store original imageId + that._imageId = emp.utilities.getDefaultIcon().iconUrl; + that._image = emp.utilities.getDefaultIcon().iconUrl; + //that._image = new Cesium.ConstantProperty(empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl); + that._imageWidth = emp.utilities.getDefaultIcon().offset.width; + that._imageHeight = emp.utilities.getDefaultIcon().offset.height; + that.pixelOffset = new Cesium.Cartesian2(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y); + //that._pixelOffset = new Cesium.Cartesian2(isNaN(that._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000)); + that._alignedAxis = Cesium.Cartesian3.ZERO; + that._verticalOrigin = Cesium.VerticalOrigin.BOTTOM; + that._imageIndexPromise = undefined; + //that._loadImage(); + //} + if (defined(image)) { + imageIndexPromise2 = atlas.addImage(that._imageId, that._image); + } + if (defined(imageSubRegion)) { + imageIndexPromise2 = atlas.addSubRegion(that._imageId, that._imageSubRegion); + } + + that._imageIndexPromise = imageIndexPromise2; + + if (!defined(imageIndexPromise2)) { + return; + } + + var that2 = that; + that2._id = that._id; + imageIndexPromise2.then(function(index) { + var textureCoordinates = atlas.textureCoordinates[index]; + that2._imageWidth = emp.utilities.getDefaultIcon().offset.width; + that2._imageHeight = emp.utilities.getDefaultIcon().offset.height; + that2.pixelOffset = new Cesium.Cartesian2(isNaN(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y)); + //that2._pixelOffset = new Cesium.Cartesian2(isNaN(that2._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that2._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000)); + that2.imageLoaded = true; + that2._alignedAxis = Cesium.Cartesian3.ZERO; + that2._verticalOrigin = Cesium.VerticalOrigin.BOTTOM; + that2._imageIndex = index; + that2._ready = true; + that2._image = undefined; + that2._imageIndexPromise = undefined; + + makeDirty(that2, IMAGE_INDEX_INDEX); + }).otherwise(function(error) { + /*global console*/ + console.error('Error loading image for billboard: ' + error); + that2._imageIndexPromise = undefined; + that2.imageLoaded = false; + }); + + + }); + }; + + + /* 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; + }); + }; */ + /** - * Given a time, returns true if this object should have data during that time. + *

    + * 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 {JulianDate} time The time to check availability for. - * @returns {Boolean} true if the object should have data during the provided time, false otherwise. + * @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); */ - Entity.prototype.isAvailable = function(time) { + Billboard.prototype.setImage = function(id, image) { - var availability = this._availability; - return !defined(availability) || availability.contains(time); + if (this._imageId === id) { + return; + } + + this._imageIndex = -1; + this._imageSubRegion = undefined; + this._imageId = id; + this._image = image; + + if (defined(this._billboardCollection._textureAtlas)) { + this._loadImage(); + } }; /** - * Adds a property to this object. Once a property is added, it can be - * observed with {@link Entity#definitionChanged} and composited - * with {@link CompositeEntityCollection} + * 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} propertyName The name of the property to add. + * @param {String} id The id of the image to use. + * @param {BoundingRectangle} subRegion The sub-region of the image. * - * @exception {DeveloperError} "propertyName" is a reserved property name. - * @exception {DeveloperError} "propertyName" is already a registered property. + * @exception {RuntimeError} image with id must be in the atlas */ - Entity.prototype.addProperty = function(propertyName) { - var propertyNames = this._propertyNames; + Billboard.prototype.setImageSubRegion = function(id, subRegion) { + + 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) { - propertyNames.push(propertyName); - Object.defineProperty(this, propertyName, createRawPropertyDescriptor(propertyName, true)); + 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); + /** - * Removed a property previously added with addProperty. + * 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 {String} propertyName The name of the property to remove. + * @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} "propertyName" is a reserved property name. - * @exception {DeveloperError} "propertyName" is not a registered property. + * @exception {DeveloperError} Billboard must be in a collection. + * + * @example + * console.log(b.computeScreenSpacePosition(scene).toString()); + * + * @see Billboard#eyeOffset + * @see Billboard#pixelOffset */ - Entity.prototype.removeProperty = function(propertyName) { - var propertyNames = this._propertyNames; - var index = propertyNames.indexOf(propertyName); + Billboard.prototype.computeScreenSpacePosition = function(scene, result) { + var billboardCollection = this._billboardCollection; + if (!defined(result)) { + result = new Cartesian2(); + } - this._propertyNames.splice(index, 1); - delete this[propertyName]; + // pixel offset for screen space computation is the pixelOffset + screen space translate + Cartesian2.clone(this._pixelOffset, scratchPixelOffset); + Cartesian2.add(scratchPixelOffset, this._translate, scratchPixelOffset); + + var modelMatrix = billboardCollection.modelMatrix; + var position = this._position; + if (defined(this._clampedPosition)) { + position = this._clampedPosition; + if (scene.mode !== SceneMode.SCENE3D) { + // position needs to be in world coordinates + var projection = scene.mapProjection; + var ellipsoid = projection.ellipsoid; + var cart = projection.unproject(position, scratchCartographic); + position = ellipsoid.cartographicToCartesian(cart, scratchCartesian3); + modelMatrix = Matrix4.IDENTITY; + } + } + + var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, position, + this._eyeOffset, scratchPixelOffset, scene, result); + return windowCoordinates; }; /** - * Assigns each unassigned property on this object to the value - * of the same property on the provided source object. + * 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. * - * @param {Entity} source The object to be merged into this object. + * @private */ - Entity.prototype.merge = function(source) { - - //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]; + Billboard.getScreenSpaceBoundingBox = function(billboard, screenSpacePosition, result) { + var width = billboard.width; + var height = billboard.height; - //Ignore parent when merging, this only happens at construction time. - if (name === 'parent') { - continue; - } + var scale = billboard.scale; + width *= scale; + height *= scale; - var targetProperty = this[name]; - var sourceProperty = source[name]; + var x = screenSpacePosition.x; + if (billboard.horizontalOrigin === HorizontalOrigin.RIGHT) { + x -= width; + } else if (billboard.horizontalOrigin === HorizontalOrigin.CENTER) { + x -= width * 0.5; + } - //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); - } + 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(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; - } - } + if (!defined(result)) { + result = new BoundingRectangle(); } - }; - var matrix3Scratch = new Matrix3(); - var positionScratch = new Cartesian3(); - var orientationScratch = new Quaternion(); + result.x = x; + result.y = y; + result.width = width; + result.height = height; + + return result; + }; /** - * @private + * 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. */ - Entity.prototype._getModelMatrix = function(time, result) { - var position = Property.getValueOrUndefined(this._position, time, positionScratch); - if (!defined(position)) { - return undefined; + 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) && + this._disableDepthTestDistance === other._disableDepthTestDistance; + }; + + Billboard.prototype._destroy = function() { + if (defined(this._customData)) { + this._billboardCollection._scene.globe._surface.removeTileCustomData(this._customData); + this._customData = 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); + + if (defined(this._removeCallbackFunc)) { + this._removeCallbackFunc(); + this._removeCallbackFunc = undefined; } - return result; + + this.image = undefined; + this._pickId = this._pickId && this._pickId.destroy(); + this._billboardCollection = undefined; }; - return Entity; + /** + * 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('DataSources/EntityCollection',[ - '../Core/AssociativeArray', - '../Core/createGuid', +define('Renderer/VertexArrayFacade',[ + '../Core/ComponentDatatype', + '../Core/defaultValue', '../Core/defined', - '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Event', - '../Core/Iso8601', - '../Core/JulianDate', - '../Core/RuntimeError', - '../Core/TimeInterval', - './Entity' + '../Core/Math', + './Buffer', + './BufferUsage', + './VertexArray' ], function( - AssociativeArray, - createGuid, + ComponentDatatype, + defaultValue, defined, - defineProperties, + destroyObject, DeveloperError, - Event, - Iso8601, - JulianDate, - RuntimeError, - TimeInterval, - Entity) { + CesiumMath, + Buffer, + BufferUsage, + VertexArray) { 'use strict'; - var entityOptionsScratch = { - id : undefined + /** + * @private + */ + function VertexArrayFacade(context, attributes, sizeInVertices, instanced) { + + 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); + + } + + // 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; + uniqueIndices[index] = true; + } + + return attrs; }; - function fireChangedEvent(collection) { - if (collection._firing) { - collection._refire = true; - return; + 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)); } - 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); + 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 + }); - added.removeAll(); - removed.removeAll(); - changed.removeAll(); - collection._collectionChanged.raiseEvent(collection, addedArray, removedArray, changedArray); - } while (collection._refire); - collection._firing = false; - } + offsetInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(componentDatatype)); } - } - - /** - * 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++; + return views; }; /** - * 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. + * Invalidates writers. Can't render again until commit is called. */ - EntityCollection.prototype.resumeEvents = function() { - - this._suspendCount--; - fireChangedEvent(this); + 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); }; - /** - * 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; + VertexArrayFacade._resize = function(buffer, size) { + if (buffer.vertexSizeInBytes > 0) { + // Create larger array buffer + var arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes); - 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; + // 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]; + } } - }, - /** - * 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; + + // 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); } - }, - /** - * 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 (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(); + buffer.arrayBuffer = arrayBuffer; + } + }; - var i; - var oldShows = []; - var entities = this._entities.values; - var entitiesLength = entities.length; + var createWriters = [ + // 1 component per attribute + function(buffer, view, vertexSizeInComponentType) { + return function(index, attribute) { + view[index * vertexSizeInComponentType] = attribute; + buffer.needsCommit = true; + }; + }, - for (i = 0; i < entitiesLength; i++) { - oldShows.push(entities[i].isShowing); - } + // 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; + }; + }, - this._show = value; + // 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; + }; + }, - 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); - } + // 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); } - 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; + 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). + }); } } - }); + }; - /** - * 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; + 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 } - } - if (Iso8601.MAXIMUM_VALUE.equals(startTime)) { - startTime = Iso8601.MINIMUM_VALUE; + buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer); } - if (Iso8601.MINIMUM_VALUE.equals(stopTime)) { - stopTime = Iso8601.MAXIMUM_VALUE; + + 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 + }); } - 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) { + VertexArrayFacade.prototype.subCommit = function(offsetInVertices, lengthInVertices) { - 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.'); + var allBuffers = this._allBuffers; + for (var i = 0, len = allBuffers.length; i < len; ++i) { + subCommit(allBuffers[i], offsetInVertices, lengthInVertices); } + }; - entity.entityCollection = this; - entities.set(id, entity); + function subCommit(buffer, offsetInVertices, lengthInVertices) { + if (buffer.needsCommit && (buffer.vertexSizeInBytes > 0)) { + var byteOffset = buffer.vertexSizeInBytes * offsetInVertices; + var byteLength = buffer.vertexSizeInBytes * lengthInVertices; - if (!this._removedEntities.remove(id)) { - this._addedEntities.set(id, entity); + // 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); } - entity.definitionChanged.addEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this); + } - fireChangedEvent(this); - return entity; - }; + VertexArrayFacade.prototype.endSubCommits = function() { + var allBuffers = this._allBuffers; - /** - * 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; + for (var i = 0, len = allBuffers.length; i < len; ++i) { + allBuffers[i].needsCommit = 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) { - 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; + function destroyVA(vertexArrayFacade) { + var va = vertexArrayFacade.va; + if (!defined(va)) { + return; } - var entities = this._entities; - var entity = entities.get(id); - if (!this._entities.remove(id)) { - return false; + var length = va.length; + for (var i = 0; i < length; ++i) { + va[i].va.destroy(); } - 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); + vertexArrayFacade.va = undefined; + } - return true; + VertexArrayFacade.prototype.isDestroyed = function() { + return false; }; - /** - * 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); - } + 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(); } - entities.removeAll(); - addedEntities.removeAll(); - this._changedEntities.removeAll(); - fireChangedEvent(this); - }; + destroyVA(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) { - - return this._entities.get(id); + return destroyObject(this); }; + return VertexArrayFacade; +}); + +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/BillboardCollectionFS',[],function() { + 'use strict'; + return "uniform sampler2D u_atlas;\n\ +\n\ +varying vec2 v_textureCoordinates;\n\ +\n\ +#ifdef RENDER_FOR_PICK\n\ +varying vec4 v_pickColor;\n\ +#else\n\ +varying vec4 v_color;\n\ +#endif\n\ +\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\ +\n\ + vec4 color = texture2D(u_atlas, v_textureCoordinates) * vertexColor;\n\ +\n\ +// Fully transparent parts of the billboard are not pickable.\n\ +#if defined(RENDER_FOR_PICK) || (!defined(OPAQUE) && !defined(TRANSLUCENT))\n\ + if (color.a < 0.005) // matches 0/255 and 1/255\n\ + {\n\ + discard;\n\ + }\n\ +#else\n\ +// The billboard is rendered twice. The opaque pass discards translucent fragments\n\ +// and the translucent pass discards opaque fragments.\n\ +#ifdef OPAQUE\n\ + if (color.a < 0.995) // matches < 254/255\n\ + {\n\ + discard;\n\ + }\n\ +#else\n\ + if (color.a >= 0.995) // matches 254/255 and 255/255\n\ + {\n\ + discard;\n\ + }\n\ +#endif\n\ +#endif\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. +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; // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates (texture offset)\n\ +attribute vec4 compressedAttribute1; // aligned axis, translucency by distance, image width\n\ +attribute vec4 compressedAttribute2; // image height, color, pick color, size in meters, valid aligned axis, 13 bits free\n\ +attribute vec4 eyeOffset; // eye offset in meters, 4 bytes free (texture range)\n\ +attribute vec4 scaleByDistance; // near, nearScale, far, farScale\n\ +attribute vec4 pixelOffsetScaleByDistance; // near, nearScale, far, farScale\n\ +attribute vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance\n\ +\n\ +varying vec2 v_textureCoordinates;\n\ +\n\ +#ifdef RENDER_FOR_PICK\n\ +varying vec4 v_pickColor;\n\ +#else\n\ +varying vec4 v_color;\n\ +#endif\n\ +\n\ +const float UPPER_BOUND = 32768.0;\n\ +\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\ +\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\ +\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\ + // Note the halfSize cannot be computed in JavaScript because it is sent via\n\ + // compressed vertex attributes that coerce it to an integer.\n\ + vec2 halfSize = imageSize * scale * czm_resolutionScale * 0.5;\n\ + halfSize *= ((direction * 2.0) - 1.0);\n\ +\n\ + vec2 originTranslate = origin * abs(halfSize);\n\ +\n\ +#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\ + if (validAlignedAxis || rotation != 0.0)\n\ + {\n\ + float angle = rotation;\n\ + if (validAlignedAxis)\n\ + {\n\ + vec4 projectedAlignedAxis = czm_modelViewProjection * vec4(alignedAxis, 0.0);\n\ + angle += sign(-projectedAlignedAxis.x) * acos( sign(projectedAlignedAxis.y) * (projectedAlignedAxis.y * projectedAlignedAxis.y) /\n\ + (projectedAlignedAxis.x * projectedAlignedAxis.x + projectedAlignedAxis.y * projectedAlignedAxis.y) );\n\ + }\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\ +\n\ + if (sizeInMeters)\n\ + {\n\ + positionEC.xy += halfSize;\n\ + }\n\ +\n\ + vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\ +\n\ + if (sizeInMeters)\n\ + {\n\ + originTranslate /= czm_metersPerPixel(positionEC);\n\ + }\n\ +\n\ + positionWC.xy += originTranslate;\n\ + if (!sizeInMeters)\n\ + {\n\ + positionWC.xy += halfSize;\n\ + }\n\ +\n\ + positionWC.xy += translate;\n\ + positionWC.xy += (pixelOffset * czm_resolutionScale);\n\ +\n\ + return positionWC;\n\ +}\n\ +\n\ +void main()\n\ +{\n\ + // Modifying this shader may also require modifications to Billboard._computeScreenSpacePosition\n\ +\n\ + // unpack attributes\n\ + vec3 positionHigh = positionHighAndScale.xyz;\n\ + vec3 positionLow = positionLowAndRotation.xyz;\n\ + float scale = positionHighAndScale.w;\n\ +\n\ +#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\ + float rotation = positionLowAndRotation.w;\n\ +#else\n\ + float rotation = 0.0;\n\ +#endif\n\ +\n\ + float compressed = compressedAttribute0.x;\n\ +\n\ + vec2 pixelOffset;\n\ + pixelOffset.x = floor(compressed * SHIFT_RIGHT7);\n\ + compressed -= pixelOffset.x * SHIFT_LEFT7;\n\ + pixelOffset.x -= UPPER_BOUND;\n\ +\n\ + vec2 origin;\n\ + origin.x = floor(compressed * SHIFT_RIGHT5);\n\ + compressed -= origin.x * SHIFT_LEFT5;\n\ +\n\ + origin.y = floor(compressed * SHIFT_RIGHT3);\n\ + compressed -= origin.y * SHIFT_LEFT3;\n\ +\n\ + origin -= vec2(1.0);\n\ +\n\ + float show = floor(compressed * SHIFT_RIGHT2);\n\ + compressed -= show * SHIFT_LEFT2;\n\ +\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\ +\n\ + vec2 textureCoordinates = czm_decompressTextureCoordinates(compressedAttribute0.w);\n\ +#endif\n\ +\n\ + float temp = compressedAttribute0.y * SHIFT_RIGHT8;\n\ + pixelOffset.y = -(floor(temp) - UPPER_BOUND);\n\ +\n\ + vec2 translate;\n\ + translate.y = (temp - floor(temp)) * SHIFT_LEFT16;\n\ +\n\ + temp = compressedAttribute0.z * SHIFT_RIGHT8;\n\ + translate.x = floor(temp) - UPPER_BOUND;\n\ +\n\ + translate.y += (temp - floor(temp)) * SHIFT_LEFT8;\n\ + translate.y -= UPPER_BOUND;\n\ +\n\ + temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\ +\n\ + vec2 imageSize = vec2(floor(temp), compressedAttribute2.w);\n\ +\n\ +#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ + vec4 translucencyByDistance;\n\ + translucencyByDistance.x = compressedAttribute1.z;\n\ + translucencyByDistance.z = compressedAttribute1.w;\n\ +\n\ + translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ +\n\ + temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\ + translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ +#endif\n\ +\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\ +\n\ +#ifdef RENDER_FOR_PICK\n\ + temp = compressedAttribute2.y;\n\ +#else\n\ + temp = compressedAttribute2.x;\n\ +#endif\n\ +\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\ +\n\ + temp = compressedAttribute2.z * SHIFT_RIGHT8;\n\ + bool sizeInMeters = floor((temp - floor(temp)) * SHIFT_LEFT7) > 0.0;\n\ + temp = floor(temp) * SHIFT_RIGHT8;\n\ +\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\ +\n\ + ///////////////////////////////////////////////////////////////////////////\n\ +\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\ +\n\ + ///////////////////////////////////////////////////////////////////////////\n\ +\n\ +#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(EYE_DISTANCE_PIXEL_OFFSET) || defined(DISTANCE_DISPLAY_CONDITION) || defined(DISABLE_DEPTH_DISTANCE)\n\ + float lengthSq;\n\ + if (czm_sceneMode == czm_sceneMode2D)\n\ + {\n\ + // 2D camera distance is a special case\n\ + // treat all billboards as flattened to the z=0.0 plane\n\ + lengthSq = czm_eyeHeight2D.y;\n\ + }\n\ + else\n\ + {\n\ + lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\ + }\n\ +#endif\n\ +\n\ +#ifdef EYE_DISTANCE_SCALING\n\ + float distanceScale = czm_nearFarScalar(scaleByDistance, lengthSq);\n\ + scale *= distanceScale;\n\ + translate *= distanceScale;\n\ + // push vertex behind near plane for clipping\n\ + if (scale == 0.0)\n\ + {\n\ + positionEC.xyz = vec3(0.0);\n\ + }\n\ +#endif\n\ +\n\ + float translucency = 1.0;\n\ +#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ + translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\ + // push vertex behind near plane for clipping\n\ + if (translucency == 0.0)\n\ + {\n\ + positionEC.xyz = vec3(0.0);\n\ + }\n\ +#endif\n\ +\n\ +#ifdef EYE_DISTANCE_PIXEL_OFFSET\n\ + float pixelOffsetScale = czm_nearFarScalar(pixelOffsetScaleByDistance, lengthSq);\n\ + pixelOffset *= pixelOffsetScale;\n\ +#endif\n\ +\n\ +#ifdef DISTANCE_DISPLAY_CONDITION\n\ + float nearSq = distanceDisplayConditionAndDisableDepth.x;\n\ + float farSq = distanceDisplayConditionAndDisableDepth.y;\n\ + if (lengthSq < nearSq || lengthSq > farSq)\n\ + {\n\ + positionEC.xyz = vec3(0.0);\n\ + }\n\ +#endif\n\ +\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\ +\n\ +#ifdef DISABLE_DEPTH_DISTANCE\n\ + float disableDepthTestDistance = distanceDisplayConditionAndDisableDepth.z;\n\ + if (disableDepthTestDistance == 0.0 && czm_minimumDisableDepthTestDistance != 0.0)\n\ + {\n\ + disableDepthTestDistance = czm_minimumDisableDepthTestDistance;\n\ + }\n\ +\n\ + if (disableDepthTestDistance != 0.0)\n\ + {\n\ + // Don't try to \"multiply both sides\" by w. Greater/less-than comparisons won't work for negative values of w.\n\ + float zclip = gl_Position.z / gl_Position.w;\n\ + bool clipped = (zclip < -1.0 || zclip > 1.0);\n\ + if (!clipped && (disableDepthTestDistance < 0.0 || (lengthSq > 0.0 && lengthSq < disableDepthTestDistance)))\n\ + {\n\ + // Position z on the near plane.\n\ + gl_Position.z = -gl_Position.w;\n\ + }\n\ + }\n\ +#endif\n\ +\n\ +#ifdef RENDER_FOR_PICK\n\ + v_pickColor = pickColor;\n\ +#else\n\ + v_color = color;\n\ + v_color.a *= translucency;\n\ +#endif\n\ +}\n\ +"; +}); +define('Scene/BlendOption',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + /** - * Gets an entity with the specified id or creates it and adds it to the collection if it does not exist. + * Determines how opaque and translucent parts of billboards, points, and labels are blended with the scene. * - * @param {Object} id The id of the entity to retrieve or create. - * @returns {Entity} The new or existing object. + * @exports BlendOption */ - EntityCollection.prototype.getOrCreateEntity = function(id) { - - var entity = this._entities.get(id); - if (!defined(entity)) { - entityOptionsScratch.id = id; - entity = new Entity(entityOptionsScratch); - this.add(entity); - } - return entity; - }; + var BlendOption = { + /** + * The billboards, points, or labels in the collection are completely opaque. + * @type {Number} + * @constant + */ + OPAQUE : 0, - EntityCollection.prototype._onEntityDefinitionChanged = function(entity) { - var id = entity.id; - if (!this._addedEntities.contains(id)) { - this._changedEntities.set(id, entity); - } - fireChangedEvent(this); + /** + * The billboards, points, or labels in the collection are completely translucent. + * @type {Number} + * @constant + */ + TRANSLUCENT : 1, + + /** + * The billboards, points, or labels in the collection are both opaque and translucent. + * @type {Number} + * @constant + */ + OPAQUE_AND_TRANSLUCENT : 2 }; - return EntityCollection; + return freezeObject(BlendOption); }); -/*global define*/ -define('DataSources/CompositeEntityCollection',[ - '../Core/createGuid', +define('Renderer/Framebuffer',[ + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Math', - './Entity', - './EntityCollection' + '../Core/PixelFormat', + './ContextLimits' ], function( - createGuid, + defaultValue, defined, defineProperties, + destroyObject, DeveloperError, - CesiumMath, - Entity, - EntityCollection) { + PixelFormat, + ContextLimits) { '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 attachTexture(framebuffer, attachment, texture) { + var gl = framebuffer._gl; + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, texture._target, texture._texture, 0); } - 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 attachRenderbuffer(framebuffer, attachment, renderbuffer) { + var gl = framebuffer._gl; + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer._getRenderbuffer()); } - function unsubscribeFromEntity(that, eventHash, collectionId, entity) { - entityIdScratch[0] = collectionId; - entityIdScratch[1] = entity.id; - var id = JSON.stringify(entityIdScratch); - eventHash[id](); - eventHash[id] = undefined; - } + /** + * 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); + + + 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. + + + // 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)); + + + /////////////////////////////////////////////////////////////////// + + 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; + + + for (i = 0; i < length; ++i) { + texture = textures[i]; - function recomposite(that) { - that._shouldRecomposite = true; - if (that._suspendCount !== 0) { - return; + + attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i; + attachTexture(this, attachmentEnum, texture); + this._activeColorAttachments[i] = attachmentEnum; + this._colorTextures[i] = texture; + } } - var collections = that._collections; - var collectionsLength = collections.length; + if (defined(options.colorRenderbuffers)) { + var renderbuffers = options.colorRenderbuffers; + length = this._colorRenderbuffers.length = this._activeColorAttachments.length = renderbuffers.length; - var collectionsCopy = that._collectionsCopy; - var collectionsCopyLength = collectionsCopy.length; + + 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; + } + } - 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; + if (defined(options.depthTexture)) { + texture = options.depthTexture; - 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); - } + + attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture); + this._depthTexture = texture; } - for (i = collectionsLength - 1; i >= 0; i--) { - collection = collections[i]; - collection.collectionChanged.addEventListener(CompositeEntityCollection.prototype._onCollectionChanged, that); + if (defined(options.depthRenderbuffer)) { + renderbuffer = options.depthRenderbuffer; + attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer); + this._depthRenderbuffer = renderbuffer; + } - //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); + if (defined(options.stencilRenderbuffer)) { + renderbuffer = options.stencilRenderbuffer; + attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer); + this._stencilRenderbuffer = renderbuffer; + } - 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); - } + if (defined(options.depthStencilTexture)) { + texture = options.depthStencilTexture; + + + attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture); + this._depthStencilTexture = texture; } - that._collectionsCopy = collections.slice(0); - composite.suspendEvents(); - composite.removeAll(); - var newEntitiesArray = newEntities.values; - for (i = 0; i < newEntitiesArray.length; i++) { - composite.add(newEntitiesArray[i]); + if (defined(options.depthStencilRenderbuffer)) { + renderbuffer = options.depthStencilRenderbuffer; + attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer); + this._depthStencilRenderbuffer = renderbuffer; } - 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; + this._unBind(); } - defineProperties(CompositeEntityCollection.prototype, { + defineProperties(Framebuffer.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} + * 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} */ - collectionChanged : { + status : { get : function() { - return this._composite._collectionChanged; + this._bind(); + var status = this._gl.checkFramebufferStatus(this._gl.FRAMEBUFFER); + this._unBind(); + return status; } }, - /** - * Gets a globally unique identifier for this collection. - * @memberof CompositeEntityCollection.prototype - * @readonly - * @type {String} - */ - id : { + numberOfColorAttachments : { get : function() { - return this._id; + return this._activeColorAttachments.length; } }, - /** - * Gets the array of Entity instances in the collection. - * This array should not be modified directly. - * @memberof CompositeEntityCollection.prototype - * @readonly - * @type {Entity[]} - */ - values : { + depthTexture: { get : function() { - return this._composite.values; + 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; } }, + /** - * 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} + * 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} */ - owner : { + hasDepthAttachment : { get : function() { - return this._owner; + return !!(this.depthTexture || this.depthRenderbuffer || this.depthStencilTexture || this.depthStencilRenderbuffer); } } }); - /** - * 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 (!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); + Framebuffer.prototype._bind = function() { + var gl = this._gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer); }; - /** - * Gets a collection by index from the composite. - * - * @param {Number} index the index to retrieve. - */ - CompositeEntityCollection.prototype.getCollection = function(index) { - - return this._collections[index]; + Framebuffer.prototype._unBind = function() { + var gl = this._gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; - /** - * Gets the number of collections in this composite. - */ - CompositeEntityCollection.prototype.getCollectionsLength = function() { - return this._collections.length; + Framebuffer.prototype._getActiveColorAttachments = function() { + return this._activeColorAttachments; }; - function getCollectionIndex(collections, collection) { - - var index = collections.indexOf(collection); - + Framebuffer.prototype.getColorTexture = function(index) { - 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(); + return this._colorTextures[index]; }; - /** - * 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() { + Framebuffer.prototype.getColorRenderbuffer = function(index) { - 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(); + return this._colorRenderbuffers[index]; }; - /** - * 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); + Framebuffer.prototype.isDestroyed = function() { + return false; }; - 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); + 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(); } } - 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); + var renderbuffers = this._colorRenderbuffers; + length = renderbuffers.length; + for (i = 0; i < length; ++i) { + var renderbuffer = renderbuffers[i]; + if (defined(renderbuffer)) { + renderbuffer.destroy(); } } - } - if (newProperty && compositeEntity.propertyNames.indexOf(propertyName) === -1) { - compositeEntity.addProperty(propertyName); + 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(); } - compositeEntity[propertyName] = compositeProperty; + this._gl.deleteFramebuffer(this._framebuffer); + return destroyObject(this); }; - return CompositeEntityCollection; + return Framebuffer; }); -/*global define*/ -define('DataSources/CompositeProperty',[ +define('Scene/TextureAtlas',[ + '../Core/BoundingRectangle', + '../Core/Cartesian2', + '../Core/createGuid', + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Event', - '../Core/EventHelper', - '../Core/TimeIntervalCollection', - './Property' + '../Core/loadImage', + '../Core/PixelFormat', + '../Core/RuntimeError', + '../Renderer/Framebuffer', + '../Renderer/Texture', + '../ThirdParty/when' ], function( + BoundingRectangle, + Cartesian2, + createGuid, + defaultValue, defined, defineProperties, + destroyObject, DeveloperError, - Event, - EventHelper, - TimeIntervalCollection, - Property) { + loadImage, + PixelFormat, + RuntimeError, + Framebuffer, + Texture, + when) { '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); - } - } + // 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 {@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. + * 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 CompositeProperty + * @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. * - * @example - * var constantProperty = ...; - * var sampledProperty = ...; + * @exception {DeveloperError} borderWidthInPixels must be greater than or equal to zero. + * @exception {DeveloperError} initialSize must be greater than zero. * - * //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 + * @private */ - function CompositeProperty() { - this._eventHelper = new EventHelper(); - this._definitionChanged = new Event(); - this._intervals = new TimeIntervalCollection(); - this._intervals.changedEvent.addEventListener(CompositeProperty.prototype._intervalsChanged, this); + function TextureAtlas(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var borderWidthInPixels = defaultValue(options.borderWidthInPixels, 1.0); + var initialSize = defaultValue(options.initialSize, defaultInitialSize); + + + 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; + + //acevedo + //use imageId as the key to store the atlas textureCoordinates index. + // Storing the index based on imageId allows dynamic tagged images (canvas) to reuse the same space + // in the atlas and solves the texture atlas overflow when updating the canvas image in the billboard. + //start modification + TextureAtlas.prototype.storeIdHashIndex = function (imageId, indexId){ + this._idHash[imageId] = indexId; + }; + + TextureAtlas.prototype.getIdHashIndex = function (imageId){ + return this._idHash[imageId]; + }; + + TextureAtlas.prototype.isIdHashIndexPresent = function(imageId) { + if (this._idHash.hasOwnProperty(imageId)){ + return true; + } + else + { + return false; + } + }; + //end modification } - defineProperties(CompositeProperty.prototype, { + defineProperties(TextureAtlas.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 + * The amount of spacing between adjacent images in pixels. + * @memberof TextureAtlas.prototype + * @type {Number} */ - isConstant : { + borderWidthInPixels : { get : function() { - return this._intervals.isEmpty; + return this._borderWidthInPixels; } }, + /** - * 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 + * 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[]} */ - definitionChanged : { + textureCoordinates : { get : function() { - return this._definitionChanged; + return this._textureCoordinates; } }, + /** - * Gets the interval collection. - * @memberof CompositeProperty.prototype - * - * @type {TimeIntervalCollection} + * The texture that all of the images are being written to. + * @memberof TextureAtlas.prototype + * @type {Texture} */ - intervals : { + texture : { get : function() { - return this._intervals; + 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; } } }); - /** - * 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) { - - var innerProperty = this._intervals.findDataForIntervalContainingDate(time); - if (defined(innerProperty)) { - return innerProperty.getValue(time, result); - } - return undefined; - }; + // 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; - /** - * 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)); - }; + // 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); - /** - * @private - */ - CompositeProperty.prototype._intervalsChanged = function() { - subscribeAll(this, this._eventHelper, this._definitionChanged, this._intervals); - this._definitionChanged.raiseEvent(this); - }; + // 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; + } + } - return CompositeProperty; -}); + // Copy larger texture. + var newTexture = new Texture({ + context : textureAtlas._context, + width : atlasWidth, + height : atlasHeight, + pixelFormat : textureAtlas._pixelFormat + }); -/*global define*/ -define('DataSources/CompositeMaterialProperty',[ - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - './CompositeProperty', - './Property' - ], function( - defined, - defineProperties, - DeveloperError, - Event, - CompositeProperty, - Property) { - 'use strict'; + var framebuffer = new Framebuffer({ + context : context, + colorTextures : [textureAtlas._texture], + destroyAttachments : false + }); - /** - * 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); + 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)); + } } - 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; + // 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; } - }, - /** - * 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; + + 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; } - }, - /** - * Gets the interval collection. - * @memberof CompositeMaterialProperty.prototype - * - * @type {TimeIntervalCollection} - */ - intervals : { - get : function() { - return this._composite._intervals; + + // 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) { + + 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); + } 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; + }; /** - * Gets the {@link Material} type at the provided time. + * Add a sub-region of an existing atlas image as additional image indices. * - * @param {JulianDate} time The time for which to retrieve the type. - * @returns {String} The type of material. + * @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. */ - CompositeMaterialProperty.prototype.getType = function(time) { + TextureAtlas.prototype.addSubRegion = function(id, subRegion) { - var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time); - if (defined(innerProperty)) { - return innerProperty.getType(time); + var indexPromise = this._idHash[id]; + if (!defined(indexPromise)) { + throw new RuntimeError('image with id "' + id + '" not found in the atlas.'); } - return undefined; + + 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; + }); }; + + +/** +* acevedo +* update an image in the atlas. If the image is already in the atlas, the atlas image is replaced 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.updateImage = function(imageId, image)// id is the imageId + { + var indexPromise = undefined; + if (!defined(imageId)) { + throw new DeveloperError('id is required.'); + } + if (!defined(image)) { + throw new DeveloperError('image is required.'); + } + + if (typeof image === 'function') { + // if image is a function, call it + image = image(imageId); + 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; + //acevedo + // check if the image already has a space in the texture atlas + if (that.isIdHashIndexPresent(imageId)) + { + //reuse space in texture + index = that.getIdHashIndex(imageId); + addImage(that, image, index); + } + else + { + that.storeIdHashIndex(imageId,index); + addImage(that, image, index); + } + return index; + }); + + return indexPromise; + }; + /** - * Gets the value of the property at the provided time. + * 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. * - * @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. + * @returns {Boolean} True if this object was destroyed; otherwise, false. + * + * @see TextureAtlas#destroy */ - CompositeMaterialProperty.prototype.getValue = function(time, result) { - - var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time); - if (defined(innerProperty)) { - return innerProperty.getValue(time, result); - } - return undefined; + TextureAtlas.prototype.isDestroyed = function() { + return false; }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * 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. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * + * @example + * atlas = atlas && atlas.destroy(); + * + * @see TextureAtlas#isDestroyed */ - CompositeMaterialProperty.prototype.equals = function(other) { - return this === other || // - (other instanceof CompositeMaterialProperty && // - this._composite.equals(other._composite, Property.equals)); + TextureAtlas.prototype.destroy = function() { + this._texture = this._texture && this._texture.destroy(); + return destroyObject(this); }; /** - * @private + * 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. */ - CompositeMaterialProperty.prototype._raiseDefinitionChanged = function() { - this._definitionChanged.raiseEvent(this); - }; - return CompositeMaterialProperty; + return TextureAtlas; }); -/*global define*/ -define('DataSources/CompositePositionProperty',[ +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/Event', - '../Core/ReferenceFrame', - './CompositeProperty', - './Property' + '../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', + './BlendOption', + './HeightReference', + './HorizontalOrigin', + './SceneMode', + './TextureAtlas', + './VerticalOrigin' ], function( + AttributeCompression, + BoundingSphere, + Cartesian2, + Cartesian3, + Color, + ComponentDatatype, defaultValue, defined, defineProperties, + destroyObject, DeveloperError, - Event, - ReferenceFrame, - CompositeProperty, - Property) { + EncodedCartesian3, + IndexDatatype, + CesiumMath, + Matrix4, + WebGLConstants, + Buffer, + BufferUsage, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + VertexArrayFacade, + BillboardCollectionFS, + BillboardCollectionVS, + Billboard, + BlendingState, + BlendOption, + 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 DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE; + 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, + distanceDisplayConditionAndDisableDepth : 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, + distanceDisplayConditionAndDisableDepth : 9 + }; + /** - * A {@link CompositeProperty} which is also a {@link PositionProperty}. + * 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 CompositePositionProperty + * @alias BillboardCollection * @constructor * - * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined. + * @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. + * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The billboard blending option. The default + * is used for rendering both opaque and translucent billboards. However, if either all of the billboards are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. + * + * @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 CompositePositionProperty(referenceFrame) { - this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED); - this._definitionChanged = new Event(); - this._composite = new CompositeProperty(); - this._composite.definitionChanged.addEventListener(CompositePositionProperty.prototype._raiseDefinitionChanged, this); - } + 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._spTranslucent = undefined; + this._spPick = undefined; + this._rsOpaque = undefined; + this._rsTranslucent = undefined; + this._vaf = 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._shaderDisableDepthDistance = false; + this._compiledShaderDisableDepthDistance = false; + this._compiledShaderDisableDepthDistancePick = 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 = []; - 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 + * 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} - * @readonly + * + * @default false */ - isConstant : { - get : function() { - return this._composite.isConstant; + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + + /** + * The billboard blending option. The default is used for rendering both opaque and translucent billboards. + * However, if either all of the billboards are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve + * performance by up to 2x. + * @type {BlendOption} + * @default BlendOption.OPAQUE_AND_TRANSLUCENT + */ + this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); + this._blendOption = undefined; + + 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) && defined(scene.terrainProviderChanged)) { + 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 */ - referenceFrame : { + destroyTextureAtlas : { get : function() { - return this._referenceFrame; + return this._destroyTextureAtlas; }, set : function(value) { - this._referenceFrame = value; + this._destroyTextureAtlas = value; } } }); + function destroyBillboards(billboards) { + var length = billboards.length; + for (var i = 0; i < length; ++i) { + if (billboards[i]) { + billboards[i]._destroy(); + } + } + } + /** - * Gets the value of the property at the provided time in the fixed frame. + * 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 {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. + * @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 */ - CompositePositionProperty.prototype.getValue = function(time, result) { - return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result); + 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; }; /** - * Gets the value of the property at the provided time and in the provided reference frame. + * Removes a billboard from the collection. * - * @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. + * @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 */ - CompositePositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { - - var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time); - if (defined(innerProperty)) { - return innerProperty.getValueInReferenceFrame(time, referenceFrame, result); + 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 undefined; + + return false; }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * Removes all billboards from the collection. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @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 */ - CompositePositionProperty.prototype.equals = function(other) { - return this === other || // - (other instanceof CompositePositionProperty && // - this._referenceFrame === other._referenceFrame && // - this._composite.equals(other._composite, Property.equals)); + 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]; }; /** - * @private + * 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 */ - CompositePositionProperty.prototype._raiseDefinitionChanged = function() { - this._definitionChanged.raiseEvent(this); + BillboardCollection.prototype.contains = function(billboard) { + return defined(billboard) && billboard._billboardCollection === this; }; - return CompositePositionProperty; -}); - -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/ShadowVolumeFS',[],function() { - 'use strict'; - return "#ifdef GL_EXT_frag_depth\n\ -#extension GL_EXT_frag_depth : enable\n\ -#endif\n\ -\n\ -// emulated noperspective\n\ -varying float v_WindowZ;\n\ -varying vec4 v_color;\n\ -\n\ -void writeDepthClampedToFarPlane()\n\ -{\n\ -#ifdef GL_EXT_frag_depth\n\ - gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n\ -#endif\n\ -}\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 vec3 extrudeDirection;\n\ -attribute vec4 color;\n\ -attribute float batchId;\n\ -\n\ -uniform float u_globeMinimumAltitude;\n\ -\n\ -// emulated noperspective\n\ -varying float v_WindowZ;\n\ -varying vec4 v_color;\n\ -\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\ -\n\ -void main()\n\ -{\n\ - v_color = color;\n\ -\n\ - vec4 position = czm_computePosition();\n\ - float delta = min(u_globeMinimumAltitude, czm_geometricToleranceOverMeter * length(position.xyz));\n\ - delta *= czm_sceneMode == czm_sceneMode3D ? 1.0 : 0.0;\n\ -\n\ - //extrudeDirection is zero for the top layer\n\ - position = position + vec4(extrudeDirection * delta, 0.0);\n\ - gl_Position = depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position);\n\ -}\n\ -"; -}); -/*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. + * 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. * - * @exports StencilFunction + * @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 */ - var StencilFunction = { - /** - * The stencil test never passes. - * - * @type {Number} - * @constant - */ - NEVER : WebGLConstants.NEVER, + BillboardCollection.prototype.get = function(index) { + + removeBillboards(this); + return this._billboards[index]; + }; - /** - * The stencil test passes when the masked reference value is less than the masked stencil value. - * - * @type {Number} - * @constant - */ - LESS : WebGLConstants.LESS, + var getIndexBuffer; - /** - * The stencil test passes when the masked reference value is equal to the masked stencil value. - * - * @type {Number} - * @constant - */ - EQUAL : WebGLConstants.EQUAL, + function getIndexBufferBatched(context) { + var sixteenK = 16 * 1024; - /** - * 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, + var indexBuffer = context.cache.billboardCollection_indexBufferBatched; + if (defined(indexBuffer)) { + return indexBuffer; + } - /** - * The stencil test passes when the masked reference value is greater than the masked stencil value. - * - * @type {Number} - * @constant - */ - GREATER : WebGLConstants.GREATER, + // 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; - /** - * The stencil test passes when the masked reference value is not equal to the masked stencil value. - * - * @type {Number} - * @constant - */ - NOT_EQUAL : WebGLConstants.NOTEQUAL, + indices[i + 3] = j + 0; + indices[i + 4] = j + 2; + indices[i + 5] = j + 3; + } - /** - * 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, + // 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; + } - /** - * The stencil test always passes. - * - * @type {Number} - * @constant - */ - ALWAYS : WebGLConstants.ALWAYS + 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; }; - return freezeObject(StencilFunction); -}); + 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.distanceDisplayConditionAndDisableDepth, + componentsPerAttribute : 3, + 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); + } -/*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, + // Four vertices per billboard. Each has the same position, etc., but a different screen-space direction vector. - /** - * Does not change the stencil buffer. - * - * @type {Number} - * @constant - */ - KEEP : WebGLConstants.KEEP, + // 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. - /** - * Replaces the stencil buffer value with the reference value. - * - * @type {Number} - * @constant - */ - REPLACE : WebGLConstants.REPLACE, + var writePositionScratch = new EncodedCartesian3(); - /** - * Increments the stencil buffer value, clamping to unsigned byte. - * - * @type {Number} - * @constant - */ - INCREMENT : WebGLConstants.INCR, + function writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) { + var i; + var positionHighWriter = vafWriters[attributeLocations.positionHighAndScale]; + var positionLowWriter = vafWriters[attributeLocations.positionLowAndRotation]; + var position = billboard._getActualPosition(); - /** - * Decrements the stencil buffer value, clamping to zero. - * - * @type {Number} - * @constant - */ - DECREMENT : WebGLConstants.DECR, + if (billboardCollection._mode === SceneMode.SCENE3D) { + BoundingSphere.expand(billboardCollection._baseVolume, position, billboardCollection._baseVolume); + billboardCollection._boundingVolumeDirty = true; + } - /** - * Bitwise inverts the existing stencil buffer value. - * - * @type {Number} - * @constant - */ - INVERT : WebGLConstants.INVERT, + EncodedCartesian3.fromCartesian(position, writePositionScratch); + var scale = billboard.scale; + var rotation = billboard.rotation; - /** - * Increments the stencil buffer value, wrapping to zero when exceeding the unsigned byte range. - * - * @type {Number} - * @constant - */ - INCREMENT_WRAP : WebGLConstants.INCR_WRAP, + if (rotation !== 0.0) { + billboardCollection._shaderRotation = true; + } - /** - * Decrements the stencil buffer value, wrapping to the maximum unsigned byte instead of going below zero. - * - * @type {Number} - * @constant - */ - DECREMENT_WRAP : WebGLConstants.DECR_WRAP - }; + billboardCollection._maxScale = Math.max(billboardCollection._maxScale, scale); - return freezeObject(StencilOperation); -}); + var high = writePositionScratch.high; + var low = writePositionScratch.low; -/*global define*/ -define('Scene/GroundPrimitive',[ - '../Core/BoundingSphere', - '../Core/buildModuleUrl', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartographic', - '../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, - 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'; + 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); - /** - * 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. - *

    - *

    - * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there - * will be rendering artifacts for some viewing angles. - *

    - *

    - * 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); + 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); + } + } - /** - * 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); + var scratchCartesian2 = new Cartesian2(); - /** - * 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. - *

    - * - * @type {Boolean} - * - * @default false - */ - this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); - this._debugShowShadowVolume = false; + var UPPER_BOUND = 32768.0; // 2^15 - this._sp = undefined; - this._spPick = undefined; + 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; - this._rsStencilPreloadPass = undefined; - this._rsStencilDepthPass = undefined; - this._rsColorPass = undefined; - this._rsPickPass = undefined; + var RIGHT_SHIFT8 = 1.0 / 256.0; - this._uniformMap = { - u_globeMinimumAltitude: function() { - return 55000.0; - } - }; + var LOWER_LEFT = 0.0; + var LOWER_RIGHT = 2.0; + var UPPER_RIGHT = 3.0; + var UPPER_LEFT = 1.0; - this._boundingVolumes = []; - this._boundingVolumes2D = []; + 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; - this._ready = false; - this._readyPromise = when.defer(); + var translate = billboard._translate; + var translateX = translate.x; + var translateY = translate.y; - this._primitive = undefined; + billboardCollection._maxPixelOffset = Math.max(billboardCollection._maxPixelOffset, Math.abs(pixelOffsetX + translateX), Math.abs(-pixelOffsetY + translateY)); - this._maxHeight = undefined; - this._minHeight = undefined; + var horizontalOrigin = billboard.horizontalOrigin; + var verticalOrigin = billboard._verticalOrigin; + var show = billboard.show && billboard.clusterShow; - this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; + // 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; + } - this._boundingSpheresKeys = []; - this._boundingSpheres = []; + // Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that. + if (verticalOrigin === VerticalOrigin.BASELINE) { + verticalOrigin = VerticalOrigin.BOTTOM; + } - var appearance = new PerInstanceColorAppearance({ - flat : true - }); + billboardCollection._allHorizontalCenter = billboardCollection._allHorizontalCenter && horizontalOrigin === HorizontalOrigin.CENTER; + billboardCollection._allVerticalCenter = billboardCollection._allVerticalCenter && verticalOrigin === VerticalOrigin.CENTER; - var readOnlyAttributes; - if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { - readOnlyAttributes = readOnlyInstanceAttributesScratch; + var bottomLeftX = 0; + var bottomLeftY = 0; + var width = 0; + var height = 0; + var index = billboard._imageIndex; + if (index !== -1) { + var imageRectangle = textureAtlasCoordinates[index]; + + + bottomLeftX = imageRectangle.x; + bottomLeftY = imageRectangle.y; + width = imageRectangle.width; + height = imageRectangle.height; } + var topRightX = bottomLeftX + width; + var topRightY = bottomLeftY + height; - 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 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 readOnlyInstanceAttributesScratch = ['color']; + 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; - 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; - } - }, + 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); - /** - * 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; - } - }, + compressed1 += upperTranslateY; + compressed2 += lowerTranslateY; - /** - * 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; - } - }, + 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); - /** - * 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; - } - }, + 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); + } + } - /** - * 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; - } - }, + 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; + } - /** - * 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; - } - }, + var near = 0.0; + var nearValue = 1.0; + var far = 1.0; + var farValue = 1.0; - /** - * 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; - } - }, + var translucency = billboard.translucencyByDistance; + if (defined(translucency)) { + near = translucency.near; + nearValue = translucency.nearValue; + far = translucency.far; + farValue = translucency.farValue; - /** - * 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; + 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; } } - }); - - /** - * 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.stencilBuffer; - }; - GroundPrimitive._defaultMaxTerrainHeight = 9000.0; - GroundPrimitive._defaultMinTerrainHeight = -100000.0; + var width = 0; + var index = billboard._imageIndex; + if (index !== -1) { + var imageRectangle = textureAtlasCoordinates[index]; - GroundPrimitive._terrainHeights = undefined; - GroundPrimitive._terrainHeightsMaxLevel = 6; + + width = imageRectangle.width; + } - function getComputeMaximumHeightFunction(primitive) { - return function(granularity, ellipsoid) { - var r = ellipsoid.maximumRadius; - var delta = (r / Math.cos(granularity * 0.5)) - r; - return primitive._maxHeight + delta; - }; - } + var textureWidth = billboardCollection._textureAtlas.texture.width; + var imageWidth = Math.round(defaultValue(billboard.width, textureWidth * width)); + billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth); - function getComputeMinimumHeightFunction(primitive) { - return function(granularity, ellipsoid) { - return primitive._minHeight; - }; - } + var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16); + var compressed1 = 0.0; - function getStencilPreloadRenderState(enableStencil) { - return { - colorMask : { - red : false, - green : false, - blue : false, - alpha : false - }, - stencilTest : { - enabled : enableStencil, - 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 - }; - } + if (Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) < CesiumMath.EPSILON6) { + compressed1 = AttributeCompression.octEncodeFloat(alignedAxis); + } - function getStencilDepthRenderState(enableStencil) { - return { - colorMask : { - red : false, - green : false, - blue : false, - alpha : false - }, - stencilTest : { - enabled : enableStencil, - 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 - }; - } + 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; - function getColorRenderState(enableStencil) { - return { - stencilTest : { - enabled : enableStencil, - 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 - }; + 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); + } } - 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 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; - function getRectangle(frameState, geometry) { - var ellipsoid = frameState.mapProjection.ellipsoid; + billboardCollection._allSizedInMeters = billboardCollection._allSizedInMeters && sizeInMeters === 1.0; - if (!defined(geometry.attributes) || !defined(geometry.attributes.position3DHigh)) { - if (defined(geometry.rectangle)) { - return geometry.rectangle; - } + var height = 0; + var index = billboard._imageIndex; + if (index !== -1) { + var imageRectangle = textureAtlasCoordinates[index]; - return undefined; + + height = imageRectangle.height; } - var highPositions = geometry.attributes.position3DHigh.values; - var lowPositions = geometry.attributes.position3DLow.values; - var length = highPositions.length; + var dimensions = billboardCollection._textureAtlas.texture.dimensions; + var imageHeight = Math.round(defaultValue(billboard.height, dimensions.y * height)); + billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight); - var minLat = Number.POSITIVE_INFINITY; - var minLon = Number.POSITIVE_INFINITY; - var maxLat = Number.NEGATIVE_INFINITY; - var maxLon = Number.NEGATIVE_INFINITY; + 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; - for (var i = 0; i < length; i +=3) { - var highPosition = Cartesian3.unpack(highPositions, i, scratchBVCartesianHigh); - var lowPosition = Cartesian3.unpack(lowPositions, i, scratchBVCartesianLow); + 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 position = Cartesian3.add(highPosition, lowPosition, scratchBVCartesian); - var cartographic = ellipsoid.cartesianToCartographic(position, scratchBVCartographic); + var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT16 + Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8; + compressed2 += sizeInMeters * 2.0 + validAlignedAxis; - var latitude = cartographic.latitude; - var longitude = cartographic.longitude; + 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); + } + } - minLat = Math.min(minLat, latitude); - minLon = Math.min(minLon, longitude); - maxLat = Math.max(maxLat, latitude); - maxLon = Math.max(maxLon, longitude); + 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)); - var rectangle = scratchBVRectangle; - rectangle.north = maxLat; - rectangle.south = minLat; - rectangle.east = maxLon; - rectangle.west = minLon; + if (billboardCollection._instanced) { + var width = 0; + var height = 0; + var index = billboard._imageIndex; + if (index !== -1) { + var imageRectangle = textureAtlasCoordinates[index]; - return rectangle; - } + + width = imageRectangle.width; + height = imageRectangle.height; + } - var scratchDiagonalCartesianNE = new Cartesian3(); - var scratchDiagonalCartesianSW = new Cartesian3(); - var scratchDiagonalCartographic = new Cartographic(); - var scratchCenterCartesian = new Cartesian3(); - var scratchSurfaceCartesian = new Cartesian3(); + scratchCartesian2.x = width; + scratchCartesian2.y = height; + var compressedTexCoordsRange = AttributeCompression.compressTextureCoordinates(scratchCartesian2); - 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]); + 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); + } + } - // 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; - } - } + 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; - if (failed) { - break; + 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; } + } - lastLevelX = currentX; - lastLevelY = currentY; + 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); } + } - if (i === 0) { - return undefined; + 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; + } } - return { - x : lastLevelX, - y : lastLevelY, - level : (i > maxLevel) ? maxLevel : (i - 1) - }; + 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 setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) { - var xyLevel = getTileXYLevel(rectangle); + function writeDistanceDisplayConditionAndDepthDisable(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) { + var i; + var writer = vafWriters[attributeLocations.distanceDisplayConditionAndDisableDepth]; + var near = 0.0; + var far = Number.MAX_VALUE; - // 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]; - } + var distanceDisplayCondition = billboard.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + near = distanceDisplayCondition.near; + far = distanceDisplayCondition.far; - // 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); + near *= near; + far *= far; - 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; + billboardCollection._shaderDistanceDisplayCondition = true; + } + + var disableDepthTestDistance = billboard.disableDepthTestDistance; + disableDepthTestDistance *= disableDepthTestDistance; + if (disableDepthTestDistance > 0.0) { + billboardCollection._shaderDisableDepthDistance = true; + if (disableDepthTestDistance === Number.POSITIVE_INFINITY) { + disableDepthTestDistance = -1.0; } } - primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight); - primitive._maxTerrainHeight = maxTerrainHeight; + if (billboardCollection._instanced) { + i = billboard._index; + writer(i, near, far, disableDepthTestDistance); + } else { + i = billboard._index * 4; + writer(i + 0, near, far, disableDepthTestDistance); + writer(i + 1, near, far, disableDepthTestDistance); + writer(i + 2, near, far, disableDepthTestDistance); + writer(i + 3, near, far, disableDepthTestDistance); + } + } + + 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); + writeDistanceDisplayConditionAndDepthDisable(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); } - var scratchBoundingSphere = new BoundingSphere(); - function getInstanceBoundingSphere(rectangle, ellipsoid) { - var xyLevel = getTileXYLevel(rectangle); + 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; + } - // 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 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); + } } } - var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0); - BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere); - - return BoundingSphere.union(result, scratchBoundingSphere, result); + if (recomputeBoundingVolume) { + BoundingSphere.fromPoints(positions, boundingVolume); + } } - function createBoundingVolume(groundPrimitive, frameState, geometry) { - var ellipsoid = frameState.mapProjection.ellipsoid; - var rectangle = getRectangle(frameState, geometry); + function updateMode(billboardCollection, frameState) { + var mode = frameState.mode; - // 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, groundPrimitive._maxHeight, groundPrimitive._minHeight, ellipsoid); - groundPrimitive._boundingVolumes.push(obb); - } else { - var highPositions = geometry.attributes.position3DHigh.values; - var lowPositions = geometry.attributes.position3DLow.values; - groundPrimitive._boundingVolumes.push(BoundingSphere.fromEncodedCartesianVertices(highPositions, lowPositions)); - } + var billboards = billboardCollection._billboards; + var billboardsToUpdate = billboardCollection._billboardsToUpdate; + var modelMatrix = billboardCollection._modelMatrix; - if (!frameState.scene3DOnly) { - var projection = frameState.mapProjection; - var boundingVolume = BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, groundPrimitive._maxHeight, groundPrimitive._minHeight); - Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center); + if (billboardCollection._createVertexArray || + billboardCollection._mode !== mode || + mode !== SceneMode.SCENE3D && + !Matrix4.equals(modelMatrix, billboardCollection.modelMatrix)) { - groundPrimitive._boundingVolumes2D.push(boundingVolume); + 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 createRenderStates(groundPrimitive, context, appearance, twoPasses) { - if (defined(groundPrimitive._rsStencilPreloadPass)) { - return; + 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 stencilEnabled = !groundPrimitive.debugShowShadowVolume; - groundPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled)); - groundPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled)); - groundPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled)); - groundPrimitive._rsPickPass = RenderState.fromCache(pickRenderState); - } - - function modifyForEncodedNormals(primitive, vertexShaderSource) { - if (!primitive.compressVertices) { - return vertexShaderSource; + var size = pixelScale * collection._maxScale * collection._maxSize * 2.0; + if (collection._allHorizontalCenter && collection._allVerticalCenter ) { + size *= 0.5; } - if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) { - var attributeName = 'compressedAttributes'; + var offset = pixelScale * collection._maxPixelOffset + collection._maxEyeOffset; + boundingVolume.radius += size + offset; + } - //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes - var attributeDecl = 'attribute vec2 ' + attributeName + ';'; + var scratchWriterArray = []; - var globalDecl = 'vec3 extrudeDirection;\n'; - var decode = ' extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n'; + /** + * 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 modifiedVS = vertexShaderSource; - modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, ''); - modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); - var compressedMain = - 'void main() \n' + - '{ \n' + - decode + - ' czm_non_compressed_main(); \n' + - '}'; + var context = frameState.context; + this._instanced = context.instancedArrays; + attributeLocations = this._instanced ? attributeLocationsInstanced : attributeLocationsBatched; + getIndexBuffer = this._instanced ? getIndexBufferInstanced : getIndexBufferBatched; - return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); + var textureAtlas = this._textureAtlas; + if (!defined(textureAtlas)) { + textureAtlas = this._textureAtlas = new TextureAtlas({ + context : context + }); + + for (var ii = 0; ii < billboardsLength; ++ii) { + billboards[ii]._loadImage(); + } } - } - function createShaderProgram(groundPrimitive, frameState, appearance) { - if (defined(groundPrimitive._sp)) { + var textureAtlasCoordinates = textureAtlas.textureCoordinates; + if (textureAtlasCoordinates.length === 0) { + // Can't write billboard vertices until we have texture coordinates + // provided by a texture atlas return; } - var context = frameState.context; - var primitive = groundPrimitive._primitive; - var vs = ShadowVolumeVS; - vs = groundPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); - vs = Primitive._appendShowToShader(primitive, vs); - vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); - vs = Primitive._modifyShaderPosition(groundPrimitive, vs, frameState.scene3DOnly); - vs = Primitive._updateColorAttribute(primitive, vs); - vs = modifyForEncodedNormals(primitive, vs); + updateMode(this, frameState); - var fs = ShadowVolumeFS; - var attributeLocations = groundPrimitive._primitive._attributeLocations; + billboards = this._billboards; + billboardsLength = billboards.length; + var billboardsToUpdate = this._billboardsToUpdate; + var billboardsToUpdateLength = this._billboardsToUpdateIndex; - groundPrimitive._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPrimitive._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); + var properties = this._propertiesChanged; - if (groundPrimitive._primitive.allowPicking) { - var vsPick = ShaderSource.createPickVertexShaderSource(vs); - vsPick = Primitive._updatePickColorAttribute(vsPick); + var textureAtlasGUID = textureAtlas.guid; + var createVertexArray = this._createVertexArray || this._textureAtlasGUID !== textureAtlasGUID; + this._textureAtlasGUID = textureAtlasGUID; - var pickFS = new ShaderSource({ - sources : [fs], - pickColorQualifier : 'varying' - }); - groundPrimitive._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPrimitive._spPick, - vertexShaderSource : vsPick, - fragmentShaderSource : pickFS, - attributeLocations : attributeLocations - }); - } else { - groundPrimitive._spPick = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } - } + var vafWriters; + var pass = frameState.passes; + var picking = pass.pick; - function createColorCommands(groundPrimitive, colorCommands) { - var primitive = groundPrimitive._primitive; - var length = primitive._va.length * 3; - colorCommands.length = length; + // PERFORMANCE_IDEA: Round robin multiple buffers. + if (createVertexArray || (!picking && this.computeNewBuffersUsage())) { + this._createVertexArray = false; - var vaIndex = 0; - var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); + for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) { + properties[k] = 0; + } - for (var i = 0; i < length; i += 3) { - var vertexArray = primitive._va[vaIndex++]; + this._vaf = this._vaf && this._vaf.destroy(); - // stencil preload command - var command = colorCommands[i]; - if (!defined(command)) { - command = colorCommands[i] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } + 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; - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsStencilPreloadPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; + // 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); + } - // stencil depth command - command = colorCommands[i + 1]; - if (!defined(command)) { - command = colorCommands[i + 1] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); + // Different billboard collections share the same index buffer. + this._vaf.commit(getIndexBuffer(context)); } - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsStencilDepthPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; + this._billboardsToUpdateIndex = 0; + } else if (billboardsToUpdateLength > 0) { + // Billboards were modified, but none were added or removed. + var writers = scratchWriterArray; + writers.length = 0; - // color command - command = colorCommands[i + 2]; - if (!defined(command)) { - command = colorCommands[i + 2] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); + if (properties[POSITION_INDEX] || properties[ROTATION_INDEX] || properties[SCALE_INDEX]) { + writers.push(writePositionScaleAndRotation); } - 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; + 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); + } + } - var pickIndex = 0; - var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); + if (properties[IMAGE_INDEX_INDEX] || properties[ALIGNED_AXIS_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) { + writers.push(writeCompressedAttrib1); + writers.push(writeCompressedAttrib2); + } - for (var j = 0; j < length; j += 3) { - var pickOffset = pickOffsets[pickIndex++]; + if (properties[IMAGE_INDEX_INDEX] || properties[COLOR_INDEX]) { + writers.push(writeCompressedAttrib2); + } - var offset = pickOffset.offset; - var count = pickOffset.count; - var vertexArray = primitive._va[pickOffset.index]; + if (properties[EYE_OFFSET_INDEX]) { + writers.push(writeEyeOffset); + } - // stencil preload command - var command = pickCommands[j]; - if (!defined(command)) { - command = pickCommands[j] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); + if (properties[SCALE_BY_DISTANCE_INDEX]) { + writers.push(writeScaleByDistance); } - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsStencilPreloadPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; + if (properties[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]) { + writers.push(writePixelOffsetScaleByDistance); + } - // stencil depth command - command = pickCommands[j + 1]; - if (!defined(command)) { - command = pickCommands[j + 1] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); + if (properties[DISTANCE_DISPLAY_CONDITION_INDEX] || properties[DISABLE_DEPTH_DISTANCE]) { + writers.push(writeDistanceDisplayConditionAndDepthDisable); } - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsStencilDepthPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; + var numWriters = writers.length; + vafWriters = this._vaf.writers; - // color command - command = pickCommands[j + 2]; - if (!defined(command)) { - command = pickCommands[j + 2] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); + 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(); } - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsPickPass; - command.shaderProgram = groundPrimitive._spPick; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; + this._billboardsToUpdateIndex = 0; } - } - function createCommands(groundPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { - createColorCommands(groundPrimitive, colorCommands); - createPickCommands(groundPrimitive, pickCommands); - } + // 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; + } - function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { - var boundingVolumes; + 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) { - boundingVolumes = groundPrimitive._boundingVolumes; - } else if (frameState.mode !== SceneMode.SCENE3D && defined(groundPrimitive._boundingVolumes2D)) { - boundingVolumes = groundPrimitive._boundingVolumes2D; + modelMatrix = this.modelMatrix; + boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume); + } else { + boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume); } + updateBoundingVolume(this, frameState, boundingVolume); - 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; + var blendOptionChanged = this._blendOption !== this.blendOption; + this._blendOption = this.blendOption; + + if (blendOptionChanged) { + if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + this._rsOpaque = RenderState.fromCache({ + depthTest : { + enabled : true, + func : WebGLConstants.LESS + }, + depthMask : true + }); + } else { + this._rsOpaque = undefined; + } + + // If OPAQUE_AND_TRANSLUCENT is in use, only the opaque pass gets the benefit of the depth buffer, + // not the translucent pass. Otherwise, if the TRANSLUCENT pass is on its own, it turns on + // a depthMask in lieu of full depth sorting (because it has opaque-ish fragments that look bad in OIT). + // When the TRANSLUCENT depth mask is in use, label backgrounds require the depth func to be LEQUAL. + var useTranslucentDepthMask = this._blendOption === BlendOption.TRANSLUCENT; - commandList.push(colorCommands[j]); + if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + this._rsTranslucent = RenderState.fromCache({ + depthTest : { + enabled : true, + func : (useTranslucentDepthMask ? WebGLConstants.LEQUAL : WebGLConstants.LESS) + }, + depthMask : useTranslucentDepthMask, + blending : BlendingState.ALPHA_BLEND + }); + } else { + this._rsTranslucent = undefined; } } - if (passes.pick) { - var primitive = groundPrimitive._primitive; - var pickOffsets = primitive._pickOffsets; - var length = pickOffsets.length * 3; - pickCommands.length = length; + this._shaderDisableDepthDistance = this._shaderDisableDepthDistance || frameState.minimumDisableDepthTestDistance !== 0.0; + var vs; + var fs; - var pickIndex = 0; - for (var k = 0; k < length; k += 3) { - var pickOffset = pickOffsets[pickIndex++]; - var bv = boundingVolumes[pickOffset.index]; + if (blendOptionChanged || + (this._shaderRotation !== this._compiledShaderRotation) || + (this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) || + (this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) || + (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) || + (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) || + (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition) || + (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistance)) { - pickCommands[k].modelMatrix = modelMatrix; - pickCommands[k].boundingVolume = bv; - pickCommands[k].cull = cull; + 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'); + } + if (this._shaderDisableDepthDistance) { + vs.defines.push('DISABLE_DEPTH_DISTANCE'); + } - pickCommands[k + 1].modelMatrix = modelMatrix; - pickCommands[k + 1].boundingVolume = bv; - pickCommands[k + 1].cull = cull; + if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + fs = new ShaderSource({ + defines : ['OPAQUE'], + sources : [BillboardCollectionFS] + }); + this._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._sp, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); - pickCommands[k + 2].modelMatrix = modelMatrix; - pickCommands[k + 2].boundingVolume = bv; - pickCommands[k + 2].cull = cull; + fs = new ShaderSource({ + defines : ['TRANSLUCENT'], + sources : [BillboardCollectionFS] + }); + this._spTranslucent = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spTranslucent, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } - commandList.push(pickCommands[k], pickCommands[k + 1], pickCommands[k + 2]); + if (this._blendOption === BlendOption.OPAQUE) { + fs = new ShaderSource({ + sources : [BillboardCollectionFS] + }); + this._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._sp, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); } - } - } - GroundPrimitive._initialized = false; - GroundPrimitive._initPromise = undefined; + if (this._blendOption === BlendOption.TRANSLUCENT) { + fs = new ShaderSource({ + sources : [BillboardCollectionFS] + }); + this._spTranslucent = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spTranslucent, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } - /** - * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the - * GroundPrimitive synchronously. - * - * @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; + this._compiledShaderRotation = this._shaderRotation; + this._compiledShaderAlignedAxis = this._shaderAlignedAxis; + this._compiledShaderScaleByDistance = this._shaderScaleByDistance; + this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; + this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance; + this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; + this._compiledShaderDisableDepthDistance = this._shaderDisableDepthDistance; } - GroundPrimitive._initPromise = loadJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) { - GroundPrimitive._initialized = true; - GroundPrimitive._terrainHeights = json; - }); + 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) || + (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistancePick)) { - return GroundPrimitive._initPromise; - }; + vs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [BillboardCollectionVS] + }); - /** - * 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) { - if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) { - return; - } + 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'); + } + if (this._shaderDisableDepthDistance) { + vs.defines.push('DISABLE_DEPTH_DISTANCE'); + } - if (!GroundPrimitive._initialized) { - - GroundPrimitive.initializeTerrainHeights(); - return; + 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; + this._compiledShaderDisableDepthDistancePick = this._shaderDisableDepthDistance; } - var that = this; - var primitiveOptions = this._primitiveOptions; + var va; + var vaLength; + var command; + var j; - if (!defined(this._primitive)) { - var ellipsoid = frameState.mapProjection.ellipsoid; + var commandList = frameState.commandList; - var instance; - var geometry; - var instanceType; + if (pass.render) { + var colorList = this._colorCommands; - var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; - var length = instances.length; - var groundInstances = new Array(length); + var opaque = this._blendOption === BlendOption.OPAQUE; + var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT; - 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); - } - } + va = this._vaf.va; + vaLength = va.length; - var id = instance.id; - if (defined(id) && defined(instanceRectangle)) { - var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid); - this._boundingSpheresKeys.push(id); - this._boundingSpheres.push(boundingSphere); + colorList.length = vaLength; + var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; + for (j = 0; j < totalLength; ++j) { + command = colorList[j]; + if (!defined(command)) { + command = colorList[j] = new DrawCommand(); } - instanceType = geometry.constructor; - if (defined(instanceType) && defined(instanceType.createShadowVolume)) { - var attributes = instance.attributes; - - } else { - } - } + var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0); - // 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; + command.pass = opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT; + command.owner = this; - 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 - }); - } + var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j; + command.boundingVolume = boundingVolume; + command.modelMatrix = modelMatrix; + command.count = va[index].indicesCount; + command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent; + command.uniformMap = this._uniforms; + command.vertexArray = va[index].va; + command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent; + command.debugShowBoundingVolume = this.debugShowBoundingVolume; - primitiveOptions.geometryInstances = groundInstances; + if (this._instanced) { + command.count = 6; + command.instanceCount = billboardsLength; + } - 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); - }; + commandList.push(command); + } + } - this._primitive = new Primitive(primitiveOptions); - this._primitive.readyPromise.then(function(primitive) { - that._ready = true; + if (picking) { + var pickList = this._pickCommands; - if (that.releaseGeometryInstances) { - that.geometryInstances = undefined; - } + va = this._vaf.va; + vaLength = va.length; - var error = primitive._error; - if (!defined(error)) { - that._readyPromise.resolve(that); - } else { - that._readyPromise.reject(error); + 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 + }); } - }); - } - if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) { - this._debugShowShadowVolume = true; - this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false)); - this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false)); - this._rsColorPass = RenderState.fromCache(getColorRenderState(false)); - } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) { - this._debugShowShadowVolume = false; - this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true)); - this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); - this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); - } + 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._rsOpaque; - this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; - this._primitive.update(frameState); - }; + if (this._instanced) { + command.count = 6; + command.instanceCount = billboardsLength; + } - /** - * @private - */ - GroundPrimitive.prototype.getBoundingSphere = function(id) { - var index = this._boundingSpheresKeys.indexOf(id); - if (index !== -1) { - return this._boundingSpheres[index]; + commandList.push(command); + } } - - 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) { - 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 + * @see BillboardCollection#destroy */ - GroundPrimitive.prototype.isDestroyed = function() { + 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 - * e = e && e.destroy(); + * billboards = billboards && billboards.destroy(); * - * @see GroundPrimitive#isDestroyed + * @see BillboardCollection#isDestroyed */ - GroundPrimitive.prototype.destroy = function() { - this._primitive = this._primitive && this._primitive.destroy(); + 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._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); this._spPick = this._spPick && this._spPick.destroy(); + this._vaf = this._vaf && this._vaf.destroy(); + destroyBillboards(this._billboards); + return destroyObject(this); }; - return GroundPrimitive; + return BillboardCollection; }); -/*global define*/ -define('DataSources/CorridorGeometryUpdater',[ +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); +}); + +define('Scene/Label',[ + '../Core/BoundingRectangle', + '../Core/Cartesian2', + '../Core/Cartesian3', '../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' + '../Core/NearFarScalar', + './Billboard', + './HeightReference', + './HorizontalOrigin', + './LabelStyle', + './VerticalOrigin' ], function( + BoundingRectangle, + Cartesian2, + Cartesian3, 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) { + NearFarScalar, + Billboard, + HeightReference, + HorizontalOrigin, + LabelStyle, + VerticalOrigin) { '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 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 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; + 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 {@link GeometryUpdater} for corridors. - * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}. - * @alias CorridorGeometryUpdater - * @constructor + * 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}. * - * @param {Entity} entity The entity containing the geometry to be visualized. - * @param {Scene} scene The scene where visualization is taking place. + * @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 CorridorGeometryUpdater(entity, scene) { + function Label(options, labelCollection) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + - 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); + var translucencyByDistance = options.translucencyByDistance; + var pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance; + var scaleByDistance = options.scaleByDistance; + var distanceDisplayCondition = options.distanceDisplayCondition; + if (defined(translucencyByDistance)) { + translucencyByDistance = NearFarScalar.clone(translucencyByDistance); + } + if (defined(pixelOffsetScaleByDistance)) { + pixelOffsetScaleByDistance = NearFarScalar.clone(pixelOffsetScaleByDistance); + } + if (defined(scaleByDistance)) { + scaleByDistance = NearFarScalar.clone(scaleByDistance); + } + if (defined(distanceDisplayCondition)) { + distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); + } - this._onEntityPropertyChanged(entity, 'corridor', entity.corridor, undefined); - } + 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 = translucencyByDistance; + this._pixelOffsetScaleByDistance = pixelOffsetScaleByDistance; + this._scaleByDistance = scaleByDistance; + this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); + this._distanceDisplayCondition = distanceDisplayCondition; + this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); - 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 - } - }); + this._labelCollection = labelCollection; + this._glyphs = []; + this._backgroundBillboard = undefined; - defineProperties(CorridorGeometryUpdater.prototype, { + this._rebindAllGlyphs = true; + this._repositionAllGlyphs = true; + + this._actualClampedPosition = undefined; + this._removeCallbackFunc = undefined; + this._mode = undefined; + + this._clusterShow = true; + + this._updateClamping(); + } + + defineProperties(Label.prototype, { /** - * Gets the entity associated with this geometry. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Entity} - * @readonly + * 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 */ - entity : { + show : { get : function() { - return this._entity; + return this._show; + }, + set : function(value) { + + 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 a value indicating if the geometry has a fill component. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * Gets or sets the Cartesian position of this label. + * @memberof Label.prototype + * @type {Cartesian3} */ - fillEnabled : { + position : { get : function() { - return this._fillEnabled; + return this._position; + }, + set : function(value) { + + 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; + } + + this._updateClamping(); + } } }, + /** - * Gets a value indicating if fill visibility varies with simulation time. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * Gets or sets the height reference of this billboard. + * @memberof Label.prototype + * @type {HeightReference} + * @default HeightReference.NONE */ - hasConstantFill : { + heightReference : { get : function() { - return !this._fillEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._fillProperty)); + return this._heightReference; + }, + set : function(value) { + + 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 the material property used to fill the geometry. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {MaterialProperty} - * @readonly + * Gets or sets the text of this label. + * @memberof Label.prototype + * @type {String} */ - fillMaterialProperty : { + text : { get : function() { - return this._materialProperty; + return this._text; + }, + set : function(value) { + + if (this._text !== value) { + this._text = value; + rebindAllGlyphs(this); + } } }, + /** - * Gets a value indicating if the geometry has an outline component. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * 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} */ - outlineEnabled : { + font : { get : function() { - return this._outlineEnabled; + return this._font; + }, + set : function(value) { + + if (this._font !== value) { + this._font = value; + rebindAllGlyphs(this); + } } }, + /** - * Gets a value indicating if the geometry has an outline component. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * 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} */ - hasConstantOutline : { + fillColor : { get : function() { - return !this._outlineEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._showOutlineProperty)); + return this._fillColor; + }, + set : function(value) { + + var fillColor = this._fillColor; + if (!Color.equals(fillColor, value)) { + Color.clone(value, fillColor); + rebindAllGlyphs(this); + } } }, + /** - * Gets the {@link Color} property for the geometry outline. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Property} - * @readonly + * 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} */ - outlineColorProperty : { + outlineColor : { get : function() { - return this._outlineColorProperty; + return this._outlineColor; + }, + set : function(value) { + + var outlineColor = this._outlineColor; + if (!Color.equals(outlineColor, value)) { + Color.clone(value, outlineColor); + rebindAllGlyphs(this); + } } }, + /** - * Gets the constant with of the geometry outline, in pixels. - * This value is only valid if isDynamic is false. - * @memberof CorridorGeometryUpdater.prototype - * + * Gets or sets the outline width of this label. + * @memberof Label.prototype * @type {Number} - * @readonly + * @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 (this._outlineWidth !== value) { + this._outlineWidth = value; + rebindAllGlyphs(this); + } } }, + /** - * Gets the property specifying whether the geometry - * casts or receives shadows from each light source. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Property} - * @readonly + * Determines if a background behind this label will be shown. + * @memberof Label.prototype + * @default false + * @type {Boolean} */ - shadowsProperty : { + showBackground : { get : function() { - return this._shadowsProperty; + return this._showBackground; + }, + set : function(value) { + + if (this._showBackground !== value) { + this._showBackground = value; + rebindAllGlyphs(this); + } } }, + /** - * 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 + * 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) */ - distanceDisplayConditionProperty : { + backgroundColor : { get : function() { - return this._distanceDisplayCondition; + return this._backgroundColor; + }, + set : function(value) { + + var backgroundColor = this._backgroundColor; + if (!Color.equals(backgroundColor, value)) { + Color.clone(value, backgroundColor); + + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.color = backgroundColor; + } + } } }, + /** - * 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 + * 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) */ - isDynamic : { + backgroundPadding : { get : function() { - return this._dynamic; + return this._backgroundPadding; + }, + set : function(value) { + + var backgroundPadding = this._backgroundPadding; + if (!Cartesian2.equals(backgroundPadding, value)) { + Cartesian2.clone(value, backgroundPadding); + repositionAllGlyphs(this); + } } }, + /** - * Gets a value indicating if the geometry is closed. - * This property is only valid for static geometry. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * Gets or sets the style of this label. + * @memberof Label.prototype + * @type {LabelStyle} + * @default LabelStyle.FILL */ - isClosed : { + style : { get : function() { - return this._isClosed; + return this._style; + }, + set : function(value) { + + if (this._style !== value) { + this._style = value; + rebindAllGlyphs(this); + } } }, + /** - * Gets a value indicating if the geometry should be drawn on terrain. - * @memberof CorridorGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * 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 */ - onTerrain : { + pixelOffset : { get : function() { - return this._onTerrain; + return this._pixelOffset; + }, + set : function(value) { + + 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 an event that is raised whenever the public properties - * of this updater change. - * @memberof CorridorGeometryUpdater.prototype + * 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} * - * @type {Boolean} - * @readonly + * @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; */ - geometryChanged : { + translucencyByDistance : { 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) { - - 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); - var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + return this._translucencyByDistance; + }, + set : function(value) { + + var translucencyByDistance = this._translucencyByDistance; + if (!NearFarScalar.equals(translucencyByDistance, value)) { + this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance); - 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)) + 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; + } + } } - }); - }; - - /** - * 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; + /** + * 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) { + + var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance; + if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) { + this._pixelOffsetScaleByDistance = NearFarScalar.clone(value, pixelOffsetScaleByDistance); - if (!defined(corridor)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); + 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; + } + } } - 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); - } + /** + * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera. + * A label'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 label's scale remains clamped to the nearest bound. If undefined, + * scaleByDistance will be disabled. + * @memberof Label.prototype + * @type {NearFarScalar} + * + * @example + * // Example 1. + * // Set a label's scaleByDistance to scale by 1.5 when the + * // camera is 1500 meters from the label and disappear as + * // the camera distance approaches 8.0e6 meters. + * label.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0); + * + * @example + * // Example 2. + * // disable scaling by distance + * label.scaleByDistance = undefined; + */ + scaleByDistance : { + get : function() { + return this._scaleByDistance; + }, + set : function(value) { + + var scaleByDistance = this._scaleByDistance; + if (!NearFarScalar.equals(scaleByDistance, value)) { + this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance); - if (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); + var glyphs = this._glyphs; + for (var i = 0, len = glyphs.length; i < len; i++) { + var glyph = glyphs[i]; + if (defined(glyph.billboard)) { + glyph.billboard.scaleByDistance = value; + } + } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.scaleByDistance = value; + } + } } - return; - } + }, - var positions = corridor.positions; + /** + * 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) { + + var eyeOffset = this._eyeOffset; + if (!Cartesian3.equals(eyeOffset, value)) { + Cartesian3.clone(value, eyeOffset); - 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); + 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; + } + } } - 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); + /** + * 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 (this._horizontalOrigin !== value) { + this._horizontalOrigin = value; + repositionAllGlyphs(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. - * @param {PrimitiveCollection} groundPrimitives The ground primitives 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) { - - 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) { - - 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); - } + /** + * 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 (this._verticalOrigin !== value) { + this._verticalOrigin = value; - this._primitive = groundPrimitives.add(new GroundPrimitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new CorridorGeometry(options), - attributes: { - color: ColorGeometryInstanceAttribute.fromColor(currentColor), - distanceDisplayCondition : distanceDisplayConditionAttribute + 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; } - }), - asynchronous : false, - shadows : shadows - })); - } else { - var appearance = new MaterialAppearance({ - material : material, - translucent : material.isTranslucent(), - closed : defined(options.extrudedHeight) - }); - options.vertexFormat = appearance.vertexFormat; + } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.verticalOrigin = value; + } - this._primitive = primitives.add(new Primitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new CorridorGeometry(options), - attributes : { - distanceDisplayCondition : distanceDisplayConditionAttribute - } - }), - appearance : appearance, - asynchronous : false, - shadows : shadows - })); + repositionAllGlyphs(this); + } } - } - - 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; + /** + * 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 (this._scale !== value) { + this._scale = value; - this._outlinePrimitive = primitives.add(new Primitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new CorridorOutlineGeometry(options), - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(outlineColor), - distanceDisplayCondition : distanceDisplayConditionAttribute + 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; + } } - }), - appearance : new PerInstanceColorAppearance({ - flat : true, - translucent : translucent, - renderState : { - lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth) + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.scale = value; } - }), - 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 + repositionAllGlyphs(this); + } + } }, + /** - * Gets a value indicating if the data source is currently loading data. - * @memberof DataSource.prototype - * @type {Boolean} + * 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 */ - isLoading : { - get : DeveloperError.throwInstantiationError + distanceDisplayCondition : { + get : function() { + return this._distanceDisplayCondition; + }, + set : function(value) { + 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 an event that will be raised when the underlying data changes. - * @memberof DataSource.prototype - * @type {Event} + * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. + * @memberof Label.prototype + * @type {Number} + * @default 0.0 */ - changedEvent : { - get : DeveloperError.throwInstantiationError + disableDepthTestDistance : { + get : function() { + return this._disableDepthTestDistance; + }, + set : function(value) { + if (this._disableDepthTestDistance !== value) { + this._disableDepthTestDistance = 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.disableDepthTestDistance = value; + } + } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.disableDepthTestDistance = value; + } + } + } }, + /** - * Gets an event that will be raised if an error is encountered during processing. - * @memberof DataSource.prototype - * @type {Event} + * Gets or sets the user-defined object returned when the label is picked. + * @memberof Label.prototype + * @type {Object} */ - errorEvent : { - get : DeveloperError.throwInstantiationError + 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; + } + } + } }, + /** - * Gets an event that will be raised when the value of isLoading changes. - * @memberof DataSource.prototype - * @type {Event} + * Keeps track of the position of the label based on the height reference. + * @memberof Label.prototype + * @type {Cartesian3} + * @private */ - loadingEvent : { - get : DeveloperError.throwInstantiationError + _clampedPosition : { + get : function() { + return this._actualClampedPosition; + }, + set : function(value) { + this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition); + + var glyphs = this._glyphs; + 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; + } + } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard._clampedPosition = value; + } + } }, + /** - * Gets whether or not this data source should be displayed. - * @memberof DataSource.prototype + * Determines whether or not this label will be shown or hidden because it was clustered. + * @memberof Label.prototype * @type {Boolean} + * @default true + * @private */ - show : { - get : DeveloperError.throwInstantiationError - }, + clusterShow : { + get : function() { + return this._clusterShow; + }, + set : function(value) { + if (this._clusterShow !== value) { + this._clusterShow = value; - /** - * 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 + 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; + } + } + } } }); - /** - * 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); - } + Label.prototype._updateClamping = function() { + Billboard._updateClamping(this._labelCollection, this); }; - return DataSource; -}); - -/*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. + * 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. * - * @alias CullingVolume - * @constructor + * @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. * - * @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(1.0, 0.0, 0.0), 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. + * @example + * console.log(l.computeScreenSpacePosition(scene).toString()); + * + * @see Label#eyeOffset + * @see Label#pixelOffset */ - CullingVolume.fromBoundingSphere = function(boundingSphere, result) { + Label.prototype.computeScreenSpacePosition = function(scene, result) { if (!defined(result)) { - result = new CullingVolume(); + result = new Cartesian2(); } - var length = faces.length; - var planes = result.planes; - planes.length = 2 * length; - - var center = boundingSphere.center; - var radius = boundingSphere.radius; + var labelCollection = this._labelCollection; + var modelMatrix = labelCollection.modelMatrix; + var actualPosition = defined(this._actualClampedPosition) ? this._actualClampedPosition : this._position; - var planeIndex = 0; + var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, actualPosition, + this._eyeOffset, this._pixelOffset, scene, result); + return windowCoordinates; + }; - for (var i = 0; i < length; ++i) { - var faceNormal = faces[i]; + /** + * 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 plane0 = planes[planeIndex]; - var plane1 = planes[planeIndex + 1]; + 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 (!defined(plane0)) { - plane0 = planes[planeIndex] = new Cartesian4(); - } - if (!defined(plane1)) { - plane1 = planes[planeIndex + 1] = new Cartesian4(); + 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; + } - Cartesian3.multiplyByScalar(faceNormal, -radius, scratchPlaneCenter); - Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter); + 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; - plane0.x = faceNormal.x; - plane0.y = faceNormal.y; - plane0.z = faceNormal.z; - plane0.w = -Cartesian3.dot(faceNormal, scratchPlaneCenter); + if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) { + glyphY -= glyphHeight; + } else if (label.verticalOrigin === VerticalOrigin.CENTER) { + glyphY -= glyphHeight * 0.5; + } - Cartesian3.multiplyByScalar(faceNormal, radius, scratchPlaneCenter); - Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter); + x = Math.min(x, glyphX); + y = Math.min(y, glyphY); + maxX = Math.max(maxX, glyphX + glyphWidth); + maxY = Math.max(maxY, glyphY + glyphHeight); + } - plane1.x = -faceNormal.x; - plane1.y = -faceNormal.y; - plane1.z = -faceNormal.z; - plane1.w = -Cartesian3.dot(Cartesian3.negate(faceNormal, scratchPlaneNormal), scratchPlaneCenter); + width = maxX - x; + height = maxY - y; + } - planeIndex += 2; + if (!defined(result)) { + result = new BoundingRectangle(); } + result.x = x; + result.y = y; + result.width = width; + result.height = height; + return result; }; /** - * Determines whether a bounding volume intersects the culling volume. + * Determines if this label equals another label. Labels are equal if all their properties + * are equal. Labels in different collections can be equal. * - * @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. + * @param {Label} other The label to compare for equality. + * @returns {Boolean} true if the labels are equal; otherwise, false. */ - CullingVolume.prototype.computeVisibility = function(boundingVolume) { - - 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; + 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) && + NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) && + DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition) && + this._disableDepthTestDistance === other._disableDepthTestDistance && + this._id === other._id; }; /** - * 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). + * 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. * - * @private + * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - CullingVolume.prototype.computeVisibilityWithPlaneMask = function(boundingVolume, parentPlaneMask) { - - 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; + Label.prototype.isDestroyed = function() { + return false; }; - /** - * 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; + return Label; }); -/*global define*/ -define('Scene/OrthographicOffCenterFrustum',[ - '../Core/Cartesian3', - '../Core/Cartesian4', +define('Scene/LabelCollection',[ + '../Core/BoundingRectangle', + '../Core/Cartesian2', + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', '../Core/Matrix4', - './CullingVolume' + '../Core/writeTextToCanvas', + './BillboardCollection', + './BlendOption', + './HorizontalOrigin', + './Label', + './LabelStyle', + './TextureAtlas', + './VerticalOrigin' ], function( - Cartesian3, - Cartesian4, + BoundingRectangle, + Cartesian2, + defaultValue, defined, defineProperties, + destroyObject, DeveloperError, Matrix4, - CullingVolume) { + writeTextToCanvas, + BillboardCollection, + BlendOption, + HorizontalOrigin, + Label, + LabelStyle, + TextureAtlas, + VerticalOrigin) { '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 OrthographicOffCenterFrustum - * @constructor - * - * @example - * var maxRadii = ellipsoid.maximumRadius; - * - * var frustum = new Cesium.OrthographicOffCenterFrustum(); - * 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 OrthographicOffCenterFrustum() { - /** - * The left clipping plane. - * @type {Number} - * @default undefined - */ - this.left = undefined; - this._left = undefined; + // 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; + } - /** - * The right clipping plane. - * @type {Number} - * @default undefined - */ - this.right = undefined; - this._right = 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; + } - /** - * The top clipping plane. - * @type {Number} - * @default undefined - */ - this.top = undefined; - this._top = undefined; + // Traditionally, leading is %20 of the font size. + var defaultLineSpacingPercent = 1.2; - /** - * The bottom clipping plane. - * @type {Number} - * @default undefined - */ - this.bottom = undefined; - this._bottom = undefined; + var whitePixelCanvasId = 'ID_WHITE_PIXEL'; + var whitePixelSize = new Cartesian2(4, 4); + var whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1); - /** - * The distance of the near plane. - * @type {Number} - * @default 1.0 - */ - this.near = 1.0; - this._near = this.near; + function addWhitePixelCanvas(textureAtlas, labelCollection) { + var canvas = document.createElement('canvas'); + canvas.width = whitePixelSize.x; + canvas.height = whitePixelSize.y; - /** - * The distance of the far plane. - * @type {Number} - * @default 500000000.0; - */ - this.far = 500000000.0; - this._far = this.far; + var context2D = canvas.getContext('2d'); + context2D.fillStyle = '#fff'; + context2D.fillRect(0, 0, canvas.width, canvas.height); - this._cullingVolume = new CullingVolume(); - this._orthographicMatrix = new Matrix4(); + textureAtlas.addImage(whitePixelCanvasId, canvas).then(function(index) { + labelCollection._whitePixelIndex = index; + }); } - function update(frustum) { - - 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) { + // 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; - - 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); + 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); } - defineProperties(OrthographicOffCenterFrustum.prototype, { - /** - * Gets the orthographic projection matrix computed from the view frustum. - * @memberof OrthographicOffCenterFrustum.prototype - * @type {Matrix4} - * @readonly - */ - projectionMatrix : { - get : function() { - update(this); - return this._orthographicMatrix; + function unbindGlyph(labelCollection, glyph) { + glyph.textureInfo = undefined; + glyph.dimensions = undefined; + + var billboard = glyph.billboard; + if (defined(billboard)) { + billboard.show = false; + billboard.image = undefined; + if (defined(billboard._removeCallbackFunc)) { + billboard._removeCallbackFunc(); + billboard._removeCallbackFunc = undefined; } + labelCollection._spareBillboards.push(billboard); + glyph.billboard = undefined; } - }); + } - var getPlanesRight = new Cartesian3(); - var getPlanesNearCenter = new Cartesian3(); - var getPlanesPoint = new Cartesian3(); - var negateScratch = new Cartesian3(); + function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) { + textureAtlas.addImage(id, canvas).then(function(index, id) { + glyphTextureInfo.index = index; + }); + } - /** - * 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); - */ - OrthographicOffCenterFrustum.prototype.computeCullingVolume = function(position, direction, up) { - - 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; + function rebindAllGlyphs(labelCollection, label) { + var text = label._text; + var textLength = text.length; + var glyphs = label._glyphs; + var glyphsLength = glyphs.length; - var right = Cartesian3.cross(direction, up, getPlanesRight); - Cartesian3.normalize(right, right); - var nearCenter = getPlanesNearCenter; - Cartesian3.multiplyByScalar(direction, n, nearCenter); - Cartesian3.add(position, nearCenter, nearCenter); + var glyph; + var glyphIndex; + var textIndex; - var point = getPlanesPoint; + // if we have more glyphs than needed, unbind the extras. + if (textLength < glyphsLength) { + for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) { + unbindGlyph(labelCollection, glyphs[glyphIndex]); + } + } - // Left plane - Cartesian3.multiplyByScalar(right, l, point); - Cartesian3.add(nearCenter, point, point); + // presize glyphs to match the new text length + glyphs.length = textLength; - var plane = planes[0]; - if (!defined(plane)) { - plane = planes[0] = new Cartesian4(); + 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.scaleByDistance = label._scaleByDistance; + backgroundBillboard.distanceDisplayCondition = label._distanceDisplayCondition; + backgroundBillboard.disableDepthTestDistance = label._disableDepthTestDistance; } - 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); + var glyphTextureCache = labelCollection._glyphTextureCache; - 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); + // 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; - // Bottom plane - Cartesian3.multiplyByScalar(up, b, point); - Cartesian3.add(nearCenter, point, point); + // 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 + ]); - 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); + var glyphTextureInfo = glyphTextureCache[id]; + if (!defined(glyphTextureInfo)) { + var canvas = createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin); - // Top plane - Cartesian3.multiplyByScalar(up, t, point); - Cartesian3.add(nearCenter, point, point); + glyphTextureInfo = new GlyphTextureInfo(labelCollection, -1, canvas.dimensions); + glyphTextureCache[id] = glyphTextureInfo; - plane = planes[3]; - if (!defined(plane)) { - plane = planes[3] = new Cartesian4(); + 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 if (defined(glyph.textureInfo)) { + // we have a texture and billboard. If we had one before, release + // our reference to that texture info, but reuse the billboard. + 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.scaleByDistance = label._scaleByDistance; + billboard.distanceDisplayCondition = label._distanceDisplayCondition; + billboard.disableDepthTestDistance = label._disableDepthTestDistance; + } } - 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(); + // 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); } - plane.x = direction.x; - plane.y = direction.y; - plane.z = direction.z; - plane.w = -Cartesian3.dot(direction, nearCenter); + return backgroundPadding.x; + } - // Far plane - Cartesian3.multiplyByScalar(direction, f, point); - Cartesian3.add(position, point, point); + // reusable Cartesian2 instances + var glyphPixelOffset = new Cartesian2(); + var scratchBackgroundPadding = new Cartesian2(); - plane = planes[5]; - if (!defined(plane)) { - plane = planes[5] = new Cartesian4(); + 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); + } } - plane.x = -direction.x; - plane.y = -direction.y; - plane.z = -direction.z; - plane.w = -Cartesian3.dot(Cartesian3.negate(direction, negateScratch), point); + lineWidths.push(lastLineWidth); + var maxLineHeight = maxGlyphY + maxGlyphDescent; - return this._cullingVolume; - }; + 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); - /** - * 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()); - */ - OrthographicOffCenterFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) { - update(this); + glyphPixelOffset.x = widthOffset * scale * resolutionScale; + glyphPixelOffset.y = 0; - - var frustumWidth = this.right - this.left; - var frustumHeight = this.top - this.bottom; - var pixelWidth = frustumWidth / drawingBufferWidth; - var pixelHeight = frustumHeight / drawingBufferHeight; + 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; - result.x = pixelWidth; - result.y = pixelHeight; - return result; - }; + 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; - /** - * Returns a duplicate of a OrthographicOffCenterFrustum instance. - * - * @param {OrthographicOffCenterFrustum} [result] The object onto which to store the result. - * @returns {OrthographicOffCenterFrustum} The modified result parameter or a new OrthographicOffCenterFrustum instance if one was not provided. - */ - OrthographicOffCenterFrustum.prototype.clone = function(result) { - if (!defined(result)) { - result = new OrthographicOffCenterFrustum(); + 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; + } + } } - result.left = this.left; - result.right = this.right; - result.top = this.top; - result.bottom = this.bottom; - result.near = this.near; - result.far = this.far; + 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; - // 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; + 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; - return result; - }; + backgroundBillboard.width = maxLineWidth + (backgroundPadding.x * 2); + backgroundBillboard.height = maxLineHeight + otherLinesHeight + (backgroundPadding.y * 2); + backgroundBillboard._setTranslate(glyphPixelOffset); + } + } - /** - * Compares the provided OrthographicOffCenterFrustum componentwise and returns - * true if they are equal, false otherwise. - * - * @param {OrthographicOffCenterFrustum} [other] The right hand side OrthographicOffCenterFrustum. - * @returns {Boolean} true if they are equal, false otherwise. - */ - OrthographicOffCenterFrustum.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); - }; + 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; - return OrthographicOffCenterFrustum; -}); + if (defined(label._removeCallbackFunc)) { + label._removeCallbackFunc(); + } -/*global define*/ -define('Scene/OrthographicFrustum',[ - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - './OrthographicOffCenterFrustum' - ], function( - defined, - defineProperties, - DeveloperError, - OrthographicOffCenterFrustum) { - 'use strict'; + destroyObject(label); + } /** - * 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. + * 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 OrthographicOffCenterFrustum + * @alias LabelCollection * @constructor * - * @example - * var maxRadii = ellipsoid.maximumRadius; + * @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. + * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The label blending option. The default + * is used for rendering both opaque and translucent labels. However, if either all of the labels are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. * - * var frustum = new Cesium.OrthographicOffCenterFrustum(); - * frustum.near = 0.01 * maxRadii; - * frustum.far = 50.0 * maxRadii; + * @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 OrthographicFrustum() { - this._offCenterFrustum = new OrthographicOffCenterFrustum(); + function LabelCollection(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this.width = undefined; - this._width = undefined; + this._scene = options.scene; - this.aspectRatio = undefined; - this._aspectRatio = undefined; + 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 distance of the near plane. - * @type {Number} - * @default 1.0 + * 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.near = 1.0; - this._near = this.near; + this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); /** - * The distance of the far plane. - * @type {Number} - * @default 500000000.0; + * 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.far = 500000000.0; - this._far = this.far; - } - - function update(frustum) { - - var f = frustum._offCenterFrustum; - - if (frustum.width !== frustum._width || frustum.aspectRatio !== frustum._aspectRatio || - frustum.near !== frustum._near || frustum.far !== frustum._far) { - - frustum._aspectRatio = frustum.aspectRatio; - frustum._width = frustum.width; - frustum._near = frustum.near; - frustum._far = frustum.far; - - var ratio = 1.0 / frustum.aspectRatio; - f.right = frustum.width * 0.5; - f.left = -f.right; - f.top = ratio * f.right; - f.bottom = -f.top; - f.near = frustum.near; - f.far = frustum.far; + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); - } + /** + * The label blending option. The default is used for rendering both opaque and translucent labels. + * However, if either all of the labels are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve + * performance by up to 2x. + * @type {BlendOption} + * @default BlendOption.OPAQUE_AND_TRANSLUCENT + */ + this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); } - defineProperties(OrthographicFrustum.prototype, { + defineProperties(LabelCollection.prototype, { /** - * Gets the orthographic projection matrix computed from the view frustum. - * @memberof OrthographicFrustum.prototype - * @type {Matrix4} - * @readonly + * 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} */ - projectionMatrix : { + length : { get : function() { - update(this); - return this._offCenterFrustum.projectionMatrix; + return this._labels.length; } } - }); /** - * Creates a culling volume for this frustum. + * 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 {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. + * @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. * - * @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) { - update(this); - return this._offCenterFrustum.computeCullingVolume(position, direction, up); - }; - - /** - * Returns the pixel's width and height in meters. + * @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. * - * @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} This object was destroyed, i.e., destroy() was called. * - * @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); - return this._offCenterFrustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, result); - }; - - /** - * Returns a duplicate of a OrthographicFrustum instance. + * // 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 + * }); * - * @param {OrthographicFrustum} [result] The object onto which to store the result. - * @returns {OrthographicFrustum} The modified result parameter or a new OrthographicFrustum instance if one was not provided. + * @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 */ - OrthographicFrustum.prototype.clone = function(result) { - if (!defined(result)) { - result = new OrthographicFrustum(); - } - - result.aspectRatio = this.aspectRatio; - result.width = this.width; - result.near = this.near; - result.far = this.far; - - // force update of clone to compute matrices - result._aspectRatio = undefined; - result._width = undefined; - result._near = undefined; - result._far = undefined; + LabelCollection.prototype.add = function(options) { + var label = new Label(options, this); - this._offCenterFrustum.clone(result._offCenterFrustum); + this._labels.push(label); + this._labelsToUpdate.push(label); - return result; + return label; }; /** - * Compares the provided OrthographicFrustum componentwise and returns - * true if they are equal, false otherwise. + * Removes a label from the collection. Once removed, a label is no longer usable. * - * @param {OrthographicFrustum} [other] The right hand side OrthographicFrustum. - * @returns {Boolean} true if they are equal, false otherwise. + * @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 */ - OrthographicFrustum.prototype.equals = function(other) { - if (!defined(other)) { - return false; + 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; + } } - - update(this); - update(other); - - return (this.width === other.width && - this.aspectRatio === other.aspectRatio && - this.near === other.near && - this.far === other.far && - this._offCenterFrustum.equals(other._offCenterFrustum)); + return false; }; - return OrthographicFrustum; -}); - -/*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', - './OrthographicFrustum', - './OrthographicOffCenterFrustum', - './SceneMode' - ], function( - BoundingRectangle, - Cartesian2, - Cartesian3, - Cartesian4, - Cartographic, - defined, - DeveloperError, - CesiumMath, - Matrix4, - Transforms, - OrthographicFrustum, - OrthographicOffCenterFrustum, - SceneMode) { - 'use strict'; - /** - * Functions that do scene-dependent transforms between rendering-related coordinate systems. + * Removes all labels from the collection. * - * @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. + * @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. * - * @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); + * labels.add(...); + * labels.add(...); + * labels.removeAll(); + * + * @see LabelCollection#add + * @see LabelCollection#remove */ - 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; + LabelCollection.prototype.removeAll = function() { + var labels = this._labels; - return Matrix4.multiplyByVector(camera.frustum.projectionMatrix, positionEC, result); - } + for (var i = 0, len = labels.length; i < len; ++i) { + destroyLabel(this, labels[i]); + } - var scratchMaxCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO); - var scratchProjectedCartesian = new Cartesian3(); - var scratchCameraPosition = new Cartesian3(); + labels.length = 0; + }; /** - * @private + * 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 */ - SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates = function(scene, position, eyeOffset, result) { - - // 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 && !(camera.frustum instanceof OrthographicFrustum) && !(camera.frustum instanceof OrthographicOffCenterFrustum)) { - return undefined; - } - - result = SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, result); - } - - result.y = canvas.clientHeight - result.y; - return result; + LabelCollection.prototype.contains = function(label) { + return defined(label) && label._labelCollection === this; }; /** - * 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. + * 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. * - * @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); + * // 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 */ - SceneTransforms.wgs84ToDrawingBufferCoordinates = function(scene, position, result) { - result = SceneTransforms.wgs84ToWindowCoordinates(scene, position, result); - if (!defined(result)) { - return undefined; - } - - return SceneTransforms.transformWindowToDrawingBuffer(scene, result, result); + LabelCollection.prototype.get = function(index) { + + return this._labels[index]; }; - var projectedPosition = new Cartesian3(); - var positionInCartographic = new Cartographic(); - /** * @private */ - SceneTransforms.computeActualWgs84Position = function(frameState, position, result) { - var mode = frameState.mode; + LabelCollection.prototype.update = function(frameState) { + var billboardCollection = this._billboardCollection; + var backgroundBillboardCollection = this._backgroundBillboardCollection; - if (mode === SceneMode.SCENE3D) { - return Cartesian3.clone(position, result); + 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; } - var projection = frameState.mapProjection; - var cartographic = projection.ellipsoid.cartesianToCartographic(position, positionInCartographic); - if (!defined(cartographic)) { - return undefined; + if (!defined(this._backgroundTextureAtlas)) { + this._backgroundTextureAtlas = new TextureAtlas({ + context : context, + initialSize : whitePixelSize + }); + backgroundBillboardCollection.textureAtlas = this._backgroundTextureAtlas; + addWhitePixelCanvas(this._backgroundTextureAtlas, this); } - projection.project(cartographic, projectedPosition); + var uniformState = context.uniformState; + var resolutionScale = uniformState.resolutionScale; + var resolutionChanged = this._resolutionScale !== resolutionScale; + this._resolutionScale = resolutionScale; - if (mode === SceneMode.COLUMBUS_VIEW) { - return Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, result); + var labelsToUpdate; + if (resolutionChanged) { + labelsToUpdate = this._labels; + } else { + labelsToUpdate = this._labelsToUpdate; } - if (mode === SceneMode.SCENE2D) { - return Cartesian3.fromElements(0.0, projectedPosition.x, projectedPosition.y, result); - } + var len = labelsToUpdate.length; + for (var i = 0; i < len; ++i) { + var label = labelsToUpdate[i]; + if (label.isDestroyed()) { + continue; + } - // 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 preUpdateGlyphCount = label._glyphs.length; - var positionNDC = new Cartesian3(); - var positionWC = new Cartesian3(); - var viewportTransform = new Matrix4(); + if (label._rebindAllGlyphs) { + rebindAllGlyphs(this, label); + label._rebindAllGlyphs = false; + } - /** - * @private - */ - SceneTransforms.clipToGLWindowCoordinates = function(viewport, position, result) { - // Perspective divide to transform from clip coordinates to normalized device coordinates - Cartesian3.divideByScalar(position, position.w, positionNDC); + if (resolutionChanged || label._repositionAllGlyphs) { + repositionAllGlyphs(label, resolutionScale); + label._repositionAllGlyphs = false; + } - // Viewport transform to transform from clip coordinates to window coordinates - Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform); - Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC); + var glyphCountDifference = label._glyphs.length - preUpdateGlyphCount; + this._totalGlyphCount += glyphCountDifference; + } - return Cartesian2.fromCartesian3(positionWC, result); + var blendOption = backgroundBillboardCollection.length > 0 ? BlendOption.TRANSLUCENT : this.blendOption; + billboardCollection.blendOption = blendOption; + backgroundBillboardCollection.blendOption = blendOption; + + this._labelsToUpdate.length = 0; + backgroundBillboardCollection.update(frameState); + billboardCollection.update(frameState); }; /** - * @private + * 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 */ - 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); + LabelCollection.prototype.isDestroyed = function() { + return false; }; - var scratchNDC = new Cartesian4(); - var scratchWorldCoords = new Cartesian4(); - /** - * @private + * 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 */ - 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; - var frustum = scene.camera.frustum; - if (!defined(frustum.fovy)) { - if (defined(frustum._offCenterFrustum)) { - frustum = frustum._offCenterFrustum; - } - var currentFrustum = uniformState.currentFrustum; - worldCoords = scratchWorldCoords; - worldCoords.x = (ndc.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; - worldCoords.y = (ndc.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; - worldCoords.z = (ndc.z * (currentFrustum.x - currentFrustum.y) - currentFrustum.x - currentFrustum.y) * 0.5; - worldCoords.w = 1.0; - - worldCoords = Matrix4.multiplyByVector(uniformState.inverseView, worldCoords, worldCoords); - } else { - worldCoords = Matrix4.multiplyByVector(uniformState.inverseViewProjection, ndc, scratchWorldCoords); - - // Reverse perspective divide - var w = 1.0 / worldCoords.w; - Cartesian3.multiplyByScalar(worldCoords, w, worldCoords); - } + 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 Cartesian3.fromCartesian4(worldCoords, result); + return destroyObject(this); }; - return SceneTransforms; + return LabelCollection; }); -/*global define*/ -define('Scene/Billboard',[ +define('Scene/PointPrimitive',[ '../Core/BoundingRectangle', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', - '../Core/Cartographic', '../Core/Color', - '../Core/createGuid', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -103807,19 +114059,14 @@ define('Scene/Billboard',[ '../Core/DistanceDisplayCondition', '../Core/Matrix4', '../Core/NearFarScalar', - './HeightReference', - './HorizontalOrigin', './SceneMode', - './SceneTransforms', - './VerticalOrigin' + './SceneTransforms' ], function( BoundingRectangle, Cartesian2, Cartesian3, Cartesian4, - Cartographic, Color, - createGuid, defaultValue, defined, defineProperties, @@ -103827,155 +114074,100 @@ define('Scene/Billboard',[ DistanceDisplayCondition, Matrix4, NearFarScalar, - HeightReference, - HorizontalOrigin, SceneMode, - SceneTransforms, - VerticalOrigin) { + SceneTransforms) { '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 - *
    + * 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 Billboard + * @alias PointPrimitive * - * @performance Reading a property, e.g., {@link Billboard#show}, is constant time. + * @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 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. + * 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} 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 + * @see PointPrimitiveCollection + * @see PointPrimitiveCollection#add * * @internalConstructor * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo} + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Points.html|Cesium Sandcastle Points Demo} */ - function Billboard(options, billboardCollection) { + function PointPrimitive(options, pointPrimitiveCollection) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var translucencyByDistance = options.translucencyByDistance; + var scaleByDistance = options.scaleByDistance; + var distanceDisplayCondition = options.distanceDisplayCondition; + if (defined(translucencyByDistance)) { + translucencyByDistance = NearFarScalar.clone(translucencyByDistance); + } + if (defined(scaleByDistance)) { + scaleByDistance = NearFarScalar.clone(scaleByDistance); + } + if (defined(distanceDisplayCondition)) { + distanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition); + } + 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._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.TRANSPARENT)); + this._outlineWidth = defaultValue(options.outlineWidth, 0.0); + this._pixelSize = defaultValue(options.pixelSize, 10.0); + this._scaleByDistance = scaleByDistance; + this._translucencyByDistance = translucencyByDistance; + this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); 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._collection = defaultValue(options.collection, pointPrimitiveCollection); this._clusterShow = true; - this._updateClamping(); + this._pickId = undefined; + this._pointPrimitiveCollection = pointPrimitiveCollection; + this._dirty = false; + this._index = -1; //Used only by PointPrimitiveCollection } - 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; - var DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE = 15; - Billboard.NUMBER_OF_PROPERTIES = 16; + 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_INDEX = 8; + var DISABLE_DEPTH_DISTANCE_INDEX = PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX = 9; + PointPrimitive.NUMBER_OF_PROPERTIES = 10; - function makeDirty(billboard, propertyChanged) { - var billboardCollection = billboard._billboardCollection; - if (defined(billboardCollection)) { - billboardCollection._updateBillboard(billboard, propertyChanged); - billboard._dirty = true; + function makeDirty(pointPrimitive, propertyChanged) { + var pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection; + if (defined(pointPrimitiveCollection)) { + pointPrimitiveCollection._updatePointPrimitive(pointPrimitive, propertyChanged); + pointPrimitive._dirty = true; } } - defineProperties(Billboard.prototype, { + defineProperties(PointPrimitive.prototype, { /** - * Determines if this billboard will be shown. Use this to hide or show a billboard, instead + * 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 Billboard.prototype + * @memberof PointPrimitive.prototype * @type {Boolean} - * @default true */ show : { get : function() { @@ -103991,8 +114183,8 @@ define('Scene/Billboard',[ }, /** - * Gets or sets the Cartesian position of this billboard. - * @memberof Billboard.prototype + * Gets or sets the Cartesian position of this point. + * @memberof PointPrimitive.prototype * @type {Cartesian3} */ position : { @@ -104003,86 +114195,36 @@ define('Scene/Billboard',[ 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) { - - 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) { - - var pixelOffset = this._pixelOffset; - if (!Cartesian2.equals(pixelOffset, value)) { - Cartesian2.clone(value, pixelOffset); - makeDirty(this, PIXEL_OFFSET_INDEX); + Cartesian3.clone(value, position); + Cartesian3.clone(value, this._actualPosition); + + makeDirty(this, POSITION_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 + * 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 billboard's scale remains clamped to the nearest bound. If undefined, + * 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 Billboard.prototype + * @memberof PointPrimitive.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 + * // 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. - * b.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0); + * p.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 15, 8.0e6, 0.0); * * @example * // Example 2. * // disable scaling by distance - * b.scaleByDistance = undefined; + * p.scaleByDistance = undefined; */ scaleByDistance : { get : function() { @@ -104099,26 +114241,26 @@ define('Scene/Billboard',[ }, /** - * 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 + * 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 billboard's translucency remains clamped to the nearest bound. If undefined, + * Outside of these ranges the point's translucency remains clamped to the nearest bound. If undefined, * translucencyByDistance will be disabled. - * @memberof Billboard.prototype + * @memberof PointPrimitive.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 + * // 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. - * b.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0); + * p.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0); * * @example * // Example 2. * // disable translucency by distance - * b.translucencyByDistance = undefined; + * p.translucencyByDistance = undefined; */ translucencyByDistance : { get : function() { @@ -104135,185 +114277,38 @@ define('Scene/Billboard',[ }, /** - * 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) { - - 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) { - - 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 (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 (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 + * Gets or sets the inner size of the point in pixels. + * @memberof PointPrimitive.prototype * @type {Number} */ - scale : { + pixelSize : { get : function() { - return this._scale; + return this._pixelSize; }, set : function(value) { - if (this._scale !== value) { - this._scale = value; - makeDirty(this, SCALE_INDEX); + if (this._pixelSize !== value) { + this._pixelSize = value; + makeDirty(this, PIXEL_SIZE_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
    - *
    - *
    + * 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 Billboard.prototype + * @memberof PointPrimitive.prototype * @type {Color} * * @example * // Example 1. Assign yellow. - * b.color = Cesium.Color.YELLOW; + * p.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); + * // Example 2. Make a pointPrimitive 50% translucent. + * p.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5); */ color : { get : function() { @@ -104330,114 +114325,46 @@ define('Scene/Billboard',[ }, /** - * Gets or sets the rotation angle in radians. - * @memberof Billboard.prototype - * @type {Number} - */ - rotation : { - get : function() { - return this._rotation; - }, - set : function(value) { - - 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; + * Gets or sets the outline color of the point. + * @memberof PointPrimitive.prototype + * @type {Color} */ - alignedAxis : { + outlineColor : { get : function() { - return this._alignedAxis; + return this._outlineColor; }, set : function(value) { - 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); + var outlineColor = this._outlineColor; + if (!Color.equals(outlineColor, value)) { + Color.clone(value, outlineColor); + makeDirty(this, OUTLINE_COLOR_INDEX); } } }, /** - * Gets or sets a height for the billboard. If undefined, the image height will be used. - * @memberof Billboard.prototype + * 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} */ - 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 : { + outlineWidth : { get : function() { - return this._sizeInMeters; + return this._outlineWidth; }, set : function(value) { - if (this._sizeInMeters !== value) { - this._sizeInMeters = value; - makeDirty(this, COLOR_INDEX); + + 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 billboard will be displayed. - * @memberof Billboard.prototype + * 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 */ @@ -104446,9 +114373,9 @@ define('Scene/Billboard',[ return this._distanceDisplayCondition; }, set : function(value) { - if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { - this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); - makeDirty(this, DISTANCE_DISPLAY_CONDITION); + if (!DistanceDisplayCondition.equals(this._distanceDisplayCondition, value)) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + makeDirty(this, DISTANCE_DISPLAY_CONDITION_INDEX); } } }, @@ -104456,7 +114383,7 @@ define('Scene/Billboard',[ /** * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. - * @memberof Billboard.prototype + * @memberof PointPrimitive.prototype * @type {Number} * @default 0.0 */ @@ -104467,14 +114394,14 @@ define('Scene/Billboard',[ set : function(value) { if (this._disableDepthTestDistance !== value) { this._disableDepthTestDistance = value; - makeDirty(this, DISABLE_DEPTH_DISTANCE); + makeDirty(this, DISABLE_DEPTH_DISTANCE_INDEX); } } }, /** - * Gets or sets the user-defined object returned when the billboard is picked. - * @memberof Billboard.prototype + * Gets or sets the user-defined object returned when the point is picked. + * @memberof PointPrimitive.prototype * @type {Object} */ id : { @@ -104490,100 +114417,8 @@ define('Scene/Billboard',[ }, /** - * 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 + * Determines whether or not this point will be shown or hidden because it was clustered. + * @memberof PointPrimitive.prototype * @type {Boolean} * @private */ @@ -104600,10 +114435,10 @@ define('Scene/Billboard',[ } }); - Billboard.prototype.getPickId = function(context) { + PointPrimitive.prototype.getPickId = function(context) { if (!defined(this._pickId)) { this._pickId = context.createPickId({ - primitive : this._pickPrimitive, + primitive : this, collection : this._collection, id : this._id }); @@ -104612,9023 +114447,10057 @@ define('Scene/Billboard',[ return this._pickId; }; - Billboard.prototype._updateClamping = function() { - Billboard._updateClamping(this._billboardCollection, this); + PointPrimitive.prototype._getActualPosition = function() { + return this._actualPosition; }; - var scratchCartographic = new Cartographic(); - var scratchPosition = new Cartesian3(); + PointPrimitive.prototype._setActualPosition = function(value) { + Cartesian3.clone(value, this._actualPosition); + makeDirty(this, POSITION_INDEX); + }; - Billboard._updateClamping = function(collection, owner) { - var scene = collection._scene; - if (!defined(scene)) { - return; + var tempCartesian3 = new Cartesian4(); + PointPrimitive._computeActualPosition = function(position, frameState, modelMatrix) { + if (frameState.mode === SceneMode.SCENE3D) { + return position; } - var globe = scene.globe; - var ellipsoid = globe.ellipsoid; - var surface = globe._surface; + Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3); + return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3); + }; - var mode = scene.frameState.mode; + var scratchCartesian4 = new Cartesian4(); - var modeChanged = mode !== owner._mode; - owner._mode = mode; + // 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; + }; - if ((owner._heightReference === HeightReference.NONE || modeChanged) && defined(owner._removeCallbackFunc)) { - owner._removeCallbackFunc(); - owner._removeCallbackFunc = undefined; - owner._clampedPosition = undefined; + /** + * 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 (owner._heightReference === HeightReference.NONE || !defined(owner._position)) { - return; + + var modelMatrix = pointPrimitiveCollection.modelMatrix; + var windowCoordinates = PointPrimitive._computeScreenSpacePosition(modelMatrix, this._actualPosition, scene, result); + if (!defined(windowCoordinates)) { + return undefined; } - var position = ellipsoid.cartesianToCartographic(owner._position); - if (!defined(position)) { - owner._actualClampedPosition = undefined; - return; - } + windowCoordinates.y = scene.canvas.clientHeight - windowCoordinates.y; + return windowCoordinates; + }; - if (defined(owner._removeCallbackFunc)) { - owner._removeCallbackFunc(); - } + /** + * 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; - 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); + var x = screenSpacePosition.x - halfSize; + var y = screenSpacePosition.y - halfSize; + var width = size; + var height = size; - Cartographic.clone(position, scratchCartographic); - var height = globe.getHeight(position); - if (defined(height)) { - scratchCartographic.height = height; + if (!defined(result)) { + result = new BoundingRectangle(); } - ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition); + result.x = x; + result.y = y; + result.width = width; + result.height = height; - updateFunction(scratchPosition); + 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) && + this._disableDepthTestDistance === other._disableDepthTestDistance; + }; + + 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. +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\ +\n\ +#ifdef RENDER_FOR_PICK\n\ +varying vec4 v_pickColor;\n\ +#endif\n\ +\n\ +void main()\n\ +{\n\ + // The distance in UV space from this fragment to the center of the point, at most 0.5.\n\ + float distanceToCenter = length(gl_PointCoord - vec2(0.5));\n\ + // The max distance stops one pixel shy of the edge to leave space for anti-aliasing.\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\ +\n\ + vec4 color = mix(v_outlineColor, v_color, innerAlpha);\n\ + color.a *= wholeAlpha;\n\ +\n\ +// Fully transparent parts of the billboard are not pickable.\n\ +#if defined(RENDER_FOR_PICK) || (!defined(OPAQUE) && !defined(TRANSLUCENT))\n\ + if (color.a < 0.005) // matches 0/255 and 1/255\n\ + {\n\ + discard;\n\ + }\n\ +#else\n\ +// The billboard is rendered twice. The opaque pass discards translucent fragments\n\ +// and the translucent pass discards opaque fragments.\n\ +#ifdef OPAQUE\n\ + if (color.a < 0.995) // matches < 254/255\n\ + {\n\ + discard;\n\ + }\n\ +#else\n\ + if (color.a >= 0.995) // matches 254/255 and 255/255\n\ + {\n\ + discard;\n\ + }\n\ +#endif\n\ +#endif\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. +define('Shaders/PointPrimitiveCollectionVS',[],function() { + 'use strict'; + return "uniform float u_maxTotalPointSize;\n\ +\n\ +attribute vec4 positionHighAndSize;\n\ +attribute vec4 positionLowAndOutline;\n\ +attribute vec4 compressedAttribute0; // color, outlineColor, pick color\n\ +attribute vec4 compressedAttribute1; // show, translucency by distance, some free space\n\ +attribute vec4 scaleByDistance; // near, nearScale, far, farScale\n\ +attribute vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance\n\ +\n\ +varying vec4 v_color;\n\ +varying vec4 v_outlineColor;\n\ +varying float v_innerPercent;\n\ +varying float v_pixelDistance;\n\ +\n\ +#ifdef RENDER_FOR_PICK\n\ +varying vec4 v_pickColor;\n\ +#endif\n\ +\n\ +const float SHIFT_LEFT8 = 256.0;\n\ +const float SHIFT_RIGHT8 = 1.0 / 256.0;\n\ +\n\ +void main()\n\ +{\n\ + // Modifying this shader may also require modifications to PointPrimitive._computeScreenSpacePosition\n\ +\n\ + // unpack attributes\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\ + // Scale in response to browser-zoom.\n\ + totalSize *= czm_resolutionScale;\n\ + // Add padding for anti-aliasing on both sides.\n\ + totalSize += 3.0;\n\ +\n\ + float temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\ + float show = floor(temp);\n\ +\n\ +#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ + vec4 translucencyByDistance;\n\ + translucencyByDistance.x = compressedAttribute1.z;\n\ + translucencyByDistance.z = compressedAttribute1.w;\n\ +\n\ + translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ +\n\ + temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\ + translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ +#endif\n\ +\n\ + ///////////////////////////////////////////////////////////////////////////\n\ +\n\ + vec4 color;\n\ + vec4 outlineColor;\n\ +#ifdef RENDER_FOR_PICK\n\ + // compressedAttribute0.z => pickColor.rgb\n\ +\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\ + // compressedAttribute0.x => color.rgb\n\ +\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\ +\n\ + // compressedAttribute0.y => outlineColor.rgb\n\ +\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\ +\n\ + // compressedAttribute0.w => color.a, outlineColor.a, pickColor.a\n\ +\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\ +\n\ + ///////////////////////////////////////////////////////////////////////////\n\ +\n\ + vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\ + vec4 positionEC = czm_modelViewRelativeToEye * p;\n\ + positionEC.xyz *= show;\n\ +\n\ + ///////////////////////////////////////////////////////////////////////////\n\ +\n\ +#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(DISTANCE_DISPLAY_CONDITION) || defined(DISABLE_DEPTH_DISTANCE)\n\ + float lengthSq;\n\ + if (czm_sceneMode == czm_sceneMode2D)\n\ + {\n\ + // 2D camera distance is a special case\n\ + // treat all billboards as flattened to the z=0.0 plane\n\ + lengthSq = czm_eyeHeight2D.y;\n\ + }\n\ + else\n\ + {\n\ + lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\ + }\n\ +#endif\n\ +\n\ +#ifdef EYE_DISTANCE_SCALING\n\ + totalSize *= czm_nearFarScalar(scaleByDistance, lengthSq);\n\ +#endif\n\ + // Clamp to max point size.\n\ + totalSize = min(totalSize, u_maxTotalPointSize);\n\ + // If size is too small, push vertex behind near plane for clipping.\n\ + // Note that context.minimumAliasedPointSize \"will be at most 1.0\".\n\ + if (totalSize < 1.0)\n\ + {\n\ + positionEC.xyz = vec3(0.0);\n\ + totalSize = 1.0;\n\ + }\n\ +\n\ + float translucency = 1.0;\n\ +#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ + translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\ + // push vertex behind near plane for clipping\n\ + if (translucency < 0.004)\n\ + {\n\ + positionEC.xyz = vec3(0.0);\n\ + }\n\ +#endif\n\ +\n\ +#ifdef DISTANCE_DISPLAY_CONDITION\n\ + float nearSq = distanceDisplayConditionAndDisableDepth.x;\n\ + float farSq = distanceDisplayConditionAndDisableDepth.y;\n\ + if (lengthSq < nearSq || lengthSq > farSq) {\n\ + positionEC.xyz = vec3(0.0);\n\ + }\n\ +#endif\n\ +\n\ + vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\ +\n\ + gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0);\n\ +\n\ +#ifdef DISABLE_DEPTH_DISTANCE\n\ + float disableDepthTestDistance = distanceDisplayConditionAndDisableDepth.z;\n\ + if (disableDepthTestDistance == 0.0 && czm_minimumDisableDepthTestDistance != 0.0)\n\ + {\n\ + disableDepthTestDistance = czm_minimumDisableDepthTestDistance;\n\ + }\n\ +\n\ + if (disableDepthTestDistance != 0.0)\n\ + {\n\ + // Don't try to \"multiply both sides\" by w. Greater/less-than comparisons won't work for negative values of w.\n\ + float zclip = gl_Position.z / gl_Position.w;\n\ + bool clipped = (zclip < -1.0 || zclip > 1.0);\n\ + if (!clipped && (disableDepthTestDistance < 0.0 || (lengthSq > 0.0 && lengthSq < disableDepthTestDistance)))\n\ + {\n\ + // Position z on the near plane.\n\ + gl_Position.z = -gl_Position.w;\n\ + }\n\ + }\n\ +#endif\n\ +\n\ + v_color = color;\n\ + v_color.a *= translucency;\n\ + v_outlineColor = outlineColor;\n\ + v_outlineColor.a *= translucency;\n\ +\n\ + v_innerPercent = 1.0 - outlinePercent;\n\ + v_pixelDistance = 2.0 / totalSize;\n\ + gl_PointSize = totalSize;\n\ +\n\ +#ifdef RENDER_FOR_PICK\n\ + v_pickColor = pickColor;\n\ +#endif\n\ +}\n\ +"; +}); +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', + './BlendOption', + './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, + BlendOption, + 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 DISABLE_DEPTH_DISTANCE_INDEX = PointPrimitive.DISABLE_DEPTH_DISTANCE_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, + distanceDisplayConditionAndDisableDepth : 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. + * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The point blending option. The default + * is used for rendering both opaque and translucent points. However, if either all of the points are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. + * + * @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); - // acevedo - handle default emp icon and dynamic icons - Billboard.prototype._loadImage = function() { - - var atlas = this._billboardCollection._textureAtlas; - var isDynamic = false; - if (this._id && this.isDynamic) - { - this._imageId = this._id; - isDynamic = true; - } - var imageId = this._imageId; - var image = this._image; - var imageSubRegion = this._imageSubRegion; - var imageIndexPromise; - if (defined(image) && isDynamic) - { - imageIndexPromise = atlas.updateImage(imageId, image); - } - else 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; - that._id = this._id; - 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; - } - // else if (that._imageId === emp.urlProxy + "?" + emp.utilities.getDefaultIcon().iconUrl) - // { - // 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) { - /*global console*/ - var atlas = that._billboardCollection._textureAtlas; - var imageId = that._imageId; - var image = that._image; - var imageSubRegion = that._imageSubRegion; - var imageIndexPromise2; - //acevedo - next flag bilboard that failed to load image. - that.imageLoaded = false; - //stat acevedo edit - //add default icon when Cesium failed to load billboard icon - //if (!(that._imageId === emp.urlProxy + "?url=" + emp.utilities.getDefaultIcon().iconUrl) && emp.util.config.getUseProxySetting() ) - // { - // using proxy - //storeUrlNotAccessible(that._imageId); // store original imageId - // that._imageId = emp.urlProxy + "?url=" + emp.utilities.getDefaultIcon().iconUrl; - // that._image = emp.urlProxy + "?url=" + emp.utilities.getDefaultIcon().iconUrl; - // //that._image = new Cesium.ConstantProperty(empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl); - // that._imageWidth = emp.utilities.getDefaultIcon().offset.width; - // that._imageHeight = emp.utilities.getDefaultIcon().offset.height; - // that.pixelOffset = new Cesium.Cartesian2(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y); - // //that._pixelOffset = new Cesium.Cartesian2(isNaN(that._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000)); - // that._alignedAxis = Cesium.Cartesian3.ZERO; - // that._verticalOrigin = Cesium.VerticalOrigin.BOTTOM; - // that._imageIndexPromise = undefined; - // //that._loadImage(); - // } - // else if (!(that._imageId === emp.utilities.getDefaultIcon().iconUrl) && !emp.util.config.getUseProxySetting()) - // { - // not using proxy - //storeUrlNotAccessible(that._imageId); // store original imageId - that._imageId = emp.utilities.getDefaultIcon().iconUrl; - that._image = emp.utilities.getDefaultIcon().iconUrl; - //that._image = new Cesium.ConstantProperty(empGlobe.getProxyUrl() + "?" + emp.utilities.getDefaultIcon().iconUrl); - that._imageWidth = emp.utilities.getDefaultIcon().offset.width; - that._imageHeight = emp.utilities.getDefaultIcon().offset.height; - that.pixelOffset = new Cesium.Cartesian2(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y); - //that._pixelOffset = new Cesium.Cartesian2(isNaN(that._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000)); - that._alignedAxis = Cesium.Cartesian3.ZERO; - that._verticalOrigin = Cesium.VerticalOrigin.BOTTOM; - that._imageIndexPromise = undefined; - //that._loadImage(); - //} - if (defined(image)) { - imageIndexPromise2 = atlas.addImage(that._imageId, that._image); - } - if (defined(imageSubRegion)) { - imageIndexPromise2 = atlas.addSubRegion(that._imageId, that._imageSubRegion); - } - - that._imageIndexPromise = imageIndexPromise2; - - if (!defined(imageIndexPromise2)) { - return; - } - - var that2 = that; - that2._id = that._id; - imageIndexPromise2.then(function(index) { - var textureCoordinates = atlas.textureCoordinates[index]; - that2._imageWidth = emp.utilities.getDefaultIcon().offset.width; - that2._imageHeight = emp.utilities.getDefaultIcon().offset.height; - that2.pixelOffset = new Cesium.Cartesian2(isNaN(emp.utilities.getDefaultIcon().offset.x, emp.utilities.getDefaultIcon().offset.y)); - //that2._pixelOffset = new Cesium.Cartesian2(isNaN(that2._actualPosition.x + emp.utilities.getDefaultIcon().offset.x, that2._actualPosition.y + emp.utilities.getDefaultIcon().offset.y + 5000)); - that2.imageLoaded = true; - that2._alignedAxis = Cesium.Cartesian3.ZERO; - that2._verticalOrigin = Cesium.VerticalOrigin.BOTTOM; - that2._imageIndex = index; - that2._ready = true; - that2._image = undefined; - that2._imageIndexPromise = undefined; - - makeDirty(that2, IMAGE_INDEX_INDEX); - }).otherwise(function(error) { - /*global console*/ - console.error('Error loading image for billboard: ' + error); - that2._imageIndexPromise = undefined; - that2.imageLoaded = false; - }); - - - }); - }; + this._sp = undefined; + this._spTranslucent = undefined; + this._spPick = undefined; + this._rsOpaque = undefined; + this._rsTranslucent = undefined; + this._vaf = 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; - /* - Billboard.prototype._loadImage = function() { - var atlas = this._billboardCollection._textureAtlas; + this._shaderDistanceDisplayCondition = false; + this._compiledShaderDistanceDisplayCondition = false; + this._compiledShaderDistanceDisplayConditionPick = false; - var imageId = this._imageId; - var image = this._image; - var imageSubRegion = this._imageSubRegion; - var imageIndexPromise; + this._shaderDisableDepthDistance = false; + this._compiledShaderDisableDepthDistance = false; + this._compiledShaderDisableDepthDistancePick = 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); - if (defined(image)) { - imageIndexPromise = atlas.addImage(imageId, image); - } - if (defined(imageSubRegion)) { - imageIndexPromise = atlas.addSubRegion(imageId, imageSubRegion); - } + /** + * The point blending option. The default is used for rendering both opaque and translucent points. + * However, if either all of the points are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve + * performance by up to 2x. + * @type {BlendOption} + * @default BlendOption.OPAQUE_AND_TRANSLUCENT + */ + this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); + this._blendOption = undefined; - this._imageIndexPromise = imageIndexPromise; + this._mode = SceneMode.SCENE3D; + this._maxTotalPointSize = 1; - if (!defined(imageIndexPromise)) { - return; - } + // 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; - 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; + this._uniforms = { + u_maxTotalPointSize : function() { + return that._maxTotalPointSize; } + }; + } - // fill in imageWidth and imageHeight - var textureCoordinates = atlas.textureCoordinates[index]; - that._imageWidth = atlas.texture.width * textureCoordinates.width; - that._imageHeight = atlas.texture.height * textureCoordinates.height; + 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; + } + } + }); - 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; - }); - }; - */ + function destroyPointPrimitives(pointPrimitives) { + var length = pointPrimitives.length; + for (var i = 0; i < length; ++i) { + if (pointPrimitives[i]) { + pointPrimitives[i]._destroy(); + } + } + } /** - *

    - * 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. - *

    + * 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. + * * - * @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); + * // 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 + * }); * - * // 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); + * @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 */ - Billboard.prototype.setImage = function(id, image) { - - if (this._imageId === id) { - return; - } + PointPrimitiveCollection.prototype.add = function(pointPrimitive) { + var p = new PointPrimitive(pointPrimitive, this); + p._index = this._pointPrimitives.length; - this._imageIndex = -1; - this._imageSubRegion = undefined; - this._imageId = id; - this._image = image; + this._pointPrimitives.push(p); + this._createVertexArray = true; - if (defined(this._billboardCollection._textureAtlas)) { - this._loadImage(); - } + return p; }; /** - * Uses a sub-region of the image with the given id as the image for this billboard, - * measured in pixels from the bottom-left. + * Removes a point from the collection. * - * @param {String} id The id of the image to use. - * @param {BoundingRectangle} subRegion The sub-region of the image. + * @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. * - * @exception {RuntimeError} image with id must be in the atlas + * @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 */ - Billboard.prototype.setImageSubRegion = function(id, subRegion) { - - if (this._imageId === id && BoundingRectangle.equals(this._imageSubRegion, subRegion)) { - return; + 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; } - this._imageIndex = -1; - this._imageId = id; - this._imageSubRegion = BoundingRectangle.clone(subRegion); - - if (defined(this._billboardCollection._textureAtlas)) { - this._loadImage(); - } + return false; }; - Billboard.prototype._setTranslate = function(value) { - - var translate = this._translate; - if (!Cartesian2.equals(translate, value)) { - Cartesian2.clone(value, translate); - makeDirty(this, PIXEL_OFFSET_INDEX); - } - }; + /** + * 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; - Billboard.prototype._getActualPosition = function() { - return defined(this._clampedPosition) ? this._clampedPosition : this._actualPosition; + this._createVertexArray = true; }; - Billboard.prototype._setActualPosition = function(value) { - if (!(defined(this._clampedPosition))) { - Cartesian3.clone(value, this._actualPosition); - } - makeDirty(this, POSITION_INDEX); - }; + function removePointPrimitives(pointPrimitiveCollection) { + if (pointPrimitiveCollection._pointPrimitivesRemoved) { + pointPrimitiveCollection._pointPrimitivesRemoved = false; - var tempCartesian3 = new Cartesian4(); - Billboard._computeActualPosition = function(billboard, position, frameState, modelMatrix) { - if (defined(billboard._clampedPosition)) { - if (frameState.mode !== billboard._mode) { - billboard._updateClamping(); + 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); + } } - 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; + pointPrimitiveCollection._pointPrimitives = newPointPrimitives; } + } - // Apply pixel offset - Cartesian2.add(positionWC, pixelOffset, positionWC); + PointPrimitiveCollection.prototype._updatePointPrimitive = function(pointPrimitive, propertyChanged) { + if (!pointPrimitive._dirty) { + this._pointPrimitivesToUpdate[this._pointPrimitivesToUpdateIndex++] = pointPrimitive; + } - return positionWC; + ++this._propertiesChanged[propertyChanged]; }; - var scratchPixelOffset = new Cartesian2(0.0, 0.0); + /** + * 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; + }; /** - * 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. + * 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 {Scene} scene The scene. - * @param {Cartesian2} [result] The object onto which to store the result. - * @returns {Cartesian2} The screen-space position of the billboard. + * @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. * - * @exception {DeveloperError} Billboard must be in a collection. * * @example - * console.log(b.computeScreenSpacePosition(scene).toString()); + * // 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 Billboard#eyeOffset - * @see Billboard#pixelOffset + * @see PointPrimitiveCollection#length */ - Billboard.prototype.computeScreenSpacePosition = function(scene, result) { - var billboardCollection = this._billboardCollection; - if (!defined(result)) { - result = new Cartesian2(); - } - + PointPrimitiveCollection.prototype.get = function(index) { - // pixel offset for screen space computation is the pixelOffset + screen space translate - Cartesian2.clone(this._pixelOffset, scratchPixelOffset); - Cartesian2.add(scratchPixelOffset, this._translate, scratchPixelOffset); + removePointPrimitives(this); + return this._pointPrimitives[index]; + }; - var modelMatrix = billboardCollection.modelMatrix; - var position = this._position; - if (defined(this._clampedPosition)) { - position = this._clampedPosition; - if (scene.mode !== SceneMode.SCENE3D) { - // position needs to be in world coordinates - var projection = scene.mapProjection; - var ellipsoid = projection.ellipsoid; - var cart = projection.unproject(position, scratchCartographic); - position = ellipsoid.cartographicToCartesian(cart, scratchCartesian3); - modelMatrix = Matrix4.IDENTITY; - } + 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; } - var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, position, - this._eyeOffset, scratchPixelOffset, scene, result); - return windowCoordinates; + return usageChanged; }; - /** - * 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; + 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.distanceDisplayConditionAndDisableDepth, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.FLOAT, + usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX] + }], numberOfPointPrimitives); // 1 vertex per pointPrimitive + } - 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; - } + // 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 y = screenSpacePosition.y; - if (billboard.verticalOrigin === VerticalOrigin.BOTTOM || billboard.verticalOrigin === VerticalOrigin.BASELINE) { - y -= height; - } else if (billboard.verticalOrigin === VerticalOrigin.CENTER) { - y -= height * 0.5; - } + var writePositionScratch = new EncodedCartesian3(); - if (!defined(result)) { - result = new BoundingRectangle(); + 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; } - result.x = x; - result.y = y; - result.width = width; - result.height = height; + EncodedCartesian3.fromCartesian(position, writePositionScratch); + var pixelSize = pointPrimitive.pixelSize; + var outlineWidth = pointPrimitive.outlineWidth; - return result; - }; + pointPrimitiveCollection._maxPixelSize = Math.max(pointPrimitiveCollection._maxPixelSize, pixelSize + outlineWidth); - /** - * 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) && - this._disableDepthTestDistance === other._disableDepthTestDistance; - }; + var positionHighWriter = vafWriters[attributeLocations.positionHighAndSize]; + var high = writePositionScratch.high; + positionHighWriter(i, high.x, high.y, high.z, pixelSize); - Billboard.prototype._destroy = function() { - if (defined(this._customData)) { - this._billboardCollection._scene.globe._surface.removeTileCustomData(this._customData); - this._customData = undefined; - } + var positionLowWriter = vafWriters[attributeLocations.positionLowAndOutline]; + var low = writePositionScratch.low; + positionLowWriter(i, low.x, low.y, low.z, outlineWidth); + } - if (defined(this._removeCallbackFunc)) { - this._removeCallbackFunc(); - this._removeCallbackFunc = undefined; - } + var LEFT_SHIFT16 = 65536.0; // 2^16 + var LEFT_SHIFT8 = 256.0; // 2^8 - this.image = undefined; - this._pickId = this._pickId && this._pickId.destroy(); - this._billboardCollection = undefined; - }; + function writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { + var i = pointPrimitive._index; - /** - * 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. - */ + var color = pointPrimitive.color; + var pickColor = pointPrimitive.getPickId(context).color; + var outlineColor = pointPrimitive.outlineColor; - return Billboard; -}); + 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; -/*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'; + red = Color.floatToByte(outlineColor.red); + green = Color.floatToByte(outlineColor.green); + blue = Color.floatToByte(outlineColor.blue); + var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; - /** - * @private - */ - function VertexArrayFacade(context, attributes, sizeInVertices, instanced) { - - var attrs = VertexArrayFacade._verifyAttributes(attributes); - sizeInVertices = defaultValue(sizeInVertices, 0); - var precreatedAttributes = []; - var attributesByUsage = {}; - var attributesForUsage; - var usage; + red = Color.floatToByte(pickColor.red); + green = Color.floatToByte(pickColor.green); + blue = Color.floatToByte(pickColor.blue); + var compressed2 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; - // Bucket the attributes by usage. - var length = attrs.length; - for (var i = 0; i < length; ++i) { - var attribute = attrs[i]; + var compressed3 = + Color.floatToByte(color.alpha) * LEFT_SHIFT16 + + Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT8 + + Color.floatToByte(pickColor.alpha); - // 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; - } + var writer = vafWriters[attributeLocations.compressedAttribute0]; + writer(i, compressed0, compressed1, compressed2, compressed3); + } - usage = attribute.usage; - attributesForUsage = attributesByUsage[usage]; - if (!defined(attributesForUsage)) { - attributesForUsage = attributesByUsage[usage] = []; - } + function writeCompressedAttrib1(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { + var i = pointPrimitive._index; - attributesForUsage.push(attribute); + 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; + } } - // 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); + 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; } - this._allBuffers = []; + 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; - for (usage in attributesByUsage) { - if (attributesByUsage.hasOwnProperty(usage)) { - attributesForUsage = attributesByUsage[usage]; + farValue = CesiumMath.clamp(farValue, 0.0, 1.0); + farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0; + var compressed1 = farValue; - attributesForUsage.sort(compare); - var vertexSizeInBytes = VertexArrayFacade._vertexSizeInBytes(attributesForUsage); + var writer = vafWriters[attributeLocations.compressedAttribute1]; + writer(i, compressed0, compressed1, near, far); + } - var bufferUsage = attributesForUsage[0].usage; + 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 buffer = { - vertexSizeInBytes : vertexSizeInBytes, - vertexBuffer : undefined, - usage : bufferUsage, - needsCommit : false, - arrayBuffer : undefined, - arrayViews : VertexArrayFacade._createArrayViews(attributesForUsage, vertexSizeInBytes) - }; + var scale = pointPrimitive.scaleByDistance; + if (defined(scale)) { + near = scale.near; + nearValue = scale.nearValue; + far = scale.far; + farValue = scale.farValue; - this._allBuffers.push(buffer); + 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; } } - this._size = 0; - this._instanced = defaultValue(instanced, false); - - this._precreated = precreatedAttributes; - this._context = context; - - this.writers = undefined; - this.va = undefined; - - this.resize(sizeInVertices); + writer(i, near, nearValue, far, farValue); } - VertexArrayFacade._verifyAttributes = function(attributes) { - var attrs = []; - for ( var i = 0; i < attributes.length; ++i) { - var attribute = attributes[i]; + function writeDistanceDisplayConditionAndDepthDisable(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { + var i = pointPrimitive._index; + var writer = vafWriters[attributeLocations.distanceDisplayConditionAndDisableDepth]; + var near = 0.0; + var far = Number.MAX_VALUE; - 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), + var distanceDisplayCondition = pointPrimitive.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + near = distanceDisplayCondition.near; + far = distanceDisplayCondition.far; - // There will be either a vertexBuffer or an [optional] usage. - vertexBuffer : attribute.vertexBuffer, - usage : defaultValue(attribute.usage, BufferUsage.STATIC_DRAW) - }; - attrs.push(attr); + near *= near; + far *= far; - } + pointPrimitiveCollection._shaderDistanceDisplayCondition = true; + } - // 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; - uniqueIndices[index] = true; + var disableDepthTestDistance = pointPrimitive.disableDepthTestDistance; + disableDepthTestDistance *= disableDepthTestDistance; + if (disableDepthTestDistance > 0.0) { + pointPrimitiveCollection._shaderDisableDepthDistance = true; + if (disableDepthTestDistance === Number.POSITIVE_INFINITY) { + disableDepthTestDistance = -1.0; + } } - return attrs; - }; + writer(i, near, far, disableDepthTestDistance); + } - VertexArrayFacade._vertexSizeInBytes = function(attributes) { - var sizeInBytes = 0; + 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); + writeDistanceDisplayConditionAndDepthDisable(pointPrimitiveCollection, context, vafWriters, pointPrimitive); + } - var length = attributes.length; - for ( var i = 0; i < length; ++i) { - var attribute = attributes[i]; - sizeInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(attribute.componentDatatype)); + 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 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; + 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); - return sizeInBytes; - }; + if (recomputeBoundingVolume) { + positions.push(actualPosition); + } else { + BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume); + } + } + } - VertexArrayFacade._createArrayViews = function(attributes, vertexSizeInBytes) { - var views = []; - var offsetInBytes = 0; + if (recomputeBoundingVolume) { + BoundingSphere.fromPoints(positions, boundingVolume); + } + } - var length = attributes.length; - for ( var i = 0; i < length; ++i) { - var attribute = attributes[i]; - var componentDatatype = attribute.componentDatatype; + function updateMode(pointPrimitiveCollection, frameState) { + var mode = frameState.mode; - views.push({ - index : attribute.index, - enabled : attribute.enabled, - componentsPerAttribute : attribute.componentsPerAttribute, - componentDatatype : componentDatatype, - normalize : attribute.normalize, + var pointPrimitives = pointPrimitiveCollection._pointPrimitives; + var pointPrimitivesToUpdate = pointPrimitiveCollection._pointPrimitivesToUpdate; + var modelMatrix = pointPrimitiveCollection._modelMatrix; - offsetInBytes : offsetInBytes, - vertexSizeInComponentType : vertexSizeInBytes / ComponentDatatype.getSizeInBytes(componentDatatype), + if (pointPrimitiveCollection._createVertexArray || + pointPrimitiveCollection._mode !== mode || + mode !== SceneMode.SCENE3D && + !Matrix4.equals(modelMatrix, pointPrimitiveCollection.modelMatrix)) { - view : undefined - }); + pointPrimitiveCollection._mode = mode; + Matrix4.clone(pointPrimitiveCollection.modelMatrix, modelMatrix); + pointPrimitiveCollection._createVertexArray = true; - offsetInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(componentDatatype)); + 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); } + } - return views; - }; + 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 = []; /** - * Invalidates writers. Can't render again until commit is called. + * @private */ - VertexArrayFacade.prototype.resize = function(sizeInVertices) { - this._size = sizeInVertices; + PointPrimitiveCollection.prototype.update = function(frameState) { + removePointPrimitives(this); - var allBuffers = this._allBuffers; - this.writers = []; + this._maxTotalPointSize = ContextLimits.maximumAliasedPointSize; - for (var i = 0, len = allBuffers.length; i < len; ++i) { - var buffer = allBuffers[i]; + updateMode(this, frameState); - VertexArrayFacade._resize(buffer, this._size); + var pointPrimitives = this._pointPrimitives; + var pointPrimitivesLength = pointPrimitives.length; + var pointPrimitivesToUpdate = this._pointPrimitivesToUpdate; + var pointPrimitivesToUpdateLength = this._pointPrimitivesToUpdateIndex; - // Reserving invalidates the writers, so if client's cache them, they need to invalidate their cache. - VertexArrayFacade._appendWriters(this.writers, buffer); - } + var properties = this._propertiesChanged; - // VAs are recreated next time commit is called. - destroyVA(this); - }; + var createVertexArray = this._createVertexArray; - VertexArrayFacade._resize = function(buffer, size) { - if (buffer.vertexSizeInBytes > 0) { - // Create larger array buffer - var arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes); + var vafWriters; + var context = frameState.context; + var pass = frameState.passes; + var picking = pass.pick; - // 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]; + // 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(); } - // 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); + this._pointPrimitivesToUpdateIndex = 0; + } else if (pointPrimitivesToUpdateLength > 0) { + // PointPrimitives were modified, but none were added or removed. + var writers = scratchWriterArray; + writers.length = 0; + + if (properties[POSITION_INDEX] || properties[OUTLINE_WIDTH_INDEX] || properties[PIXEL_SIZE_INDEX]) { + writers.push(writePositionSizeAndOutline); } - buffer.arrayBuffer = arrayBuffer; - } - }; + if (properties[COLOR_INDEX] || properties[OUTLINE_COLOR_INDEX]) { + writers.push(writeCompressedAttrib0); + } - var createWriters = [ - // 1 component per attribute - function(buffer, view, vertexSizeInComponentType) { - return function(index, attribute) { - view[index * vertexSizeInComponentType] = attribute; - buffer.needsCommit = true; - }; - }, + if (properties[SHOW_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) { + writers.push(writeCompressedAttrib1); + } - // 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; - }; - }, + if (properties[SCALE_BY_DISTANCE_INDEX]) { + writers.push(writeScaleByDistance); + } - // 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; - }; - }, + if (properties[DISTANCE_DISPLAY_CONDITION_INDEX] || properties[DISABLE_DEPTH_DISTANCE_INDEX]) { + writers.push(writeDistanceDisplayConditionAndDepthDisable); + } - // 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; - }; - }]; + var numWriters = writers.length; - 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); + 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; } - }; - VertexArrayFacade.prototype.commit = function(indexBuffer) { - var recreateVA = false; + // 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; + } - var allBuffers = this._allBuffers; - var buffer; - var i; - var length; + if (!defined(this._vaf) || !defined(this._vaf.va)) { + return; + } - for (i = 0, length = allBuffers.length; i < length; ++i) { - buffer = allBuffers[i]; - recreateVA = commit(this, buffer) || recreateVA; + 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); - if (recreateVA || !defined(this.va)) { - destroyVA(this); - var va = this.va = []; + var blendOptionChanged = this._blendOption !== this.blendOption; + this._blendOption = this.blendOption; - 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); - } + if (blendOptionChanged) { + if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + this._rsOpaque = RenderState.fromCache({ + depthTest : { + enabled : true, + func : WebGLConstants.LEQUAL + }, + depthMask : true + }); + } else { + this._rsOpaque = undefined; + } + + if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + this._rsTranslucent = RenderState.fromCache({ + depthTest : { + enabled : true, + func : WebGLConstants.LEQUAL + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }); + } else { + this._rsTranslucent = undefined; + } + } + + this._shaderDisableDepthDistance = this._shaderDisableDepthDistance || frameState.minimumDisableDepthTestDistance !== 0.0; + var vs; + var fs; + + if (blendOptionChanged || + (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) || + (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) || + (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition) || + (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistance)) { + + 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'); + } + if (this._shaderDisableDepthDistance) { + vs.defines.push('DISABLE_DEPTH_DISTANCE'); + } - attributes = attributes.concat(this._precreated); + if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + fs = new ShaderSource({ + defines : ['OPAQUE'], + sources : [PointPrimitiveCollectionFS] + }); + this._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._sp, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); - 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). + fs = new ShaderSource({ + defines : ['TRANSLUCENT'], + sources : [PointPrimitiveCollectionFS] + }); + this._spTranslucent = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spTranslucent, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations }); } - } - }; - - 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 + if (this._blendOption === BlendOption.OPAQUE) { + fs = new ShaderSource({ + sources : [PointPrimitiveCollectionFS] }); - buffer.vertexBuffer.vertexArrayDestroyable = false; + this._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._sp, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } - return true; // Created new vertex buffer + if (this._blendOption === BlendOption.TRANSLUCENT) { + fs = new ShaderSource({ + sources : [PointPrimitiveCollectionFS] + }); + this._spTranslucent = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spTranslucent, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); } - buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer); + this._compiledShaderScaleByDistance = this._shaderScaleByDistance; + this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; + this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; + this._compiledShaderDisableDepthDistance = this._shaderDisableDepthDistance; } - return false; // Did not create new vertex buffer - } + if (!defined(this._spPick) || + (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) || + (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) || + (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick) || + (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistancePick)) { - 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]; + vs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [PointPrimitiveCollectionVS] + }); - 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 + 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'); + } + if (this._shaderDisableDepthDistance) { + vs.defines.push('DISABLE_DEPTH_DISTANCE'); + } + + fs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [PointPrimitiveCollectionFS] }); - } - }; - VertexArrayFacade.prototype.subCommit = function(offsetInVertices, lengthInVertices) { - - var allBuffers = this._allBuffers; - for (var i = 0, len = allBuffers.length; i < len; ++i) { - subCommit(allBuffers[i], offsetInVertices, lengthInVertices); + 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; + this._compiledShaderDisableDepthDistancePick = this._shaderDisableDepthDistance; } - }; - function subCommit(buffer, offsetInVertices, lengthInVertices) { - if (buffer.needsCommit && (buffer.vertexSizeInBytes > 0)) { - var byteOffset = buffer.vertexSizeInBytes * offsetInVertices; - var byteLength = buffer.vertexSizeInBytes * lengthInVertices; + var va; + var vaLength; + var command; + var j; - // 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); - } - } + var commandList = frameState.commandList; - VertexArrayFacade.prototype.endSubCommits = function() { - var allBuffers = this._allBuffers; + if (pass.render) { + var colorList = this._colorCommands; - for (var i = 0, len = allBuffers.length; i < len; ++i) { - allBuffers[i].needsCommit = false; - } - }; + var opaque = this._blendOption === BlendOption.OPAQUE; + var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT; - function destroyVA(vertexArrayFacade) { - var va = vertexArrayFacade.va; - if (!defined(va)) { - return; - } + va = this._vaf.va; + vaLength = va.length; - var length = va.length; - for (var i = 0; i < length; ++i) { - va[i].va.destroy(); - } + colorList.length = vaLength; + var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; + for (j = 0; j < totalLength; ++j) { + var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0); - vertexArrayFacade.va = undefined; - } + command = colorList[j]; + if (!defined(command)) { + command = colorList[j] = new DrawCommand(); + } - VertexArrayFacade.prototype.isDestroyed = function() { - return false; - }; + command.primitiveType = PrimitiveType.POINTS; + command.pass = opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT; + command.owner = this; - 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(); + var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j; + command.boundingVolume = boundingVolume; + command.modelMatrix = modelMatrix; + command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent; + command.uniformMap = this._uniforms; + command.vertexArray = va[index].va; + command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent; + command.debugShowBoundingVolume = this.debugShowBoundingVolume; + + commandList.push(command); + } } - destroyVA(this); + if (picking) { + var pickList = this._pickCommands; - return destroyObject(this); - }; + va = this._vaf.va; + vaLength = va.length; - return VertexArrayFacade; -}); + 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 + }); + } -//This file is automatically rebuilt by the Cesium build process. -/*global define*/ -define('Shaders/BillboardCollectionFS',[],function() { - 'use strict'; - return "uniform sampler2D u_atlas;\n\ -\n\ -varying vec2 v_textureCoordinates;\n\ -\n\ -#ifdef RENDER_FOR_PICK\n\ -varying vec4 v_pickColor;\n\ -#else\n\ -varying vec4 v_color;\n\ -#endif\n\ -\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\ -\n\ - vec4 color = texture2D(u_atlas, v_textureCoordinates) * vertexColor;\n\ -\n\ -// Fully transparent parts of the billboard are not pickable.\n\ -#if defined(RENDER_FOR_PICK) || (!defined(OPAQUE) && !defined(TRANSLUCENT))\n\ - if (color.a < 0.005) // matches 0/255 and 1/255\n\ - {\n\ - discard;\n\ - }\n\ -#else\n\ -// The billboard is rendered twice. The opaque pass discards translucent fragments\n\ -// and the translucent pass discards opaque fragments.\n\ -#ifdef OPAQUE\n\ - if (color.a < 0.995) // matches < 254/255\n\ - {\n\ - discard;\n\ - }\n\ -#else\n\ - if (color.a >= 0.995) // matches 254/255 and 255/255\n\ - {\n\ - discard;\n\ - }\n\ -#endif\n\ -#endif\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; // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates (texture offset)\n\ -attribute vec4 compressedAttribute1; // aligned axis, translucency by distance, image width\n\ -attribute vec4 compressedAttribute2; // image height, color, pick color, size in meters, valid aligned axis, 13 bits free\n\ -attribute vec4 eyeOffset; // eye offset in meters, 4 bytes free (texture range)\n\ -attribute vec4 scaleByDistance; // near, nearScale, far, farScale\n\ -attribute vec4 pixelOffsetScaleByDistance; // near, nearScale, far, farScale\n\ -attribute vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance\n\ -\n\ -varying vec2 v_textureCoordinates;\n\ -\n\ -#ifdef RENDER_FOR_PICK\n\ -varying vec4 v_pickColor;\n\ -#else\n\ -varying vec4 v_color;\n\ -#endif\n\ -\n\ -const float UPPER_BOUND = 32768.0;\n\ -\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\ -\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\ -\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\ - // Note the halfSize cannot be computed in JavaScript because it is sent via\n\ - // compressed vertex attributes that coerce it to an integer.\n\ - vec2 halfSize = imageSize * scale * czm_resolutionScale * 0.5;\n\ - halfSize *= ((direction * 2.0) - 1.0);\n\ -\n\ - vec2 originTranslate = origin * abs(halfSize);\n\ -\n\ -#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\ - if (validAlignedAxis || rotation != 0.0)\n\ - {\n\ - float angle = rotation;\n\ - if (validAlignedAxis)\n\ - {\n\ - vec4 projectedAlignedAxis = czm_modelViewProjection * vec4(alignedAxis, 0.0);\n\ - angle += sign(-projectedAlignedAxis.x) * acos( sign(projectedAlignedAxis.y) * (projectedAlignedAxis.y * projectedAlignedAxis.y) /\n\ - (projectedAlignedAxis.x * projectedAlignedAxis.x + projectedAlignedAxis.y * projectedAlignedAxis.y) );\n\ - }\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\ -\n\ - if (sizeInMeters)\n\ - {\n\ - positionEC.xy += halfSize;\n\ - }\n\ -\n\ - vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\ -\n\ - if (sizeInMeters)\n\ - {\n\ - originTranslate /= czm_metersPerPixel(positionEC);\n\ - }\n\ -\n\ - positionWC.xy += originTranslate;\n\ - if (!sizeInMeters)\n\ - {\n\ - positionWC.xy += halfSize;\n\ - }\n\ -\n\ - positionWC.xy += translate;\n\ - positionWC.xy += (pixelOffset * czm_resolutionScale);\n\ -\n\ - return positionWC;\n\ -}\n\ -\n\ -void main()\n\ -{\n\ - // Modifying this shader may also require modifications to Billboard._computeScreenSpacePosition\n\ -\n\ - // unpack attributes\n\ - vec3 positionHigh = positionHighAndScale.xyz;\n\ - vec3 positionLow = positionLowAndRotation.xyz;\n\ - float scale = positionHighAndScale.w;\n\ -\n\ -#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\ - float rotation = positionLowAndRotation.w;\n\ -#else\n\ - float rotation = 0.0;\n\ -#endif\n\ -\n\ - float compressed = compressedAttribute0.x;\n\ -\n\ - vec2 pixelOffset;\n\ - pixelOffset.x = floor(compressed * SHIFT_RIGHT7);\n\ - compressed -= pixelOffset.x * SHIFT_LEFT7;\n\ - pixelOffset.x -= UPPER_BOUND;\n\ -\n\ - vec2 origin;\n\ - origin.x = floor(compressed * SHIFT_RIGHT5);\n\ - compressed -= origin.x * SHIFT_LEFT5;\n\ -\n\ - origin.y = floor(compressed * SHIFT_RIGHT3);\n\ - compressed -= origin.y * SHIFT_LEFT3;\n\ -\n\ - origin -= vec2(1.0);\n\ -\n\ - float show = floor(compressed * SHIFT_RIGHT2);\n\ - compressed -= show * SHIFT_LEFT2;\n\ -\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\ -\n\ - vec2 textureCoordinates = czm_decompressTextureCoordinates(compressedAttribute0.w);\n\ -#endif\n\ -\n\ - float temp = compressedAttribute0.y * SHIFT_RIGHT8;\n\ - pixelOffset.y = -(floor(temp) - UPPER_BOUND);\n\ -\n\ - vec2 translate;\n\ - translate.y = (temp - floor(temp)) * SHIFT_LEFT16;\n\ -\n\ - temp = compressedAttribute0.z * SHIFT_RIGHT8;\n\ - translate.x = floor(temp) - UPPER_BOUND;\n\ -\n\ - translate.y += (temp - floor(temp)) * SHIFT_LEFT8;\n\ - translate.y -= UPPER_BOUND;\n\ -\n\ - temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\ -\n\ - vec2 imageSize = vec2(floor(temp), compressedAttribute2.w);\n\ -\n\ -#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ - vec4 translucencyByDistance;\n\ - translucencyByDistance.x = compressedAttribute1.z;\n\ - translucencyByDistance.z = compressedAttribute1.w;\n\ -\n\ - translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ -\n\ - temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\ - translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ -#endif\n\ -\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\ -\n\ -#ifdef RENDER_FOR_PICK\n\ - temp = compressedAttribute2.y;\n\ -#else\n\ - temp = compressedAttribute2.x;\n\ -#endif\n\ -\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\ -\n\ - temp = compressedAttribute2.z * SHIFT_RIGHT8;\n\ - bool sizeInMeters = floor((temp - floor(temp)) * SHIFT_LEFT7) > 0.0;\n\ - temp = floor(temp) * SHIFT_RIGHT8;\n\ -\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\ -\n\ - ///////////////////////////////////////////////////////////////////////////\n\ -\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\ -\n\ - ///////////////////////////////////////////////////////////////////////////\n\ -\n\ -#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(EYE_DISTANCE_PIXEL_OFFSET) || defined(DISTANCE_DISPLAY_CONDITION) || defined(DISABLE_DEPTH_DISTANCE)\n\ - float lengthSq;\n\ - if (czm_sceneMode == czm_sceneMode2D)\n\ - {\n\ - // 2D camera distance is a special case\n\ - // treat all billboards as flattened to the z=0.0 plane\n\ - lengthSq = czm_eyeHeight2D.y;\n\ - }\n\ - else\n\ - {\n\ - lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\ - }\n\ -#endif\n\ -\n\ -#ifdef EYE_DISTANCE_SCALING\n\ - float distanceScale = czm_nearFarScalar(scaleByDistance, lengthSq);\n\ - scale *= distanceScale;\n\ - translate *= distanceScale;\n\ - // push vertex behind near plane for clipping\n\ - if (scale == 0.0)\n\ - {\n\ - positionEC.xyz = vec3(0.0);\n\ - }\n\ -#endif\n\ -\n\ - float translucency = 1.0;\n\ -#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ - translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\ - // push vertex behind near plane for clipping\n\ - if (translucency == 0.0)\n\ - {\n\ - positionEC.xyz = vec3(0.0);\n\ - }\n\ -#endif\n\ -\n\ -#ifdef EYE_DISTANCE_PIXEL_OFFSET\n\ - float pixelOffsetScale = czm_nearFarScalar(pixelOffsetScaleByDistance, lengthSq);\n\ - pixelOffset *= pixelOffsetScale;\n\ -#endif\n\ -\n\ -#ifdef DISTANCE_DISPLAY_CONDITION\n\ - float nearSq = distanceDisplayConditionAndDisableDepth.x;\n\ - float farSq = distanceDisplayConditionAndDisableDepth.y;\n\ - if (lengthSq < nearSq || lengthSq > farSq)\n\ - {\n\ - positionEC.xyz = vec3(0.0);\n\ - }\n\ -#endif\n\ -\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\ -\n\ -#ifdef DISABLE_DEPTH_DISTANCE\n\ - float disableDepthTestDistance = distanceDisplayConditionAndDisableDepth.z;\n\ - if (disableDepthTestDistance == 0.0 && czm_minimumDisableDepthTestDistance != 0.0)\n\ - {\n\ - disableDepthTestDistance = czm_minimumDisableDepthTestDistance;\n\ - }\n\ -\n\ - if (disableDepthTestDistance != 0.0)\n\ - {\n\ - gl_Position.z = min(gl_Position.z, gl_Position.w);\n\ -\n\ - bool clipped = gl_Position.z < -gl_Position.w || gl_Position.z > gl_Position.w;\n\ - if (!clipped && (disableDepthTestDistance < 0.0 || (lengthSq > 0.0 && lengthSq < disableDepthTestDistance)))\n\ - {\n\ - gl_Position.z = -gl_Position.w;\n\ - }\n\ - }\n\ -#endif\n\ -\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('Scene/BlendOption',[ - '../Core/freezeObject' -], function( - freezeObject) { - 'use strict'; + command.boundingVolume = boundingVolume; + command.modelMatrix = modelMatrix; + command.shaderProgram = this._spPick; + command.uniformMap = this._uniforms; + command.vertexArray = va[j].va; + command.renderState = this._rsOpaque; + + commandList.push(command); + } + } + }; /** - * Determines how opaque and translucent parts of billboards, points, and labels are blended with the scene. + * 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. * - * @exports BlendOption + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see PointPrimitiveCollection#destroy */ - var BlendOption = { - /** - * The billboards, points, or labels in the collection are completely opaque. - * @type {Number} - * @constant - */ - OPAQUE : 0, - - /** - * The billboards, points, or labels in the collection are completely translucent. - * @type {Number} - * @constant - */ - TRANSLUCENT : 1, - - /** - * The billboards, points, or labels in the collection are both opaque and translucent. - * @type {Number} - * @constant - */ - OPAQUE_AND_TRANSLUCENT : 2 + PointPrimitiveCollection.prototype.isDestroyed = function() { + return false; }; - return freezeObject(BlendOption); -}); - -/*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. + * 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. * - * @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. + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * - * @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 - * }) - * }); + * pointPrimitives = pointPrimitives && pointPrimitives.destroy(); * - * @private + * @see PointPrimitiveCollection#isDestroyed */ - function Framebuffer(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + PointPrimitiveCollection.prototype.destroy = function() { + this._sp = this._sp && this._sp.destroy(); + this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); + this._spPick = this._spPick && this._spPick.destroy(); + this._vaf = this._vaf && this._vaf.destroy(); + destroyPointPrimitives(this._pointPrimitives); - - var gl = options.context._gl; - var maximumColorAttachments = ContextLimits.maximumColorAttachments; + return destroyObject(this); + }; - this._gl = gl; - this._framebuffer = gl.createFramebuffer(); + return PointPrimitiveCollection; +}); - this._colorTextures = []; - this._colorRenderbuffers = []; - this._activeColorAttachments = []; +define('ThirdParty/kdbush',[], function() { +'use strict'; - this._depthTexture = undefined; - this._depthRenderbuffer = undefined; - this._stencilRenderbuffer = undefined; - this._depthStencilTexture = undefined; - this._depthStencilRenderbuffer = undefined; +function kdbush(points, getX, getY, nodeSize, ArrayType) { + return new KDBush(points, getX, getY, nodeSize, ArrayType); +} - /** - * 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); +function KDBush(points, getX, getY, nodeSize, ArrayType) { + getX = getX || defaultGetX; + getY = getY || defaultGetY; + ArrayType = ArrayType || Array; - // 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. + this.nodeSize = nodeSize || 64; + this.points = points; - - // 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)); + 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]); + } - this._bind(); + sort(this.ids, this.coords, this.nodeSize, 0, this.ids.length - 1, 0); +} - var texture; - var renderbuffer; - var i; - var length; - var attachmentEnum; +KDBush.prototype = { + range: function (minX, minY, maxX, maxY) { + return range(this.ids, this.coords, minX, minY, maxX, maxY, this.nodeSize); + }, - if (defined(options.colorTextures)) { - var textures = options.colorTextures; - length = this._colorTextures.length = this._activeColorAttachments.length = textures.length; + within: function (x, y, r) { + return within(this.ids, this.coords, x, y, r, this.nodeSize); + } +}; - - for (i = 0; i < length; ++i) { - texture = textures[i]; +function defaultGetX(p) { return p[0]; } +function defaultGetY(p) { return p[1]; } - - attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i; - attachTexture(this, attachmentEnum, texture); - this._activeColorAttachments[i] = attachmentEnum; - this._colorTextures[i] = texture; - } - } +function range(ids, coords, minX, minY, maxX, maxY, nodeSize) { + var stack = [0, ids.length - 1, 0]; + var result = []; + var x, y; - if (defined(options.colorRenderbuffers)) { - var renderbuffers = options.colorRenderbuffers; - length = this._colorRenderbuffers.length = this._activeColorAttachments.length = renderbuffers.length; + while (stack.length) { + var axis = stack.pop(); + var right = stack.pop(); + var left = stack.pop(); - - 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 (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; } - if (defined(options.depthTexture)) { - texture = options.depthTexture; + var m = Math.floor((left + right) / 2); - - attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture); - this._depthTexture = texture; - } + x = coords[2 * m]; + y = coords[2 * m + 1]; - if (defined(options.depthRenderbuffer)) { - renderbuffer = options.depthRenderbuffer; - attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer); - this._depthRenderbuffer = renderbuffer; - } + if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]); - if (defined(options.stencilRenderbuffer)) { - renderbuffer = options.stencilRenderbuffer; - attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer); - this._stencilRenderbuffer = renderbuffer; + 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); } + } - if (defined(options.depthStencilTexture)) { - texture = options.depthStencilTexture; + return result; +} - - attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture); - this._depthStencilTexture = texture; - } +function sort(ids, coords, nodeSize, left, right, depth) { + if (right - left <= nodeSize) return; - if (defined(options.depthStencilRenderbuffer)) { - renderbuffer = options.depthStencilRenderbuffer; - attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer); - this._depthStencilRenderbuffer = renderbuffer; - } + var m = Math.floor((left + right) / 2); - this._unBind(); - } + select(ids, coords, m, left, right, depth % 2); - 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; - } - }, + sort(ids, coords, nodeSize, left, m - 1, depth + 1); + sort(ids, coords, nodeSize, m + 1, right, depth + 1); +} - /** - * 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); - } +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); } - }); - Framebuffer.prototype._bind = function() { - var gl = this._gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer); - }; + var t = coords[2 * k + inc]; + var i = left; + var j = right; - Framebuffer.prototype._unBind = function() { - var gl = this._gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - }; + swapItem(ids, coords, left, k); + if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right); - Framebuffer.prototype._getActiveColorAttachments = function() { - return this._activeColorAttachments; - }; + while (i < j) { + swapItem(ids, coords, i, j); + i++; + j--; + while (coords[2 * i + inc] < t) i++; + while (coords[2 * j + inc] > t) j--; + } - Framebuffer.prototype.getColorTexture = function(index) { - - return this._colorTextures[index]; - }; + if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j); + else { + j++; + swapItem(ids, coords, j, right); + } - Framebuffer.prototype.getColorRenderbuffer = function(index) { - - return this._colorRenderbuffers[index]; - }; + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} - Framebuffer.prototype.isDestroyed = function() { - return false; - }; +function swapItem(ids, coords, i, j) { + swap(ids, i, j); + swap(coords, 2 * i, 2 * j); + swap(coords, 2 * i + 1, 2 * j + 1); +} - 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(); - } - } +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} - var renderbuffers = this._colorRenderbuffers; - length = renderbuffers.length; - for (i = 0; i < length; ++i) { - var renderbuffer = renderbuffers[i]; - if (defined(renderbuffer)) { - renderbuffer.destroy(); - } +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; + } - 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(); + 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); } + } - this._gl.deleteFramebuffer(this._framebuffer); - return destroyObject(this); - }; + return result; +} - return Framebuffer; +function sqDist(ax, ay, bx, by) { + var dx = ax - bx; + var dy = ay - by; + return dx * dx + dy * dy; +} + +return kdbush; }); -/*global define*/ -define('Scene/TextureAtlas',[ +define('DataSources/EntityCluster',[ '../Core/BoundingRectangle', '../Core/Cartesian2', - '../Core/createGuid', + '../Core/Cartesian3', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/destroyObject', - '../Core/DeveloperError', - '../Core/loadImage', - '../Core/PixelFormat', - '../Core/RuntimeError', - '../Renderer/Framebuffer', - '../Renderer/Texture', - '../ThirdParty/when' + '../Core/EllipsoidalOccluder', + '../Core/Event', + '../Core/Matrix4', + '../Scene/Billboard', + '../Scene/BillboardCollection', + '../Scene/Label', + '../Scene/LabelCollection', + '../Scene/PointPrimitive', + '../Scene/PointPrimitiveCollection', + '../Scene/SceneMode', + '../ThirdParty/kdbush' ], function( BoundingRectangle, Cartesian2, - createGuid, + Cartesian3, defaultValue, defined, defineProperties, - destroyObject, - DeveloperError, - loadImage, - PixelFormat, - RuntimeError, - Framebuffer, - Texture, - when) { + EllipsoidalOccluder, + Event, + Matrix4, + Billboard, + BillboardCollection, + Label, + LabelCollection, + PointPrimitive, + PointPrimitiveCollection, + SceneMode, + kdbush) { '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 + * Defines how screen space objects (billboards, points, labels) are clustered. * - * @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. + * @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. * - * @exception {DeveloperError} borderWidthInPixels must be greater than or equal to zero. - * @exception {DeveloperError} initialSize must be greater than zero. + * @alias EntityCluster + * @constructor * - * @private + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Clustering.html|Cesium Sandcastle Clustering Demo} */ - function TextureAtlas(options) { + function EntityCluster(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var borderWidthInPixels = defaultValue(options.borderWidthInPixels, 1.0); - var initialSize = defaultValue(options.initialSize, defaultInitialSize); - - - 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; - - //acevedo - //use imageId as the key to store the atlas textureCoordinates index. - // Storing the index based on imageId allows dynamic tagged images (canvas) to reuse the same space - // in the atlas and solves the texture atlas overflow when updating the canvas image in the billboard. - //start modification - TextureAtlas.prototype.storeIdHashIndex = function (imageId, indexId){ - this._idHash[imageId] = indexId; - }; - - TextureAtlas.prototype.getIdHashIndex = function (imageId){ - return this._idHash[imageId]; - }; - - TextureAtlas.prototype.isIdHashIndexPresent = function(imageId) { - if (this._idHash.hasOwnProperty(imageId)){ - return true; - } - else - { - return false; - } - }; - //end modification - - - } + 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); - defineProperties(TextureAtlas.prototype, { - /** - * The amount of spacing between adjacent images in pixels. - * @memberof TextureAtlas.prototype - * @type {Number} - */ - borderWidthInPixels : { - get : function() { - return this._borderWidthInPixels; - } - }, + this._labelCollection = undefined; + this._billboardCollection = undefined; + this._pointCollection = undefined; - /** - * 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; - } - }, + this._clusterBillboardCollection = undefined; + this._clusterLabelCollection = undefined; + this._clusterPointCollection = undefined; - /** - * 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; - } - }, + this._collectionIndicesByEntity = {}; - /** - * 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; - } - }, + this._unusedLabelIndices = []; + this._unusedBillboardIndices = []; + this._unusedPointIndices = []; - /** - * 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; - } - } - }); + this._previousClusters = []; + this._previousHeight = undefined; - // 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; + this._enabledDirty = false; + this._clusterDirty = false; - // 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); + this._cluster = undefined; + this._removeEventListener = undefined; - // 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; - } - } + this._clusterEvent = new Event(); + } - // Copy larger texture. - var newTexture = new Texture({ - context : textureAtlas._context, - width : atlasWidth, - height : atlasHeight, - pixelFormat : textureAtlas._pixelFormat - }); + function getX(point) { + return point.coord.x; + } - var framebuffer = new Framebuffer({ - context : context, - colorTextures : [textureAtlas._texture], - destroyAttachments : false - }); + function getY(point) { + return point.coord.y; + } - 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)); - } + function expandBoundingBox(bbox, pixelRange) { + bbox.x -= pixelRange; + bbox.y -= pixelRange; + bbox.width += pixelRange * 2.0; + bbox.height += pixelRange * 2.0; } - // 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; - } + var labelBoundingBoxScratch = new BoundingRectangle(); - // If a leaf node - if (!defined(node.childNode1) && - !defined(node.childNode2)) { + 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); + } - // Node already contains an image, don't add to it. - if (defined(node.imageIndex)) { - return undefined; - } + expandBoundingBox(result, pixelRange); - 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; + 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); + } - // Node is smaller than the image. - if (widthDifference < 0 || heightDifference < 0) { - return undefined; - } + return result; + } - // If the node is the same size as the image, return the node - if (widthDifference === 0 && heightDifference === 0) { - return node; - } + function addNonClusteredItem(item, entityCluster) { + item.clusterShow = true; - // 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 (!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; } - - // 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; + function addCluster(position, numPoints, ids, entityCluster) { + var cluster = { + billboard : entityCluster._clusterBillboardCollection.add(), + label : entityCluster._clusterLabelCollection.add(), + point : entityCluster._clusterPointCollection.add() + }; - // 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); - } + cluster.billboard.show = false; + cluster.point.show = false; + cluster.label.show = true; + cluster.label.text = numPoints.toLocaleString(); + cluster.label.id = ids; + cluster.billboard.position = cluster.label.position = cluster.point.position = position; - textureAtlas._guid = createGuid(); + entityCluster._clusterEvent.raiseEvent(ids, cluster); } - /** - * 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) { - - 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 + function hasLabelIndex(entityCluster, entityId) { + return defined(entityCluster) && defined(entityCluster._collectionIndicesByEntity[entityId]) && defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex); + } - if (typeof image === 'function') { - // if image is a function, call it - image = image(id); - } else if (typeof image === 'string') { - // if image is a string, load it as an image - image = loadImage(image); + function getScreenSpacePositions(collection, points, scene, occluder, entityCluster) { + if (!defined(collection)) { + return; } - var that = this; + var length = collection.length; + for (var i = 0; i < length; ++i) { + var item = collection.get(i); + item.clusterShow = false; - indexPromise = when(image, function(image) { - if (that.isDestroyed()) { - return -1; + if (!item.show || (entityCluster._scene.mode === SceneMode.SCENE3D && !occluder.isPointVisible(item.position))) { + continue; } - var index = that.numberOfImages; + 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; + } - addImage(that, image, index); + var coord = item.computeScreenSpacePosition(scene); + if (!defined(coord)) { + continue; + } - return index; - }); + points.push({ + index : i, + collection : collection, + clustered : false, + coord : coord + }); + } + } - // store the promise - this._idHash[id] = indexPromise; + var pointBoundinRectangleScratch = new BoundingRectangle(); + var totalBoundingRectangleScratch = new BoundingRectangle(); + var neighborBoundingRectangleScratch = new BoundingRectangle(); - return indexPromise; - }; + function createDeclutterCallback(entityCluster) { + return function(amount) { + if ((defined(amount) && amount < 0.05) || !entityCluster.enabled) { + return; + } - /** - * 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) { - - var indexPromise = this._idHash[id]; - if (!defined(indexPromise)) { - throw new RuntimeError('image with id "' + id + '" not found in the atlas.'); - } + var scene = entityCluster._scene; - var that = this; - return when(indexPromise, function(index) { - if (index === -1) { - // the atlas is destroyed - return -1; + 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 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(); + var clusteredLabelCollection = entityCluster._clusterLabelCollection; + var clusteredBillboardCollection = entityCluster._clusterBillboardCollection; + var clusteredPointCollection = entityCluster._clusterPointCollection; - return numImages; - }); - }; + 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; - /** - * acevedo - * update an image in the atlas. If the image is already in the atlas, the atlas image is replaced 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.updateImage = function(imageId, image)// id is the imageId - { - var indexPromise = undefined; - if (!defined(imageId)) { - throw new DeveloperError('id is required.'); - } - if (!defined(image)) { - throw new DeveloperError('image is required.'); - } - - if (typeof image === 'function') { - // if image is a function, call it - image = image(imageId); - 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; - //acevedo - // check if the image already has a space in the texture atlas - if (that.isIdHashIndexPresent(imageId)) - { - //reuse space in texture - index = that.getIdHashIndex(imageId); - addImage(that, image, index); - } - else - { - that.storeIdHashIndex(imageId,index); - addImage(that, image, index); - } - return index; - }); - - return indexPromise; - }; + var clusters = entityCluster._previousClusters; + var newClusters = []; + var previousHeight = entityCluster._previousHeight; + var currentHeight = scene.camera.positionCartographic.height; - /** - * 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; - }; + var ellipsoid = scene.mapProjection.ellipsoid; + var cameraPosition = scene.camera.positionWC; + var occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition); - /** - * 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); - }; + 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); + } - /** - * 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. - */ + var i; + var j; + var length; + var bbox; + var neighbors; + var neighborLength; + var neighborIndex; + var neighborPoint; + var ids; + var numPoints; - return TextureAtlas; -}); + var collection; + var collectionIndex; -/*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', - './BlendOption', - './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, - BlendOption, - HeightReference, - HorizontalOrigin, - SceneMode, - TextureAtlas, - VerticalOrigin) { - 'use strict'; + var index = kdbush(points, getX, getY, 64, Int32Array); - 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 DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE; - var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES; + if (currentHeight < previousHeight) { + length = clusters.length; + for (i = 0; i < length; ++i) { + var cluster = clusters[i]; - var attributeLocations; + if (!occluder.isPointVisible(cluster.position)) { + continue; + } - 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, - distanceDisplayConditionAndDisableDepth : 8 - }; + var coord = Billboard._computeScreenSpacePosition(Matrix4.IDENTITY, cluster.position, Cartesian3.ZERO, Cartesian2.ZERO, scene); + if (!defined(coord)) { + continue; + } - 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, - distanceDisplayConditionAndDisableDepth : 9 - }; + var factor = 1.0 - currentHeight / previousHeight; + var width = cluster.width = cluster.width * factor; + var height = cluster.height = cluster.height * factor; - /** - * 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. - * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The billboard blending option. The default - * is used for rendering both opaque and translucent billboards. However, if either all of the billboards are completely opaque or all are completely translucent, - * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. - * - * @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); + width = Math.max(width, cluster.minimumWidth); + height = Math.max(height, cluster.minimumHeight); - this._scene = options.scene; + var minX = coord.x - width * 0.5; + var minY = coord.y - height * 0.5; + var maxX = coord.x + width; + var maxY = coord.y + height; - this._textureAtlas = undefined; - this._textureAtlasGUID = undefined; - this._destroyTextureAtlas = true; - this._sp = undefined; - this._spTranslucent = undefined; - this._spPick = undefined; - this._rsOpaque = undefined; - this._rsTranslucent = undefined; - this._vaf = undefined; + neighbors = index.range(minX, minY, maxX, maxY); + neighborLength = neighbors.length; + numPoints = 0; + ids = []; - this._billboards = []; - this._billboardsToUpdate = []; - this._billboardsToUpdateIndex = 0; - this._billboardsRemoved = false; - this._createVertexArray = false; + for (j = 0; j < neighborLength; ++j) { + neighborIndex = neighbors[j]; + neighborPoint = points[neighborIndex]; + if (!neighborPoint.clustered) { + ++numPoints; - this._shaderRotation = false; - this._compiledShaderRotation = false; - this._compiledShaderRotationPick = false; + collection = neighborPoint.collection; + collectionIndex = neighborPoint.index; + ids.push(collection.get(collectionIndex).id); + } + } - this._shaderAlignedAxis = false; - this._compiledShaderAlignedAxis = false; - this._compiledShaderAlignedAxisPick = false; + if (numPoints >= minimumClusterSize) { + addCluster(cluster.position, numPoints, ids, entityCluster); + newClusters.push(cluster); - this._shaderScaleByDistance = false; - this._compiledShaderScaleByDistance = false; - this._compiledShaderScaleByDistancePick = false; + for (j = 0; j < neighborLength; ++j) { + points[neighbors[j]].clustered = true; + } + } + } + } - this._shaderTranslucencyByDistance = false; - this._compiledShaderTranslucencyByDistance = false; - this._compiledShaderTranslucencyByDistancePick = false; + length = points.length; + for (i = 0; i < length; ++i) { + var point = points[i]; + if (point.clustered) { + continue; + } - this._shaderPixelOffsetScaleByDistance = false; - this._compiledShaderPixelOffsetScaleByDistance = false; - this._compiledShaderPixelOffsetScaleByDistancePick = false; + point.clustered = true; - this._shaderDistanceDisplayCondition = false; - this._compiledShaderDistanceDisplayCondition = false; - this._compiledShaderDistanceDisplayConditionPick = false; + collection = point.collection; + collectionIndex = point.index; - this._shaderDisableDepthDistance = false; - this._compiledShaderDisableDepthDistance = false; - this._compiledShaderDisableDepthDistancePick = false; + var item = collection.get(collectionIndex); + bbox = getBoundingBox(item, point.coord, pixelRange, entityCluster, pointBoundinRectangleScratch); + var totalBBox = BoundingRectangle.clone(bbox, totalBoundingRectangleScratch); - this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES); + neighbors = index.range(bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height); + neighborLength = neighbors.length; - 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; + var clusterPosition = Cartesian3.clone(item.position); + numPoints = 1; + ids = [item.id]; - this._baseVolume = new BoundingSphere(); - this._baseVolumeWC = new BoundingSphere(); - this._baseVolume2D = new BoundingSphere(); - this._boundingVolume = new BoundingSphere(); - this._boundingVolumeDirty = false; + 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); - this._colorCommands = []; - this._pickCommands = []; + Cartesian3.add(neighborItem.position, clusterPosition, clusterPosition); - /** - * 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); + BoundingRectangle.union(totalBBox, neighborBBox, totalBBox); + ++numPoints; - /** - * 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); + ids.push(neighborItem.id); + } + } - /** - * The billboard blending option. The default is used for rendering both opaque and translucent billboards. - * However, if either all of the billboards are completely opaque or all are completely translucent, - * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve - * performance by up to 2x. - * @type {BlendOption} - * @default BlendOption.OPAQUE_AND_TRANSLUCENT - */ - this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); - this._blendOption = undefined; + 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 + }); - this._mode = SceneMode.SCENE3D; + for (j = 0; j < neighborLength; ++j) { + points[neighbors[j]].clustered = true; + } + } else { + addNonClusteredItem(item, entityCluster); + } + } - // 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 - ]; + if (clusteredLabelCollection.length === 0) { + clusteredLabelCollection.destroy(); + entityCluster._clusterLabelCollection = undefined; + } - var that = this; - this._uniforms = { - u_atlas : function() { - return that._textureAtlas.texture; + if (clusteredBillboardCollection.length === 0) { + clusteredBillboardCollection.destroy(); + entityCluster._clusterBillboardCollection = undefined; + } + + if (clusteredPointCollection.length === 0) { + clusteredPointCollection.destroy(); + entityCluster._clusterPointCollection = undefined; } - }; - 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 + * 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} - * @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 : { + clusterBillboards : { get : function() { - return this._destroyTextureAtlas; + return this._clusterBillboards; }, set : function(value) { - this._destroyTextureAtlas = 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 destroyBillboards(billboards) { - var length = billboards.length; - for (var i = 0; i < length; ++i) { - if (billboards[i]) { - billboards[i]._destroy(); + 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]; } } /** - * 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) - * }); + * 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. * - * @see BillboardCollection#remove - * @see BillboardCollection#removeAll + * @private */ - BillboardCollection.prototype.add = function(billboard) { - var b = new Billboard(billboard, this); - b._index = this._billboards.length; - - this._billboards.push(b); - this._createVertexArray = true; + EntityCluster.prototype.getLabel = createGetEntity('_labelCollection', LabelCollection, '_unusedLabelIndices', 'labelIndex'); - 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 + * 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. * - * @see BillboardCollection#add - * @see BillboardCollection#removeAll - * @see Billboard#show + * @private */ - 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; + EntityCluster.prototype.removeLabel = function(entity) { + var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id]; + if (!defined(this._labelCollection) || !defined(entityIndices) || !defined(entityIndices.labelIndex)) { + return; } - return false; + 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; }; /** - * 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(); + * 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. * - * @see BillboardCollection#add - * @see BillboardCollection#remove + * @private */ - BillboardCollection.prototype.removeAll = function() { - destroyBillboards(this._billboards); - this._billboards = []; - this._billboardsToUpdate = []; - this._billboardsToUpdateIndex = 0; - this._billboardsRemoved = false; + EntityCluster.prototype.getBillboard = createGetEntity('_billboardCollection', BillboardCollection, '_unusedBillboardIndices', 'billboardIndex'); - this._createVertexArray = true; - }; - function removeBillboards(billboardCollection) { - if (billboardCollection._billboardsRemoved) { - billboardCollection._billboardsRemoved = false; + /** + * 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 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); - } - } + var index = entityIndices.billboardIndex; + entityIndices.billboardIndex = undefined; + removeEntityIndicesIfUnused(this, entity.id); - billboardCollection._billboards = newBillboards; - } - } + var billboard = this._billboardCollection.get(index); + billboard.id = undefined; + billboard.show = false; + billboard.image = undefined; - BillboardCollection.prototype._updateBillboard = function(billboard, propertyChanged) { - if (!billboard._dirty) { - this._billboardsToUpdate[this._billboardsToUpdateIndex++] = billboard; - } + this._unusedBillboardIndices.push(index); - ++this._propertiesChanged[propertyChanged]; + this._clusterDirty = true; }; /** - * 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. + * 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. * - * @see BillboardCollection#get + * @private */ - BillboardCollection.prototype.contains = function(billboard) { - return defined(billboard) && billboard._billboardCollection === this; - }; + EntityCluster.prototype.getPoint = createGetEntity('_pointCollection', PointPrimitiveCollection, '_unusedPointIndices', 'pointIndex'); /** - * 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; - * } + * 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. * - * @see BillboardCollection#length + * @private */ - BillboardCollection.prototype.get = function(index) { - - removeBillboards(this); - return this._billboards[index]; - }; + EntityCluster.prototype.removePoint = function(entity) { + var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id]; + if (!defined(this._pointCollection) || !defined(entityIndices) || !defined(entityIndices.pointIndex)) { + return; + } - var getIndexBuffer; + var index = entityIndices.pointIndex; + entityIndices.pointIndex = undefined; + removeEntityIndicesIfUnused(this, entity.id); - function getIndexBufferBatched(context) { - var sixteenK = 16 * 1024; + var point = this._pointCollection.get(index); + point.show = false; + point.id = undefined; - var indexBuffer = context.cache.billboardCollection_indexBufferBatched; - if (defined(indexBuffer)) { - return indexBuffer; - } + this._unusedPointIndices.push(index); - // 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; + this._clusterDirty = true; + }; - indices[i + 3] = j + 0; - indices[i + 4] = j + 2; - indices[i + 5] = j + 3; + function disableCollectionClustering(collection) { + if (!defined(collection)) { + return; } - // 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; + var length = collection.length; + for (var i = 0; i < length; ++i) { + collection.get(i).clusterShow = true; + } } - function getIndexBufferInstanced(context) { - var indexBuffer = context.cache.billboardCollection_indexBufferInstanced; - if (defined(indexBuffer)) { - return indexBuffer; + function updateEnable(entityCluster) { + if (entityCluster.enabled) { + return; } - 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; + if (defined(entityCluster._clusterLabelCollection)) { + entityCluster._clusterLabelCollection.destroy(); + } + if (defined(entityCluster._clusterBillboardCollection)) { + entityCluster._clusterBillboardCollection.destroy(); + } + if (defined(entityCluster._clusterPointCollection)) { + entityCluster._clusterPointCollection.destroy(); } - 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 - }); + entityCluster._clusterLabelCollection = undefined; + entityCluster._clusterBillboardCollection = undefined; + entityCluster._clusterPointCollection = undefined; - vertexBuffer.vertexArrayDestroyable = false; - context.cache.billboardCollection_vertexBufferInstanced = vertexBuffer; - return vertexBuffer; + disableCollectionClustering(entityCluster._labelCollection); + disableCollectionClustering(entityCluster._billboardCollection); + disableCollectionClustering(entityCluster._pointCollection); } - 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; + /** + * 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. + var commandList; + if (defined(this._labelCollection) && this._labelCollection.length > 0 && this._labelCollection.get(0)._glyphs.length === 0) { + commandList = frameState.commandList; + frameState.commandList = []; + this._labelCollection.update(frameState); + frameState.commandList = commandList; } - 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.distanceDisplayConditionAndDisableDepth, - componentsPerAttribute : 3, - 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) - }); + // If clustering is enabled before the billboard collection is updated, + // the images haven't been added to the image atlas so the screen space bounding boxes + // are incorrect. + if (defined(this._billboardCollection) && this._billboardCollection.length > 0 && !defined(this._billboardCollection.get(0).width)) { + commandList = frameState.commandList; + frameState.commandList = []; + this._billboardCollection.update(frameState); + frameState.commandList = commandList; } - // 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; + if (this._enabledDirty) { + this._enabledDirty = false; + updateEnable(this); + this._clusterDirty = true; } - EncodedCartesian3.fromCartesian(position, writePositionScratch); - var scale = billboard.scale; - var rotation = billboard.rotation; - - if (rotation !== 0.0) { - billboardCollection._shaderRotation = true; + if (this._clusterDirty) { + this._clusterDirty = false; + this._cluster(); } - 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); + if (defined(this._clusterLabelCollection)) { + this._clusterLabelCollection.update(frameState); } - } - - 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; + if (defined(this._clusterBillboardCollection)) { + this._clusterBillboardCollection.update(frameState); } - - // Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that. - if (verticalOrigin === VerticalOrigin.BASELINE) { - verticalOrigin = VerticalOrigin.BOTTOM; + if (defined(this._clusterPointCollection)) { + this._clusterPointCollection.update(frameState); } - 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]; - - - bottomLeftX = imageRectangle.x; - bottomLeftY = imageRectangle.y; - width = imageRectangle.width; - height = imageRectangle.height; + if (defined(this._labelCollection)) { + this._labelCollection.update(frameState); } - 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); + if (defined(this._billboardCollection)) { + this._billboardCollection.update(frameState); } - } - - 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; + if (defined(this._pointCollection)) { + this._pointCollection.update(frameState); } + }; - var near = 0.0; - var nearValue = 1.0; - var far = 1.0; - var farValue = 1.0; + /** + * 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(); - var translucency = billboard.translucencyByDistance; - if (defined(translucency)) { - near = translucency.near; - nearValue = translucency.nearValue; - far = translucency.far; - farValue = translucency.farValue; + this._clusterLabelCollection = this._clusterLabelCollection && this._clusterLabelCollection.destroy(); + this._clusterBillboardCollection = this._clusterBillboardCollection && this._clusterBillboardCollection.destroy(); + this._clusterPointCollection = this._clusterPointCollection && this._clusterPointCollection.destroy(); - 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; - } + if (defined(this._removeEventListener)) { + this._removeEventListener(); + this._removeEventListener = undefined; } - var width = 0; - var index = billboard._imageIndex; - if (index !== -1) { - var imageRectangle = textureAtlasCoordinates[index]; + this._labelCollection = undefined; + this._billboardCollection = undefined; + this._pointCollection = undefined; - - width = imageRectangle.width; - } + this._clusterBillboardCollection = undefined; + this._clusterLabelCollection = undefined; + this._clusterPointCollection = undefined; - var textureWidth = billboardCollection._textureAtlas.texture.width; - var imageWidth = Math.round(defaultValue(billboard.width, textureWidth * width)); - billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth); + this._collectionIndicesByEntity = undefined; - var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16); - var compressed1 = 0.0; + this._unusedLabelIndices = []; + this._unusedBillboardIndices = []; + this._unusedPointIndices = []; - if (Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) < CesiumMath.EPSILON6) { - compressed1 = AttributeCompression.octEncodeFloat(alignedAxis); - } + this._previousClusters = []; + this._previousHeight = undefined; - nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0); - nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0; - compressed0 = compressed0 * LEFT_SHIFT8 + nearValue; + this._enabledDirty = false; + this._pixelRangeDirty = false; + this._minimumClusterSizeDirty = false; - farValue = CesiumMath.clamp(farValue, 0.0, 1.0); - farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0; - compressed1 = compressed1 * LEFT_SHIFT8 + farValue; + return undefined; + }; - 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); - } - } + /** + * 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(); + * }); + */ - 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; + return EntityCluster; +}); - billboardCollection._allSizedInMeters = billboardCollection._allSizedInMeters && sizeInMeters === 1.0; +define('DataSources/CustomDataSource',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + './DataSource', + './EntityCluster', + './EntityCollection' + ], function( + defined, + defineProperties, + DeveloperError, + Event, + DataSource, + EntityCluster, + EntityCollection) { + 'use strict'; - var height = 0; - var index = billboard._imageIndex; - if (index !== -1) { - var imageRectangle = textureAtlasCoordinates[index]; + /** + * 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; + } + }, - - height = imageRectangle.height; + /** + * 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) { + this._entityCluster = value; + } } + }); - var dimensions = billboardCollection._textureAtlas.texture.dimensions; - var imageHeight = Math.round(defaultValue(billboard.height, dimensions.y * height)); - billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight); + return CustomDataSource; +}); - 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; +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'; - 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 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 compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT16 + Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8; - compressed2 += sizeInMeters * 2.0 + validAlignedAxis; + var scratchColor = new Color(); - 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 GeometryOptions(entity) { + this.id = entity; + this.vertexFormat = undefined; + this.length = undefined; + this.topRadius = undefined; + this.bottomRadius = undefined; + this.slices = undefined; + this.numberOfVerticalLines = undefined; } - function writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) { - var i; - var writer = vafWriters[attributeLocations.eyeOffset]; - var eyeOffset = billboard.eyeOffset; + /** + * 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) { + + 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); + } - // 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; + 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 } - 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]; + }); - - width = imageRectangle.width; - height = imageRectangle.height; + 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; } + } + }); - scratchCartesian2.x = width; - scratchCartesian2.y = height; - var compressedTexCoordsRange = AttributeCompression.compressTextureCoordinates(scratchCartesian2); + /** + * 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); + }; - 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); - } - } + /** + * 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); + }; - 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; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); - var scale = billboard.scaleByDistance; - if (defined(scale)) { - near = scale.near; - nearValue = scale.nearValue; - far = scale.far; - farValue = scale.farValue; + var attributes; - 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; + 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); } - } - - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, near, nearValue, far, farValue); + color = ColorGeometryInstanceAttribute.fromColor(currentColor); + attributes = { + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute, + color : color + }; } 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); + attributes = { + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute + }; } - } - 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; + return new GeometryInstance({ + id : entity, + geometry : new CylinderGeometry(this._options), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), + attributes : attributes + }); + }; - var pixelOffsetScale = billboard.pixelOffsetScaleByDistance; - if (defined(pixelOffsetScale)) { - near = pixelOffsetScale.near; - nearValue = pixelOffsetScale.nearValue; - far = pixelOffsetScale.far; - farValue = pixelOffsetScale.farValue; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - 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; + return new GeometryInstance({ + id : entity, + geometry : new CylinderOutlineGeometry(this._options), + modelMatrix : entity.computeModelMatrix(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) } - } - - 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 writeDistanceDisplayConditionAndDepthDisable(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) { - var i; - var writer = vafWriters[attributeLocations.distanceDisplayConditionAndDisableDepth]; - var near = 0.0; - var far = Number.MAX_VALUE; + }); + }; - var distanceDisplayCondition = billboard.distanceDisplayCondition; - if (defined(distanceDisplayCondition)) { - near = distanceDisplayCondition.near; - far = distanceDisplayCondition.far; + /** + * 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; + }; - near *= near; - far *= far; + /** + * 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); + }; - billboardCollection._shaderDistanceDisplayCondition = true; + CylinderGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'cylinder')) { + return; } - var disableDepthTestDistance = billboard.disableDepthTestDistance; - disableDepthTestDistance *= disableDepthTestDistance; - if (disableDepthTestDistance > 0.0) { - billboardCollection._shaderDisableDepthDistance = true; - if (disableDepthTestDistance === Number.POSITIVE_INFINITY) { - disableDepthTestDistance = -1.0; - } - } + var cylinder = entity.cylinder; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, near, far, disableDepthTestDistance); - } else { - i = billboard._index * 4; - writer(i + 0, near, far, disableDepthTestDistance); - writer(i + 1, near, far, disableDepthTestDistance); - writer(i + 2, near, far, disableDepthTestDistance); - writer(i + 3, near, far, disableDepthTestDistance); + if (!defined(cylinder)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; } - } - 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); - writeDistanceDisplayConditionAndDepthDisable(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard); - } + var fillProperty = cylinder.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - 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 outlineProperty = cylinder.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); } - 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 (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - 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; + var position = entity.position; + var length = cylinder.length; + var topRadius = cylinder.topRadius; + var bottomRadius = cylinder.bottomRadius; - if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) { - recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true); + 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); } - } 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); + return; } - } - 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 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 size = pixelScale * collection._maxScale * collection._maxSize * 2.0; - if (collection._allHorizontalCenter && collection._allVerticalCenter ) { - size *= 0.5; - } + var slices = cylinder.slices; + var outlineWidth = cylinder.outlineWidth; + var numberOfVerticalLines = cylinder.numberOfVerticalLines; - var offset = pixelScale * collection._maxPixelOffset + collection._maxEyeOffset; - boundingVolume.radius += size + offset; - } + this._fillEnabled = fillEnabled; + this._outlineEnabled = outlineEnabled; - var scratchWriterArray = []; + 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); + } + }; /** - * 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: - *

    + * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true. * - * @exception {RuntimeError} image with id must be in the atlas. + * @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. */ - 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; + CylinderGeometryUpdater.prototype.createDynamicUpdater = function(primitives) { + + return new DynamicGeometryUpdater(primitives, this); + }; - var textureAtlas = this._textureAtlas; - if (!defined(textureAtlas)) { - textureAtlas = this._textureAtlas = new TextureAtlas({ - context : context - }); + /** + * @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) { + + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + this._primitive = undefined; + this._outlinePrimitive = undefined; - for (var ii = 0; ii < billboardsLength; ++ii) { - billboards[ii]._loadImage(); - } + 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 textureAtlasCoordinates = textureAtlas.textureCoordinates; - if (textureAtlasCoordinates.length === 0) { - // Can't write billboard vertices until we have texture coordinates - // provided by a texture atlas + var options = this._options; + var modelMatrix = entity.computeModelMatrix(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; } - updateMode(this, frameState); - - billboards = this._billboards; - billboardsLength = billboards.length; - var billboardsToUpdate = this._billboardsToUpdate; - var billboardsToUpdateLength = this._billboardsToUpdateIndex; - - var properties = this._propertiesChanged; + options.length = length; + options.topRadius = topRadius; + options.bottomRadius = bottomRadius; + options.slices = Property.getValueOrUndefined(cylinder.slices, time); + options.numberOfVerticalLines = Property.getValueOrUndefined(cylinder.numberOfVerticalLines, time); - var textureAtlasGUID = textureAtlas.guid; - var createVertexArray = this._createVertexArray || this._textureAtlasGUID !== textureAtlasGUID; - this._textureAtlasGUID = textureAtlasGUID; + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - var vafWriters; - var pass = frameState.passes; - var picking = pass.pick; + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); - // PERFORMANCE_IDEA: Round robin multiple buffers. - if (createVertexArray || (!picking && this.computeNewBuffersUsage())) { - this._createVertexArray = false; + if (Property.getValueOrDefault(cylinder.fill, time, true)) { + var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); + this._material = material; - for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) { - properties[k] = 0; - } + var appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : true + }); + options.vertexFormat = appearance.vertexFormat; - this._vaf = this._vaf && this._vaf.destroy(); + 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 (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; + if (Property.getValueOrDefault(cylinder.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - // 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); - } + 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; - // Different billboard collections share the same index buffer. - this._vaf.commit(getIndexBuffer(context)); - } + 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 + })); + } + }; - this._billboardsToUpdateIndex = 0; - } else { - // Billboards were modified, but none were added or removed. - if (billboardsToUpdateLength > 0) { - var writers = scratchWriterArray; - writers.length = 0; + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + }; - if (properties[POSITION_INDEX] || properties[ROTATION_INDEX] || properties[SCALE_INDEX]) { - writers.push(writePositionScaleAndRotation); - } + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; - 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); - } - } + DynamicGeometryUpdater.prototype.destroy = function() { + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + destroyObject(this); + }; - if (properties[IMAGE_INDEX_INDEX] || properties[ALIGNED_AXIS_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) { - writers.push(writeCompressedAttrib1); - writers.push(writeCompressedAttrib2); - } + return CylinderGeometryUpdater; +}); - if (properties[IMAGE_INDEX_INDEX] || properties[COLOR_INDEX]) { - writers.push(writeCompressedAttrib2); - } +define('Scene/ColorBlendMode',[ + '../Core/freezeObject', + '../Core/Math' + ], function( + freezeObject, + CesiumMath) { + 'use strict'; - if (properties[EYE_OFFSET_INDEX]) { - writers.push(writeEyeOffset); - } + /** + * 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 + }; - if (properties[SCALE_BY_DISTANCE_INDEX]) { - writers.push(writeScaleByDistance); - } + /** + * @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); + } + }; - if (properties[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]) { - writers.push(writePixelOffsetScaleByDistance); - } + return freezeObject(ColorBlendMode); +}); - if (properties[DISTANCE_DISPLAY_CONDITION_INDEX] || properties[DISABLE_DEPTH_DISTANCE]) { - writers.push(writeDistanceDisplayConditionAndDepthDisable); - } +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'; - var numWriters = writers.length; - vafWriters = this._vaf.writers; + /** + * 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(); + } - if ((billboardsToUpdateLength / billboardsLength) > 0.1) { - // If more than 10% of billboard change, rewrite the entire buffer. + 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; + } + }, - // PERFORMANCE_IDEA: I totally made up 10% :). + /** + * Gets or sets the desired start time of the clock. + * See {@link Clock#startTime}. + * @memberof DataSourceClock.prototype + * @type {JulianDate} + */ + startTime : createRawPropertyDescriptor('startTime'), - for (var m = 0; m < billboardsToUpdateLength; ++m) { - var b = billboardsToUpdate[m]; - b._dirty = false; + /** + * Gets or sets the desired stop time of the clock. + * See {@link Clock#stopTime}. + * @memberof DataSourceClock.prototype + * @type {JulianDate} + */ + stopTime : createRawPropertyDescriptor('stopTime'), - 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; + /** + * 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'), - for ( var o = 0; o < numWriters; ++o) { - writers[o](this, context, textureAtlasCoordinates, vafWriters, bb); - } + /** + * Gets or sets the desired clock range setting. + * See {@link Clock#clockRange}. + * @memberof DataSourceClock.prototype + * @type {ClockRange} + */ + clockRange : createRawPropertyDescriptor('clockRange'), - if (this._instanced) { - this._vaf.subCommit(bb._index, 1); - } else { - this._vaf.subCommit(bb._index * 4, 4); - } - } - this._vaf.endSubCommits(); - } + /** + * Gets or sets the desired clock step setting. + * See {@link Clock#clockStep}. + * @memberof DataSourceClock.prototype + * @type {ClockStep} + */ + clockStep : createRawPropertyDescriptor('clockStep'), - this._billboardsToUpdateIndex = 0; - } - } + /** + * Gets or sets the desired clock multiplier. + * See {@link Clock#multiplier}. + * @memberof DataSourceClock.prototype + * @type {Number} + */ + multiplier : createRawPropertyDescriptor('multiplier') + }); - // 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; + /** + * 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; + }; - if (!defined(this._vaf) || !defined(this._vaf.va)) { - return; - } + /** + * 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; + }; - if (this._boundingVolumeDirty) { - this._boundingVolumeDirty = false; - BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC); - } + /** + * 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) { + + 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); + }; - 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); + /** + * 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(); } - updateBoundingVolume(this, frameState, boundingVolume); + 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; + }; - var blendOptionChanged = this._blendOption !== this.blendOption; - this._blendOption = this.blendOption; + return DataSourceClock; +}); - if (blendOptionChanged) { - if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { - this._rsOpaque = RenderState.fromCache({ - depthTest : { - enabled : true, - func : WebGLConstants.LESS - }, - depthMask : true - }); - } else { - this._rsOpaque = undefined; - } +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'; - // If OPAQUE_AND_TRANSLUCENT is in use, only the opaque pass gets the benefit of the depth buffer, - // not the translucent pass. Otherwise, if the TRANSLUCENT pass is on its own, it turns on - // a depthMask in lieu of full depth sorting (because it has opaque-ish fragments that look bad in OIT). - // When the TRANSLUCENT depth mask is in use, label backgrounds require the depth func to be LEQUAL. - var useTranslucentDepthMask = this._blendOption === BlendOption.TRANSLUCENT; + 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); - if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { - this._rsTranslucent = RenderState.fromCache({ - depthTest : { - enabled : true, - func : (useTranslucentDepthMask ? WebGLConstants.LEQUAL : WebGLConstants.LESS) - }, - depthMask : useTranslucentDepthMask, - blending : BlendingState.ALPHA_BLEND - }); - } else { - this._rsTranslucent = undefined; - } - } + /** + * 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._shaderDisableDepthDistance = this._shaderDisableDepthDistance || frameState.minimumDisableDepthTestDistance !== 0.0; + 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; - if (blendOptionChanged || - (this._shaderRotation !== this._compiledShaderRotation) || - (this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) || - (this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) || - (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) || - (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) || - (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition) || - (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistance)) { + this.color = options.color; + this.cellAlpha = options.cellAlpha; + this.lineCount = options.lineCount; + this.lineThickness = options.lineThickness; + this.lineOffset = options.lineOffset; + } - 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'); + 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); } - if (this._shaderDisableDepthDistance) { - vs.defines.push('DISABLE_DEPTH_DISTANCE'); + }, + /** + * 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') + }); - if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { - fs = new ShaderSource({ - defines : ['OPAQUE'], - sources : [BillboardCollectionFS] - }); - this._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - - fs = new ShaderSource({ - defines : ['TRANSLUCENT'], - sources : [BillboardCollectionFS] - }); - this._spTranslucent = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spTranslucent, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } + /** + * 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'; + }; - if (this._blendOption === BlendOption.OPAQUE) { - fs = new ShaderSource({ - sources : [BillboardCollectionFS] - }); - this._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } + /** + * 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; + }; - if (this._blendOption === BlendOption.TRANSLUCENT) { - fs = new ShaderSource({ - sources : [BillboardCollectionFS] - }); - this._spTranslucent = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spTranslucent, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } + /** + * 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)); + }; - this._compiledShaderRotation = this._shaderRotation; - this._compiledShaderAlignedAxis = this._shaderAlignedAxis; - this._compiledShaderScaleByDistance = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; - this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance; - this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; - this._compiledShaderDisableDepthDistance = this._shaderDisableDepthDistance; - } + return GridMaterialProperty; +}); - 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) || - (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistancePick)) { +define('DataSources/PolylineArrowMaterialProperty',[ + '../Core/Color', + '../Core/defined', + '../Core/defineProperties', + '../Core/Event', + './createPropertyDescriptor', + './Property' + ], function( + Color, + defined, + defineProperties, + Event, + createPropertyDescriptor, + Property) { + 'use strict'; - vs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [BillboardCollectionVS] - }); + /** + * 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; + } - 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'); + 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); } - if (this._shaderDisableDepthDistance) { - vs.defines.push('DISABLE_DEPTH_DISTANCE'); + }, + /** + * 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') + }); - fs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [BillboardCollectionFS] - }); + /** + * 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'; + }; - 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; - this._compiledShaderDisableDepthDistancePick = this._shaderDisableDepthDistance; + /** + * 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; + }; - var va; - var vaLength; - var command; - var vs; - var fs; - var j; - - var commandList = frameState.commandList; + /** + * 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)); + }; - if (pass.render) { - var colorList = this._colorCommands; + return PolylineArrowMaterialProperty; +}); - var opaque = this._blendOption === BlendOption.OPAQUE; - var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT; +define('DataSources/PolylineDashMaterialProperty',[ + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/Event', + './createPropertyDescriptor', + './Property' + ], function( + Color, + defaultValue, + defined, + defineProperties, + Event, + createPropertyDescriptor, + Property) { + 'use strict'; - va = this._vaf.va; - vaLength = va.length; + var defaultColor = Color.WHITE; + var defaultGapColor = Color.TRANSPARENT; + var defaultDashLength = 16.0; + var defaultDashPattern = 255.0; - colorList.length = vaLength; - var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; - for (j = 0; j < totalLength; ++j) { - command = colorList[j]; - if (!defined(command)) { - command = colorList[j] = new DrawCommand(); - } + /** + * A {@link MaterialProperty} that maps to polyline dash {@link Material} uniforms. + * @alias PolylineDashMaterialProperty + * @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.gapColor=Color.TRANSPARENT] A Property specifying the {@link Color} of the gaps in the line. + * @param {Property} [options.dashLength=16.0] A numeric Property specifying the length of the dash pattern in pixel.s + * @param {Property} [options.dashPattern=255.0] A numeric Property specifying a 16 bit pattern for the dash + */ + function PolylineDashMaterialProperty(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0); + this._definitionChanged = new Event(); + this._color = undefined; + this._colorSubscription = undefined; + this._gapColor = undefined; + this._gapColorSubscription = undefined; + this._dashLength = undefined; + this._dashLengthSubscription = undefined; + this._dashPattern = undefined; + this._dashPatternSubscription = undefined; - command.pass = opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT; - command.owner = this; + this.color = options.color; + this.gapColor = options.gapColor; + this.dashLength = options.dashLength; + this.dashPattern = options.dashPattern; + } - var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j; - command.boundingVolume = boundingVolume; - command.modelMatrix = modelMatrix; - command.count = va[index].indicesCount; - command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent; - command.uniformMap = this._uniforms; - command.vertexArray = va[index].va; - command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent; - command.debugShowBoundingVolume = this.debugShowBoundingVolume; + defineProperties(PolylineDashMaterialProperty.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 PolylineDashMaterialProperty.prototype + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return (Property.isConstant(this._color) && + Property.isConstant(this._gapColor) && + Property.isConstant(this._dashLength) && + Property.isConstant(this._dashPattern)); + } + }, + /** + * 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 PolylineDashMaterialProperty.prototype + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets or sets the Property specifying the {@link Color} of the line. + * @memberof PolylineDashMaterialProperty.prototype + * @type {Property} + */ + color : createPropertyDescriptor('color'), + /** + * Gets or sets the Property specifying the {@link Color} of the gaps in the line. + * @memberof PolylineDashMaterialProperty.prototype + * @type {Property} + */ + gapColor : createPropertyDescriptor('gapColor'), + /** + * Gets or sets the numeric Property specifying the length of a dash cycle + * @memberof PolylineDashMaterialProperty.prototype + * @type {Property} + */ + dashLength : createPropertyDescriptor('dashLength'), + /** + * Gets or sets the numeric Property specifying a dash pattern + * @memberof PolylineDashMaterialProperty.prototype + * @type {Property} + */ + dashPattern : createPropertyDescriptor('dashPattern') + }); - if (this._instanced) { - command.count = 6; - command.instanceCount = billboardsLength; - } + /** + * 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. + */ + PolylineDashMaterialProperty.prototype.getType = function(time) { + return 'PolylineDash'; + }; - commandList.push(command); - } + /** + * 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. + */ + PolylineDashMaterialProperty.prototype.getValue = function(time, result) { + if (!defined(result)) { + result = {}; } + result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color); + result.gapColor = Property.getValueOrClonedDefault(this._gapColor, time, defaultGapColor, result.gapColor); + result.dashLength = Property.getValueOrDefault(this._dashLength, time, defaultDashLength, result.dashLength); + result.dashPattern = Property.getValueOrDefault(this._dashPattern, time, defaultDashPattern, result.dashPattern); + return result; + }; - if (picking) { - var pickList = this._pickCommands; + /** + * 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. + */ + PolylineDashMaterialProperty.prototype.equals = function(other) { + return this === other || // + (other instanceof PolylineDashMaterialProperty && + Property.equals(this._color, other._color) && + Property.equals(this._gapColor, other._gapColor) && + Property.equals(this._dashLength, other._dashLength) && + Property.equals(this._dashPattern, other._dashPattern) + ); + }; - va = this._vaf.va; - vaLength = va.length; + return PolylineDashMaterialProperty; +}); - 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 - }); - } +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'; - 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._rsOpaque; + var defaultColor = Color.WHITE; + var defaultGlowPower = 0.25; - if (this._instanced) { - command.count = 6; - command.instanceCount = billboardsLength; - } + /** + * 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); - commandList.push(command); + 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') + }); /** - * 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. + * Gets the {@link Material} type at the provided time. * - * @see BillboardCollection#destroy + * @param {JulianDate} time The time for which to retrieve the type. + * @returns {String} The type of material. */ - BillboardCollection.prototype.isDestroyed = function() { - return false; + PolylineGlowMaterialProperty.prototype.getType = function(time) { + return 'PolylineGlow'; }; /** - * 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(); + * Gets the value of the property at the provided time. * - * @see BillboardCollection#isDestroyed + * @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. */ - BillboardCollection.prototype.destroy = function() { - if (defined(this._removeCallbackFunc)) { - this._removeCallbackFunc(); - this._removeCallbackFunc = undefined; + 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; + }; - this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy(); - this._sp = this._sp && this._sp.destroy(); - this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); - this._spPick = this._spPick && this._spPick.destroy(); - this._vaf = this._vaf && this._vaf.destroy(); - destroyBillboards(this._billboards); - - return destroyObject(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. + */ + PolylineGlowMaterialProperty.prototype.equals = function(other) { + return this === other || // + (other instanceof PolylineGlowMaterialProperty && // + Property.equals(this._color, other._color) && + Property.equals(this._glowPower, other._glowPower)); }; - return BillboardCollection; + return PolylineGlowMaterialProperty; }); -/*global define*/ -define('Scene/LabelStyle',[ - '../Core/freezeObject' +define('DataSources/PolylineOutlineMaterialProperty',[ + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/Event', + './createPropertyDescriptor', + './Property' ], function( - freezeObject) { + Color, + defaultValue, + defined, + defineProperties, + Event, + createPropertyDescriptor, + Property) { 'use strict'; + var defaultColor = Color.WHITE; + var defaultOutlineColor = Color.BLACK; + var defaultOutlineWidth = 1.0; + /** - * Describes how to draw a label. - * - * @exports LabelStyle + * A {@link MaterialProperty} that maps to polyline outline {@link Material} uniforms. + * @alias PolylineOutlineMaterialProperty + * @constructor * - * @see Label#style + * @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. */ - var LabelStyle = { + 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, { /** - * Fill the text of the label, but do not outline. + * 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 {Number} - * @constant + * @type {Boolean} + * @readonly */ - FILL : 0, - + isConstant : { + get : function() { + return Property.isConstant(this._color) && Property.isConstant(this._outlineColor) && Property.isConstant(this._outlineWidth); + } + }, /** - * Outline the text of the label, but do not fill. + * 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 {Number} - * @constant + * @type {Event} + * @readonly */ - OUTLINE : 1, - + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, /** - * Fill and outline the text of the label. - * - * @type {Number} - * @constant + * Gets or sets the Property specifying the {@link Color} of the line. + * @memberof PolylineOutlineMaterialProperty.prototype + * @type {Property} + * @default Color.WHITE */ - FILL_AND_OUTLINE : 2 + 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 freezeObject(LabelStyle); + return PolylineOutlineMaterialProperty; }); -/*global define*/ -define('Scene/Label',[ - '../Core/BoundingRectangle', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Color', +define('DataSources/PositionPropertyArray',[ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', - '../Core/DistanceDisplayCondition', - '../Core/NearFarScalar', - './Billboard', - './HeightReference', - './HorizontalOrigin', - './LabelStyle', - './VerticalOrigin' + '../Core/Event', + '../Core/EventHelper', + '../Core/ReferenceFrame', + './Property' ], function( - BoundingRectangle, - Cartesian2, - Cartesian3, - Color, defaultValue, defined, defineProperties, DeveloperError, - DistanceDisplayCondition, - NearFarScalar, - Billboard, - HeightReference, - HorizontalOrigin, - LabelStyle, - VerticalOrigin) { + Event, + EventHelper, + ReferenceFrame, + Property) { '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 + * A {@link PositionProperty} whose value is an array whose items are the computed value + * of other PositionProperty instances. * - * @see LabelCollection - * @see LabelCollection#add + * @alias PositionPropertyArray + * @constructor * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo} + * @param {Property[]} [value] An array of Property instances. + * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined. */ - function Label(options, labelCollection) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - - 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._scaleByDistance = options.scaleByDistance; - this._heightReference = defaultValue(options.heightReference, HeightReference.NONE); - this._distanceDisplayCondition = options.distanceDisplayCondition; - this._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); - - 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(); + 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(Label.prototype, { + defineProperties(PositionPropertyArray.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 + * 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} - * @default true + * @readonly */ - show : { + isConstant : { get : function() { - return this._show; - }, - set : function(value) { - - if (this._show !== value) { - this._show = value; + var value = this._value; + if (!defined(value)) { + return true; + } - 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; + var length = value.length; + for (var i = 0; i < length; i++) { + if (!Property.isConstant(value[i])) { + return false; } } + return true; } }, - /** - * Gets or sets the Cartesian position of this label. - * @memberof Label.prototype - * @type {Cartesian3} + * 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 */ - position : { + definitionChanged : { get : function() { - return this._position; - }, - set : function(value) { - - 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(); - } - } + return this._definitionChanged; } }, - /** - * Gets or sets the height reference of this billboard. - * @memberof Label.prototype - * @type {HeightReference} - * @default HeightReference.NONE + * Gets the reference frame in which the position is defined. + * @memberof PositionPropertyArray.prototype + * @type {ReferenceFrame} + * @default ReferenceFrame.FIXED; */ - heightReference : { + referenceFrame : { get : function() { - return this._heightReference; - }, - set : function(value) { - - if (value !== this._heightReference) { - this._heightReference = value; + return this._referenceFrame; + } + } + }); - 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; - } + /** + * 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); + }; - repositionAllGlyphs(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. + */ + PositionPropertyArray.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { + + var value = this._value; + if (!defined(value)) { + return undefined; + } - this._updateClamping(); - } + 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; + }; - /** - * Gets or sets the text of this label. - * @memberof Label.prototype - * @type {String} - */ - text : { - get : function() { - return this._text; - }, - set : function(value) { - - if (this._text !== value) { - this._text = value; - rebindAllGlyphs(this); - } - } - }, + /** + * 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(); - /** - * 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 (this._font !== value) { - this._font = value; - rebindAllGlyphs(this); + 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); + }; - /** - * 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) { - - var fillColor = this._fillColor; - if (!Color.equals(fillColor, value)) { - Color.clone(value, fillColor); - rebindAllGlyphs(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)); + }; - /** - * 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) { - - var outlineColor = this._outlineColor; - if (!Color.equals(outlineColor, value)) { - Color.clone(value, outlineColor); - rebindAllGlyphs(this); - } - } - }, + PositionPropertyArray.prototype._raiseDefinitionChanged = function() { + this._definitionChanged.raiseEvent(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 (this._outlineWidth !== value) { - this._outlineWidth = value; - rebindAllGlyphs(this); - } - } - }, + return PositionPropertyArray; +}); + +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, { /** - * Determines if a background behind this label will be shown. - * @memberof Label.prototype - * @default false + * 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 */ - showBackground : { + isConstant : { get : function() { - return this._showBackground; - }, - set : function(value) { - - if (this._showBackground !== value) { - this._showBackground = value; - rebindAllGlyphs(this); + var value = this._value; + if (!defined(value)) { + return true; } - } - }, - - /** - * 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) { - - var backgroundColor = this._backgroundColor; - if (!Color.equals(backgroundColor, value)) { - Color.clone(value, backgroundColor); - - var backgroundBillboard = this._backgroundBillboard; - if (defined(backgroundBillboard)) { - backgroundBillboard.color = backgroundColor; + var length = value.length; + for (var i = 0; i < length; i++) { + if (!Property.isConstant(value[i])) { + return false; } } + return true; } }, - - /** - * 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) { - - 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 + * 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 */ - style : { + definitionChanged : { get : function() { - return this._style; - }, - set : function(value) { - - if (this._style !== value) { - this._style = value; - rebindAllGlyphs(this); - } + return this._definitionChanged; } - }, + } + }); - /** - * 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) { - - var pixelOffset = this._pixelOffset; - if (!Cartesian2.equals(pixelOffset, value)) { - Cartesian2.clone(value, pixelOffset); + /** + * 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) { + + var value = this._value; + if (!defined(value)) { + return undefined; + } - 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; - } - } + 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; + }; - /** - * 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) { - - var translucencyByDistance = this._translucencyByDistance; - if (!NearFarScalar.equals(translucencyByDistance, value)) { - this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance); + /** + * 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(); - 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; - } + 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); + }; - /** - * 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) { - - var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance; - if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) { - this._pixelOffsetScaleByDistance = NearFarScalar.clone(value, pixelOffsetScaleByDistance); + /** + * 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)); + }; - 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; - } - } - } - }, + PropertyArray.prototype._raiseDefinitionChanged = function() { + this._definitionChanged.raiseEvent(this); + }; - /** - * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera. - * A label'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 label's scale remains clamped to the nearest bound. If undefined, - * scaleByDistance will be disabled. - * @memberof Label.prototype - * @type {NearFarScalar} - * - * @example - * // Example 1. - * // Set a label's scaleByDistance to scale by 1.5 when the - * // camera is 1500 meters from the label and disappear as - * // the camera distance approaches 8.0e6 meters. - * label.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0); - * - * @example - * // Example 2. - * // disable scaling by distance - * label.scaleByDistance = undefined; - */ - scaleByDistance : { - get : function() { - return this._scaleByDistance; - }, - set : function(value) { - - var scaleByDistance = this._scaleByDistance; - if (!NearFarScalar.equals(scaleByDistance, value)) { - this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance); + return PropertyArray; +}); - var glyphs = this._glyphs; - for (var i = 0, len = glyphs.length; i < len; i++) { - var glyph = glyphs[i]; - if (defined(glyph.billboard)) { - glyph.billboard.scaleByDistance = value; - } - } - var backgroundBillboard = this._backgroundBillboard; - if (defined(backgroundBillboard)) { - backgroundBillboard.scaleByDistance = value; - } - } - } - }, +define('DataSources/ReferenceProperty',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/RuntimeError', + './Property' + ], function( + defined, + defineProperties, + DeveloperError, + Event, + RuntimeError, + Property) { + 'use strict'; - /** - * 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) { - - var eyeOffset = this._eyeOffset; - if (!Cartesian3.equals(eyeOffset, value)) { - Cartesian3.clone(value, eyeOffset); + function resolveEntity(that) { + var entityIsResolved = true; + if (that._resolveEntity) { + var targetEntity = that._targetCollection.getById(that._targetId); - 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; - } - } + 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; } - }, - /** - * 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 (this._horizontalOrigin !== value) { - this._horizontalOrigin = value; - repositionAllGlyphs(this); - } + if (!defined(targetEntity)) { + throw new RuntimeError('target entity "' + that._targetId + '" could not be resolved.'); } - }, + } + return entityIsResolved; + } - /** - * 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 (this._verticalOrigin !== value) { - this._verticalOrigin = value; + function resolve(that) { + var targetProperty = that._targetProperty; - 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; - } + if (that._resolveProperty) { + var entityIsResolved = resolveEntity(that); - repositionAllGlyphs(this); - } + var names = that._targetPropertyNames; + targetProperty = that._targetEntity; + var length = names.length; + for (var i = 0; i < length && defined(targetProperty); i++) { + targetProperty = targetProperty[names[i]]; } - }, - /** - * 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 (this._scale !== value) { - this._scale = value; + 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.'); + } + } - 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; - } + return targetProperty; + } - repositionAllGlyphs(this); - } + /** + * 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) { + + 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 or sets the condition specifying at what distance from the camera that this label will be displayed. - * @memberof Label.prototype - * @type {DistanceDisplayCondition} - * @default undefined + * 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 */ - distanceDisplayCondition : { + definitionChanged : { get : function() { - return this._distanceDisplayCondition; - }, - set : function(value) { - 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; - } - } + return this._definitionChanged; } }, - /** - * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. - * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. - * @memberof Label.prototype - * @type {Number} - * @default 0.0 + * 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 */ - disableDepthTestDistance : { + referenceFrame : { get : function() { - return this._disableDepthTestDistance; - }, - set : function(value) { - if (this._disableDepthTestDistance !== value) { - this._disableDepthTestDistance = 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.disableDepthTestDistance = value; - } - } - var backgroundBillboard = this._backgroundBillboard; - if (defined(backgroundBillboard)) { - backgroundBillboard.disableDepthTestDistance = value; - } - } + return resolve(this).referenceFrame; } }, - /** - * Gets or sets the user-defined object returned when the label is picked. - * @memberof Label.prototype - * @type {Object} + * Gets the id of the entity being referenced. + * @memberof ReferenceProperty.prototype + * @type {String} + * @readonly */ - id : { + targetId : { 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; - } - } + return this._targetId; } }, - /** - * Keeps track of the position of the label based on the height reference. - * @memberof Label.prototype - * @type {Cartesian3} - * @private + * Gets the collection containing the entity being referenced. + * @memberof ReferenceProperty.prototype + * @type {EntityCollection} + * @readonly */ - _clampedPosition : { + targetCollection : { 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); - } + return this._targetCollection; } }, - /** - * Determines whether or not this label will be shown or hidden because it was clustered. - * @memberof Label.prototype - * @type {Boolean} - * @default true - * @private + * Gets the array of property names used to retrieve the referenced property. + * @memberof ReferenceProperty.prototype + * @type {String[]} + * @readonly */ - clusterShow : { + targetPropertyNames : { 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; - } - } + return this._targetPropertyNames; + } + }, + /** + * Gets the resolved instance of the underlying referenced property. + * @memberof ReferenceProperty.prototype + * @type {Property} + * @readonly + */ + resolvedProperty : { + get : function() { + return resolve(this); } } }); - 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. - * + * 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. * - * @example - * console.log(l.computeScreenSpacePosition(scene).toString()); + * @param {EntityCollection} targetCollection + * @param {String} referenceString + * @returns {ReferenceProperty} A new instance of ReferenceProperty. * - * @see Label#eyeOffset - * @see Label#pixelOffset + * @exception {DeveloperError} invalid referenceString. */ - Label.prototype.computeScreenSpacePosition = function(scene, result) { + ReferenceProperty.fromString = function(targetCollection, referenceString) { - if (!defined(result)) { - result = new Cartesian2(); + 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); - var labelCollection = this._labelCollection; - var modelMatrix = labelCollection.modelMatrix; - var actualPosition = defined(this._actualClampedPosition) ? this._actualClampedPosition : this._position; + return new ReferenceProperty(targetCollection, identifier, values); + }; - var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, actualPosition, - this._eyeOffset, this._pixelOffset, scene, result); - return windowCoordinates; + /** + * 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 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. + * 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}. * - * @private + * @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. */ - 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; + ReferenceProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { + return resolve(this).getValueInReferenceFrame(time, referenceFrame, result); + }; - 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; + /** + * 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); + }; - 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; - } + /** + * 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 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; + var names = this._targetPropertyNames; + var otherNames = other._targetPropertyNames; - if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) { - glyphY -= glyphHeight; - } else if (label.verticalOrigin === VerticalOrigin.CENTER) { - glyphY -= glyphHeight * 0.5; - } + if (this._targetCollection !== other._targetCollection || // + this._targetId !== other._targetId || // + names.length !== otherNames.length) { + return false; + } - x = Math.min(x, glyphX); - y = Math.min(y, glyphY); - maxX = Math.max(maxX, glyphX + glyphWidth); - maxY = Math.max(maxY, glyphY + glyphHeight); + var length = this._targetPropertyNames.length; + for (var i = 0; i < length; i++) { + if (names[i] !== otherNames[i]) { + return false; } + } - width = maxX - x; - height = maxY - y; + return true; + }; + + ReferenceProperty.prototype._onTargetEntityDefinitionChanged = function(targetEntity, name, value, oldValue) { + if (this._targetPropertyNames[0] === name) { + this._resolveProperty = true; + this._definitionChanged.raiseEvent(this); } + }; - if (!defined(result)) { - result = new BoundingRectangle(); + 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); + } + } } + }; - result.x = x; - result.y = y; - result.width = width; - result.height = height; + return ReferenceProperty; +}); - return result; - }; +define('DataSources/Rotation',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/DeveloperError', + '../Core/Math' + ], function( + defaultValue, + defined, + DeveloperError, + CesiumMath) { + 'use strict'; /** - * Determines if this label equals another label. Labels are equal if all their properties - * are equal. Labels in different collections can be equal. + * 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. * - * @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) && - NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) && - DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition) && - this._disableDepthTestDistance === other._disableDepthTestDistance && - 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. + * @exports Rotation * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * + * @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 */ - Label.prototype.isDestroyed = function() { - return false; + 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) { + + 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) { + + 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) { + + 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} [firstIndex=0] The firstIndex 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) { + + result = array[0]; + if (result < 0) { + return result + CesiumMath.TWO_PI; + } + return result; + } }; - return Label; + return Rotation; }); -/*global define*/ -define('Scene/LabelCollection',[ - '../Core/BoundingRectangle', - '../Core/Cartesian2', +define('DataSources/SampledProperty',[ + '../Core/binarySearch', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Matrix4', - '../Core/writeTextToCanvas', - './BillboardCollection', - './BlendOption', - './HorizontalOrigin', - './Label', - './LabelStyle', - './TextureAtlas', - './VerticalOrigin' + '../Core/Event', + '../Core/ExtrapolationType', + '../Core/JulianDate', + '../Core/LinearApproximation' ], function( - BoundingRectangle, - Cartesian2, + binarySearch, defaultValue, defined, defineProperties, - destroyObject, DeveloperError, - Matrix4, - writeTextToCanvas, - BillboardCollection, - BlendOption, - HorizontalOrigin, - Label, - LabelStyle, - TextureAtlas, - VerticalOrigin) { + Event, + ExtrapolationType, + JulianDate, + LinearApproximation) { '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 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]; + } + }; - var whitePixelCanvasId = 'ID_WHITE_PIXEL'; - var whitePixelSize = new Cartesian2(4, 4); - var whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1); + //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; - function addWhitePixelCanvas(textureAtlas, labelCollection) { - var canvas = document.createElement('canvas'); - canvas.width = whitePixelSize.x; - canvas.height = whitePixelSize.y; + array.length = newLength; + if (arrayLength !== startIndex) { + var q = arrayLength - 1; + for (i = newLength - 1; i >= startIndex; i--) { + array[i] = array[q--]; + } + } - var context2D = canvas.getContext('2d'); - context2D.fillStyle = '#fff'; - context2D.fillRect(0, 0, canvas.width, canvas.height); + for (i = 0; i < itemsLength; i++) { + array[startIndex++] = items[i]; + } + } - textureAtlas.addImage(whitePixelCanvasId, canvas).then(function(index) { - labelCollection._whitePixelIndex = index; - }); + 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()); } - // 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; + var timesSpliceArgs = []; + var valuesSpliceArgs = []; - if (verticalOrigin === VerticalOrigin.CENTER) { - writeTextToCanvasParameters.textBaseline = 'middle'; - } else if (verticalOrigin === VerticalOrigin.TOP) { - writeTextToCanvasParameters.textBaseline = 'top'; - } else { - // VerticalOrigin.BOTTOM and VerticalOrigin.BASELINE - writeTextToCanvasParameters.textBaseline = 'bottom'; - } + function mergeNewSamples(epoch, times, values, newData, packedLength) { + var newDataIndex = 0; + var i; + var prevItem; + var timesInsertionPoint; + var valuesInsertionPoint; + var currentTime; + var nextTime; - writeTextToCanvasParameters.fill = style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE; - writeTextToCanvasParameters.stroke = style === LabelStyle.OUTLINE || style === LabelStyle.FILL_AND_OUTLINE; + while (newDataIndex < newData.length) { + currentTime = convertDate(newData[newDataIndex], epoch); + timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare); + var timesSpliceArgsCount = 0; + var valuesSpliceArgsCount = 0; - return writeTextToCanvas(character, writeTextToCanvasParameters); - } + if (timesInsertionPoint < 0) { + //Doesn't exist, insert as many additional values as we can. + timesInsertionPoint = ~timesInsertionPoint; - function unbindGlyph(labelCollection, glyph) { - glyph.textureInfo = undefined; - glyph.dimensions = undefined; + 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; + } - var billboard = glyph.billboard; - if (defined(billboard)) { - billboard.show = false; - billboard.image = undefined; - labelCollection._spareBillboards.push(billboard); - glyph.billboard = undefined; - } - } + if (timesSpliceArgsCount > 0) { + valuesSpliceArgs.length = valuesSpliceArgsCount; + arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs); - function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) { - textureAtlas.addImage(id, canvas).then(function(index, id) { - glyphTextureInfo.index = index; - }); + 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++; + } + } } - 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; + /** + * 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) { + + var innerType = type; + if (innerType === Number) { + innerType = PackableNumber; + } + var packedLength = innerType.packedLength; + var packedInterpolationLength = defaultValue(innerType.packedInterpolationLength, packedLength); - // if we have more glyphs than needed, unbind the extras. - if (textLength < glyphsLength) { - for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) { - unbindGlyph(labelCollection, glyphs[glyphIndex]); + 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; } - // presize glyphs to match the new text length - glyphs.length = textLength; + 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; + } - 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; + 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; } - } else { - if (!defined(backgroundBillboard)) { - backgroundBillboard = backgroundBillboardCollection.add({ - collection : labelCollection, - image : whitePixelCanvasId, - imageSubRegion : whitePixelBoundingRegion - }); - label._backgroundBillboard = backgroundBillboard; + }, + /** + * 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); + } } + } + }); - 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.scaleByDistance = label._scaleByDistance; - backgroundBillboard.distanceDisplayCondition = label._distanceDisplayCondition; - backgroundBillboard.disableDepthTestDistance = label._disableDepthTestDistance; + /** + * 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) { + + var times = this._times; + var timesLength = times.length; + if (timesLength === 0) { + return undefined; } - var glyphTextureCache = labelCollection._glyphTextureCache; + var timeout; + var innerType = this._innerType; + var values = this._values; + var index = binarySearch(times, time, JulianDate.compare); - // 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; + if (index < 0) { + index = ~index; - // 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 - ]); + 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); + } + } - var glyphTextureInfo = glyphTextureCache[id]; - if (!defined(glyphTextureInfo)) { - var canvas = createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin); + 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); + } + } - glyphTextureInfo = new GlyphTextureInfo(labelCollection, -1, canvas.dimensions); - glyphTextureCache[id] = glyphTextureInfo; + var xTable = this._xTable; + var yTable = this._yTable; + var interpolationAlgorithm = this._interpolationAlgorithm; + var packedInterpolationLength = this._packedInterpolationLength; + var inputOrder = this._inputOrder; - if (canvas.width > 0 && canvas.height > 0) { - addGlyphToTextureAtlas(labelCollection._textureAtlas, id, canvas, glyphTextureInfo); + 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; } } - glyph = glyphs[textIndex]; + var degree = this._numberOfPoints - 1; + if (degree < 1) { + return undefined; + } - 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; + 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; } } - } else { - // create a glyph object - glyph = new Glyph(); - glyphs[textIndex] = glyph; + + firstIndex = computedFirstIndex; + lastIndex = computedLastIndex; } + var length = lastIndex - firstIndex + 1; - glyph.textureInfo = glyphTextureInfo; - glyph.dimensions = glyphTextureInfo.dimensions; + // Build the tables + for (var i = 0; i < length; ++i) { + xTable[i] = JulianDate.secondsDifference(times[firstIndex + i], times[lastIndex]); + } - // 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; + 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); + } - 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.scaleByDistance = label._scaleByDistance; - billboard.distanceDisplayCondition = label._distanceDisplayCondition; - billboard.disableDepthTestDistance = label._disableDepthTestDistance; + // 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); } - } - // changing glyphs will cause the position of the - // glyphs to change, since different characters have different widths - label._repositionAllGlyphs = true; - } + 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); + }; - function calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding) { - if (horizontalOrigin === HorizontalOrigin.CENTER) { - return -lineWidth / 2; - } else if (horizontalOrigin === HorizontalOrigin.RIGHT) { - return -(lineWidth + backgroundPadding.x); + /** + * 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)) { + return; } - return backgroundPadding.x; - } - // reusable Cartesian2 instances - var glyphPixelOffset = new Cartesian2(); - var scratchBackgroundPadding = new Cartesian2(); + var valuesChanged = false; - 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 interpolationAlgorithm = options.interpolationAlgorithm; + var interpolationDegree = options.interpolationDegree; - var backgroundBillboard = label._backgroundBillboard; - var backgroundPadding = scratchBackgroundPadding; - Cartesian2.clone( - (defined(backgroundBillboard) ? label._backgroundPadding : Cartesian2.ZERO), - backgroundPadding); + if (defined(interpolationAlgorithm) && this._interpolationAlgorithm !== interpolationAlgorithm) { + this._interpolationAlgorithm = interpolationAlgorithm; + valuesChanged = true; + } - 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); + if (defined(interpolationDegree) && this._interpolationDegree !== interpolationDegree) { + this._interpolationDegree = interpolationDegree; + valuesChanged = true; + } - //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); - } + if (valuesChanged) { + this._updateTableLength = true; + this._definitionChanged.raiseEvent(this); } - 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); + /** + * 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); - glyphPixelOffset.x = widthOffset * scale * resolutionScale; - glyphPixelOffset.y = 0; + + var innerType = this._innerType; + var data = []; + data.push(time); + innerType.pack(value, data, data.length); - 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 (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); + }; - 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; + /** + * 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(glyph.billboard)) { - glyph.billboard._setTranslate(glyphPixelOffset); - } + + 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); - //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 (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); + }; - 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; + /** + * 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) { + + mergeNewSamples(epoch, this._times, this._values, packedSamples, this._packedLength); + this._updateTableLength = true; + this._definitionChanged.raiseEvent(this); + }; - 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; + /** + * 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; } - glyphPixelOffset.y = glyphPixelOffset.y * scale * resolutionScale; - backgroundBillboard.width = maxLineWidth + (backgroundPadding.x * 2); - backgroundBillboard.height = maxLineHeight + otherLinesHeight + (backgroundPadding.y * 2); - backgroundBillboard._setTranslate(glyphPixelOffset); + for (i = 0; i < length; i++) { + if (derivativeTypes[i] !== otherDerivativeTypes[i]) { + return false; + } + } } - } - function destroyLabel(labelCollection, label) { - var glyphs = label._glyphs; - for (var i = 0, len = glyphs.length; i < len; ++i) { - unbindGlyph(labelCollection, glyphs[i]); + var times = this._times; + var otherTimes = other._times; + length = times.length; + + if (length !== otherTimes.length) { + return false; } - if (defined(label._backgroundBillboard)) { - labelCollection._backgroundBillboardCollection.remove(label._backgroundBillboard); - label._backgroundBillboard = undefined; + + for (i = 0; i < length; i++) { + if (!JulianDate.equals(times[i], otherTimes[i])) { + return false; + } } - label._labelCollection = undefined; - if (defined(label._removeCallbackFunc)) { - label._removeCallbackFunc(); + var values = this._values; + var otherValues = other._values; + for (i = 0; i < length; i++) { + if (values[i] !== otherValues[i]) { + return false; + } } - destroyObject(label); - } + return true; + }; + + //Exposed for testing. + SampledProperty._mergeNewSamples = mergeNewSamples; + + return SampledProperty; +}); + +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 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}. + * A {@link SampledProperty} which is also a {@link PositionProperty}. * - * @alias LabelCollection + * @alias SampledPositionProperty * @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. - * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The label blending option. The default - * is used for rendering both opaque and translucent labels. However, if either all of the labels are completely opaque or all are completely translucent, - * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. - * - * @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' - * }); + * @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 LabelCollection(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - this._scene = options.scene; - - this._textureAtlas = undefined; - this._backgroundTextureAtlas = undefined; - this._whitePixelIndex = undefined; + function SampledPositionProperty(referenceFrame, numberOfDerivatives) { + numberOfDerivatives = defaultValue(numberOfDerivatives, 0); - this._backgroundBillboardCollection = new BillboardCollection({ - scene : this._scene - }); - this._backgroundBillboardCollection.destroyTextureAtlas = false; + var derivativeTypes; + if (numberOfDerivatives > 0) { + derivativeTypes = new Array(numberOfDerivatives); + for (var i = 0; i < numberOfDerivatives; i++) { + derivativeTypes[i] = Cartesian3; + } + } - this._billboardCollection = new BillboardCollection({ - scene : this._scene - }); - this._billboardCollection.destroyTextureAtlas = false; + this._numberOfDerivatives = numberOfDerivatives; + this._property = new SampledProperty(Cartesian3, derivativeTypes); + this._definitionChanged = new Event(); + this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED); - this._spareBillboards = []; - this._glyphTextureCache = {}; - this._labels = []; - this._labelsToUpdate = []; - this._totalGlyphCount = 0; - this._resolutionScale = undefined; + this._property._definitionChanged.addEventListener(function() { + this._definitionChanged.raiseEvent(this); + }, this); + } + defineProperties(SampledPositionProperty.prototype, { /** - * 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}. + * 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 Matrix4 - * @default {@link Matrix4.IDENTITY} + * @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 * - * @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' - * }); + * @type {Event} + * @readonly */ - this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); - + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, /** - * 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. - *

    + * 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 {Boolean} + * @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 */ - this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); - + numberOfDerivatives : { + get : function() { + return this._numberOfDerivatives; + } + }, /** - * The label blending option. The default is used for rendering both opaque and translucent labels. - * However, if either all of the labels are completely opaque or all are completely translucent, - * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve - * performance by up to 2x. - * @type {BlendOption} - * @default BlendOption.OPAQUE_AND_TRANSLUCENT + * 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 */ - this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); - } - - defineProperties(LabelCollection.prototype, { + forwardExtrapolationType : { + get : function() { + return this._property.forwardExtrapolationType; + }, + set : function(value) { + this._property.forwardExtrapolationType = value; + } + }, /** - * 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 + * 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 */ - length : { + forwardExtrapolationDuration : { get : function() { - return this._labels.length; + 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; } } }); /** - * 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', - * }); + * Gets the position at the provided time. * - * @see LabelCollection#remove - * @see LabelCollection#removeAll + * @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. */ - LabelCollection.prototype.add = function(options) { - var label = new Label(options, this); - - this._labels.push(label); - this._labelsToUpdate.push(label); - - return label; + SampledPositionProperty.prototype.getValue = function(time, result) { + return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result); }; /** - * 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 + * Gets the position at the provided time and in the provided reference frame. * - * @see LabelCollection#add - * @see LabelCollection#removeAll - * @see Label#show + * @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. */ - 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; - } + SampledPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { + + result = this._property.getValue(time, result); + if (defined(result)) { + return PositionProperty.convertToReferenceFrame(time, result, this._referenceFrame, referenceFrame, result); } - return false; + return undefined; }; /** - * 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(); + * Sets the algorithm and degree to use when interpolating a position. * - * @see LabelCollection#add - * @see LabelCollection#remove + * @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. */ - 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; + SampledPositionProperty.prototype.setInterpolationOptions = function(options) { + this._property.setInterpolationOptions(options); }; /** - * 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. + * Adds a new sample. * - * @see LabelCollection#get + * @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. */ - LabelCollection.prototype.contains = function(label) { - return defined(label) && label._labelCollection === this; + SampledPositionProperty.prototype.addSample = function(time, position, derivatives) { + var numberOfDerivatives = this._numberOfDerivatives; + this._property.addSample(time, position, derivatives); }; /** - * 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. - * + * Adds multiple samples via parallel arrays. * - * @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; - * } + * @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. * - * @see LabelCollection#length + * @exception {DeveloperError} All arrays must be the same length. */ - LabelCollection.prototype.get = function(index) { - - return this._labels[index]; + SampledPositionProperty.prototype.addSamples = function(times, positions, derivatives) { + this._property.addSamples(times, positions, derivatives); }; /** - * @private + * 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. */ - 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; - } - - var blendOption = backgroundBillboardCollection.length > 0 ? BlendOption.TRANSLUCENT : this.blendOption; - billboardCollection.blendOption = blendOption; - backgroundBillboardCollection.blendOption = blendOption; - - this._labelsToUpdate.length = 0; - backgroundBillboardCollection.update(frameState); - billboardCollection.update(frameState); + SampledPositionProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) { + this._property.addSamplesPackedArray(packedSamples, epoch); }; /** - * 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. + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. * - * @see LabelCollection#destroy + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. */ - LabelCollection.prototype.isDestroyed = function() { - return false; + SampledPositionProperty.prototype.equals = function(other) { + return this === other || // + (other instanceof SampledPositionProperty && + Property.equals(this._property, other._property) && // + this._referenceFrame === other._referenceFrame); }; + return SampledPositionProperty; +}); + +define('DataSources/StripeOrientation',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + /** - * 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(); + * Defined the orientation of stripes in {@link StripeMaterialProperty}. * - * @see LabelCollection#isDestroyed + * @exports StripeOrientation */ - 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(); + var StripeOrientation = { + /** + * Horizontal orientation. + * @type {Number} + */ + HORIZONTAL : 0, - return destroyObject(this); + /** + * Vertical orientation. + * @type {Number} + */ + VERTICAL : 1 }; - return LabelCollection; + return freezeObject(StripeOrientation); }); -/*global define*/ -define('Scene/PointPrimitive',[ - '../Core/BoundingRectangle', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartesian4', +define('DataSources/StripeMaterialProperty',[ '../Core/Color', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/DistanceDisplayCondition', - '../Core/Matrix4', - '../Core/NearFarScalar', - './SceneMode', - './SceneTransforms' + '../Core/Event', + './createPropertyDescriptor', + './Property', + './StripeOrientation' ], function( - BoundingRectangle, - Cartesian2, - Cartesian3, - Cartesian4, Color, defaultValue, defined, defineProperties, - DeveloperError, - DistanceDisplayCondition, - Matrix4, - NearFarScalar, - SceneMode, - SceneTransforms) { + 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 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 + * A {@link MaterialProperty} that maps to stripe {@link Material} uniforms. + * @alias StripeMaterialProperty + * @constructor * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Points.html|Cesium Sandcastle Points Demo} + * @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 PointPrimitive(options, pointPrimitiveCollection) { + function StripeMaterialProperty(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - 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._disableDepthTestDistance = defaultValue(options.disableDepthTestDistance, 0.0); - this._id = options.id; - this._collection = defaultValue(options.collection, pointPrimitiveCollection); + this._definitionChanged = new Event(); - this._clusterShow = true; + this._orientation = undefined; + this._orientationSubscription = undefined; - this._pickId = undefined; - this._pointPrimitiveCollection = pointPrimitiveCollection; - this._dirty = false; - this._index = -1; //Used only by PointPrimitiveCollection - } + this._evenColor = undefined; + this._evenColorSubscription = undefined; - 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_INDEX = 8; - var DISABLE_DEPTH_DISTANCE_INDEX = PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX = 9; - PointPrimitive.NUMBER_OF_PROPERTIES = 10; + this._oddColor = undefined; + this._oddColorSubscription = undefined; - function makeDirty(pointPrimitive, propertyChanged) { - var pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection; - if (defined(pointPrimitiveCollection)) { - pointPrimitiveCollection._updatePointPrimitive(pointPrimitive, propertyChanged); - pointPrimitive._dirty = true; - } + 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(PointPrimitive.prototype, { + defineProperties(StripeMaterialProperty.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 + * 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 */ - show : { - get : function() { - return this._show; - }, - set : function(value) { - - 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 : { + isConstant : { get : function() { - return this._position; - }, - set : function(value) { - - var position = this._position; - if (!Cartesian3.equals(position, value)) { - Cartesian3.clone(value, position); - Cartesian3.clone(value, this._actualPosition); - - makeDirty(this, POSITION_INDEX); - } + return Property.isConstant(this._orientation) && // + Property.isConstant(this._evenColor) && // + Property.isConstant(this._oddColor) && // + Property.isConstant(this._offset) && // + Property.isConstant(this._repeat); } }, - /** - * 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); + * 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 * - * @example - * // Example 2. - * // disable scaling by distance - * p.scaleByDistance = undefined; + * @type {Event} + * @readonly */ - scaleByDistance : { + definitionChanged : { get : function() { - return this._scaleByDistance; - }, - set : function(value) { - - var scaleByDistance = this._scaleByDistance; - if (!NearFarScalar.equals(scaleByDistance, value)) { - this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance); - makeDirty(this, SCALE_BY_DISTANCE_INDEX); - } + return this._definitionChanged; } }, - /** - * 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; + * Gets or sets the Property specifying the {@link StripeOrientation}/ + * @memberof StripeMaterialProperty.prototype + * @type {Property} + * @default StripeOrientation.HORIZONTAL */ - translucencyByDistance : { - get : function() { - return this._translucencyByDistance; - }, - set : function(value) { - - var translucencyByDistance = this._translucencyByDistance; - if (!NearFarScalar.equals(translucencyByDistance, value)) { - this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance); - makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX); - } - } - }, - + orientation : createPropertyDescriptor('orientation'), /** - * Gets or sets the inner size of the point in pixels. - * @memberof PointPrimitive.prototype - * @type {Number} + * Gets or sets the Property specifying the first {@link Color}. + * @memberof StripeMaterialProperty.prototype + * @type {Property} + * @default Color.WHITE */ - pixelSize : { - get : function() { - return this._pixelSize; - }, - set : function(value) { - - if (this._pixelSize !== value) { - this._pixelSize = value; - makeDirty(this, PIXEL_SIZE_INDEX); - } - } - }, - + evenColor : createPropertyDescriptor('evenColor'), /** - * 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); + * Gets or sets the Property specifying the second {@link Color}. + * @memberof StripeMaterialProperty.prototype + * @type {Property} + * @default Color.BLACK */ - color : { - get : function() { - return this._color; - }, - set : function(value) { - - var color = this._color; - if (!Color.equals(color, value)) { - Color.clone(value, color); - makeDirty(this, COLOR_INDEX); - } - } - }, - + oddColor : createPropertyDescriptor('oddColor'), /** - * Gets or sets the outline color of the point. - * @memberof PointPrimitive.prototype - * @type {Color} + * 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 */ - outlineColor : { - get : function() { - return this._outlineColor; - }, - set : function(value) { - - var outlineColor = this._outlineColor; - if (!Color.equals(outlineColor, value)) { - Color.clone(value, outlineColor); - makeDirty(this, OUTLINE_COLOR_INDEX); - } - } - }, - + offset : createPropertyDescriptor('offset'), /** - * 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} + * Gets or sets the numeric Property specifying how many times the stripes repeat. + * @memberof StripeMaterialProperty.prototype + * @type {Property} + * @default 1.0 */ - outlineWidth : { - get : function() { - return this._outlineWidth; - }, - set : function(value) { - - if (this._outlineWidth !== value) { - this._outlineWidth = value; - makeDirty(this, OUTLINE_WIDTH_INDEX); - } - } - }, + 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; +}); + +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 or sets the condition specifying at what distance from the camera that this point will be displayed. - * @memberof PointPrimitive.prototype - * @type {DistanceDisplayCondition} - * @default undefined + * 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 */ - distanceDisplayCondition : { + isConstant : { get : function() { - return this._distanceDisplayCondition; - }, - set : function(value) { - if (!DistanceDisplayCondition.equals(this._distanceDisplayCondition, value)) { - this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); - makeDirty(this, DISTANCE_DISPLAY_CONDITION_INDEX); - } + return this._intervals.isEmpty; } }, - /** - * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. - * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. - * @memberof PointPrimitive.prototype - * @type {Number} - * @default 0.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 TimeIntervalCollectionPositionProperty.prototype + * + * @type {Event} + * @readonly */ - disableDepthTestDistance : { + definitionChanged : { get : function() { - return this._disableDepthTestDistance; - }, - set : function(value) { - if (this._disableDepthTestDistance !== value) { - this._disableDepthTestDistance = value; - makeDirty(this, DISABLE_DEPTH_DISTANCE_INDEX); - } + return this._definitionChanged; } }, - /** - * Gets or sets the user-defined object returned when the point is picked. - * @memberof PointPrimitive.prototype - * @type {Object} + * Gets the interval collection. + * @memberof TimeIntervalCollectionPositionProperty.prototype + * @type {TimeIntervalCollection} */ - id : { + intervals : { get : function() { - return this._id; - }, - set : function(value) { - this._id = value; - if (defined(this._pickId)) { - this._pickId.object.id = value; - } + return this._intervals; } }, - /** - * Determines whether or not this point will be shown or hidden because it was clustered. - * @memberof PointPrimitive.prototype - * @type {Boolean} - * @private + * Gets the reference frame in which the position is defined. + * @memberof TimeIntervalCollectionPositionProperty.prototype + * @type {ReferenceFrame} + * @default ReferenceFrame.FIXED; */ - clusterShow : { + referenceFrame : { get : function() { - return this._clusterShow; - }, - set : function(value) { - if (this._clusterShow !== value) { - this._clusterShow = value; - makeDirty(this, SHOW_INDEX); - } + return this._referenceFrame; } } }); - PointPrimitive.prototype.getPickId = function(context) { - if (!defined(this._pickId)) { - this._pickId = context.createPickId({ - primitive : this, - collection : this._collection, - id : this._id - }); - } - - return this._pickId; + /** + * 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); }; - PointPrimitive.prototype._getActualPosition = function() { - return this._actualPosition; + /** + * 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) { + + var position = this._intervals.findDataForIntervalContainingDate(time); + if (defined(position)) { + return PositionProperty.convertToReferenceFrame(time, position, this._referenceFrame, referenceFrame, result); + } + return undefined; }; - PointPrimitive.prototype._setActualPosition = function(value) { - Cartesian3.clone(value, this._actualPosition); - makeDirty(this, POSITION_INDEX); + /** + * 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); }; - 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); + /** + * @private + */ + TimeIntervalCollectionPositionProperty.prototype._intervalsChanged = function() { + this._definitionChanged.raiseEvent(this); }; - var scratchCartesian4 = new Cartesian4(); + return TimeIntervalCollectionPositionProperty; +}); - // 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; - }; +define('DataSources/TimeIntervalCollectionProperty',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/TimeIntervalCollection', + './Property' + ], function( + defined, + defineProperties, + DeveloperError, + Event, + TimeIntervalCollection, + Property) { + 'use strict'; /** - * 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. + * A {@link Property} which is defined by a {@link TimeIntervalCollection}, where the + * data property of each {@link TimeInterval} represents the value at time. * - * @exception {DeveloperError} PointPrimitive must be in a collection. + * @alias TimeIntervalCollectionProperty + * @constructor * * @example - * console.log(p.computeScreenSpacePosition(scene).toString()); + * //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) + * })); */ - PointPrimitive.prototype.computeScreenSpacePosition = function(scene, result) { - var pointPrimitiveCollection = this._pointPrimitiveCollection; - if (!defined(result)) { - result = new Cartesian2(); - } + function TimeIntervalCollectionProperty() { + this._definitionChanged = new Event(); + this._intervals = new TimeIntervalCollection(); + this._intervals.changedEvent.addEventListener(TimeIntervalCollectionProperty.prototype._intervalsChanged, this); + } - - var modelMatrix = pointPrimitiveCollection.modelMatrix; - var windowCoordinates = PointPrimitive._computeScreenSpacePosition(modelMatrix, this._actualPosition, scene, result); - if (!defined(windowCoordinates)) { - return undefined; + 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; + } } - - 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. + * Gets the value of the property at the provided time. * - * @private + * @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. */ - 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(); + TimeIntervalCollectionProperty.prototype.getValue = function(time, result) { + + var value = this._intervals.findDataForIntervalContainingDate(time); + if (defined(value) && (typeof value.clone === 'function')) { + return value.clone(result); } - - result.x = x; - result.y = y; - result.width = width; - result.height = height; - - return result; + return value; }; /** - * Determines if this point equals another point. Points are equal if all their properties - * are equal. Points in different collections can be equal. + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. * - * @param {PointPrimitive} other The point to compare for equality. - * @returns {Boolean} true if the points are equal; otherwise, false. + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. */ - 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) && - this._disableDepthTestDistance === other._disableDepthTestDistance; + TimeIntervalCollectionProperty.prototype.equals = function(other) { + return this === other || // + (other instanceof TimeIntervalCollectionProperty && // + this._intervals.equals(other._intervals, Property.equals)); }; - PointPrimitive.prototype._destroy = function() { - this._pickId = this._pickId && this._pickId.destroy(); - this._pointPrimitiveCollection = undefined; + /** + * @private + */ + TimeIntervalCollectionProperty.prototype._intervalsChanged = function() { + this._definitionChanged.raiseEvent(this); }; - - 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\ -\n\ -#ifdef RENDER_FOR_PICK\n\ -varying vec4 v_pickColor;\n\ -#endif\n\ -\n\ -void main()\n\ -{\n\ - // The distance in UV space from this fragment to the center of the point, at most 0.5.\n\ - float distanceToCenter = length(gl_PointCoord - vec2(0.5));\n\ - // The max distance stops one pixel shy of the edge to leave space for anti-aliasing.\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\ -\n\ - vec4 color = mix(v_outlineColor, v_color, innerAlpha);\n\ - color.a *= wholeAlpha;\n\ -\n\ -// Fully transparent parts of the billboard are not pickable.\n\ -#if defined(RENDER_FOR_PICK) || (!defined(OPAQUE) && !defined(TRANSLUCENT))\n\ - if (color.a < 0.005) // matches 0/255 and 1/255\n\ - {\n\ - discard;\n\ - }\n\ -#else\n\ -// The billboard is rendered twice. The opaque pass discards translucent fragments\n\ -// and the translucent pass discards opaque fragments.\n\ -#ifdef OPAQUE\n\ - if (color.a < 0.995) // matches < 254/255\n\ - {\n\ - discard;\n\ - }\n\ -#else\n\ - if (color.a >= 0.995) // matches 254/255 and 255/255\n\ - {\n\ - discard;\n\ - }\n\ -#endif\n\ -#endif\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\ -\n\ -attribute vec4 positionHighAndSize;\n\ -attribute vec4 positionLowAndOutline;\n\ -attribute vec4 compressedAttribute0; // color, outlineColor, pick color\n\ -attribute vec4 compressedAttribute1; // show, translucency by distance, some free space\n\ -attribute vec4 scaleByDistance; // near, nearScale, far, farScale\n\ -attribute vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance\n\ -\n\ -varying vec4 v_color;\n\ -varying vec4 v_outlineColor;\n\ -varying float v_innerPercent;\n\ -varying float v_pixelDistance;\n\ -\n\ -#ifdef RENDER_FOR_PICK\n\ -varying vec4 v_pickColor;\n\ -#endif\n\ -\n\ -const float SHIFT_LEFT8 = 256.0;\n\ -const float SHIFT_RIGHT8 = 1.0 / 256.0;\n\ -\n\ -void main()\n\ -{\n\ - // Modifying this shader may also require modifications to PointPrimitive._computeScreenSpacePosition\n\ -\n\ - // unpack attributes\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\ - // Scale in response to browser-zoom.\n\ - totalSize *= czm_resolutionScale;\n\ - // Add padding for anti-aliasing on both sides.\n\ - totalSize += 3.0;\n\ -\n\ - float temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\ - float show = floor(temp);\n\ -\n\ -#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ - vec4 translucencyByDistance;\n\ - translucencyByDistance.x = compressedAttribute1.z;\n\ - translucencyByDistance.z = compressedAttribute1.w;\n\ -\n\ - translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ -\n\ - temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\ - translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\ -#endif\n\ -\n\ - ///////////////////////////////////////////////////////////////////////////\n\ -\n\ - vec4 color;\n\ - vec4 outlineColor;\n\ -#ifdef RENDER_FOR_PICK\n\ - // compressedAttribute0.z => pickColor.rgb\n\ -\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\ - // compressedAttribute0.x => color.rgb\n\ -\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\ -\n\ - // compressedAttribute0.y => outlineColor.rgb\n\ -\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\ -\n\ - // compressedAttribute0.w => color.a, outlineColor.a, pickColor.a\n\ -\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\ -\n\ - ///////////////////////////////////////////////////////////////////////////\n\ -\n\ - vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\ - vec4 positionEC = czm_modelViewRelativeToEye * p;\n\ - positionEC.xyz *= show;\n\ -\n\ - ///////////////////////////////////////////////////////////////////////////\n\ -\n\ -#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(DISTANCE_DISPLAY_CONDITION) || defined(DISABLE_DEPTH_DISTANCE)\n\ - float lengthSq;\n\ - if (czm_sceneMode == czm_sceneMode2D)\n\ - {\n\ - // 2D camera distance is a special case\n\ - // treat all billboards as flattened to the z=0.0 plane\n\ - lengthSq = czm_eyeHeight2D.y;\n\ - }\n\ - else\n\ - {\n\ - lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\ - }\n\ -#endif\n\ -\n\ -#ifdef EYE_DISTANCE_SCALING\n\ - totalSize *= czm_nearFarScalar(scaleByDistance, lengthSq);\n\ -#endif\n\ - // Clamp to max point size.\n\ - totalSize = min(totalSize, u_maxTotalPointSize);\n\ - // If size is too small, push vertex behind near plane for clipping.\n\ - // Note that context.minimumAliasedPointSize \"will be at most 1.0\".\n\ - if (totalSize < 1.0)\n\ - {\n\ - positionEC.xyz = vec3(0.0);\n\ - totalSize = 1.0;\n\ - }\n\ -\n\ - float translucency = 1.0;\n\ -#ifdef EYE_DISTANCE_TRANSLUCENCY\n\ - translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\ - // push vertex behind near plane for clipping\n\ - if (translucency < 0.004)\n\ - {\n\ - positionEC.xyz = vec3(0.0);\n\ - }\n\ -#endif\n\ -\n\ -#ifdef DISTANCE_DISPLAY_CONDITION\n\ - float nearSq = distanceDisplayConditionAndDisableDepth.x;\n\ - float farSq = distanceDisplayConditionAndDisableDepth.y;\n\ - if (lengthSq < nearSq || lengthSq > farSq) {\n\ - positionEC.xyz = vec3(0.0);\n\ - }\n\ -#endif\n\ -\n\ - vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\ -\n\ - gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0);\n\ -\n\ -#ifdef DISABLE_DEPTH_DISTANCE\n\ - float disableDepthTestDistance = distanceDisplayConditionAndDisableDepth.z;\n\ - if (disableDepthTestDistance == 0.0 && czm_minimumDisableDepthTestDistance != 0.0)\n\ - {\n\ - disableDepthTestDistance = czm_minimumDisableDepthTestDistance;\n\ - }\n\ -\n\ - if (disableDepthTestDistance != 0.0)\n\ - {\n\ - gl_Position.z = min(gl_Position.z, gl_Position.w);\n\ -\n\ - bool clipped = gl_Position.z < -gl_Position.w || gl_Position.z > gl_Position.w;\n\ - if (!clipped && (disableDepthTestDistance < 0.0 || (lengthSq > 0.0 && lengthSq < disableDepthTestDistance)))\n\ - {\n\ - gl_Position.z = -gl_Position.w;\n\ - }\n\ - }\n\ -#endif\n\ -\n\ - v_color = color;\n\ - v_color.a *= translucency;\n\ - v_outlineColor = outlineColor;\n\ - v_outlineColor.a *= translucency;\n\ -\n\ - v_innerPercent = 1.0 - outlinePercent;\n\ - v_pixelDistance = 2.0 / totalSize;\n\ - gl_PointSize = totalSize;\n\ -\n\ -#ifdef RENDER_FOR_PICK\n\ - v_pickColor = pickColor;\n\ -#endif\n\ -}\n\ -"; + + return TimeIntervalCollectionProperty; }); -/*global define*/ -define('Scene/PointPrimitiveCollection',[ - '../Core/BoundingSphere', - '../Core/Color', - '../Core/ComponentDatatype', + +define('DataSources/VelocityVectorProperty',[ + '../Core/Cartesian3', '../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', - './BlendOption', - './PointPrimitive', - './SceneMode' + '../Core/Event', + '../Core/JulianDate', + './Property' ], function( - BoundingSphere, - Color, - ComponentDatatype, + Cartesian3, defaultValue, defined, defineProperties, - destroyObject, DeveloperError, - EncodedCartesian3, - CesiumMath, - Matrix4, - PrimitiveType, - WebGLConstants, - BufferUsage, - ContextLimits, - DrawCommand, - Pass, - RenderState, - ShaderProgram, - ShaderSource, - VertexArrayFacade, - PointPrimitiveCollectionFS, - PointPrimitiveCollectionVS, - BlendingState, - BlendOption, - PointPrimitive, - SceneMode) { + Event, + JulianDate, + Property) { '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 DISABLE_DEPTH_DISTANCE_INDEX = PointPrimitive.DISABLE_DEPTH_DISTANCE_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, - distanceDisplayConditionAndDisableDepth : 5 - }; - /** - * A renderable collection of points. - *

    - * Points are added and removed from the collection using {@link PointPrimitiveCollection#add} - * and {@link PointPrimitiveCollection#remove}. + * A {@link Property} which evaluates to a {@link Cartesian3} vector + * based on the velocity of the provided {@link PositionProperty}. * - * @alias PointPrimitiveCollection + * @alias VelocityVectorProperty * @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. - * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The point blending option. The default - * is used for rendering both opaque and translucent points. However, if either all of the points are completely opaque or all are completely translucent, - * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. - * - * @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. - * + * @param {Property} [position] The position property used to compute the velocity. + * @param {Boolean} [normalize=true] Whether to normalize the computed velocity vector. * * @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 + * //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 PointPrimitiveCollection(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - this._sp = undefined; - this._spTranslucent = undefined; - this._spPick = undefined; - this._rsOpaque = undefined; - this._rsTranslucent = undefined; - this._vaf = 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._shaderDisableDepthDistance = false; - this._compiledShaderDisableDepthDistance = false; - this._compiledShaderDisableDepthDistancePick = 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; + function VelocityVectorProperty(position, normalize) { + this._position = undefined; + this._subscription = undefined; + this._definitionChanged = new Event(); + this._normalize = defaultValue(normalize, true); - this._colorCommands = []; - this._pickCommands = []; + this.position = position; + } + defineProperties(VelocityVectorProperty.prototype, { /** - * 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 - * }); + * Gets a value indicating if this property is constant. + * @memberof VelocityVectorProperty.prototype * - * @see Transforms.eastNorthUpToFixedFrame + * @type {Boolean} + * @readonly */ - this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); - this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY); - + isConstant : { + get : function() { + return Property.isConstant(this._position); + } + }, /** - * 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} + * Gets the event that is raised whenever the definition of this property changes. + * @memberof VelocityVectorProperty.prototype * - * @default false + * @type {Event} + * @readonly */ - this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); - + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, /** - * The point blending option. The default is used for rendering both opaque and translucent points. - * However, if either all of the points are completely opaque or all are completely translucent, - * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve - * performance by up to 2x. - * @type {BlendOption} - * @default BlendOption.OPAQUE_AND_TRANSLUCENT + * Gets or sets the position property used to compute the velocity vector. + * @memberof VelocityVectorProperty.prototype + * + * @type {Property} */ - this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); - this._blendOption = undefined; + position : { + get : function() { + return this._position; + }, + set : function(value) { + var oldValue = this._position; + if (oldValue !== value) { + if (defined(oldValue)) { + this._subscription(); + } - this._mode = SceneMode.SCENE3D; - this._maxTotalPointSize = 1; + this._position = value; - // 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 - ]; + if (defined(value)) { + this._subscription = value._definitionChanged.addEventListener(function() { + this._definitionChanged.raiseEvent(this); + }, this); + } - var that = this; - this._uniforms = { - u_maxTotalPointSize : function() { - return that._maxTotalPointSize; + this._definitionChanged.raiseEvent(this); + } } - }; - } - - 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} + * Gets or sets whether the vector produced by this property + * will be normalized or not. + * @memberof VelocityVectorProperty.prototype + * + * @type {Boolean} */ - length : { + normalize : { get : function() { - removePointPrimitives(this); - return this._pointPrimitives.length; + return this._normalize; + }, + set : function(value) { + if (this._normalize === value) { + return; + } + + this._normalize = value; + this._definitionChanged.raiseEvent(this); } } }); - function destroyPointPrimitives(pointPrimitives) { - var length = pointPrimitives.length; - for (var i = 0; i < length; ++i) { - if (pointPrimitives[i]) { - pointPrimitives[i]._destroy(); - } - } - } + var position1Scratch = new Cartesian3(); + var position2Scratch = new Cartesian3(); + var timeScratch = new JulianDate(); + var step = 1.0 / 60.0; /** - * 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) - * }); + * Gets the value of the property at the provided time. * - * @see PointPrimitiveCollection#remove - * @see PointPrimitiveCollection#removeAll + * @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. */ - 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; + VelocityVectorProperty.prototype.getValue = function(time, result) { + return this._getValue(time, result); }; /** - * 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 + * @private */ - 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; + VelocityVectorProperty.prototype._getValue = function(time, velocityResult, positionResult) { + + if (!defined(velocityResult)) { + velocityResult = new Cartesian3(); } - return false; + 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); + } + + return Cartesian3.divideByScalar(velocity, step, velocityResult); }; /** - * Removes all points from the collection. + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. * - * @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. + * @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; +}); + +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}. * - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * @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 - * pointPrimitives.add(...); - * pointPrimitives.add(...); - * pointPrimitives.removeAll(); - * - * @see PointPrimitiveCollection#add - * @see PointPrimitiveCollection#remove + * //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) + * })); */ - PointPrimitiveCollection.prototype.removeAll = function() { - destroyPointPrimitives(this._pointPrimitives); - this._pointPrimitives = []; - this._pointPrimitivesToUpdate = []; - this._pointPrimitivesToUpdateIndex = 0; - this._pointPrimitivesRemoved = false; + function VelocityOrientationProperty(position, ellipsoid) { + this._velocityVectorProperty = new VelocityVectorProperty(position, true); + this._subscription = undefined; + this._ellipsoid = undefined; + this._definitionChanged = new Event(); - this._createVertexArray = true; - }; + this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - function removePointPrimitives(pointPrimitiveCollection) { - if (pointPrimitiveCollection._pointPrimitivesRemoved) { - pointPrimitiveCollection._pointPrimitivesRemoved = false; + var that = this; + this._velocityVectorProperty.definitionChanged.addEventListener(function() { + that._definitionChanged.raiseEvent(that); + }); + } - 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); + 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); } } - - pointPrimitiveCollection._pointPrimitives = newPointPrimitives; - } - } - - PointPrimitiveCollection.prototype._updatePointPrimitive = function(pointPrimitive, propertyChanged) { - if (!pointPrimitive._dirty) { - this._pointPrimitivesToUpdate[this._pointPrimitivesToUpdateIndex++] = pointPrimitive; } + }); - ++this._propertiesChanged[propertyChanged]; - }; + var positionScratch = new Cartesian3(); + var velocityScratch = new Cartesian3(); + var rotationScratch = new Matrix3(); /** - * 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. + * Gets the value of the property at the provided time. * - * @see PointPrimitiveCollection#get + * @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. */ - PointPrimitiveCollection.prototype.contains = function(pointPrimitive) { - return defined(pointPrimitive) && pointPrimitive._pointPrimitiveCollection === this; + 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); }; /** - * 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; - * } + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. * - * @see PointPrimitiveCollection#length + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. */ - PointPrimitiveCollection.prototype.get = function(index) { - - removePointPrimitives(this); - return this._pointPrimitives[index]; + 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))); }; - PointPrimitiveCollection.prototype.computeNewBuffersUsage = function() { - var buffersUsage = this._buffersUsage; - var usageChanged = false; + return VelocityOrientationProperty; +}); - 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; +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/joinUrls', + '../Core/JulianDate', + '../Core/LagrangePolynomialApproximation', + '../Core/LinearApproximation', + '../Core/loadJson', + '../Core/Math', + '../Core/NearFarScalar', + '../Core/objectToQuery', + '../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', + './PolylineDashMaterialProperty', + './PolylineGlowMaterialProperty', + './PolylineGraphics', + './PolylineOutlineMaterialProperty', + './PositionPropertyArray', + './PropertyArray', + './PropertyBag', + './RectangleGraphics', + './ReferenceProperty', + './Rotation', + './SampledPositionProperty', + './SampledProperty', + './StripeMaterialProperty', + './StripeOrientation', + './TimeIntervalCollectionPositionProperty', + './TimeIntervalCollectionProperty', + './VelocityOrientationProperty', + './VelocityVectorProperty', + './WallGraphics' + ], function( + BoundingRectangle, + Cartesian2, + Cartesian3, + Cartographic, + ClockRange, + ClockStep, + Color, + CornerType, + createGuid, + defaultValue, + defined, + defineProperties, + DeveloperError, + Ellipsoid, + Event, + ExtrapolationType, + getAbsoluteUri, + getFilenameFromUri, + HermitePolynomialApproximation, + isArray, + Iso8601, + joinUrls, + JulianDate, + LagrangePolynomialApproximation, + LinearApproximation, + loadJson, + CesiumMath, + NearFarScalar, + objectToQuery, + 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, + PolylineDashMaterialProperty, + PolylineGlowMaterialProperty, + PolylineGraphics, + PolylineOutlineMaterialProperty, + PositionPropertyArray, + PropertyArray, + PropertyBag, + RectangleGraphics, + ReferenceProperty, + Rotation, + SampledPositionProperty, + SampledProperty, + StripeMaterialProperty, + StripeOrientation, + TimeIntervalCollectionPositionProperty, + TimeIntervalCollectionProperty, + VelocityOrientationProperty, + VelocityVectorProperty, + WallGraphics) { + 'use strict'; + + // A marker type to distinguish CZML properties where we need to end up with a unit vector. + // The data is still loaded into Cartesian3 objects but they are normalized. + function UnitCartesian3() {} + UnitCartesian3.packedLength = Cartesian3.packedLength; + UnitCartesian3.unpack = Cartesian3.unpack; + UnitCartesian3.pack = Cartesian3.pack; + + // As a side note, for the purposes of CZML, Quaternion always indicates a unit quaternion. + + var currentId; + + function createReferenceProperty(entityCollection, referenceString) { + if (referenceString[0] === '#') { + referenceString = currentId + referenceString; } + return ReferenceProperty.fromString(entityCollection, referenceString); + } - return usageChanged; - }; + function createSpecializedProperty(type, entityCollection, packetData) { + if (defined(packetData.reference)) { + return createReferenceProperty(entityCollection, packetData.reference); + } - 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.distanceDisplayConditionAndDisableDepth, - componentsPerAttribute : 3, - componentDatatype : ComponentDatatype.FLOAT, - usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX] - }], numberOfPointPrimitives); // 1 vertex per pointPrimitive + if (defined(packetData.velocityReference)) { + var referenceProperty = createReferenceProperty(entityCollection, packetData.velocityReference); + switch (type) { + case Cartesian3: + case UnitCartesian3: + return new VelocityVectorProperty(referenceProperty, type === UnitCartesian3); + case Quaternion: + return new VelocityOrientationProperty(referenceProperty); + } + } + + throw new RuntimeError(JSON.stringify(packetData) + ' is not valid CZML.'); } - /////////////////////////////////////////////////////////////////////////// + var scratchCartesian = new Cartesian3(); + var scratchSpherical = new Spherical(); + var scratchCartographic = new Cartographic(); + var scratchTimeInterval = new TimeInterval(); + var scratchQuaternion = new Quaternion(); - // 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. + 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, query) { + var result = defaultValue(czmlInterval.uri, czmlInterval); + if (defined(sourceUri)) { + result = getAbsoluteUri(result, getAbsoluteUri(sourceUri)); + + if (defined(query)) { + result = joinUrls(result, '?' + query, false); + } + } + 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]; + } + + 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]; + } + + 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]; + } + + 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]; + } - var writePositionScratch = new EncodedCartesian3(); + var result = new Array(length); + for (var i = 0; i < length; i += 4) { + result[i] = cartographicDegrees[i]; - function writePositionSizeAndOutline(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { - var i = pointPrimitive._index; - var position = pointPrimitive._getActualPosition(); + scratchCartographic.longitude = CesiumMath.toRadians(cartographicDegrees[i + 1]); + scratchCartographic.latitude = CesiumMath.toRadians(cartographicDegrees[i + 2]); + scratchCartographic.height = cartographicDegrees[i + 3]; + Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian); - if (pointPrimitiveCollection._mode === SceneMode.SCENE3D) { - BoundingSphere.expand(pointPrimitiveCollection._baseVolume, position, pointPrimitiveCollection._baseVolume); - pointPrimitiveCollection._boundingVolumeDirty = true; + result[i + 1] = scratchCartesian.x; + result[i + 2] = scratchCartesian.y; + result[i + 3] = scratchCartesian.z; } + return result; + } - EncodedCartesian3.fromCartesian(position, writePositionScratch); - var pixelSize = pointPrimitive.pixelSize; - var outlineWidth = pointPrimitive.outlineWidth; + function unwrapCartesianInterval(czmlInterval) { + var cartesian = czmlInterval.cartesian; + if (defined(cartesian)) { + return cartesian; + } - pointPrimitiveCollection._maxPixelSize = Math.max(pointPrimitiveCollection._maxPixelSize, pixelSize + outlineWidth); + var cartesianVelocity = czmlInterval.cartesianVelocity; + if (defined(cartesianVelocity)) { + return cartesianVelocity; + } - var positionHighWriter = vafWriters[attributeLocations.positionHighAndSize]; - var high = writePositionScratch.high; - positionHighWriter(i, high.x, high.y, high.z, pixelSize); + var unitCartesian = czmlInterval.unitCartesian; + if (defined(unitCartesian)) { + return unitCartesian; + } - var positionLowWriter = vafWriters[attributeLocations.positionLowAndOutline]; - var low = writePositionScratch.low; - positionLowWriter(i, low.x, low.y, low.z, outlineWidth); - } + var unitSpherical = czmlInterval.unitSpherical; + if (defined(unitSpherical)) { + return convertUnitSphericalToCartesian(unitSpherical); + } - var LEFT_SHIFT16 = 65536.0; // 2^16 - var LEFT_SHIFT8 = 256.0; // 2^8 + var spherical = czmlInterval.spherical; + if (defined(spherical)) { + return convertSphericalToCartesian(spherical); + } - function writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { - var i = pointPrimitive._index; + var cartographicRadians = czmlInterval.cartographicRadians; + if (defined(cartographicRadians)) { + return convertCartographicRadiansToCartesian(cartographicRadians); + } - var color = pointPrimitive.color; - var pickColor = pointPrimitive.getPickId(context).color; - var outlineColor = pointPrimitive.outlineColor; + var cartographicDegrees = czmlInterval.cartographicDegrees; + if (defined(cartographicDegrees)) { + return convertCartographicDegreesToCartesian(cartographicDegrees); + } - 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; + throw new RuntimeError(JSON.stringify(czmlInterval) + ' is not a valid CZML interval.'); + } - red = Color.floatToByte(outlineColor.red); - green = Color.floatToByte(outlineColor.green); - blue = Color.floatToByte(outlineColor.blue); - var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; + function normalizePackedCartesianArray(array, startingIndex) { + Cartesian3.unpack(array, startingIndex, scratchCartesian); + Cartesian3.normalize(scratchCartesian, scratchCartesian); + Cartesian3.pack(scratchCartesian, array, startingIndex); + } - red = Color.floatToByte(pickColor.red); - green = Color.floatToByte(pickColor.green); - blue = Color.floatToByte(pickColor.blue); - var compressed2 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue; + function unwrapUnitCartesianInterval(czmlInterval) { + var cartesian = unwrapCartesianInterval(czmlInterval); + if (cartesian.length === 3) { + normalizePackedCartesianArray(cartesian, 0); + return cartesian; + } - var compressed3 = - Color.floatToByte(color.alpha) * LEFT_SHIFT16 + - Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT8 + - Color.floatToByte(pickColor.alpha); + for (var i = 1; i < cartesian.length; i += 4) { + normalizePackedCartesianArray(cartesian, i); + } - var writer = vafWriters[attributeLocations.compressedAttribute0]; - writer(i, compressed0, compressed1, compressed2, compressed3); + return cartesian; } - 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; + function normalizePackedQuaternionArray(array, startingIndex) { + Quaternion.unpack(array, startingIndex, scratchQuaternion); + Quaternion.normalize(scratchQuaternion, scratchQuaternion); + Quaternion.pack(scratchQuaternion, array, startingIndex); + } - var translucency = pointPrimitive.translucencyByDistance; - if (defined(translucency)) { - near = translucency.near; - nearValue = translucency.nearValue; - far = translucency.far; - farValue = translucency.farValue; + function unwrapQuaternionInterval(czmlInterval) { + var unitQuaternion = czmlInterval.unitQuaternion; + if (defined(unitQuaternion)) { + if (unitQuaternion.length === 4) { + normalizePackedQuaternionArray(unitQuaternion, 0); + return unitQuaternion; + } - 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; + for (var i = 1; i < unitQuaternion.length; i += 5) { + normalizePackedQuaternionArray(unitQuaternion, i); } } + return unitQuaternion; + } - var show = pointPrimitive.show && pointPrimitive.clusterShow; + function getPropertyType(czmlInterval) { + // The associations in this function need to be kept in sync with the + // associations in unwrapInterval. - // 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; + // Intentionally omitted due to conficts in CZML property names: + // * Image (conflicts with Uri) + // * Rotation (conflicts with Number) + // + // cartesianVelocity is also omitted due to incomplete support for + // derivative information in CZML properties. + // (Currently cartesianVelocity is hacked directly into the position processing code) + if (typeof czmlInterval === 'boolean') { + return Boolean; + } else if (typeof czmlInterval === 'number') { + return Number; + } else if (typeof czmlInterval === 'string') { + return String; + } else if (czmlInterval.hasOwnProperty('array')) { + return Array; + } else if (czmlInterval.hasOwnProperty('boolean')) { + return Boolean; + } else if (czmlInterval.hasOwnProperty('boundingRectangle')) { + return BoundingRectangle; + } else if (czmlInterval.hasOwnProperty('cartesian2')) { + return Cartesian2; + } else if (czmlInterval.hasOwnProperty('cartesian') || + czmlInterval.hasOwnProperty('spherical') || + czmlInterval.hasOwnProperty('cartographicRadians') || + czmlInterval.hasOwnProperty('cartographicDegrees')) { + return Cartesian3; + } else if (czmlInterval.hasOwnProperty('unitCartesian') || + czmlInterval.hasOwnProperty('unitSpherical')) { + return UnitCartesian3; + } else if (czmlInterval.hasOwnProperty('rgba') || + czmlInterval.hasOwnProperty('rgbaf')) { + return Color; + } else if (czmlInterval.hasOwnProperty('colorBlendMode')) { + return ColorBlendMode; + } else if (czmlInterval.hasOwnProperty('cornerType')) { + return CornerType; + } else if (czmlInterval.hasOwnProperty('heightReference')) { + return HeightReference; + } else if (czmlInterval.hasOwnProperty('horizontalOrigin')) { + return HorizontalOrigin; + } else if (czmlInterval.hasOwnProperty('date')) { + return JulianDate; + } else if (czmlInterval.hasOwnProperty('labelStyle')) { + return LabelStyle; + } else if (czmlInterval.hasOwnProperty('number')) { + return Number; + } else if (czmlInterval.hasOwnProperty('nearFarScalar')) { + return NearFarScalar; + } else if (czmlInterval.hasOwnProperty('object') || + czmlInterval.hasOwnProperty('value')) { + return Object; + } else if (czmlInterval.hasOwnProperty('unitQuaternion')) { + return Quaternion; + } else if (czmlInterval.hasOwnProperty('shadowMode')) { + return ShadowMode; + } else if (czmlInterval.hasOwnProperty('string')) { + return String; + } else if (czmlInterval.hasOwnProperty('stripeOrientation')) { + return StripeOrientation; + } else if (czmlInterval.hasOwnProperty('wsen') || + czmlInterval.hasOwnProperty('wsenDegrees')) { + return Rectangle; + } else if (czmlInterval.hasOwnProperty('uri')) { + return Uri; + } else if (czmlInterval.hasOwnProperty('verticalOrigin')) { + return VerticalOrigin; } + // fallback case + return Object; + } - 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; + function unwrapInterval(type, czmlInterval, sourceUri, query) { + // The associations in this function need to be kept in sync with the + // associations in getPropertyType + 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 UnitCartesian3: + return unwrapUnitCartesianInterval(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, query); + 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 Object: + return defaultValue(defaultValue(czmlInterval.object, czmlInterval.value), czmlInterval); + case Quaternion: + return unwrapQuaternionInterval(czmlInterval); + case Rotation: + return defaultValue(czmlInterval.number, czmlInterval); + case ShadowMode: + return ShadowMode[defaultValue(defaultValue(czmlInterval.shadowMode, 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, query); + case VerticalOrigin: + return VerticalOrigin[defaultValue(czmlInterval.verticalOrigin, czmlInterval)]; + default: + throw new RuntimeError(type); + } + } - farValue = CesiumMath.clamp(farValue, 0.0, 1.0); - farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0; - var compressed1 = farValue; + var interpolators = { + HERMITE : HermitePolynomialApproximation, + LAGRANGE : LagrangePolynomialApproximation, + LINEAR : LinearApproximation + }; - var writer = vafWriters[attributeLocations.compressedAttribute1]; - writer(i, compressed0, compressed1, near, far); - } + function updateInterpolationSettings(packetData, property) { + var interpolationAlgorithm = packetData.interpolationAlgorithm; + if (defined(interpolationAlgorithm) || defined(packetData.interpolationDegree)) { + property.setInterpolationOptions({ + interpolationAlgorithm : interpolators[interpolationAlgorithm], + interpolationDegree : packetData.interpolationDegree + }); + } - 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 forwardExtrapolationType = packetData.forwardExtrapolationType; + if (defined(forwardExtrapolationType)) { + property.forwardExtrapolationType = ExtrapolationType[forwardExtrapolationType]; + } - var scale = pointPrimitive.scaleByDistance; - if (defined(scale)) { - near = scale.near; - nearValue = scale.nearValue; - far = scale.far; - farValue = scale.farValue; + var forwardExtrapolationDuration = packetData.forwardExtrapolationDuration; + if (defined(forwardExtrapolationDuration)) { + property.forwardExtrapolationDuration = forwardExtrapolationDuration; + } - 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; - } + var backwardExtrapolationType = packetData.backwardExtrapolationType; + if (defined(backwardExtrapolationType)) { + property.backwardExtrapolationType = ExtrapolationType[backwardExtrapolationType]; } - writer(i, near, nearValue, far, farValue); + var backwardExtrapolationDuration = packetData.backwardExtrapolationDuration; + if (defined(backwardExtrapolationDuration)) { + property.backwardExtrapolationDuration = backwardExtrapolationDuration; + } } - function writeDistanceDisplayConditionAndDepthDisable(pointPrimitiveCollection, context, vafWriters, pointPrimitive) { - var i = pointPrimitive._index; - var writer = vafWriters[attributeLocations.distanceDisplayConditionAndDisableDepth]; - var near = 0.0; - var far = Number.MAX_VALUE; + var iso8601Scratch = { + iso8601 : undefined + }; - var distanceDisplayCondition = pointPrimitive.distanceDisplayCondition; - if (defined(distanceDisplayCondition)) { - near = distanceDisplayCondition.near; - far = distanceDisplayCondition.far; + function processProperty(type, object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection, query) { + 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; + } - near *= near; - far *= far; + var packedLength; + var isSampled; + var unwrappedInterval; + var unwrappedIntervalLength; - pointPrimitiveCollection._shaderDistanceDisplayCondition = true; + // CZML properties can be defined in many ways. Most ways represent a structure for + // encoding a single value (number, string, cartesian, etc.) Regardless of the value type, + // if it encodes a single value it will get loaded into a ConstantProperty eventually. + // Alternatively, there are ways of defining a property that require specialized + // client-side representation. Currently, these are ReferenceProperty, + // and client-side velocity computation properties such as VelocityVectorProperty. + var isValue = !defined(packetData.reference) && !defined(packetData.velocityReference); + var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL); + + if (isValue) { + unwrappedInterval = unwrapInterval(type, packetData, sourceUri, query); + packedLength = defaultValue(type.packedLength, 1); + unwrappedIntervalLength = defaultValue(unwrappedInterval.length, 1); + isSampled = !defined(packetData.array) && (typeof unwrappedInterval !== 'string') && (unwrappedIntervalLength > packedLength) && (type !== Object); } - var disableDepthTestDistance = pointPrimitive.disableDepthTestDistance; - disableDepthTestDistance *= disableDepthTestDistance; - if (disableDepthTestDistance > 0.0) { - pointPrimitiveCollection._shaderDisableDepthDistance = true; - if (disableDepthTestDistance === Number.POSITIVE_INFINITY) { - disableDepthTestDistance = -1.0; + //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 (isValue) { + object[propertyName] = new ConstantProperty(needsUnpacking ? type.unpack(unwrappedInterval, 0) : unwrappedInterval); + } else { + object[propertyName] = createSpecializedProperty(type, entityCollection, packetData); } + return; } - writer(i, near, far, disableDepthTestDistance); - } + var property = object[propertyName]; - 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); - writeDistanceDisplayConditionAndDepthDisable(pointPrimitiveCollection, context, vafWriters, pointPrimitive); - } + var epoch; + var packetEpoch = packetData.epoch; + if (defined(packetEpoch)) { + epoch = JulianDate.fromIso8601(packetEpoch); + } - 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; + //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 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); + var interval; - if (recomputeBoundingVolume) { - positions.push(actualPosition); + //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 (isValue) { + combinedInterval.data = needsUnpacking ? type.unpack(unwrappedInterval, 0) : unwrappedInterval; + } else { + combinedInterval.data = createSpecializedProperty(type, entityCollection, packetData); + } + + //If no property exists, simply use a new interval collection + if (!defined(property)) { + if (isValue) { + property = new TimeIntervalCollectionProperty(); } else { - BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume); + property = new CompositeProperty(); } + object[propertyName] = property; } - } - - if (recomputeBoundingVolume) { - BoundingSphere.fromPoints(positions, boundingVolume); - } - } - function updateMode(pointPrimitiveCollection, frameState) { - var mode = frameState.mode; + if (isValue && 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. + if (isValue) { + combinedInterval.data = new ConstantProperty(combinedInterval.data); + } + property.intervals.addInterval(combinedInterval); + } else { + //Otherwise, create a CompositeProperty but preserve the existing data. - var pointPrimitives = pointPrimitiveCollection._pointPrimitives; - var pointPrimitivesToUpdate = pointPrimitiveCollection._pointPrimitivesToUpdate; - var modelMatrix = pointPrimitiveCollection._modelMatrix; + //Put the old property in an infinite interval. + interval = Iso8601.MAXIMUM_INTERVAL.clone(); + interval.data = property; - if (pointPrimitiveCollection._createVertexArray || - pointPrimitiveCollection._mode !== mode || - mode !== SceneMode.SCENE3D && - !Matrix4.equals(modelMatrix, pointPrimitiveCollection.modelMatrix)) { + //Create the composite. + property = new CompositeProperty(); + object[propertyName] = property; - pointPrimitiveCollection._mode = mode; - Matrix4.clone(pointPrimitiveCollection.modelMatrix, modelMatrix); - pointPrimitiveCollection._createVertexArray = true; + //add the old property interval + property.intervals.addInterval(interval); - if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) { - recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, pointPrimitives.length, frameState, modelMatrix, true); + //Change the new data to a ConstantProperty and add it. + if (isValue) { + combinedInterval.data = new ConstantProperty(combinedInterval.data); + } + property.intervals.addInterval(combinedInterval); } - } 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; - } + return; + } - var scratchWriterArray = []; + //isSampled && hasInterval + if (!defined(property)) { + property = new CompositeProperty(); + object[propertyName] = property; + } - /** - * @private - */ - PointPrimitiveCollection.prototype.update = function(frameState) { - removePointPrimitives(this); + //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; - this._maxTotalPointSize = ContextLimits.maximumAliasedPointSize; + //Create the composite. + property = new CompositeProperty(); + object[propertyName] = property; - updateMode(this, frameState); + //add the old property interval + property.intervals.addInterval(interval); + } - var pointPrimitives = this._pointPrimitives; - var pointPrimitivesLength = pointPrimitives.length; - var pointPrimitivesToUpdate = this._pointPrimitivesToUpdate; - var pointPrimitivesToUpdateLength = this._pointPrimitivesToUpdateIndex; + //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); + } - var properties = this._propertiesChanged; + function processPacketData(type, object, propertyName, packetData, interval, sourceUri, entityCollection, query) { + if (!defined(packetData)) { + return; + } - var createVertexArray = this._createVertexArray; + if (isArray(packetData)) { + for (var i = 0, len = packetData.length; i < len; i++) { + processProperty(type, object, propertyName, packetData[i], interval, sourceUri, entityCollection, query); + } + } else { + processProperty(type, object, propertyName, packetData, interval, sourceUri, entityCollection, query); + } + } - var vafWriters; - var context = frameState.context; - var pass = frameState.passes; - var picking = pass.pick; + function processPositionProperty(object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection, query) { + 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; + } - // PERFORMANCE_IDEA: Round robin multiple buffers. - if (createVertexArray || (!picking && this.computeNewBuffersUsage())) { - this._createVertexArray = false; + var referenceFrame; + var unwrappedInterval; + var isSampled = false; + var unwrappedIntervalLength; + var numberOfDerivatives = defined(packetData.cartesianVelocity) ? 1 : 0; + var packedLength = Cartesian3.packedLength * (numberOfDerivatives + 1); + var isValue = !defined(packetData.reference); + var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL); - for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) { - properties[k] = 0; + if (isValue) { + if (defined(packetData.referenceFrame)) { + referenceFrame = ReferenceFrame[packetData.referenceFrame]; } + referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED); + unwrappedInterval = unwrapCartesianInterval(packetData); + unwrappedIntervalLength = defaultValue(unwrappedInterval.length, 1); + isSampled = unwrappedIntervalLength > packedLength; + } - this._vaf = this._vaf && this._vaf.destroy(); + //Any time a constant value is assigned, it completely blows away anything else. + if (!isSampled && !hasInterval) { + if (isValue) { + object[propertyName] = new ConstantPositionProperty(Cartesian3.unpack(unwrappedInterval), referenceFrame); + } else { + object[propertyName] = createReferenceProperty(entityCollection, packetData.reference); + } + return; + } - 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; + var property = object[propertyName]; - // 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); - } + var epoch; + var packetEpoch = packetData.epoch; + if (defined(packetEpoch)) { + epoch = JulianDate.fromIso8601(packetEpoch); + } - this._vaf.commit(); + //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; + } - this._pointPrimitivesToUpdateIndex = 0; - } else { - // PointPrimitives were modified, but none were added or removed. - if (pointPrimitivesToUpdateLength > 0) { - var writers = scratchWriterArray; - writers.length = 0; + var interval; - if (properties[POSITION_INDEX] || properties[OUTLINE_WIDTH_INDEX] || properties[PIXEL_SIZE_INDEX]) { - writers.push(writePositionSizeAndOutline); - } + //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 (isValue) { + combinedInterval.data = Cartesian3.unpack(unwrappedInterval); + } else { + combinedInterval.data = createReferenceProperty(entityCollection, packetData.reference); + } - if (properties[COLOR_INDEX] || properties[OUTLINE_COLOR_INDEX]) { - writers.push(writeCompressedAttrib0); + //If no property exists, simply use a new interval collection + if (!defined(property)) { + if (isValue) { + property = new TimeIntervalCollectionPositionProperty(referenceFrame); + } else { + property = new CompositePositionProperty(referenceFrame); } + object[propertyName] = property; + } - if (properties[SHOW_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) { - writers.push(writeCompressedAttrib1); + if (isValue && 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. + if (isValue) { + combinedInterval.data = new ConstantPositionProperty(combinedInterval.data, referenceFrame); } + property.intervals.addInterval(combinedInterval); + } else { + //Otherwise, create a CompositePositionProperty but preserve the existing data. - if (properties[SCALE_BY_DISTANCE_INDEX]) { - writers.push(writeScaleByDistance); - } + //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); - if (properties[DISTANCE_DISPLAY_CONDITION_INDEX] || properties[DISABLE_DEPTH_DISTANCE_INDEX]) { - writers.push(writeDistanceDisplayConditionAndDepthDisable); + //Change the new data to a ConstantPositionProperty and add it. + if (isValue) { + combinedInterval.data = new ConstantPositionProperty(combinedInterval.data, referenceFrame); } + property.intervals.addInterval(combinedInterval); + } - var numWriters = writers.length; + return; + } - vafWriters = this._vaf.writers; + //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; - if ((pointPrimitivesToUpdateLength / pointPrimitivesLength) > 0.1) { - // If more than 10% of pointPrimitive change, rewrite the entire buffer. + //Create the composite. + property = new CompositePositionProperty(property.referenceFrame); + object[propertyName] = property; - // PERFORMANCE_IDEA: I totally made up 10% :). + //add the old property interval + property.intervals.addInterval(interval); + } - for (var m = 0; m < pointPrimitivesToUpdateLength; ++m) { - var b = pointPrimitivesToUpdate[m]; - b._dirty = false; + //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); + } - 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; + function processPositionPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection, query) { + if (!defined(packetData)) { + return; + } - for ( var o = 0; o < numWriters; ++o) { - writers[o](this, context, vafWriters, bb); - } - this._vaf.subCommit(bb._index, 1); - } - this._vaf.endSubCommits(); - } + if (isArray(packetData)) { + for (var i = 0, len = packetData.length; i < len; i++) { + processPositionProperty(object, propertyName, packetData[i], interval, sourceUri, entityCollection, query); + } + } else { + processPositionProperty(object, propertyName, packetData, interval, sourceUri, entityCollection, query); + } + } - this._pointPrimitivesToUpdateIndex = 0; + function processMaterialProperty(object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection, query) { + 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; } - // 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; + 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; } - if (!defined(this._vaf) || !defined(this._vaf.va)) { - return; + 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, query); + processPacketData(Number, existingMaterial, 'cellAlpha', materialData.cellAlpha, undefined, sourceUri, entityCollection, query); + processPacketData(Cartesian2, existingMaterial, 'lineCount', materialData.lineCount, undefined, sourceUri, entityCollection, query); + processPacketData(Cartesian2, existingMaterial, 'lineThickness', materialData.lineThickness, undefined, sourceUri, entityCollection, query); + processPacketData(Cartesian2, existingMaterial, 'lineOffset', materialData.lineOffset, undefined, sourceUri, entityCollection, query); + } else if (defined(packetData.image)) { + if (!(existingMaterial instanceof ImageMaterialProperty)) { + existingMaterial = new ImageMaterialProperty(); + } + materialData = packetData.image; + processPacketData(Image, existingMaterial, 'image', materialData.image, undefined, sourceUri, entityCollection, query); + processPacketData(Cartesian2, existingMaterial, 'repeat', materialData.repeat, undefined, sourceUri, entityCollection, query); + processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection, query); + processPacketData(Boolean, existingMaterial, 'transparent', materialData.transparent, undefined, sourceUri, entityCollection, query); + } else if (defined(packetData.stripe)) { + if (!(existingMaterial instanceof StripeMaterialProperty)) { + existingMaterial = new StripeMaterialProperty(); + } + materialData = packetData.stripe; + processPacketData(StripeOrientation, existingMaterial, 'orientation', materialData.orientation, undefined, sourceUri, entityCollection, query); + processPacketData(Color, existingMaterial, 'evenColor', materialData.evenColor, undefined, sourceUri, entityCollection, query); + processPacketData(Color, existingMaterial, 'oddColor', materialData.oddColor, undefined, sourceUri, entityCollection, query); + processPacketData(Number, existingMaterial, 'offset', materialData.offset, undefined, sourceUri, entityCollection, query); + processPacketData(Number, existingMaterial, 'repeat', materialData.repeat, undefined, sourceUri, entityCollection, query); + } else if (defined(packetData.polylineOutline)) { + if (!(existingMaterial instanceof PolylineOutlineMaterialProperty)) { + existingMaterial = new PolylineOutlineMaterialProperty(); + } + materialData = packetData.polylineOutline; + processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection, query); + processPacketData(Color, existingMaterial, 'outlineColor', materialData.outlineColor, undefined, sourceUri, entityCollection, query); + processPacketData(Number, existingMaterial, 'outlineWidth', materialData.outlineWidth, undefined, sourceUri, entityCollection, query); + } else if (defined(packetData.polylineGlow)) { + if (!(existingMaterial instanceof PolylineGlowMaterialProperty)) { + existingMaterial = new PolylineGlowMaterialProperty(); + } + materialData = packetData.polylineGlow; + processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection, query); + processPacketData(Number, existingMaterial, 'glowPower', materialData.glowPower, undefined, sourceUri, entityCollection, query); + } else if (defined(packetData.polylineArrow)) { + if (!(existingMaterial instanceof PolylineArrowMaterialProperty)) { + existingMaterial = new PolylineArrowMaterialProperty(); + } + materialData = packetData.polylineArrow; + processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection); + } else if (defined(packetData.polylineDash)) { + if (!(existingMaterial instanceof PolylineDashMaterialProperty)) { + existingMaterial = new PolylineDashMaterialProperty(); + } + materialData = packetData.polylineDash; + processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection); + processPacketData(Color, existingMaterial, 'gapColor', materialData.gapColor, undefined, undefined, entityCollection); + processPacketData(Number, existingMaterial, 'dashLength', materialData.dashLength, undefined, sourceUri, entityCollection, query); + processPacketData(Number, existingMaterial, 'dashPattern', materialData.dashPattern, undefined, sourceUri, entityCollection, query); } - if (this._boundingVolumeDirty) { - this._boundingVolumeDirty = false; - BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC); + if (defined(existingInterval)) { + existingInterval.data = existingMaterial; + } else { + object[propertyName] = existingMaterial; } + } - var boundingVolume; - var modelMatrix = Matrix4.IDENTITY; - if (frameState.mode === SceneMode.SCENE3D) { - modelMatrix = this.modelMatrix; - boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume); + function processMaterialPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection, query) { + 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, query); + } } else { - boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume); + processMaterialProperty(object, propertyName, packetData, interval, sourceUri, entityCollection, query); } - updateBoundingVolume(this, frameState, boundingVolume); + } - var blendOptionChanged = this._blendOption !== this.blendOption; - this._blendOption = this.blendOption; + function processName(entity, packet, entityCollection, sourceUri, query) { + entity.name = defaultValue(packet.name, entity.name); + } - if (blendOptionChanged) { - if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { - this._rsOpaque = RenderState.fromCache({ - depthTest : { - enabled : true, - func : WebGLConstants.LEQUAL - }, - depthMask : true - }); - } else { - this._rsOpaque = undefined; + function processDescription(entity, packet, entityCollection, sourceUri, query) { + var descriptionData = packet.description; + if (defined(descriptionData)) { + processPacketData(String, entity, 'description', descriptionData, undefined, sourceUri, entityCollection, query); + } + } + + function processPosition(entity, packet, entityCollection, sourceUri, query) { + var positionData = packet.position; + if (defined(positionData)) { + processPositionPacketData(entity, 'position', positionData, undefined, sourceUri, entityCollection, query); + } + } + + function processViewFrom(entity, packet, entityCollection, sourceUri, query) { + var viewFromData = packet.viewFrom; + if (defined(viewFromData)) { + processPacketData(Cartesian3, entity, 'viewFrom', viewFromData, undefined, sourceUri, entityCollection, query); + } + } + + function processOrientation(entity, packet, entityCollection, sourceUri, query) { + var orientationData = packet.orientation; + if (defined(orientationData)) { + processPacketData(Quaternion, entity, 'orientation', orientationData, undefined, sourceUri, entityCollection, query); + } + } + + function processProperties(entity, packet, entityCollection, sourceUri, query) { + var propertiesData = packet.properties; + if (defined(propertiesData)) { + if (!defined(entity.properties)) { + entity.properties = new PropertyBag(); } + //We cannot simply call processPacketData(entity, 'properties', propertyData, undefined, sourceUri, entityCollection) + //because each property of "properties" may vary separately. + //The properties will be accessible as entity.properties.myprop.getValue(time). - if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { - this._rsTranslucent = RenderState.fromCache({ - depthTest : { - enabled : true, - func : WebGLConstants.LEQUAL - }, - depthMask : false, - blending : BlendingState.ALPHA_BLEND - }); + for (var key in propertiesData) { + if (propertiesData.hasOwnProperty(key)) { + if (!entity.properties.hasProperty(key)) { + entity.properties.addProperty(key); + } + + var propertyData = propertiesData[key]; + if (isArray(propertyData)) { + for (var i = 0, len = propertyData.length; i < len; i++) { + processProperty(getPropertyType(propertyData[i]), entity.properties, key, propertyData[i], undefined, sourceUri, entityCollection, query); + } + } else { + processProperty(getPropertyType(propertyData), entity.properties, key, propertyData, undefined, sourceUri, entityCollection, query); + } + } + } + } + } + + function processArrayPacketData(object, propertyName, packetData, entityCollection) { + var references = packetData.references; + if (defined(references)) { + var properties = references.map(function(reference) { + return createReferenceProperty(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 { - this._rsTranslucent = undefined; + object[propertyName] = new PropertyArray(properties); } + } else { + processPacketData(Array, object, propertyName, packetData, undefined, undefined, entityCollection); } + } - this._shaderDisableDepthDistance = this._shaderDisableDepthDistance || frameState.minimumDisableDepthTestDistance !== 0.0; + function processArray(object, propertyName, packetData, entityCollection) { + if (!defined(packetData)) { + return; + } - if (blendOptionChanged || - (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) || - (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) || - (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition) || - (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistance)) { + 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); + } + } - vs = new ShaderSource({ - sources : [PointPrimitiveCollectionVS] + function processPositionsPacketData(object, propertyName, positionsData, entityCollection) { + if (defined(positionsData.references)) { + var properties = positionsData.references.map(function(reference) { + return createReferenceProperty(entityCollection, reference); }); - if (this._shaderScaleByDistance) { - vs.defines.push('EYE_DISTANCE_SCALING'); - } - if (this._shaderTranslucencyByDistance) { - vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); + + 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); } - if (this._shaderDistanceDisplayCondition) { - vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } 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 (this._shaderDisableDepthDistance) { - vs.defines.push('DISABLE_DEPTH_DISTANCE'); + + if (defined(positionsData.array)) { + processPacketData(Array, object, propertyName, positionsData, undefined, undefined, entityCollection); } + } + } - if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { - fs = new ShaderSource({ - defines : ['OPAQUE'], - sources : [PointPrimitiveCollectionFS] - }); - this._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); + function processPositions(object, propertyName, positionsData, entityCollection) { + if (!defined(positionsData)) { + return; + } - fs = new ShaderSource({ - defines : ['TRANSLUCENT'], - sources : [PointPrimitiveCollectionFS] - }); - this._spTranslucent = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spTranslucent, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); + 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); + } + } - if (this._blendOption === BlendOption.OPAQUE) { - fs = new ShaderSource({ - sources : [PointPrimitiveCollectionFS] - }); - this._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } + function processAvailability(entity, packet, entityCollection, sourceUri, query) { + var interval; + var packetData = packet.availability; + if (!defined(packetData)) { + return; + } - if (this._blendOption === BlendOption.TRANSLUCENT) { - fs = new ShaderSource({ - sources : [PointPrimitiveCollectionFS] - }); - this._spTranslucent = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spTranslucent, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); + 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; + } - this._compiledShaderScaleByDistance = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; - this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; - this._compiledShaderDisableDepthDistance = this._shaderDisableDepthDistance; + function processAlignedAxis(billboard, packetData, interval, sourceUri, entityCollection, query) { + if (!defined(packetData)) { + return; + } + + processPacketData(UnitCartesian3, billboard, 'alignedAxis', packetData, interval, sourceUri, entityCollection, query); + } + + function processBillboard(entity, packet, entityCollection, sourceUri, query) { + 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(); } - if (!defined(this._spPick) || - (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) || - (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) || - (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick) || - (this._shaderDisableDepthDistance !== this._compiledShaderDisableDepthDistancePick)) { - - vs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [PointPrimitiveCollectionVS] - }); + processPacketData(Boolean, billboard, 'show', billboardData.show, interval, sourceUri, entityCollection, query); + processPacketData(Image, billboard, 'image', billboardData.image, interval, sourceUri, entityCollection, query); + processPacketData(Number, billboard, 'scale', billboardData.scale, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian2, billboard, 'pixelOffset', billboardData.pixelOffset, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian3, billboard, 'eyeOffset', billboardData.eyeOffset, interval, sourceUri, entityCollection, query); + processPacketData(HorizontalOrigin, billboard, 'horizontalOrigin', billboardData.horizontalOrigin, interval, sourceUri, entityCollection, query); + processPacketData(VerticalOrigin, billboard, 'verticalOrigin', billboardData.verticalOrigin, interval, sourceUri, entityCollection, query); + processPacketData(HeightReference, billboard, 'heightReference', billboardData.heightReference, interval, sourceUri, entityCollection, query); + processPacketData(Color, billboard, 'color', billboardData.color, interval, sourceUri, entityCollection, query); + processPacketData(Rotation, billboard, 'rotation', billboardData.rotation, interval, sourceUri, entityCollection, query); + processAlignedAxis(billboard, billboardData.alignedAxis, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, billboard, 'sizeInMeters', billboardData.sizeInMeters, interval, sourceUri, entityCollection, query); + processPacketData(Number, billboard, 'width', billboardData.width, interval, sourceUri, entityCollection, query); + processPacketData(Number, billboard, 'height', billboardData.height, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, billboard, 'scaleByDistance', billboardData.scaleByDistance, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, billboard, 'translucencyByDistance', billboardData.translucencyByDistance, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, billboard, 'pixelOffsetScaleByDistance', billboardData.pixelOffsetScaleByDistance, interval, sourceUri, entityCollection, query); + processPacketData(BoundingRectangle, billboard, 'imageSubRegion', billboardData.imageSubRegion, interval, sourceUri, entityCollection, query); + } + + function processBox(entity, packet, entityCollection, sourceUri, query) { + var boxData = packet.box; + if (!defined(boxData)) { + return; + } - 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'); - } - if (this._shaderDisableDepthDistance) { - vs.defines.push('DISABLE_DEPTH_DISTANCE'); - } + var interval; + var intervalString = boxData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } - fs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [PointPrimitiveCollectionFS] - }); + var box = entity.box; + if (!defined(box)) { + entity.box = box = new BoxGraphics(); + } - this._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spPick, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); + processPacketData(Boolean, box, 'show', boxData.show, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian3, box, 'dimensions', boxData.dimensions, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, box, 'fill', boxData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(box, 'material', boxData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, box, 'outline', boxData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, box, 'outlineColor', boxData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, box, 'outlineWidth', boxData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, box, 'shadows', boxData.shadows, interval, sourceUri, entityCollection, query); + } - this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; - this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; - this._compiledShaderDisableDepthDistancePick = this._shaderDisableDepthDistance; + function processCorridor(entity, packet, entityCollection, sourceUri, query) { + var corridorData = packet.corridor; + if (!defined(corridorData)) { + return; } - var va; - var vaLength; - var command; - var j; - var vs; - var fs; + var interval; + var intervalString = corridorData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } - var commandList = frameState.commandList; + var corridor = entity.corridor; + if (!defined(corridor)) { + entity.corridor = corridor = new CorridorGraphics(); + } - if (pass.render) { - var colorList = this._colorCommands; + processPacketData(Boolean, corridor, 'show', corridorData.show, interval, sourceUri, entityCollection, query); + processPositions(corridor, 'positions', corridorData.positions, entityCollection); + processPacketData(Number, corridor, 'width', corridorData.width, interval, sourceUri, entityCollection, query); + processPacketData(Number, corridor, 'height', corridorData.height, interval, sourceUri, entityCollection, query); + processPacketData(Number, corridor, 'extrudedHeight', corridorData.extrudedHeight, interval, sourceUri, entityCollection, query); + processPacketData(CornerType, corridor, 'cornerType', corridorData.cornerType, interval, sourceUri, entityCollection, query); + processPacketData(Number, corridor, 'granularity', corridorData.granularity, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, corridor, 'fill', corridorData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(corridor, 'material', corridorData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, corridor, 'outline', corridorData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, corridor, 'outlineColor', corridorData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, corridor, 'outlineWidth', corridorData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, corridor, 'shadows', corridorData.shadows, interval, sourceUri, entityCollection, query); + } + + function processCylinder(entity, packet, entityCollection, sourceUri, query) { + var cylinderData = packet.cylinder; + if (!defined(cylinderData)) { + return; + } - var opaque = this._blendOption === BlendOption.OPAQUE; - var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT; + var interval; + var intervalString = cylinderData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } - va = this._vaf.va; - vaLength = va.length; + var cylinder = entity.cylinder; + if (!defined(cylinder)) { + entity.cylinder = cylinder = new CylinderGraphics(); + } - colorList.length = vaLength; - var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; - for (j = 0; j < totalLength; ++j) { - var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0); + processPacketData(Boolean, cylinder, 'show', cylinderData.show, interval, sourceUri, entityCollection, query); + processPacketData(Number, cylinder, 'length', cylinderData.length, interval, sourceUri, entityCollection, query); + processPacketData(Number, cylinder, 'topRadius', cylinderData.topRadius, interval, sourceUri, entityCollection, query); + processPacketData(Number, cylinder, 'bottomRadius', cylinderData.bottomRadius, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, cylinder, 'fill', cylinderData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(cylinder, 'material', cylinderData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, cylinder, 'outline', cylinderData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, cylinder, 'outlineColor', cylinderData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, cylinder, 'outlineWidth', cylinderData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(Number, cylinder, 'numberOfVerticalLines', cylinderData.numberOfVerticalLines, interval, sourceUri, entityCollection, query); + processPacketData(Number, cylinder, 'slices', cylinderData.slices, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, cylinder, 'shadows', cylinderData.shadows, interval, sourceUri, entityCollection, query); + } - command = colorList[j]; - if (!defined(command)) { - command = colorList[j] = new DrawCommand(); + 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; } + } + } - command.primitiveType = PrimitiveType.POINTS; - command.pass = opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT; - command.owner = this; + 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 index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j; - command.boundingVolume = boundingVolume; - command.modelMatrix = modelMatrix; - command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent; - command.uniformMap = this._uniforms; - command.vertexArray = va[index].va; - command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent; - command.debugShowBoundingVolume = this.debugShowBoundingVolume; + var documentPacket = dataSource._documentPacket; - commandList.push(command); + 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); } } + } - if (picking) { - var pickList = this._pickCommands; + function processEllipse(entity, packet, entityCollection, sourceUri, query) { + var ellipseData = packet.ellipse; + if (!defined(ellipseData)) { + return; + } - va = this._vaf.va; - vaLength = va.length; + var interval; + var intervalString = ellipseData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } - 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 - }); - } + var ellipse = entity.ellipse; + if (!defined(ellipse)) { + entity.ellipse = ellipse = new EllipseGraphics(); + } - command.boundingVolume = boundingVolume; - command.modelMatrix = modelMatrix; - command.shaderProgram = this._spPick; - command.uniformMap = this._uniforms; - command.vertexArray = va[j].va; - command.renderState = this._rsOpaque; + processPacketData(Boolean, ellipse, 'show', ellipseData.show, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'semiMajorAxis', ellipseData.semiMajorAxis, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'semiMinorAxis', ellipseData.semiMinorAxis, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'height', ellipseData.height, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'extrudedHeight', ellipseData.extrudedHeight, interval, sourceUri, entityCollection, query); + processPacketData(Rotation, ellipse, 'rotation', ellipseData.rotation, interval, sourceUri, entityCollection, query); + processPacketData(Rotation, ellipse, 'stRotation', ellipseData.stRotation, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'granularity', ellipseData.granularity, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, ellipse, 'fill', ellipseData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(ellipse, 'material', ellipseData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, ellipse, 'outline', ellipseData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, ellipse, 'outlineColor', ellipseData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'outlineWidth', ellipseData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipse, 'numberOfVerticalLines', ellipseData.numberOfVerticalLines, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, ellipse, 'shadows', ellipseData.shadows, interval, sourceUri, entityCollection, query); + } + + function processEllipsoid(entity, packet, entityCollection, sourceUri, query) { + var ellipsoidData = packet.ellipsoid; + if (!defined(ellipsoidData)) { + return; + } - commandList.push(command); - } + var interval; + var intervalString = ellipsoidData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); } - }; - /** - * 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; - }; + var ellipsoid = entity.ellipsoid; + if (!defined(ellipsoid)) { + entity.ellipsoid = ellipsoid = new EllipsoidGraphics(); + } - /** - * 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._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); - this._spPick = this._spPick && this._spPick.destroy(); - this._vaf = this._vaf && this._vaf.destroy(); - destroyPointPrimitives(this._pointPrimitives); + processPacketData(Boolean, ellipsoid, 'show', ellipsoidData.show, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian3, ellipsoid, 'radii', ellipsoidData.radii, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, ellipsoid, 'fill', ellipsoidData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(ellipsoid, 'material', ellipsoidData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, ellipsoid, 'outline', ellipsoidData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, ellipsoid, 'outlineColor', ellipsoidData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipsoid, 'outlineWidth', ellipsoidData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipsoid, 'stackPartitions', ellipsoidData.stackPartitions, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipsoid, 'slicePartitions', ellipsoidData.slicePartitions, interval, sourceUri, entityCollection, query); + processPacketData(Number, ellipsoid, 'subdivisions', ellipsoidData.subdivisions, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, ellipsoid, 'shadows', ellipsoidData.shadows, interval, sourceUri, entityCollection, query); + } - return destroyObject(this); - }; + function processLabel(entity, packet, entityCollection, sourceUri, query) { + var labelData = packet.label; + if (!defined(labelData)) { + return; + } - return PointPrimitiveCollection; -}); + var interval; + var intervalString = labelData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } -/*global define*/ -define('ThirdParty/kdbush',[], function() { -'use strict'; + var label = entity.label; + if (!defined(label)) { + entity.label = label = new LabelGraphics(); + } -function kdbush(points, getX, getY, nodeSize, ArrayType) { - return new KDBush(points, getX, getY, nodeSize, ArrayType); -} + processPacketData(Boolean, label, 'show', labelData.show, interval, sourceUri, entityCollection, query); + processPacketData(String, label, 'text', labelData.text, interval, sourceUri, entityCollection, query); + processPacketData(String, label, 'font', labelData.font, interval, sourceUri, entityCollection, query); + processPacketData(LabelStyle, label, 'style', labelData.style, interval, sourceUri, entityCollection, query); + processPacketData(Number, label, 'scale', labelData.scale, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, label, 'showBackground', labelData.showBackground, interval, sourceUri, entityCollection, query); + processPacketData(Color, label, 'backgroundColor', labelData.backgroundColor, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian2, label, 'backgroundPadding', labelData.backgroundPadding, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian2, label, 'pixelOffset', labelData.pixelOffset, interval, sourceUri, entityCollection, query); + processPacketData(Cartesian3, label, 'eyeOffset', labelData.eyeOffset, interval, sourceUri, entityCollection, query); + processPacketData(HorizontalOrigin, label, 'horizontalOrigin', labelData.horizontalOrigin, interval, sourceUri, entityCollection, query); + processPacketData(VerticalOrigin, label, 'verticalOrigin', labelData.verticalOrigin, interval, sourceUri, entityCollection, query); + processPacketData(HeightReference, label, 'heightReference', labelData.heightReference, interval, sourceUri, entityCollection, query); + processPacketData(Color, label, 'fillColor', labelData.fillColor, interval, sourceUri, entityCollection, query); + processPacketData(Color, label, 'outlineColor', labelData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, label, 'outlineWidth', labelData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, label, 'translucencyByDistance', labelData.translucencyByDistance, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, label, 'pixelOffsetScaleByDistance', labelData.pixelOffsetScaleByDistance, interval, sourceUri, entityCollection, query); + } + + function processModel(entity, packet, entityCollection, sourceUri, query) { + var modelData = packet.model; + if (!defined(modelData)) { + return; + } -function KDBush(points, getX, getY, nodeSize, ArrayType) { - getX = getX || defaultGetX; - getY = getY || defaultGetY; - ArrayType = ArrayType || Array; + var interval; + var intervalString = modelData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } - this.nodeSize = nodeSize || 64; - this.points = points; + var model = entity.model; + if (!defined(model)) { + entity.model = model = new ModelGraphics(); + } - this.ids = new ArrayType(points.length); - this.coords = new ArrayType(points.length * 2); + processPacketData(Boolean, model, 'show', modelData.show, interval, sourceUri, entityCollection, query); + processPacketData(Uri, model, 'uri', modelData.gltf, interval, sourceUri, entityCollection, query); + processPacketData(Number, model, 'scale', modelData.scale, interval, sourceUri, entityCollection, query); + processPacketData(Number, model, 'minimumPixelSize', modelData.minimumPixelSize, interval, sourceUri, entityCollection, query); + processPacketData(Number, model, 'maximumScale', modelData.maximumScale, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, model, 'incrementallyLoadTextures', modelData.incrementallyLoadTextures, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, model, 'runAnimations', modelData.runAnimations, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, model, 'shadows', modelData.shadows, interval, sourceUri, entityCollection, query); + processPacketData(HeightReference, model, 'heightReference', modelData.heightReference, interval, sourceUri, entityCollection, query); + processPacketData(Color, model, 'silhouetteColor', modelData.silhouetteColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, model, 'silhouetteSize', modelData.silhouetteSize, interval, sourceUri, entityCollection, query); + processPacketData(Color, model, 'color', modelData.color, interval, sourceUri, entityCollection, query); + processPacketData(ColorBlendMode, model, 'colorBlendMode', modelData.colorBlendMode, interval, sourceUri, entityCollection, query); + processPacketData(Number, model, 'colorBlendAmount', modelData.colorBlendAmount, interval, sourceUri, entityCollection, query); - 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]); + 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, query); + } + } else { + processNodeTransformations(model, nodeTransformationsData, interval, sourceUri, entityCollection, query); + } + } } - 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); - }, + function processNodeTransformations(model, nodeTransformationsData, constrainedInterval, sourceUri, entityCollection, query) { + 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; + } - within: function (x, y, r) { - return within(this.ids, this.coords, x, y, r, this.nodeSize); - } -}; + var nodeTransformations = model.nodeTransformations; + var nodeNames = Object.keys(nodeTransformationsData); + for (var i = 0, len = nodeNames.length; i < len; ++i) { + var nodeName = nodeNames[i]; -function defaultGetX(p) { return p[0]; } -function defaultGetY(p) { return p[1]; } + if (nodeName === 'interval') { + continue; + } -function range(ids, coords, minX, minY, maxX, maxY, nodeSize) { - var stack = [0, ids.length - 1, 0]; - var result = []; - var x, y; + var nodeTransformationData = nodeTransformationsData[nodeName]; - while (stack.length) { - var axis = stack.pop(); - var right = stack.pop(); - var left = stack.pop(); + if (!defined(nodeTransformationData)) { + continue; + } - 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]); + if (!defined(nodeTransformations)) { + model.nodeTransformations = nodeTransformations = new PropertyBag(); } - continue; - } - var m = Math.floor((left + right) / 2); + if (!nodeTransformations.hasProperty(nodeName)) { + nodeTransformations.addProperty(nodeName); + } - x = coords[2 * m]; - y = coords[2 * m + 1]; + var nodeTransformation = nodeTransformations[nodeName]; + if (!defined(nodeTransformation)) { + nodeTransformations[nodeName] = nodeTransformation = new NodeTransformationProperty(); + } - if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]); + processPacketData(Cartesian3, nodeTransformation, 'translation', nodeTransformationData.translation, combinedInterval, sourceUri, entityCollection, query); + processPacketData(Quaternion, nodeTransformation, 'rotation', nodeTransformationData.rotation, combinedInterval, sourceUri, entityCollection, query); + processPacketData(Cartesian3, nodeTransformation, 'scale', nodeTransformationData.scale, combinedInterval, sourceUri, entityCollection, query); + } + } - var nextAxis = (axis + 1) % 2; + function processPath(entity, packet, entityCollection, sourceUri, query) { + var pathData = packet.path; + if (!defined(pathData)) { + return; + } - if (axis === 0 ? minX <= x : minY <= y) { - stack.push(left); - stack.push(m - 1); - stack.push(nextAxis); + var interval; + var intervalString = pathData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); } - if (axis === 0 ? maxX >= x : maxY >= y) { - stack.push(m + 1); - stack.push(right); - stack.push(nextAxis); + + var path = entity.path; + if (!defined(path)) { + entity.path = path = new PathGraphics(); } - } - return result; -} + processPacketData(Boolean, path, 'show', pathData.show, interval, sourceUri, entityCollection, query); + processPacketData(Number, path, 'width', pathData.width, interval, sourceUri, entityCollection, query); + processPacketData(Number, path, 'resolution', pathData.resolution, interval, sourceUri, entityCollection, query); + processPacketData(Number, path, 'leadTime', pathData.leadTime, interval, sourceUri, entityCollection, query); + processPacketData(Number, path, 'trailTime', pathData.trailTime, interval, sourceUri, entityCollection, query); + processMaterialPacketData(path, 'material', pathData.material, interval, sourceUri, entityCollection, query); + } -function sort(ids, coords, nodeSize, left, right, depth) { - if (right - left <= nodeSize) return; + function processPoint(entity, packet, entityCollection, sourceUri, query) { + var pointData = packet.point; + if (!defined(pointData)) { + return; + } - var m = Math.floor((left + right) / 2); + var interval; + var intervalString = pointData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } - select(ids, coords, m, left, right, depth % 2); + var point = entity.point; + if (!defined(point)) { + entity.point = point = new PointGraphics(); + } - sort(ids, coords, nodeSize, left, m - 1, depth + 1); - sort(ids, coords, nodeSize, m + 1, right, depth + 1); -} + processPacketData(Boolean, point, 'show', pointData.show, interval, sourceUri, entityCollection, query); + processPacketData(Number, point, 'pixelSize', pointData.pixelSize, interval, sourceUri, entityCollection, query); + processPacketData(HeightReference, point, 'heightReference', pointData.heightReference, interval, sourceUri, entityCollection, query); + processPacketData(Color, point, 'color', pointData.color, interval, sourceUri, entityCollection, query); + processPacketData(Color, point, 'outlineColor', pointData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, point, 'outlineWidth', pointData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, point, 'scaleByDistance', pointData.scaleByDistance, interval, sourceUri, entityCollection, query); + processPacketData(NearFarScalar, point, 'translucencyByDistance', pointData.translucencyByDistance, interval, sourceUri, entityCollection, query); + } -function select(ids, coords, k, left, right, inc) { + function processPolygon(entity, packet, entityCollection, sourceUri, query) { + var polygonData = packet.polygon; + if (!defined(polygonData)) { + return; + } - 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 interval; + var intervalString = polygonData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); } - var t = coords[2 * k + inc]; - var i = left; - var j = right; + var polygon = entity.polygon; + if (!defined(polygon)) { + entity.polygon = polygon = new PolygonGraphics(); + } - swapItem(ids, coords, left, k); - if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right); + processPacketData(Boolean, polygon, 'show', polygonData.show, interval, sourceUri, entityCollection, query); + processPositions(polygon, 'hierarchy', polygonData.positions, entityCollection); + processPacketData(Number, polygon, 'height', polygonData.height, interval, sourceUri, entityCollection, query); + processPacketData(Number, polygon, 'extrudedHeight', polygonData.extrudedHeight, interval, sourceUri, entityCollection, query); + processPacketData(Rotation, polygon, 'stRotation', polygonData.stRotation, interval, sourceUri, entityCollection, query); + processPacketData(Number, polygon, 'granularity', polygonData.granularity, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, polygon, 'fill', polygonData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(polygon, 'material', polygonData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, polygon, 'outline', polygonData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, polygon, 'outlineColor', polygonData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, polygon, 'outlineWidth', polygonData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, polygon, 'perPositionHeight', polygonData.perPositionHeight, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, polygon, 'closeTop', polygonData.closeTop, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, polygon, 'closeBottom', polygonData.closeBottom, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, polygon, 'shadows', polygonData.shadows, interval, sourceUri, entityCollection, query); + } + + function processPolyline(entity, packet, entityCollection, sourceUri, query) { + var polylineData = packet.polyline; + if (!defined(polylineData)) { + return; + } - while (i < j) { - swapItem(ids, coords, i, j); - i++; - j--; - while (coords[2 * i + inc] < t) i++; - while (coords[2 * j + inc] > t) j--; + var interval; + var intervalString = polylineData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); } - if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j); - else { - j++; - swapItem(ids, coords, j, right); + var polyline = entity.polyline; + if (!defined(polyline)) { + entity.polyline = polyline = new PolylineGraphics(); } - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; + processPacketData(Boolean, polyline, 'show', polylineData.show, interval, sourceUri, entityCollection, query); + processPositions(polyline, 'positions', polylineData.positions, entityCollection); + processPacketData(Number, polyline, 'width', polylineData.width, interval, sourceUri, entityCollection, query); + processPacketData(Number, polyline, 'granularity', polylineData.granularity, interval, sourceUri, entityCollection, query); + processMaterialPacketData(polyline, 'material', polylineData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, polyline, 'followSurface', polylineData.followSurface, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, polyline, 'shadows', polylineData.shadows, interval, sourceUri, entityCollection, query); } -} -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 processRectangle(entity, packet, entityCollection, sourceUri, query) { + var rectangleData = packet.rectangle; + if (!defined(rectangleData)) { + return; + } -function swap(arr, i, j) { - var tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; -} + var interval; + var intervalString = rectangleData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); + } -function within(ids, coords, qx, qy, r, nodeSize) { - var stack = [0, ids.length - 1, 0]; - var result = []; - var r2 = r * r; + var rectangle = entity.rectangle; + if (!defined(rectangle)) { + entity.rectangle = rectangle = new RectangleGraphics(); + } - while (stack.length) { - var axis = stack.pop(); - var right = stack.pop(); - var left = stack.pop(); + processPacketData(Boolean, rectangle, 'show', rectangleData.show, interval, sourceUri, entityCollection, query); + processPacketData(Rectangle, rectangle, 'coordinates', rectangleData.coordinates, interval, sourceUri, entityCollection, query); + processPacketData(Number, rectangle, 'height', rectangleData.height, interval, sourceUri, entityCollection, query); + processPacketData(Number, rectangle, 'extrudedHeight', rectangleData.extrudedHeight, interval, sourceUri, entityCollection, query); + processPacketData(Rotation, rectangle, 'rotation', rectangleData.rotation, interval, sourceUri, entityCollection, query); + processPacketData(Rotation, rectangle, 'stRotation', rectangleData.stRotation, interval, sourceUri, entityCollection, query); + processPacketData(Number, rectangle, 'granularity', rectangleData.granularity, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, rectangle, 'fill', rectangleData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(rectangle, 'material', rectangleData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, rectangle, 'outline', rectangleData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, rectangle, 'outlineColor', rectangleData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, rectangle, 'outlineWidth', rectangleData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, rectangle, 'closeTop', rectangleData.closeTop, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, rectangle, 'closeBottom', rectangleData.closeBottom, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, rectangle, 'shadows', rectangleData.shadows, interval, sourceUri, entityCollection, query); + } + + function processWall(entity, packet, entityCollection, sourceUri, query) { + var wallData = packet.wall; + if (!defined(wallData)) { + return; + } - 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 interval; + var intervalString = wallData.interval; + if (defined(intervalString)) { + iso8601Scratch.iso8601 = intervalString; + interval = TimeInterval.fromIso8601(iso8601Scratch); } - var m = Math.floor((left + right) / 2); + var wall = entity.wall; + if (!defined(wall)) { + entity.wall = wall = new WallGraphics(); + } - var x = coords[2 * m]; - var y = coords[2 * m + 1]; + processPacketData(Boolean, wall, 'show', wallData.show, interval, sourceUri, entityCollection, query); + 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, query); + processPacketData(Boolean, wall, 'fill', wallData.fill, interval, sourceUri, entityCollection, query); + processMaterialPacketData(wall, 'material', wallData.material, interval, sourceUri, entityCollection, query); + processPacketData(Boolean, wall, 'outline', wallData.outline, interval, sourceUri, entityCollection, query); + processPacketData(Color, wall, 'outlineColor', wallData.outlineColor, interval, sourceUri, entityCollection, query); + processPacketData(Number, wall, 'outlineWidth', wallData.outlineWidth, interval, sourceUri, entityCollection, query); + processPacketData(ShadowMode, wall, 'shadows', wallData.shadows, interval, sourceUri, entityCollection, query); + } - if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]); + function processCzmlPacket(packet, entityCollection, updaterFunctions, sourceUri, dataSource, query) { + var objectId = packet.id; + if (!defined(objectId)) { + objectId = createGuid(); + } - var nextAxis = (axis + 1) % 2; + currentId = objectId; - 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); + if (!defined(dataSource._version) && objectId !== 'document') { + throw new RuntimeError('The first CZML packet is required to be the document object.'); } - } - - return result; -} -function sqDist(ax, ay, bx, by) { - var dx = ax - bx; - var dy = ay - by; - return dx * dx + dy * dy; -} + if (packet['delete'] === true) { + entityCollection.removeById(objectId); + } else if (objectId === 'document') { + processDocument(packet, dataSource); + } else { + var entity = entityCollection.getOrCreateEntity(objectId); -return kdbush; -}); + var parentId = packet.parent; + if (defined(parentId)) { + entity.parent = entityCollection.getOrCreateEntity(parentId); + } -/*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', - '../Scene/SceneMode', - '../ThirdParty/kdbush' - ], function( - BoundingRectangle, - Cartesian2, - Cartesian3, - defaultValue, - defined, - defineProperties, - EllipsoidalOccluder, - Event, - Matrix4, - Billboard, - BillboardCollection, - Label, - LabelCollection, - PointPrimitive, - PointPrimitiveCollection, - SceneMode, - kdbush) { - 'use strict'; + for (var i = updaterFunctions.length - 1; i > -1; i--) { + updaterFunctions[i](entity, packet, entityCollection, sourceUri, query); + } + } - /** - * 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); + currentId = undefined; + } - 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); + 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); - this._labelCollection = undefined; - this._billboardCollection = undefined; - this._pointCollection = undefined; + 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; + } - this._clusterBillboardCollection = undefined; - this._clusterLabelCollection = undefined; - this._clusterPointCollection = undefined; + 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; + } - this._collectionIndicesByEntity = {}; + if (!clock.equals(dataSource._clock)) { + dataSource._clock = clock.clone(dataSource._clock); + return true; + } - this._unusedLabelIndices = []; - this._unusedBillboardIndices = []; - this._unusedPointIndices = []; + return false; + } - this._previousClusters = []; - this._previousHeight = undefined; + function load(dataSource, czml, options, clear) { + + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var promise = czml; + var sourceUri = options.sourceUri; + var query = defined(options.query) ? objectToQuery(options.query) : undefined; - this._enabledDirty = false; - this._clusterDirty = false; + // If the czml is a URL + if (typeof czml === 'string') { + if (defined(query)) { + czml = joinUrls(czml, '?' + query, false); + } + promise = loadJson(czml); + sourceUri = defaultValue(sourceUri, czml); + } - this._cluster = undefined; - this._removeEventListener = undefined; + DataSource.setLoading(dataSource, true); - this._clusterEvent = new Event(); + return when(promise, function(czml) { + return loadCzml(dataSource, czml, sourceUri, clear, query); + }).otherwise(function(error) { + DataSource.setLoading(dataSource, false); + dataSource._error.raiseEvent(dataSource, error); + console.log(error); + return when.reject(error); + }); } - function getX(point) { - return point.coord.x; - } + function loadCzml(dataSource, czml, sourceUri, clear, query) { + DataSource.setLoading(dataSource, true); + var entityCollection = dataSource._entityCollection; - function getY(point) { - return point.coord.y; - } + if (clear) { + dataSource._version = undefined; + dataSource._documentPacket = new DocumentPacket(); + entityCollection.removeAll(); + } - function expandBoundingBox(bbox, pixelRange) { - bbox.x -= pixelRange; - bbox.y -= pixelRange; - bbox.width += pixelRange * 2.0; - bbox.height += pixelRange * 2.0; - } + CzmlDataSource._processCzml(czml, entityCollection, sourceUri, undefined, dataSource, query); - var labelBoundingBoxScratch = new BoundingRectangle(); + var raiseChangedEvent = updateClock(dataSource); - 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); + 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; } - 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); + DataSource.setLoading(dataSource, false); + if (raiseChangedEvent) { + dataSource._changed.raiseEvent(dataSource); } - return result; + return dataSource; } - function addNonClusteredItem(item, entityCluster) { - item.clusterShow = true; + function DocumentPacket() { + this.name = undefined; + this.clock = undefined; + } - 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; - } + /** + * 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(); } - function addCluster(position, numPoints, ids, entityCluster) { - var cluster = { - billboard : entityCluster._clusterBillboardCollection.add(), - label : entityCluster._clusterLabelCollection.add(), - point : entityCluster._clusterPointCollection.add() - }; + /** + * Creates a Promise to a new instance loaded with the provided CZML 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. + * @param {Object} [options.query] Key-value pairs which are appended to all URIs in the CZML. + * @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); + }; - 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; + 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; + } + }, - entityCluster._clusterEvent.raiseEvent(ids, cluster); - } + /** + * 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) { + this._entityCluster = value; + } + } + }); - function hasLabelIndex(entityCluster, entityId) { - return defined(entityCluster) && defined(entityCluster._collectionIndicesByEntity[entityId]) && defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex); - } + /** + * 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, // + processProperties, // + processRectangle, // + processPosition, // + processViewFrom, // + processWall, // + processOrientation, // + processAvailability]; - function getScreenSpacePositions(collection, points, scene, occluder, entityCluster) { - if (!defined(collection)) { - return; - } + /** + * 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. + * @param {Object} [options.query] Key-value pairs which are appended to all URIs in the CZML. + * @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); + }; - var length = collection.length; - for (var i = 0; i < length; ++i) { - var item = collection.get(i); - item.clusterShow = 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. + * @param {Object} [options.query] Key-value pairs which are appended to all URIs in the CZML. + * @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); + }; - if (!item.show || (entityCluster._scene.mode === SceneMode.SCENE3D && !occluder.isPointVisible(item.position))) { - continue; - } + /** + * 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; - 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; - } + CzmlDataSource._processCzml = function(czml, entityCollection, sourceUri, updaterFunctions, dataSource, query) { + updaterFunctions = defined(updaterFunctions) ? updaterFunctions : CzmlDataSource.updaters; - var coord = item.computeScreenSpacePosition(scene); - if (!defined(coord)) { - continue; + if (isArray(czml)) { + for (var i = 0, len = czml.length; i < len; i++) { + processCzmlPacket(czml[i], entityCollection, updaterFunctions, sourceUri, dataSource, query); } - - points.push({ - index : i, - collection : collection, - clustered : false, - coord : coord - }); + } else { + processCzmlPacket(czml, entityCollection, updaterFunctions, sourceUri, dataSource, query); } - } - - 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; - } + return CzmlDataSource; +}); - var scene = entityCluster._scene; +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'; - var labelCollection = entityCluster._labelCollection; - var billboardCollection = entityCluster._billboardCollection; - var pointCollection = entityCluster._pointCollection; + /** + * A collection of {@link DataSource} instances. + * @alias DataSourceCollection + * @constructor + */ + function DataSourceCollection() { + this._dataSources = []; + this._dataSourceAdded = new Event(); + this._dataSourceRemoved = new Event(); + } - if ((!defined(labelCollection) && !defined(billboardCollection) && !defined(pointCollection)) || - (!entityCluster._clusterBillboards && !entityCluster._clusterLabels && !entityCluster._clusterPoints)) { - return; + 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; } + }, - 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 - }); + /** + * 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; } + }, - if (defined(clusteredBillboardCollection)) { - clusteredBillboardCollection.removeAll(); - } else { - clusteredBillboardCollection = entityCluster._clusterBillboardCollection = new BillboardCollection({ - scene : scene - }); + /** + * 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; } + } + }); - if (defined(clusteredPointCollection)) { - clusteredPointCollection.removeAll(); - } else { - clusteredPointCollection = entityCluster._clusterPointCollection = new PointPrimitiveCollection(); + /** + * 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) { + + 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; + }); + }; - var pixelRange = entityCluster._pixelRange; - var minimumClusterSize = entityCluster._minimumClusterSize; - - var clusters = entityCluster._previousClusters; - var newClusters = []; - - var previousHeight = entityCluster._previousHeight; - var currentHeight = scene.camera.positionCartographic.height; + /** + * 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 ellipsoid = scene.mapProjection.ellipsoid; - var cameraPosition = scene.camera.positionWC; - var occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition); + var index = this._dataSources.indexOf(dataSource); + if (index !== -1) { + this._dataSources.splice(index, 1); + this._dataSourceRemoved.raiseEvent(this, dataSource); - 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); + if (destroy && typeof dataSource.destroy === 'function') { + dataSource.destroy(); } - 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 = []; + return true; + } - for (j = 0; j < neighborLength; ++j) { - neighborIndex = neighbors[j]; - neighborPoint = points[neighborIndex]; - if (!neighborPoint.clustered) { - ++numPoints; + return false; + }; - collection = neighborPoint.collection; - collectionIndex = neighborPoint.index; - ids.push(collection.get(collectionIndex).id); - } - } + /** + * 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); - if (numPoints >= minimumClusterSize) { - addCluster(cluster.position, numPoints, ids, entityCluster); - newClusters.push(cluster); + var dataSources = this._dataSources; + for (var i = 0, len = dataSources.length; i < len; ++i) { + var dataSource = dataSources[i]; + this._dataSourceRemoved.raiseEvent(this, dataSource); - for (j = 0; j < neighborLength; ++j) { - points[neighbors[j]].clustered = true; - } - } - } + if (destroy && typeof dataSource.destroy === 'function') { + dataSource.destroy(); } + } + this._dataSources = []; + }; - length = points.length; - for (i = 0; i < length; ++i) { - var point = points[i]; - if (point.clustered) { - continue; - } + /** + * 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; + }; - point.clustered = true; + /** + * 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); + }; - collection = point.collection; - collectionIndex = point.index; + /** + * Gets a data source by index from the collection. + * + * @param {Number} index the index to retrieve. + * @returns {DataSource} The data source at the specified index. + */ + DataSourceCollection.prototype.get = function(index) { + + return this._dataSources[index]; + }; - var item = collection.get(collectionIndex); - bbox = getBoundingBox(item, point.coord, pixelRange, entityCluster, pointBoundinRectangleScratch); - var totalBBox = BoundingRectangle.clone(bbox, totalBoundingRectangleScratch); + /** + * 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; + }; - neighbors = index.range(bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height); - neighborLength = neighbors.length; + /** + * 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); + }; - var clusterPosition = Cartesian3.clone(item.position); - numPoints = 1; - ids = [item.id]; + return DataSourceCollection; +}); - 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); +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'; - Cartesian3.add(neighborItem.position, clusterPosition, clusterPosition); + 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(); - BoundingRectangle.union(totalBBox, neighborBBox, totalBBox); - ++numPoints; + 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; + } - ids.push(neighborItem.id); - } - } + /** + * 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) { + + 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); - 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 - }); + this._onEntityPropertyChanged(entity, 'ellipse', entity.ellipse, undefined); + } - for (j = 0; j < neighborLength; ++j) { - points[neighbors[j]].clustered = true; - } - } else { - addNonClusteredItem(item, entityCluster); - } - } + 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 + } + }); - if (clusteredLabelCollection.length === 0) { - clusteredLabelCollection.destroy(); - entityCluster._clusterLabelCollection = undefined; + defineProperties(EllipseGeometryUpdater.prototype, { + /** + * Gets the entity associated with this geometry. + * @memberof EllipseGeometryUpdater.prototype + * + * @type {Entity} + * @readonly + */ + entity : { + get : function() { + return this._entity; } - - if (clusteredBillboardCollection.length === 0) { - clusteredBillboardCollection.destroy(); - entityCluster._clusterBillboardCollection = undefined; + }, + /** + * Gets a value indicating if the geometry has a fill component. + * @memberof EllipseGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + fillEnabled : { + get : function() { + return this._fillEnabled; } - - if (clusteredPointCollection.length === 0) { - clusteredPointCollection.destroy(); - entityCluster._clusterPointCollection = undefined; + }, + /** + * 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)); } - - 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 + * 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 */ - enabled : { + outlineEnabled : { get : function() { - return this._enabled; - }, - set : function(value) { - this._enabledDirty = value !== this._enabled; - this._enabled = value; + return this._outlineEnabled; } }, /** - * Gets or sets the pixel range to extend the screen space bounding box. - * @memberof EntityCluster.prototype - * @type {Number} + * Gets a value indicating if outline visibility varies with simulation time. + * @memberof EllipseGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly */ - pixelRange : { + hasConstantOutline : { get : function() { - return this._pixelRange; - }, - set : function(value) { - this._clusterDirty = this._clusterDirty || value !== this._pixelRange; - this._pixelRange = value; + return !this._outlineEnabled || + (!defined(this._entity.availability) && + Property.isConstant(this._showProperty) && + Property.isConstant(this._showOutlineProperty)); } }, /** - * Gets or sets the minimum number of screen space objects that can be clustered. - * @memberof EntityCluster.prototype + * 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 */ - minimumClusterSize : { + outlineWidth : { get : function() { - return this._minimumClusterSize; - }, - set : function(value) { - this._clusterDirty = this._clusterDirty || value !== this._minimumClusterSize; - this._minimumClusterSize = value; + return this._outlineWidth; } }, /** - * 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} + * Gets the property specifying whether the geometry + * casts or receives shadows from each light source. + * @memberof EllipseGeometryUpdater.prototype + * + * @type {Property} + * @readonly */ - clusterEvent : { + shadowsProperty : { get : function() { - return this._clusterEvent; + return this._shadowsProperty; } }, /** - * Gets or sets whether clustering billboard entities is enabled. - * @memberof EntityCluster.prototype - * @type {Boolean} + * 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 */ - clusterBillboards : { + distanceDisplayConditionProperty : { get : function() { - return this._clusterBillboards; - }, - set : function(value) { - this._clusterDirty = this._clusterDirty || value !== this._clusterBillboards; - this._clusterBillboards = value; + return this._distanceDisplayConditionProperty; } }, /** - * Gets or sets whether clustering labels entities is enabled. - * @memberof EntityCluster.prototype + * 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 */ - clusterLabels : { + isDynamic : { get : function() { - return this._clusterLabels; - }, - set : function(value) { - this._clusterDirty = this._clusterDirty || value !== this._clusterLabels; - this._clusterLabels = value; + return this._dynamic; } }, /** - * Gets or sets whether clustering point entities is enabled. - * @memberof EntityCluster.prototype + * Gets a value indicating if the geometry is closed. + * This property is only valid for static geometry. + * @memberof EllipseGeometryUpdater.prototype + * * @type {Boolean} + * @readonly */ - clusterPoints : { + isClosed : { 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]); + return this._isClosed; } - - if (!defined(collection)) { - collection = this[collectionProperty] = new CollectionConstructor({ - scene : this._scene - }); + }, + /** + * Gets a value indicating if the geometry should be drawn on terrain. + * @memberof EllipseGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + onTerrain : { + get : function() { + return this._onTerrain; } - - 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; + }, + /** + * 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; } - - 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. + * Checks if the geometry is outlined at the provided time. * - * @private + * @param {JulianDate} time The time for which to retrieve visibility. + * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise. */ - EntityCluster.prototype.getLabel = createGetEntity('_labelCollection', LabelCollection, '_unusedLabelIndices', 'labelIndex'); - + EllipseGeometryUpdater.prototype.isOutlineVisible = function(time) { + var entity = this._entity; + return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); + }; /** - * 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. + * Checks if the geometry is filled at the provided time. * - * @private + * @param {JulianDate} time The time for which to retrieve visibility. + * @returns {Boolean} true if geometry is filled at the provided time, false otherwise. */ - 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; + EllipseGeometryUpdater.prototype.isFilled = function(time) { + var entity = this._entity; + return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time); }; /** - * 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. + * Creates the geometry instance which represents the fill of the geometry. * - * @private + * @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. */ - EntityCluster.prototype.getBillboard = createGetEntity('_billboardCollection', BillboardCollection, '_unusedBillboardIndices', 'billboardIndex'); + EllipseGeometryUpdater.prototype.createFillGeometryInstance = function(time) { + + 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 + }); + }; /** - * 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. + * Creates the geometry instance which represents the outline of the geometry. * - * @private + * @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. */ - 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); + EllipseGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - this._clusterDirty = true; + 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 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. + * Returns true if this object was destroyed; otherwise, false. * - * @private + * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - EntityCluster.prototype.getPoint = createGetEntity('_pointCollection', PointPrimitiveCollection, '_unusedPointIndices', 'pointIndex'); + EllipseGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; /** - * 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. + * Destroys and resources used by the object. Once an object is destroyed, it should not be used. * - * @private + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ - 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; + EllipseGeometryUpdater.prototype.destroy = function() { + this._entitySubscription(); + destroyObject(this); }; - function disableCollectionClustering(collection) { - if (!defined(collection)) { + EllipseGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'ellipse')) { return; } - var length = collection.length; - for (var i = 0; i < length; ++i) { - collection.get(i).clusterShow = true; - } - } + var ellipse = this._entity.ellipse; - function updateEnable(entityCluster) { - if (entityCluster.enabled) { + if (!defined(ellipse)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } return; } - if (defined(entityCluster._clusterLabelCollection)) { - entityCluster._clusterLabelCollection.destroy(); - } - if (defined(entityCluster._clusterBillboardCollection)) { - entityCluster._clusterBillboardCollection.destroy(); - } - if (defined(entityCluster._clusterPointCollection)) { - entityCluster._clusterPointCollection.destroy(); + 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); } - entityCluster._clusterLabelCollection = undefined; - entityCluster._clusterBillboardCollection = undefined; - entityCluster._clusterPointCollection = undefined; + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } - disableCollectionClustering(entityCluster._labelCollection); - disableCollectionClustering(entityCluster._billboardCollection); - disableCollectionClustering(entityCluster._pointCollection); - } + var position = this._entity.position; + var semiMajorAxis = ellipse.semiMajorAxis; + var semiMinorAxis = ellipse.semiMinorAxis; - /** - * 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. - var commandList; - if (defined(this._labelCollection) && this._labelCollection.length > 0 && this._labelCollection.get(0)._glyphs.length === 0) { - commandList = frameState.commandList; - frameState.commandList = []; - this._labelCollection.update(frameState); - frameState.commandList = commandList; + 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; } - // If clustering is enabled before the billboard collection is updated, - // the images haven't been added to the image atlas so the screen space bounding boxes - // are incorrect. - if (defined(this._billboardCollection) && this._billboardCollection.length > 0 && !defined(this._billboardCollection.get(0).width)) { - commandList = frameState.commandList; - frameState.commandList = []; - this._billboardCollection.update(frameState); - frameState.commandList = commandList; - } + 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); - if (this._enabledDirty) { - this._enabledDirty = false; - updateEnable(this); - this._clusterDirty = true; - } + 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 (this._clusterDirty) { - this._clusterDirty = false; - this._cluster(); + if (outlineEnabled && onTerrain) { + oneTimeWarning(oneTimeWarning.geometryOutlines); + outlineEnabled = false; } - if (defined(this._clusterLabelCollection)) { - this._clusterLabelCollection.update(frameState); - } - if (defined(this._clusterBillboardCollection)) { - this._clusterBillboardCollection.update(frameState); - } - if (defined(this._clusterPointCollection)) { - this._clusterPointCollection.update(frameState); - } + this._fillEnabled = fillEnabled; + this._onTerrain = onTerrain; + this._isClosed = defined(extrudedHeight) || onTerrain; + this._outlineEnabled = outlineEnabled; - if (defined(this._labelCollection)) { - this._labelCollection.update(frameState); - } - if (defined(this._billboardCollection)) { - this._billboardCollection.update(frameState); - } - if (defined(this._pointCollection)) { - this._pointCollection.update(frameState); + 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); } }; /** - * 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. - *

    + * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true. * - * @returns {undefined} + * @param {PrimitiveCollection} primitives The primitive collection to use. + * @param {PrimitiveCollection} groundPrimitives The ground primitives collection to use. + * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame. + * + * @exception {DeveloperError} This instance does not represent dynamic geometry. */ - 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(); + EllipseGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) { + + return new DynamicGeometryUpdater(primitives, groundPrimitives, this); + }; - this._clusterLabelCollection = this._clusterLabelCollection && this._clusterLabelCollection.destroy(); - this._clusterBillboardCollection = this._clusterBillboardCollection && this._clusterBillboardCollection.destroy(); - this._clusterPointCollection = this._clusterPointCollection && this._clusterPointCollection.destroy(); + /** + * @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) { + + var geometryUpdater = this._geometryUpdater; + var onTerrain = geometryUpdater._onTerrain; - if (defined(this._removeEventListener)) { - this._removeEventListener(); - this._removeEventListener = undefined; + 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; - this._labelCollection = undefined; - this._billboardCollection = undefined; - this._pointCollection = undefined; - this._clusterBillboardCollection = undefined; - this._clusterLabelCollection = undefined; - this._clusterPointCollection = undefined; + var entity = geometryUpdater._entity; + var ellipse = entity.ellipse; + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipse.show, time, true)) { + return; + } - this._collectionIndicesByEntity = undefined; + 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; + } - this._unusedLabelIndices = []; - this._unusedBillboardIndices = []; - this._unusedPointIndices = []; + 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); - this._previousClusters = []; - this._previousHeight = undefined; + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - this._enabledDirty = false; - this._pixelRangeDirty = false; - this._minimumClusterSizeDirty = false; + var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty; + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time); + var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition); - return undefined; - }; + if (Property.getValueOrDefault(ellipse.fill, time, true)) { + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); + this._material = material; - /** - * 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(); - * }); - */ + if (onTerrain) { + var currentColor = Color.WHITE; + if (defined(fillMaterialProperty.color)) { + currentColor = fillMaterialProperty.color.getValue(time); + } - return EntityCluster; -}); + 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; -/*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'; + this._primitive = primitives.add(new Primitive({ + geometryInstances : new GeometryInstance({ + id : entity, + geometry : new EllipseGeometry(options) + }), + attributes : { + distanceDisplayCondition : distanceDisplayConditionAttribute + }, + appearance : appearance, + asynchronous : false, + shadows : shadows + })); + } + } - /** - * 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(); - } + if (!onTerrain && Property.getValueOrDefault(ellipse.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - 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; - } - }, + 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; - /** - * 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) { - this._entityCluster = value; - } + 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 + })); } - }); + }; - return CustomDataSource; + 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/CylinderGeometryUpdater',[ +define('DataSources/EllipsoidGeometryUpdater',[ + '../Core/Cartesian3', '../Core/Color', '../Core/ColorGeometryInstanceAttribute', - '../Core/CylinderGeometry', - '../Core/CylinderOutlineGeometry', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -113636,13 +124505,17 @@ define('DataSources/CylinderGeometryUpdater',[ '../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', @@ -113650,10 +124523,9 @@ define('DataSources/CylinderGeometryUpdater',[ './MaterialProperty', './Property' ], function( + Cartesian3, Color, ColorGeometryInstanceAttribute, - CylinderGeometry, - CylinderOutlineGeometry, defaultValue, defined, defineProperties, @@ -113661,13 +124533,17 @@ define('DataSources/CylinderGeometryUpdater',[ DeveloperError, DistanceDisplayCondition, DistanceDisplayConditionGeometryInstanceAttribute, + EllipsoidGeometry, + EllipsoidOutlineGeometry, Event, GeometryInstance, Iso8601, + Matrix4, ShowGeometryInstanceAttribute, MaterialAppearance, PerInstanceColorAppearance, Primitive, + SceneMode, ShadowMode, ColorMaterialProperty, ConstantProperty, @@ -113684,32 +124560,33 @@ define('DataSources/CylinderGeometryUpdater',[ 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.length = undefined; - this.topRadius = undefined; - this.bottomRadius = undefined; - this.slices = undefined; - this.numberOfVerticalLines = undefined; + this.radii = undefined; + this.stackPartitions = undefined; + this.slicePartitions = undefined; + this.subdivisions = undefined; } /** - * A {@link GeometryUpdater} for cylinders. + * A {@link GeometryUpdater} for ellipsoids. * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}. - * @alias CylinderGeometryUpdater + * @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 CylinderGeometryUpdater(entity, scene) { + function EllipsoidGeometryUpdater(entity, scene) { - this._entity = entity; this._scene = scene; - this._entitySubscription = entity.definitionChanged.addEventListener(CylinderGeometryUpdater.prototype._onEntityPropertyChanged, this); + this._entity = entity; + this._entitySubscription = entity.definitionChanged.addEventListener(EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged, this); this._fillEnabled = false; this._dynamic = false; this._outlineEnabled = false; @@ -113723,13 +124600,13 @@ define('DataSources/CylinderGeometryUpdater',[ this._shadowsProperty = undefined; this._distanceDisplayConditionProperty = undefined; this._options = new GeometryOptions(entity); - this._onEntityPropertyChanged(entity, 'cylinder', entity.cylinder, undefined); + this._onEntityPropertyChanged(entity, 'ellipsoid', entity.ellipsoid, undefined); } - defineProperties(CylinderGeometryUpdater, { + defineProperties(EllipsoidGeometryUpdater, { /** * Gets the type of Appearance to use for simple color-based geometry. - * @memberof CylinderGeometryUpdater + * @memberof EllipsoidGeometryUpdater * @type {Appearance} */ perInstanceColorAppearanceType : { @@ -113737,7 +124614,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets the type of Appearance to use for material-based geometry. - * @memberof CylinderGeometryUpdater + * @memberof EllipsoidGeometryUpdater * @type {Appearance} */ materialAppearanceType : { @@ -113745,10 +124622,10 @@ define('DataSources/CylinderGeometryUpdater',[ } }); - defineProperties(CylinderGeometryUpdater.prototype, { + defineProperties(EllipsoidGeometryUpdater.prototype, { /** * Gets the entity associated with this geometry. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Entity} * @readonly @@ -113760,7 +124637,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets a value indicating if the geometry has a fill component. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113772,7 +124649,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets a value indicating if fill visibility varies with simulation time. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113787,7 +124664,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets the material property used to fill the geometry. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {MaterialProperty} * @readonly @@ -113799,7 +124676,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets a value indicating if the geometry has an outline component. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113811,7 +124688,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets a value indicating if outline visibility varies with simulation time. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113826,7 +124703,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets the {@link Color} property for the geometry outline. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Property} * @readonly @@ -113839,7 +124716,7 @@ define('DataSources/CylinderGeometryUpdater',[ /** * Gets the constant with of the geometry outline, in pixels. * This value is only valid if isDynamic is false. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Number} * @readonly @@ -113852,8 +124729,8 @@ define('DataSources/CylinderGeometryUpdater',[ /** * Gets the property specifying whether the geometry * casts or receives shadows from each light source. - * @memberof CylinderGeometryUpdater.prototype - * + * @memberof EllipsoidGeometryUpdater.prototype + * * @type {Property} * @readonly */ @@ -113864,7 +124741,7 @@ define('DataSources/CylinderGeometryUpdater',[ }, /** * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Property} * @readonly @@ -113878,7 +124755,7 @@ define('DataSources/CylinderGeometryUpdater',[ * 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 + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113891,7 +124768,7 @@ define('DataSources/CylinderGeometryUpdater',[ /** * Gets a value indicating if the geometry is closed. * This property is only valid for static geometry. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113902,7 +124779,7 @@ define('DataSources/CylinderGeometryUpdater',[ /** * Gets an event that is raised whenever the public properties * of this updater change. - * @memberof CylinderGeometryUpdater.prototype + * @memberof EllipsoidGeometryUpdater.prototype * * @type {Boolean} * @readonly @@ -113920,7 +124797,7 @@ define('DataSources/CylinderGeometryUpdater',[ * @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) { + EllipsoidGeometryUpdater.prototype.isOutlineVisible = function(time) { var entity = this._entity; return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); }; @@ -113931,7 +124808,7 @@ define('DataSources/CylinderGeometryUpdater',[ * @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) { + EllipsoidGeometryUpdater.prototype.isFilled = function(time) { var entity = this._entity; return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time); }; @@ -113944,7 +124821,7 @@ define('DataSources/CylinderGeometryUpdater',[ * * @exception {DeveloperError} This instance does not represent a filled geometry. */ - CylinderGeometryUpdater.prototype.createFillGeometryInstance = function(time) { + EllipsoidGeometryUpdater.prototype.createFillGeometryInstance = function(time) { var entity = this._entity; var isAvailable = entity.isAvailable(time); @@ -113975,8 +124852,8 @@ define('DataSources/CylinderGeometryUpdater',[ return new GeometryInstance({ id : entity, - geometry : new CylinderGeometry(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), + geometry : new EllipsoidGeometry(this._options), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : attributes }); }; @@ -113989,17 +124866,18 @@ define('DataSources/CylinderGeometryUpdater',[ * * @exception {DeveloperError} This instance does not represent an outlined geometry. */ - CylinderGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { + EllipsoidGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { 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), + geometry : new EllipsoidOutlineGeometry(this._options), + modelMatrix : entity.computeModelMatrix(Iso8601.MINIMUM_VALUE), attributes : { show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)), color : ColorGeometryInstanceAttribute.fromColor(outlineColor), @@ -114013,7 +124891,7 @@ define('DataSources/CylinderGeometryUpdater',[ * * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - CylinderGeometryUpdater.prototype.isDestroyed = function() { + EllipsoidGeometryUpdater.prototype.isDestroyed = function() { return false; }; @@ -114022,19 +124900,19 @@ define('DataSources/CylinderGeometryUpdater',[ * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ - CylinderGeometryUpdater.prototype.destroy = function() { + EllipsoidGeometryUpdater.prototype.destroy = function() { this._entitySubscription(); destroyObject(this); }; - CylinderGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { - if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'cylinder')) { + EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'ellipsoid')) { return; } - var cylinder = entity.cylinder; + var ellipsoid = entity.ellipsoid; - if (!defined(cylinder)) { + if (!defined(ellipsoid)) { if (this._fillEnabled || this._outlineEnabled) { this._fillEnabled = false; this._outlineEnabled = false; @@ -114043,10 +124921,10 @@ define('DataSources/CylinderGeometryUpdater',[ return; } - var fillProperty = cylinder.fill; + var fillProperty = ellipsoid.fill; var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - var outlineProperty = cylinder.outline; + var outlineProperty = ellipsoid.outline; var outlineEnabled = defined(outlineProperty); if (outlineEnabled && outlineProperty.isConstant) { outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); @@ -114062,13 +124940,11 @@ define('DataSources/CylinderGeometryUpdater',[ } var position = entity.position; - var length = cylinder.length; - var topRadius = cylinder.topRadius; - var bottomRadius = cylinder.bottomRadius; + var radii = ellipsoid.radii; - var show = cylinder.show; + var show = ellipsoid.show; if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || // - (!defined(position) || !defined(length) || !defined(topRadius) || !defined(bottomRadius))) { + (!defined(position) || !defined(radii))) { if (this._fillEnabled || this._outlineEnabled) { this._fillEnabled = false; this._outlineEnabled = false; @@ -114077,31 +124953,31 @@ define('DataSources/CylinderGeometryUpdater',[ return; } - var material = defaultValue(cylinder.material, defaultMaterial); + 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(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._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) || // - !length.isConstant || // - !topRadius.isConstant || // - !bottomRadius.isConstant || // - !Property.isConstant(slices) || // + !radii.isConstant || // + !Property.isConstant(stackPartitions) || // + !Property.isConstant(slicePartitions) || // !Property.isConstant(outlineWidth) || // - !Property.isConstant(numberOfVerticalLines)) { + !Property.isConstant(subdivisions)) { if (!this._dynamic) { this._dynamic = true; this._geometryChanged.raiseEvent(this); @@ -114109,11 +124985,10 @@ define('DataSources/CylinderGeometryUpdater',[ } 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; + 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); @@ -114128,7 +125003,7 @@ define('DataSources/CylinderGeometryUpdater',[ * * @exception {DeveloperError} This instance does not represent dynamic geometry. */ - CylinderGeometryUpdater.prototype.createDynamicUpdater = function(primitives) { + EllipsoidGeometryUpdater.prototype.createDynamicUpdater = function(primitives) { return new DynamicGeometryUpdater(primitives, this); }; @@ -114137,53 +125012,98 @@ define('DataSources/CylinderGeometryUpdater',[ * @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) { - var primitives = this._primitives; - primitives.removeAndDestroy(this._primitive); - primitives.removeAndDestroy(this._outlinePrimitive); - this._primitive = undefined; - this._outlinePrimitive = undefined; + 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; + } - var geometryUpdater = this._geometryUpdater; - var entity = geometryUpdater._entity; - var cylinder = entity.cylinder; - if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(cylinder.show, time, true)) { + if (defined(this._outlinePrimitive)) { + this._outlinePrimitive.show = false; + } 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)) { + var radii = Property.getValueOrUndefined(ellipsoid.radii, time, radiiScratch); + var modelMatrix = entity.computeModelMatrix(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; } - options.length = length; - options.topRadius = topRadius; - options.bottomRadius = bottomRadius; - options.slices = Property.getValueOrUndefined(cylinder.slices, time); - options.numberOfVerticalLines = Property.getValueOrUndefined(cylinder.numberOfVerticalLines, time); + //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); - - if (Property.getValueOrDefault(cylinder.fill, time, true)) { - var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); - this._material = material; - var appearance = new MaterialAppearance({ + //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 @@ -114193,9 +125113,10 @@ define('DataSources/CylinderGeometryUpdater',[ this._primitive = primitives.add(new Primitive({ geometryInstances : new GeometryInstance({ id : entity, - geometry : new CylinderGeometry(options), - modelMatrix : modelMatrix, + geometry : new EllipsoidGeometry(options), + modelMatrix : !in3D ? modelMatrix : undefined, attributes : { + show : new ShowGeometryInstanceAttribute(showFill), distanceDisplayCondition : distanceDisplayConditionAttribute } }), @@ -114203,37570 +125124,43535 @@ define('DataSources/CylinderGeometryUpdater',[ 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, + 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 : translucent, + translucent : outlineColor.alpha !== 1.0, renderState : { - lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth) + lineWidth : this._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); - } - }; + 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; - 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'; + primitive.show = true; + outlinePrimitive.show = true; - /** - * 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(); - } + appearance = primitive.appearance; + appearance.material = material; - 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; + 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; } - }, - - /** - * 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) { - - 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; - } + var outlineAttributes = this._outlineAttributes; - 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); + if (!defined(outlineAttributes)) { + outlineAttributes = outlinePrimitive.getGeometryInstanceAttributes(entity); + this._outlineAttributes = outlineAttributes; } - }, - /** - * 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; + + if (showOutline !== this._lastOutlineShow) { + outlineAttributes.show = ShowGeometryInstanceAttribute.toValue(showOutline, outlineAttributes.show); + this._lastOutlineShow = showOutline; } - }, - /** - * 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'; - }; + if (!Color.equals(outlineColor, this._lastOutlineColor)) { + outlineAttributes.color = ColorGeometryInstanceAttribute.toValue(outlineColor, outlineAttributes.color); + Color.clone(outlineColor, this._lastOutlineColor); + } - /** - * 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 = {}; + 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; } - 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)); + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); }; - return GridMaterialProperty; + 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/PolylineArrowMaterialProperty',[ +define('DataSources/StaticGeometryColorBatch',[ + '../Core/AssociativeArray', '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', '../Core/defined', - '../Core/defineProperties', - '../Core/Event', - './createPropertyDescriptor', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/Primitive', + './BoundingSphereState', + './ColorMaterialProperty', + './MaterialProperty', './Property' ], function( + AssociativeArray, Color, + ColorGeometryInstanceAttribute, defined, - defineProperties, - Event, - createPropertyDescriptor, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + Primitive, + BoundingSphereState, + ColorMaterialProperty, + MaterialProperty, 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; + var colorScratch = new Color(); + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + + function Batch(primitives, translucent, appearanceType, depthFailAppearanceType, depthFailMaterialProperty, closed, shadows) { + this.translucent = translucent; + this.appearanceType = appearanceType; + this.depthFailAppearanceType = depthFailAppearanceType; + this.depthFailMaterialProperty = depthFailMaterialProperty; + this.depthFailMaterial = undefined; + 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 = []; + this.invalidated = false; + + var removeMaterialSubscription; + if (defined(depthFailMaterialProperty)) { + removeMaterialSubscription = depthFailMaterialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this); + } + this.removeMaterialSubscription = removeMaterialSubscription; } - 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') - }); + Batch.prototype.onMaterialChanged = function() { + this.invalidated = true; + }; - /** - * 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'; + Batch.prototype.isMaterial = function(updater) { + var material = this.depthFailMaterialProperty; + var updaterMaterial = updater.depthFailMaterialProperty; + if (updaterMaterial === material) { + return true; + } + if (defined(material)) { + return material.equals(updaterMaterial); + } + return false; }; - /** - * 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 = {}; + 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); + } + })); } - 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)); + 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 PolylineArrowMaterialProperty; -}); + Batch.prototype.update = function(time) { + var isUpdated = true; + var removedCount = 0; + var primitive = this.primitive; + var primitives = this.primitives; + var attributes; + var i; -/*global define*/ -define('DataSources/PolylineDashMaterialProperty',[ - '../Core/Color', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/Event', - './createPropertyDescriptor', - './Property' - ], function( - Color, - defaultValue, - defined, - defineProperties, - Event, - createPropertyDescriptor, - Property) { - 'use strict'; + 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); + } + } - var defaultColor = Color.WHITE; - var defaultGapColor = Color.TRANSPARENT; - var defaultDashLength = 16.0; - var defaultDashPattern = 255.0; + for (i = 0; i < geometriesLength; i++) { + var geometryItem = geometries[i]; + var originalAttributes = geometryItem.attributes; + attributes = this.attributes.get(geometryItem.id.id); - /** - * A {@link MaterialProperty} that maps to polyline dash {@link Material} uniforms. - * @alias PolylineDashMaterialProperty - * @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.gapColor=Color.TRANSPARENT] A Property specifying the {@link Color} of the gaps in the line. - * @param {Property} [options.dashLength=16.0] A numeric Property specifying the length of the dash pattern in pixel.s - * @param {Property} [options.dashPattern=255.0] A numeric Property specifying a 16 bit pattern for the dash - */ - function PolylineDashMaterialProperty(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + if (defined(attributes)) { + if (defined(originalAttributes.show)) { + originalAttributes.show.value = attributes.show; + } + if (defined(originalAttributes.color)) { + originalAttributes.color.value = attributes.color; + } + if (defined(originalAttributes.depthFailColor)) { + originalAttributes.depthFailColor.value = attributes.depthFailColor; + } + } + } - this._definitionChanged = new Event(); - this._color = undefined; - this._colorSubscription = undefined; - this._gapColor = undefined; - this._gapColorSubscription = undefined; - this._dashLength = undefined; - this._dashLengthSubscription = undefined; - this._dashPattern = undefined; - this._dashPatternSubscription = undefined; + var depthFailAppearance; + if (defined(this.depthFailAppearanceType)) { + if (defined(this.depthFailMaterialProperty)) { + this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); + } + depthFailAppearance = new this.depthFailAppearanceType({ + material : this.depthFailMaterial, + translucent : this.translucent, + closed : this.closed + }); + } - this.color = options.color; - this.gapColor = options.gapColor; - this.dashLength = options.dashLength; - this.dashPattern = options.dashPattern; - } + primitive = new Primitive({ + asynchronous : true, + geometryInstances : geometries, + appearance : new this.appearanceType({ + translucent : this.translucent, + closed : this.closed + }), + depthFailAppearance : depthFailAppearance, + 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; + } + } - defineProperties(PolylineDashMaterialProperty.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 PolylineDashMaterialProperty.prototype - * @type {Boolean} - * @readonly - */ - isConstant : { - get : function() { - return (Property.isConstant(this._color) && - Property.isConstant(this._gapColor) && - Property.isConstant(this._dashLength) && - Property.isConstant(this._dashPattern)); + 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; } - }, - /** - * 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 PolylineDashMaterialProperty.prototype - * @type {Event} - * @readonly - */ - definitionChanged : { - get : function() { - return this._definitionChanged; + + if (defined(this.depthFailAppearanceType) && !(this.depthFailMaterialProperty instanceof ColorMaterialProperty)) { + this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); + this.primitive.depthFailAppearance.material = this.depthFailMaterial; } - }, - /** - * Gets or sets the Property specifying the {@link Color} of the line. - * @memberof PolylineDashMaterialProperty.prototype - * @type {Property} - */ - color : createPropertyDescriptor('color'), - /** - * Gets or sets the Property specifying the {@link Color} of the gaps in the line. - * @memberof PolylineDashMaterialProperty.prototype - * @type {Property} - */ - gapColor : createPropertyDescriptor('gapColor'), - /** - * Gets or sets the numeric Property specifying the length of a dash cycle - * @memberof PolylineDashMaterialProperty.prototype - * @type {Property} - */ - dashLength : createPropertyDescriptor('dashLength'), - /** - * Gets or sets the numeric Property specifying a dash pattern - * @memberof PolylineDashMaterialProperty.prototype - * @type {Property} - */ - dashPattern : createPropertyDescriptor('dashPattern') - }); - /** - * 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. - */ - PolylineDashMaterialProperty.prototype.getType = function(time) { - return 'PolylineDash'; + 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; + } + } + } + + if (defined(this.depthFailAppearanceType) && this.depthFailAppearanceType instanceof ColorMaterialProperty && (!updater.depthFailMaterialProperty.isConstant || waitingOnCreate)) { + var depthFailColorProperty = updater.depthFailMaterialProperty.color; + depthFailColorProperty.getValue(time, colorScratch); + if (!Color.equals(attributes._lastDepthFailColor, colorScratch)) { + attributes._lastDepthFailColor = Color.clone(colorScratch, attributes._lastDepthFailColor); + attributes.depthFailColor = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.depthFailColor); + } + } + + 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; }; - /** - * 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. - */ - PolylineDashMaterialProperty.prototype.getValue = function(time, result) { - if (!defined(result)) { - result = {}; + 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); + } } - result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color); - result.gapColor = Property.getValueOrClonedDefault(this._gapColor, time, defaultGapColor, result.gapColor); - result.dashLength = Property.getValueOrDefault(this._dashLength, time, defaultDashLength, result.dashLength); - result.dashPattern = Property.getValueOrDefault(this._dashPattern, time, defaultDashPattern, result.dashPattern); - return result; + this.showsUpdated.removeAll(); }; - /** - * 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. - */ - PolylineDashMaterialProperty.prototype.equals = function(other) { - return this === other || // - (other instanceof PolylineDashMaterialProperty && - Property.equals(this._color, other._color) && - Property.equals(this._gapColor, other._gapColor) && - Property.equals(this._dashLength, other._dashLength) && - Property.equals(this._dashPattern, other._dashPattern) - ); + Batch.prototype.contains = function(entity) { + return this.updaters.contains(entity.id); }; - return PolylineDashMaterialProperty; -}); + 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; + }; -/*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'; + Batch.prototype.removeAllPrimitives = function() { + var primitives = this.primitives; - var defaultColor = Color.WHITE; - var defaultGlowPower = 0.25; + 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; + } + }; + + Batch.prototype.destroy = function() { + var primitive = this.primitive; + var primitives = this.primitives; + if (defined(primitive)) { + primitives.remove(primitive); + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + } + if(defined(this.removeMaterialSubscription)) { + this.removeMaterialSubscription(); + } + }; /** - * 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. + * @private */ - function PolylineGlowMaterialProperty(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + function StaticGeometryColorBatch(primitives, appearanceType, depthFailAppearanceType, closed, shadows) { + this._solidItems = []; + this._translucentItems = []; + this._primitives = primitives; + this._appearanceType = appearanceType; + this._depthFailAppearanceType = depthFailAppearanceType; + this._closed = closed; + this._shadows = shadows; + } - this._definitionChanged = new Event(); - this._color = undefined; - this._colorSubscription = undefined; - this._glowPower = undefined; - this._glowPowerSubscription = undefined; + StaticGeometryColorBatch.prototype.add = function(time, updater) { + var items; + var translucent; + var instance = updater.createFillGeometryInstance(time); + if (instance.attributes.color.value[3] === 255) { + items = this._solidItems; + translucent = false; + } else { + items = this._translucentItems; + translucent = true; + } - this.color = options.color; - this.glowPower = options.glowPower; + var length = items.length; + for (var i = 0; i < length; i++) { + var item = items[i]; + if (item.isMaterial(updater)) { + item.add(updater, instance); + return; + } + } + var batch = new Batch(this._primitives, translucent, this._appearanceType, this._depthFailAppearanceType, updater.depthFailMaterialProperty, this._closed, this._shadows); + batch.add(updater, instance); + items.push(batch); + }; + + function removeItem(items, updater) { + 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(); + return true; + } + } + } + return false; } - 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); + StaticGeometryColorBatch.prototype.remove = function(updater) { + if (!removeItem(this._solidItems, updater)) { + removeItem(this._translucentItems, updater); + } + }; + + function moveItems(batch, items, time) { + var itemsMoved = false; + var length = items.length; + for (var i = 0; i < length; ++i) { + var item = items[i]; + var itemsToRemove = item.itemsToRemove; + var itemsToMoveLength = itemsToRemove.length; + if (itemsToMoveLength > 0) { + for (i = 0; i < itemsToMoveLength; i++) { + var updater = itemsToRemove[i]; + item.remove(updater); + batch.add(time, updater); + itemsMoved = true; + } } - }, - /** - * 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; + } + return itemsMoved; + } + + function updateItems(batch, items, time, isUpdated) { + var length = items.length; + var i; + 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++) { + batch.add(time, updaters[h]); + } + item.destroy(); } - }, - /** - * 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'; + length = items.length; + for (i = 0; i < length; ++i) { + isUpdated = items[i].update(time) && isUpdated; + } + return isUpdated; + } + + StaticGeometryColorBatch.prototype.update = function(time) { + //Perform initial update + var isUpdated = updateItems(this, this._solidItems, time, true); + isUpdated = updateItems(this, this._translucentItems, time, isUpdated) && isUpdated; + + //If any items swapped between solid/translucent, we need to + //move them between batches + var solidsMoved = moveItems(this, this._solidItems, time); + var translucentsMoved = moveItems(this, this._translucentItems, time); + + //If we moved anything around, we need to re-build the primitive + if (solidsMoved || translucentsMoved) { + isUpdated = updateItems(this, this._solidItems, time, isUpdated) && isUpdated; + isUpdated = updateItems(this, this._translucentItems, time, isUpdated)&& isUpdated; + } + + return isUpdated; }; - /** - * 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 = {}; + function getBoundingSphere(items, entity, result) { + var length = items.length; + for (var i = 0; i < length; i++) { + var item = items[i]; + if(item.contains(entity)){ + return item.getBoundingSphere(entity, result); + } } - result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color); - result.glowPower = Property.getValueOrDefault(this._glowPower, time, defaultGlowPower, result.glowPower); - return result; + return BoundingSphereState.FAILED; + } + + StaticGeometryColorBatch.prototype.getBoundingSphere = function(entity, result) { + var boundingSphere = getBoundingSphere(this._solidItems, entity, result); + if (boundingSphere === BoundingSphereState.FAILED) { + return getBoundingSphere(this._translucentItems, entity, result); + } + return boundingSphere; }; - /** - * 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)); + function removeAllPrimitives(items) { + var length = items.length; + for (var i = 0; i < length; i++) { + items[i].destroy(); + } + items.length = 0; + } + + StaticGeometryColorBatch.prototype.removeAllPrimitives = function() { + removeAllPrimitives(this._solidItems); + removeAllPrimitives(this._translucentItems); }; - return PolylineGlowMaterialProperty; + return StaticGeometryColorBatch; }); -/*global define*/ -define('DataSources/PolylineOutlineMaterialProperty',[ +define('DataSources/StaticGeometryPerMaterialBatch',[ + '../Core/AssociativeArray', '../Core/Color', - '../Core/defaultValue', + '../Core/ColorGeometryInstanceAttribute', '../Core/defined', - '../Core/defineProperties', - '../Core/Event', - './createPropertyDescriptor', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/Primitive', + './BoundingSphereState', + './ColorMaterialProperty', + './MaterialProperty', './Property' ], function( + AssociativeArray, Color, - defaultValue, + ColorGeometryInstanceAttribute, defined, - defineProperties, - Event, - createPropertyDescriptor, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + Primitive, + BoundingSphereState, + ColorMaterialProperty, + MaterialProperty, 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; + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); - this.color = options.color; - this.outlineColor = options.outlineColor; - this.outlineWidth = options.outlineWidth; + function Batch(primitives, appearanceType, materialProperty, depthFailAppearanceType, depthFailMaterialProperty, closed, shadows) { + this.primitives = primitives; + this.appearanceType = appearanceType; + this.materialProperty = materialProperty; + this.depthFailAppearanceType = depthFailAppearanceType; + this.depthFailMaterialProperty = depthFailMaterialProperty; + 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.depthFailMaterial = 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(); } - 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') - }); + Batch.prototype.onMaterialChanged = function() { + this.invalidated = true; + }; - /** - * 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'; + Batch.prototype.isMaterial = function(updater) { + var material = this.materialProperty; + var updaterMaterial = updater.fillMaterialProperty; + var depthFailMaterial = this.depthFailMaterialProperty; + var updaterDepthFailMaterial = updater.depthFailMaterialProperty; + + if (updaterMaterial === material && updaterDepthFailMaterial === depthFailMaterial) { + return true; + } + var equals = defined(material) && material.equals(updaterMaterial); + equals = ((!defined(depthFailMaterial) && !defined(updaterDepthFailMaterial)) || (defined(depthFailMaterial) && depthFailMaterial.equals(updaterDepthFailMaterial))) && equals; + return equals; }; - /** - * 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 = {}; + 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); + } + })); } - 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; + this.createPrimitive = 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. - */ - 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)); + 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; }; - return PolylineOutlineMaterialProperty; -}); + var colorScratch = new Color(); -/*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'; + 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; - /** - * 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); - } + if (this.createPrimitive) { + var geometriesLength = geometries.length; + if (geometriesLength > 0) { + if (defined(primitive)) { + if (!defined(this.oldPrimitive)) { + this.oldPrimitive = primitive; + } else { + primitives.remove(primitive); + } + } - 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; + 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; + } + if (defined(originalAttributes.depthFailColor)) { + originalAttributes.depthFailColor.value = attributes.depthFailColor; + } + } } - var length = value.length; - for (var i = 0; i < length; i++) { - if (!Property.isConstant(value[i])) { - return false; + this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + + var depthFailAppearance; + if (defined(this.depthFailMaterialProperty)) { + var translucent; + if (this.depthFailMaterialProperty instanceof MaterialProperty) { + this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); + translucent = this.depthFailMaterial.isTranslucent(); + } else { + translucent = this.material.isTranslucent(); } + depthFailAppearance = new this.depthFailAppearanceType({ + material : this.depthFailMaterial, + translucent : translucent, + closed : this.closed + }); + } + + primitive = new Primitive({ + asynchronous : true, + geometryInstances : geometries, + appearance : new this.appearanceType({ + material : this.material, + translucent : this.material.isTranslucent(), + closed : this.closed + }), + depthFailAppearance : depthFailAppearance, + 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; } - 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; + + 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; } - }, - /** - * Gets the reference frame in which the position is defined. - * @memberof PositionPropertyArray.prototype - * @type {ReferenceFrame} - * @default ReferenceFrame.FIXED; - */ - referenceFrame : { - get : function() { - return this._referenceFrame; + + this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + this.primitive.appearance.material = this.material; + + if (defined(this.depthFailAppearanceType) && !(this.depthFailMaterialProperty instanceof ColorMaterialProperty)) { + this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); + this.primitive.depthFailAppearance.material = this.depthFailMaterial; } - } - }); - /** - * 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); - }; + 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); - /** - * 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) { - - var value = this._value; - if (!defined(value)) { - return undefined; - } + attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } - 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++; + if (defined(this.depthFailAppearanceType) && this.depthFailAppearanceType instanceof ColorMaterialProperty && !updater.depthFailMaterialProperty.isConstant) { + var depthFailColorProperty = updater.depthFailMaterialProperty.color; + depthFailColorProperty.getValue(time, colorScratch); + if (!Color.equals(attributes._lastDepthFailColor, colorScratch)) { + attributes._lastDepthFailColor = Color.clone(colorScratch, attributes._lastDepthFailColor); + attributes.depthFailColor = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.depthFailColor); + } + } + + 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); + } + } } - i++; + + this.updateShows(primitive); + } else if (defined(primitive) && !primitive.ready) { + isUpdated = false; } - result.length = x; - return result; + return isUpdated; }; - /** - * 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(); + 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); - 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); - } + 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); } - } else { - this._value = undefined; } - this._definitionChanged.raiseEvent(this); + this.showsUpdated.removeAll(); }; - /** - * 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)); + Batch.prototype.contains = function(entity) { + return this.updaters.contains(entity.id); }; - PositionPropertyArray.prototype._raiseDefinitionChanged = function() { - this._definitionChanged.raiseEvent(this); + 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; }; - 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'; + Batch.prototype.destroy = function() { + 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(); + }; /** - * 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. + * @private */ - function PropertyArray(value) { - this._value = undefined; - this._definitionChanged = new Event(); - this._eventHelper = new EventHelper(); - this.setValue(value); + function StaticGeometryPerMaterialBatch(primitives, appearanceType, depthFailAppearanceType, closed, shadows) { + this._items = []; + this._primitives = primitives; + this._appearanceType = appearanceType; + this._depthFailAppearanceType = depthFailAppearanceType; + this._closed = closed; + this._shadows = shadows; } - 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; + 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; } } - }); - - /** - * 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) { - - var value = this._value; - if (!defined(value)) { - return undefined; - } + var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._depthFailAppearanceType, updater.depthFailMaterialProperty, this._closed, this._shadows); + batch.add(time, updater); + items.push(batch); + }; - 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++; + 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; } - 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(); + StaticGeometryPerMaterialBatch.prototype.update = function(time) { + var i; + var items = this._items; + var length = items.length; - 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); + 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(); } - } else { - this._value = undefined; } - this._definitionChanged.raiseEvent(this); + + var isUpdated = true; + for (i = 0; i < length; i++) { + //isUpdated = items[i].update(time) && isUpdated; + //acevedo - added condition + if (items[i]) + { + isUpdated = items[i].update(time) && isUpdated; + } + else + { + isUpdated = false; + } + } + return isUpdated; }; - /** - * 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)); + 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; }; - PropertyArray.prototype._raiseDefinitionChanged = function() { - this._definitionChanged.raiseEvent(this); + 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 PropertyArray; + return StaticGeometryPerMaterialBatch; }); -/*global define*/ -define('DataSources/ReferenceProperty',[ +define('DataSources/StaticGroundGeometryColorBatch',[ + '../Core/AssociativeArray', + '../Core/Color', '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/RuntimeError', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/GroundPrimitive', + './BoundingSphereState', './Property' ], function( + AssociativeArray, + Color, defined, - defineProperties, - DeveloperError, - Event, - RuntimeError, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + GroundPrimitive, + BoundingSphereState, Property) { 'use strict'; - function resolveEntity(that) { - var entityIsResolved = true; - if (that._resolveEntity) { - var targetEntity = that._targetCollection.getById(that._targetId); + var colorScratch = new Color(); + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); - 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; - } + 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; + } - if (!defined(targetEntity)) { - throw new RuntimeError('target entity "' + that._targetId + '" could not be resolved.'); + 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); } } - return entityIsResolved; - } + }; - function resolve(that) { - var targetProperty = that._targetProperty; + var scratchArray = new Array(4); - if (that._resolveProperty) { - var entityIsResolved = resolveEntity(that); + Batch.prototype.update = function(time) { + var isUpdated = true; + var removedCount = 0; + var primitive = this.primitive; + var primitives = this.primitives; + var attributes; + var i; - 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 (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; + } } - 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.'); + 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); - return targetProperty; - } + attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } - /** - * 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) { - - 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; + if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) { + var colorProperty = updater.fillMaterialProperty.color; + colorProperty.getValue(time, colorScratch); - targetCollection.collectionChanged.addEventListener(ReferenceProperty.prototype._onCollectionChanged, this); - } + 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; + } + } + } - 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; + 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); + } + } } - }, - /** - * Gets the array of property names used to retrieve the referenced property. - * @memberof ReferenceProperty.prototype - * @type {String[]} - * @readonly - */ - targetPropertyNames : { - get : function() { - return this._targetPropertyNames; + + 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); } - }, - /** - * Gets the resolved instance of the underlying referenced property. - * @memberof ReferenceProperty.prototype - * @type {Property} - * @readonly - */ - resolvedProperty : { - get : function() { - return resolve(this); + + var show = updater.entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); } } - }); + this.showsUpdated.removeAll(); + }; - /** - * 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) { - - var identifier; - var values = []; + Batch.prototype.contains = function(entity) { + return this.updaters.contains(entity.id); + }; - var inIdentifier = true; - var isEscaped = false; - var token = ''; - for (var i = 0; i < referenceString.length; ++i) { - var c = referenceString.charAt(i); + Batch.prototype.getBoundingSphere = function(entity, result) { + var primitive = this.primitive; + if (!primitive.ready) { + return BoundingSphereState.PENDING; + } - 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; - } + 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; } - 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. + * @private */ - ReferenceProperty.prototype.getValue = function(time, result) { - return resolve(this).getValue(time, result); - }; + function StaticGroundGeometryColorBatch(primitives) { + this._batches = new AssociativeArray(); + this._primitives = primitives; + } - /** - * 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); + 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; }; - /** - * 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); + 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; + } + } }; - /** - * 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; - } + StaticGroundGeometryColorBatch.prototype.update = function(time) { + var i; + var updater; - var names = this._targetPropertyNames; - var otherNames = other._targetPropertyNames; + //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 (this._targetCollection !== other._targetCollection || // - this._targetId !== other._targetId || // - names.length !== otherNames.length) { - return false; + //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; + } } - var length = this._targetPropertyNames.length; - for (var i = 0; i < length; i++) { - if (names[i] !== otherNames[i]) { - return false; + //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 true; + return isUpdated; }; - ReferenceProperty.prototype._onTargetEntityDefinitionChanged = function(targetEntity, name, value, oldValue) { - if (this._targetPropertyNames[0] === name) { - this._resolveProperty = true; - this._definitionChanged.raiseEvent(this); + 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; }; - 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); - } - } + StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() { + var batchesArray = this._batches.values; + var batchCount = batchesArray.length; + for (var i = 0; i < batchCount; ++i) { + batchesArray[i].removeAllPrimitives(); } }; - return ReferenceProperty; + return StaticGroundGeometryColorBatch; }); -/*global define*/ -define('DataSources/Rotation',[ - '../Core/defaultValue', +define('DataSources/StaticOutlineGeometryBatch',[ + '../Core/AssociativeArray', + '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', '../Core/defined', - '../Core/DeveloperError', - '../Core/Math' + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/PerInstanceColorAppearance', + '../Scene/Primitive', + './BoundingSphereState', + './Property' ], function( - defaultValue, + AssociativeArray, + Color, + ColorGeometryInstanceAttribute, defined, - DeveloperError, - CesiumMath) { + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + PerInstanceColorAppearance, + Primitive, + BoundingSphereState, + Property) { '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, + 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); + } + })); + } + }; - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); - array[startingIndex] = value; + 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 array; - }, + var colorScratch = new Color(); + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); - return array[startingIndex]; - }, + Batch.prototype.update = function(time) { + var isUpdated = true; + var removedCount = 0; + var primitive = this.primitive; + var primitives = this.primitives; + var attributes; + var i; - /** - * 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) { - - startingIndex = defaultValue(startingIndex, 0); - lastIndex = defaultValue(lastIndex, packedArray.length); + 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); + } + } - 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; + 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; } - 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} [firstIndex=0] The firstIndex 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) { - - result = array[0]; - if (result < 0) { - return result + CesiumMath.TWO_PI; + 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; } - return result; - } - }; - return Rotation; -}); + 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); -/*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'; + attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } - 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]; + 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; }; - //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; + 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); - array.length = newLength; - if (arrayLength !== startIndex) { - var q = arrayLength - 1; - for (i = newLength - 1; i >= startIndex; i--) { - array[i] = array[q--]; + 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(); + }; - for (i = 0; i < itemsLength; i++) { - array[startIndex++] = items[i]; + 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; + }; - function convertDate(date, epoch) { - if (date instanceof JulianDate) { - return date; + 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(); } - if (typeof date === 'string') { - return JulianDate.fromIso8601(date); + + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + this.oldPrimitive = undefined; } - return JulianDate.addSeconds(epoch, date, new JulianDate()); - } + }; - var timesSpliceArgs = []; - var valuesSpliceArgs = []; + /** + * @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); + } + }; - function mergeNewSamples(epoch, times, values, newData, packedLength) { - var newDataIndex = 0; + StaticOutlineGeometryBatch.prototype.remove = function(updater) { 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; + var solidBatches = this._solidBatches.values; + var solidBatchesLength = solidBatches.length; + for (i = 0; i < solidBatchesLength; i++) { + if (solidBatches[i].remove(updater)) { + return; + } + } - if (timesInsertionPoint < 0) { - //Doesn't exist, insert as many additional values as we can. - timesInsertionPoint = ~timesInsertionPoint; + var translucentBatches = this._translucentBatches.values; + var translucentBatchesLength = translucentBatches.length; + for (i = 0; i < translucentBatchesLength; i++) { + if (translucentBatches[i].remove(updater)) { + return; + } + } + }; - 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; + 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); } - timesSpliceArgs[timesSpliceArgsCount++] = currentTime; - newDataIndex = newDataIndex + 1; - for (i = 0; i < packedLength; i++) { - valuesSpliceArgs[valuesSpliceArgsCount++] = newData[newDataIndex]; - newDataIndex = newDataIndex + 1; + } + } + 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); } - prevItem = currentTime; } + } + } while (needUpdate); - if (timesSpliceArgsCount > 0) { - valuesSpliceArgs.length = valuesSpliceArgsCount; - arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs); + return isUpdated; + }; - 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++; + 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; +}); + +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)); + }; - /** - * 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) { - - var innerType = type; - if (innerType === Number) { - innerType = PackableNumber; + 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(); } - 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; + 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; + }; - 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; + 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); + } } - 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); + 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); + } + + var multiplier = 0; + if (defined(updater.depthFailMaterialProperty)) { + multiplier = updater.depthFailMaterialProperty instanceof ColorMaterialProperty ? 1 : 2; + } + + var index; + if (defined(shadows)) { + index = shadows + multiplier * ShadowMode.NUMBER_OF_SHADOW_MODES; + } + + if (updater.fillEnabled) { + if (updater.onTerrain) { + that._groundColorBatch.add(time, updater); + } else if (updater.isClosed) { + if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { + that._closedColorBatches[index].add(time, updater); + } else { + that._closedMaterialBatches[index].add(time, updater); } + } else if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { + that._openColorBatches[index].add(time, updater); + } else { + that._openMaterialBatches[index].add(time, updater); } } - }); + } /** - * Gets the value of the property at the provided time. + * A general purpose visualizer for geometry represented by {@link Primitive} instances. + * @alias GeometryVisualizer + * @constructor * - * @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. + * @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. */ - SampledProperty.prototype.getValue = function(time, result) { + function GeometryVisualizer(type, scene, entityCollection) { - var times = this._times; - var timesLength = times.length; - if (timesLength === 0) { - return undefined; - } + this._type = type; - var timeout; - var innerType = this._innerType; - var values = this._values; - var index = binarySearch(times, time, JulianDate.compare); + 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(); - if (index < 0) { - index = ~index; + var numberOfShadowModes = ShadowMode.NUMBER_OF_SHADOW_MODES; + this._outlineBatches = new Array(numberOfShadowModes); + this._closedColorBatches = new Array(numberOfShadowModes * 3); + this._closedMaterialBatches = new Array(numberOfShadowModes * 3); + this._openColorBatches = new Array(numberOfShadowModes * 3); + this._openMaterialBatches = new Array(numberOfShadowModes * 3); - 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); - } - } + for (var i = 0; i < numberOfShadowModes; ++i) { + this._outlineBatches[i] = new StaticOutlineGeometryBatch(primitives, scene, i); - 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); - } - } + this._closedColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, undefined, true, i); + this._closedMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, undefined, true, i); + this._openColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, undefined, false, i); + this._openMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, undefined, false, i); - var xTable = this._xTable; - var yTable = this._yTable; - var interpolationAlgorithm = this._interpolationAlgorithm; - var packedInterpolationLength = this._packedInterpolationLength; - var inputOrder = this._inputOrder; + this._closedColorBatches[i + numberOfShadowModes] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.perInstanceColorAppearanceType, true, i); + this._closedMaterialBatches[i + numberOfShadowModes] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.perInstanceColorAppearanceType, true, i); + this._openColorBatches[i + numberOfShadowModes] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.perInstanceColorAppearanceType, false, i); + this._openMaterialBatches[i + numberOfShadowModes] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.perInstanceColorAppearanceType, false, i); - 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; - } - } + this._closedColorBatches[i + numberOfShadowModes * 2] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.materialAppearanceType, true, i); + this._closedMaterialBatches[i + numberOfShadowModes * 2] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.materialAppearanceType, true, i); + this._openColorBatches[i + numberOfShadowModes * 2] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.materialAppearanceType, false, i); + this._openMaterialBatches[i + numberOfShadowModes * 2] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.materialAppearanceType, false, i); + } - var degree = this._numberOfPoints - 1; - if (degree < 1) { - return undefined; - } + this._groundColorBatch = new StaticGroundGeometryColorBatch(groundPrimitives); + this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives); - var firstIndex = 0; - var lastIndex = timesLength - 1; - var pointsInCollection = lastIndex - firstIndex + 1; + this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatch, this._dynamicBatch); - 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; - } - } + this._subscriptions = new AssociativeArray(); + this._updaters = new AssociativeArray(); - firstIndex = computedFirstIndex; - lastIndex = computedLastIndex; - } - var length = lastIndex - firstIndex + 1; + this._entityCollection = entityCollection; + entityCollection.collectionChanged.addEventListener(GeometryVisualizer.prototype._onCollectionChanged, this); + this._onCollectionChanged(entityCollection, entityCollection.values, emptyArray); + } - // Build the tables - for (var i = 0; i < length; ++i) { - xTable[i] = JulianDate.secondsDifference(times[firstIndex + i], times[lastIndex]); - } + /** + * 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) { + + var addedObjects = this._addedObjects; + var added = addedObjects.values; + var removedObjects = this._removedObjects; + var removed = removedObjects.values; + var changedObjects = this._changedObjects; + var changed = changedObjects.values; - if (!defined(innerType.convertPackedArrayForInterpolation)) { - var destinationIndex = 0; - var packedLength = this._packedLength; - var sourceIndex = firstIndex * packedLength; - var stop = (lastIndex + 1) * packedLength; + var i; + var entity; + var id; + var updater; - while (sourceIndex < stop) { - yTable[destinationIndex] = values[sourceIndex]; - sourceIndex++; - destinationIndex++; - } - } else { - innerType.convertPackedArrayForInterpolation(values, firstIndex, lastIndex, yTable); - } + for (i = changed.length - 1; i > -1; i--) { + entity = changed[i]; + id = entity.id; + updater = this._updaters.get(id); - // 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); + //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 { - var yStride = Math.floor(packedInterpolationLength / (inputOrder + 1)); - interpolationResult = interpolationAlgorithm.interpolate(x, xTable, yTable, yStride, inputOrder, inputOrder, this._interpolationResult); + removed.push(entity); + added.push(entity); } + } - if (!defined(innerType.unpackInterpolationResult)) { - return innerType.unpack(interpolationResult, 0, result); - } - return innerType.unpackInterpolationResult(interpolationResult, values, firstIndex, lastIndex, result); + 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); } - return innerType.unpack(values, index * this._packedLength, result); + + 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(); + /** - * Sets the algorithm and degree to use when interpolating a value. + * 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 {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. + * @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 */ - SampledProperty.prototype.setInterpolationOptions = function(options) { - if (!defined(options)) { - return; + GeometryVisualizer.prototype.getBoundingSphere = function(entity, result) { + + 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++; + } } - var valuesChanged = false; + if (count === 0) { + return BoundingSphereState.FAILED; + } - var interpolationAlgorithm = options.interpolationAlgorithm; - var interpolationDegree = options.interpolationDegree; + boundingSpheres.length = count; + BoundingSphere.fromBoundingSpheres(boundingSpheres, result); + return BoundingSphereState.DONE; + }; - if (defined(interpolationAlgorithm) && this._interpolationAlgorithm !== interpolationAlgorithm) { - this._interpolationAlgorithm = interpolationAlgorithm; - valuesChanged = true; + /** + * 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(); } - if (defined(interpolationDegree) && this._interpolationDegree !== interpolationDegree) { - this._interpolationDegree = interpolationDegree; - valuesChanged = true; + var subscriptions = this._subscriptions.values; + length = subscriptions.length; + for (i = 0; i < length; i++) { + subscriptions[i](); } + this._subscriptions.removeAll(); + return destroyObject(this); + }; - if (valuesChanged) { - this._updateTableLength = true; - this._definitionChanged.raiseEvent(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); } }; /** - * 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. + * @private */ - SampledProperty.prototype.addSample = function(time, value, derivatives) { - var innerDerivativeTypes = this._innerDerivativeTypes; - var hasDerivatives = defined(innerDerivativeTypes); + GeometryVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed) { + var addedObjects = this._addedObjects; + var removedObjects = this._removedObjects; + var changedObjects = this._changedObjects; - - var innerType = this._innerType; - var data = []; - data.push(time); - innerType.pack(value, data, data.length); + 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); + } + } - if (hasDerivatives) { - var derivativesLength = innerDerivativeTypes.length; - for (var x = 0; x < derivativesLength; x++) { - innerDerivativeTypes[x].pack(derivatives[x], data, data.length); + 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); } } - mergeNewSamples(undefined, this._times, this._values, data, this._packedLength); - this._updateTableLength = true; - this._definitionChanged.raiseEvent(this); }; + return GeometryVisualizer; +}); + +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 defaultDisableDepthTestDistance = 0.0; + + 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 scaleByDistance = new NearFarScalar(); + var distanceDisplayCondition = new DistanceDisplayCondition(); + + function EntityData(entity) { + this.entity = entity; + this.label = undefined; + this.index = undefined; + } + /** - * 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. + * A {@link Visualizer} which maps the {@link LabelGraphics} instance + * in {@link Entity#label} to a {@link Label}. + * @alias LabelVisualizer + * @constructor * - * @exception {DeveloperError} times and values must be the same length. - * @exception {DeveloperError} times and derivativeValues must be the same length. + * @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. */ - SampledProperty.prototype.addSamples = function(times, values, derivativeValues) { - var innerDerivativeTypes = this._innerDerivativeTypes; - var hasDerivatives = defined(innerDerivativeTypes); + function LabelVisualizer(entityCluster, entityCollection) { + + 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) { - 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); + var items = this._items.values; + var cluster = this._cluster; - 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); - } + 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.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); + label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); + label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); } - mergeNewSamples(undefined, this._times, this._values, data, this._packedLength); - this._updateTableLength = true; - this._definitionChanged.raiseEvent(this); + return true; }; /** - * 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. + * 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 {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. + * @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 */ - SampledProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) { + LabelVisualizer.prototype.getBoundingSphere = function(entity, result) { - mergeNewSamples(epoch, this._times, this._values, packedSamples, this._packedLength); - this._updateTableLength = true; - this._definitionChanged.raiseEvent(this); + 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; }; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * Returns true if this object was destroyed; otherwise, false. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - SampledProperty.prototype.equals = function(other) { - if (this === other) { - return true; + 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]); } - if (!defined(other)) { - return false; + 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)); + } } - if (this._type !== other._type || // - this._interpolationDegree !== other._interpolationDegree || // - this._interpolationAlgorithm !== other._interpolationAlgorithm) { - return false; + 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); + } } - var derivativeTypes = this._derivativeTypes; - var hasDerivatives = defined(derivativeTypes); - var otherDerivativeTypes = other._derivativeTypes; - var otherHasDerivatives = defined(otherDerivativeTypes); - if (hasDerivatives !== otherHasDerivatives) { - return false; + for (i = removed.length - 1; i > -1; i--) { + entity = removed[i]; + returnPrimitive(items.get(entity.id), entity, cluster); + items.remove(entity.id); } + }; - var i; - var length; - if (hasDerivatives) { - length = derivativeTypes.length; - if (length !== otherDerivativeTypes.length) { - return false; + function returnPrimitive(item, entity, cluster) { + if (defined(item)) { + item.label = undefined; + cluster.removeLabel(entity); + } + } + + return LabelVisualizer; +}); + +define('ThirdParty/GltfPipeline/addToArray',[], function() { + 'use strict'; + + function addToArray(array, element) { + array.push(element); + return array.length - 1; + } + return addToArray; +}); + +define('ThirdParty/GltfPipeline/ForEach',[ + '../../Core/defined' + ], function( + defined) { + 'use strict'; + + /** + * Contains traversal functions for processing elements of the glTF hierarchy. + */ + var ForEach = {}; + + ForEach.object = function(arrayOfObjects, handler) { + if (defined(arrayOfObjects)) { + for (var i = 0; i < arrayOfObjects.length; i++) { + var object = arrayOfObjects[i]; + var returnValue = handler(object, i); + if (typeof returnValue === 'number') { + i += returnValue; + } + else if (returnValue) { + break; + } } + } + }; - for (i = 0; i < length; i++) { - if (derivativeTypes[i] !== otherDerivativeTypes[i]) { - return false; + ForEach.topLevel = function(gltf, name, handler) { + var arrayOfObjects = gltf[name]; + ForEach.object(arrayOfObjects, handler); + }; + + ForEach.accessor = function(gltf, handler) { + ForEach.topLevel(gltf, 'accessors', handler); + }; + + ForEach.accessorWithSemantic = function(gltf, semantic, handler) { + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + ForEach.meshPrimitiveAttribute(primitive, function(accessorId, attributeSemantic) { + if (attributeSemantic.indexOf(semantic) === 0) { + handler(accessorId, attributeSemantic, primitive); + } + }); + }); + }); + }; + + ForEach.animation = function(gltf, handler) { + ForEach.topLevel(gltf, 'animations', handler); + }; + + ForEach.animationSampler = function(animation, handler) { + var samplers = animation.samplers; + if (defined(samplers)) { + ForEach.object(samplers, handler); + } + }; + + ForEach.buffer = function(gltf, handler) { + ForEach.topLevel(gltf, 'buffers', handler); + }; + + ForEach.bufferView = function(gltf, handler) { + ForEach.topLevel(gltf, 'bufferViews', handler); + }; + + ForEach.camera = function(gltf, handler) { + ForEach.topLevel(gltf, 'cameras', handler); + }; + + ForEach.image = function(gltf, handler) { + ForEach.topLevel(gltf, 'images', handler); + }; + + ForEach.material = function(gltf, handler) { + ForEach.topLevel(gltf, 'materials', handler); + }; + + ForEach.materialValue = function(material, handler) { + var values = material.values; + if (defined(values)) { + for (var name in values) { + if (values.hasOwnProperty(name)) { + handler(values[name], name); } } } + }; - var times = this._times; - var otherTimes = other._times; - length = times.length; + ForEach.mesh = function(gltf, handler) { + ForEach.topLevel(gltf, 'meshes', handler); + }; - if (length !== otherTimes.length) { - return false; + ForEach.meshPrimitive = function(mesh, handler) { + var primitives = mesh.primitives; + if (defined(primitives)) { + var primitivesLength = primitives.length; + for (var i = 0; i < primitivesLength; i++) { + var primitive = primitives[i]; + handler(primitive, i); + } } + }; - for (i = 0; i < length; i++) { - if (!JulianDate.equals(times[i], otherTimes[i])) { - return false; + ForEach.meshPrimitiveAttribute = function(primitive, handler) { + var attributes = primitive.attributes; + if (defined(attributes)) { + for (var semantic in attributes) { + if (attributes.hasOwnProperty(semantic)) { + handler(attributes[semantic], semantic); + } } } + }; - var values = this._values; - var otherValues = other._values; - for (i = 0; i < length; i++) { - if (values[i] !== otherValues[i]) { - return false; + ForEach.meshPrimitiveTargetAttribute = function(primitive, handler) { + var targets = primitive.targets; + if (defined(targets)) { + for (var targetId in targets) { + if (targets.hasOwnProperty(targetId)) { + var target = targets[targetId]; + for (var attributeId in target) { + if (target.hasOwnProperty(attributeId) && attributeId !== 'extras') { + handler(target[attributeId], attributeId); + } + } + } } } + }; - return true; + ForEach.node = function(gltf, handler) { + ForEach.topLevel(gltf, 'nodes', handler); }; - //Exposed for testing. - SampledProperty._mergeNewSamples = mergeNewSamples; + ForEach.nodeInTree = function(gltf, nodeIds, handler) { + var nodes = gltf.nodes; + if (defined(nodes)) { + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i]; + var node = nodes[nodeId]; + if (defined(node)) { + handler(node, nodeId); + var children = node.children; + if (defined(children)) { + ForEach.nodeInTree(gltf, children, handler); + } + } + } + } + }; - return SampledProperty; -}); + ForEach.nodeInScene = function(gltf, scene, handler) { + var sceneNodeIds = scene.nodes; + if (defined(sceneNodeIds)) { + ForEach.nodeInTree(gltf, sceneNodeIds, handler); + } + }; -/*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'; + ForEach.program = function(gltf, handler) { + ForEach.topLevel(gltf, 'programs', handler); + }; - /** - * 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); + ForEach.sampler = function(gltf, handler) { + ForEach.topLevel(gltf, 'samplers', handler); + }; - var derivativeTypes; - if (numberOfDerivatives > 0) { - derivativeTypes = new Array(numberOfDerivatives); - for (var i = 0; i < numberOfDerivatives; i++) { - derivativeTypes[i] = Cartesian3; + ForEach.scene = function(gltf, handler) { + ForEach.topLevel(gltf, 'scenes', handler); + }; + + ForEach.shader = function(gltf, handler) { + ForEach.topLevel(gltf, 'shaders', handler); + }; + + ForEach.skin = function(gltf, handler) { + ForEach.topLevel(gltf, 'skins', handler); + }; + + ForEach.techniqueAttribute = function(technique, handler) { + var attributes = technique.attributes; + if (defined(attributes)) { + for (var semantic in attributes) { + if (attributes.hasOwnProperty(semantic)) { + if (handler(attributes[semantic], semantic)) { + break; + } + } } } + }; - this._numberOfDerivatives = numberOfDerivatives; - this._property = new SampledProperty(Cartesian3, derivativeTypes); - this._definitionChanged = new Event(); - this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED); + ForEach.techniqueParameter = function(technique, handler) { + var parameters = technique.parameters; + if (defined(parameters)) { + for (var parameterName in parameters) { + if (parameters.hasOwnProperty(parameterName)) { + if (handler(parameters[parameterName], parameterName)) { + break; + } + } + } + } + }; - this._property._definitionChanged.addEventListener(function() { - this._definitionChanged.raiseEvent(this); - }, this); - } + ForEach.technique = function(gltf, handler) { + ForEach.topLevel(gltf, 'techniques', handler); + }; - 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; + ForEach.texture = function(gltf, handler) { + ForEach.topLevel(gltf, 'textures', handler); + }; + + return ForEach; +}); + +define('ThirdParty/GltfPipeline/addDefaults',[ + './addToArray', + './ForEach', + '../../Core/clone', + '../../Core/defaultValue', + '../../Core/defined', + '../../Core/WebGLConstants' + ], function( + addToArray, + ForEach, + clone, + defaultValue, + defined, + WebGLConstants) { + 'use strict'; + + var gltfTemplate = { + accessors: [], + animations : [ + { + channels : [], + samplers : [ + { + interpolation : 'LINEAR' + } + ] } - }, - /** - * Gets the reference frame in which the position is defined. - * @memberof SampledPositionProperty.prototype - * @type {ReferenceFrame} - * @default ReferenceFrame.FIXED; - */ - referenceFrame : { - get : function() { - return this._referenceFrame; + ], + asset : {}, + buffers : [ + { + byteLength: 0, + type: 'arraybuffer' } - }, - /** - * 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; + ], + bufferViews: [ + { + byteLength: 0 } - }, - /** - * Gets the interpolation algorithm to use when retrieving a value. - * @memberof SampledPositionProperty.prototype - * - * @type {InterpolationAlgorithm} - * @default LinearApproximation - */ - interpolationAlgorithm : { - get : function() { - return this._property.interpolationAlgorithm; + ], + cameras: [], + images: [], + materials: [ + { + values: function(material) { + var extensions = defaultValue(material.extensions, {}); + var materialsCommon = extensions.KHR_materials_common; + if (!defined(materialsCommon)) { + return {}; + } + }, + extensions: function(material) { + var extensions = defaultValue(material.extensions, {}); + var materialsCommon = extensions.KHR_materials_common; + if (defined(materialsCommon)) { + var technique = materialsCommon.technique; + var defaults = { + ambient: [0.0, 0.0, 0.0, 1.0], + emission: [0.0, 0.0, 0.0, 1.0], + transparency: 1.0 + }; + if (technique !== 'CONSTANT') { + defaults.diffuse = [0.0, 0.0, 0.0, 1.0]; + if (technique !== 'LAMBERT') { + defaults.specular = [0.0, 0.0, 0.0, 1.0]; + defaults.shininess = 0.0; + } + } + return { + KHR_materials_common: { + doubleSided: false, + transparent: false, + values: defaults + } + }; + } + } } - }, - /** - * 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; + ], + meshes : [ + { + primitives : [ + { + attributes : {}, + mode : WebGLConstants.TRIANGLES + } + ] } - }, - /** - * 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; + ], + nodes : [ + { + children : [], + matrix : function(node) { + if (!defined(node.translation) && !defined(node.rotation) && !defined(node.scale)) { + return [ + 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 + ]; + } + }, + rotation : function(node) { + if (defined(node.translation) || defined(node.scale)) { + return [0.0, 0.0, 0.0, 1.0]; + } + }, + scale : function(node) { + if (defined(node.translation) || defined(node.rotation)) { + return [1.0, 1.0, 1.0]; + } + }, + translation : function(node) { + if (defined(node.rotation) || defined(node.scale)) { + return [0.0, 0.0, 0.0]; + } + } } - }, - /** - * 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; + ], + programs : [ + { + attributes : [] } - }, - /** - * 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; + ], + samplers : [ + { + magFilter: WebGLConstants.LINEAR, + minFilter : WebGLConstants.NEAREST_MIPMAP_LINEAR, + wrapS : WebGLConstants.REPEAT, + wrapT : WebGLConstants.REPEAT } - }, - /** - * 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; + ], + scenes : [ + { + nodes : [] + } + ], + shaders : [], + skins : [ + { + 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 + ] + } + ], + techniques : [ + { + parameters: {}, + attributes: {}, + uniforms: {}, + states: { + enable: [] + } + } + ], + textures : [ + { + format: WebGLConstants.RGBA, + internalFormat: WebGLConstants.RGBA, + target: WebGLConstants.TEXTURE_2D, + type: WebGLConstants.UNSIGNED_BYTE + } + ], + extensionsUsed : [], + extensionsRequired : [] + }; + + function addDefaultsFromTemplate(object, template) { + for (var id in template) { + if (template.hasOwnProperty(id)) { + var templateValue = template[id]; + if (typeof templateValue === 'function') { + templateValue = templateValue(object); + } + if (defined(templateValue)) { + if (typeof templateValue === 'object') { + if (Array.isArray(templateValue)) { + var arrayValue = defaultValue(object[id], []); + if (templateValue.length > 0) { + var arrayTemplate = templateValue[0]; + if (typeof arrayTemplate === 'object') { + var arrayValueLength = arrayValue.length; + for (var i = 0; i < arrayValueLength; i++) { + addDefaultsFromTemplate(arrayValue[i], arrayTemplate); + } + } else { + arrayValue = defaultValue(object[id], templateValue); + } + } + object[id] = arrayValue; + } else { + var applyTemplate = templateValue['*']; + object[id] = defaultValue(object[id], {}); + var objectValue = object[id]; + if (defined(applyTemplate)) { + for (var subId in objectValue) { + if (objectValue.hasOwnProperty(subId) && subId !== 'extras') { + var subObject = objectValue[subId]; + addDefaultsFromTemplate(subObject, applyTemplate); + } + } + } else { + addDefaultsFromTemplate(objectValue, templateValue); + } + } + } else { + object[id] = defaultValue(object[id], templateValue); + } + } } } - }); - - /** - * 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) { - - 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); + var defaultMaterial = { + values : { + emission : [ + 0.5, 0.5, 0.5, 1.0 + ] + } }; - /** - * 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; - this._property.addSample(time, position, derivatives); + var defaultTechnique = { + attributes : { + a_position : 'position' + }, + parameters : { + modelViewMatrix : { + semantic : 'MODELVIEW', + type : WebGLConstants.FLOAT_MAT4 + }, + projectionMatrix : { + semantic : 'PROJECTION', + type : WebGLConstants.FLOAT_MAT4 + }, + emission : { + type : WebGLConstants.FLOAT_VEC4, + value : [ + 0.5, 0.5, 0.5, 1.0 + ] + }, + position : { + semantic: 'POSITION', + type: WebGLConstants.FLOAT_VEC3 + } + }, + states : { + enable : [ + WebGLConstants.CULL_FACE, + WebGLConstants.DEPTH_TEST + ] + }, + uniforms : { + u_modelViewMatrix : 'modelViewMatrix', + u_projectionMatrix : 'projectionMatrix', + u_emission : 'emission' + } }; - /** - * 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); + var defaultProgram = { + attributes : [ + 'a_position' + ] }; - /** - * 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(packedSamples, epoch) { - this._property.addSamplesPackedArray(packedSamples, epoch); + var defaultVertexShader = { + type : WebGLConstants.VERTEX_SHADER, + extras : { + _pipeline : { + extension : '.vert', + source : '' + + 'precision highp float;\n' + + '\n' + + 'uniform mat4 u_modelViewMatrix;\n' + + 'uniform mat4 u_projectionMatrix;\n' + + '\n' + + 'attribute vec3 a_position;\n' + + '\n' + + 'void main (void)\n' + + '{\n' + + ' gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(a_position, 1.0);\n' + + '}\n' + } + } }; - /** - * 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); + var defaultFragmentShader = { + type : WebGLConstants.FRAGMENT_SHADER, + extras : { + _pipeline : { + extension: '.frag', + source : '' + + 'precision highp float;\n' + + '\n' + + 'uniform vec4 u_emission;\n' + + '\n' + + 'void main(void)\n' + + '{\n' + + ' gl_FragColor = u_emission;\n' + + '}\n' + } + } }; - return SampledPositionProperty; -}); + function addDefaultMaterial(gltf) { + var materials = gltf.materials; + var meshes = gltf.meshes; -/*global define*/ -define('DataSources/StripeOrientation',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + var defaultMaterialId; - /** - * Defined the orientation of stripes in {@link StripeMaterialProperty}. - * - * @exports StripeOrientation - */ - var StripeOrientation = { - /** - * Horizontal orientation. - * @type {Number} - */ - HORIZONTAL : 0, + var meshesLength = meshes.length; + for (var meshId = 0; meshId < meshesLength; meshId++) { + var mesh = meshes[meshId]; + var primitives = mesh.primitives; + var primitivesLength = primitives.length; + for (var j = 0; j < primitivesLength; j++) { + var primitive = primitives[j]; + if (!defined(primitive.material)) { + if (!defined(defaultMaterialId)) { + defaultMaterialId = addToArray(materials, clone(defaultMaterial, true)); + } + primitive.material = defaultMaterialId; + } + } + } + } - /** - * Vertical orientation. - * @type {Number} - */ - VERTICAL : 1 - }; + function addDefaultTechnique(gltf) { + var materials = gltf.materials; + var techniques = gltf.techniques; + var programs = gltf.programs; + var shaders = gltf.shaders; - return freezeObject(StripeOrientation); -}); + var defaultTechniqueId; -/*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 materialsLength = materials.length; + for (var materialId = 0; materialId < materialsLength; materialId++) { + var material = materials[materialId]; + var techniqueId = material.technique; + var extensions = defaultValue(material.extensions, {}); + var materialsCommon = extensions.KHR_materials_common; + if (!defined(techniqueId)) { + if (!defined(defaultTechniqueId) && !defined(materialsCommon)) { + var technique = clone(defaultTechnique, true); + defaultTechniqueId = addToArray(techniques, technique); - var defaultOrientation = StripeOrientation.HORIZONTAL; - var defaultEvenColor = Color.WHITE; - var defaultOddColor = Color.BLACK; - var defaultOffset = 0; - var defaultRepeat = 1; + var program = clone(defaultProgram, true); + technique.program = addToArray(programs, program); - /** - * 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); + var vertexShader = clone(defaultVertexShader, true); + program.vertexShader = addToArray(shaders, vertexShader); - this._definitionChanged = new Event(); + var fragmentShader = clone(defaultFragmentShader, true); + program.fragmentShader = addToArray(shaders, fragmentShader); + } + material.technique = defaultTechniqueId; + } - this._orientation = undefined; - this._orientationSubscription = undefined; + } + } - this._evenColor = undefined; - this._evenColorSubscription = undefined; + function addDefaultByteOffsets(gltf) { + var accessors = gltf.accessors; - this._oddColor = undefined; - this._oddColorSubscription = undefined; + var accessorLength = accessors.length; + for (var i = 0; i < accessorLength; i++) { + var accessor = accessors[i]; + if (!defined(accessor.byteOffset)) { + accessor.byteOffset = 0; + } + } - this._offset = undefined; - this._offsetSubscription = undefined; + var bufferViews = gltf.bufferViews; - this._repeat = undefined; - this._repeatSubscription = undefined; + var bufferViewsLength = bufferViews.length; + for (var j = 0; j < bufferViewsLength; j++) { + var bufferView = bufferViews[j]; + if (!defined(bufferView.byteOffset)) { + bufferView.byteOffset = 0; + } + } + } - this.orientation = options.orientation; - this.evenColor = options.evenColor; - this.oddColor = options.oddColor; - this.offset = options.offset; - this.repeat = options.repeat; + function selectDefaultScene(gltf) { + if (defined(gltf.scenes) && !defined(gltf.scene)) { + gltf.scene = 0; + } } - 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); + function optimizeForCesium(gltf) { + // Give the diffuse uniform a semantic to support color replacement in 3D Tiles + var techniques = gltf.techniques; + var techniquesLength = techniques.length; + for (var techniqueId = 0; techniqueId < techniquesLength; techniqueId++) { + var technique = techniques[techniqueId]; + var parameters = technique.parameters; + if (defined(parameters.diffuse)) { + parameters.diffuse.semantic = '_3DTILESDIFFUSE'; } - }, - /** - * 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; + } + } + + function inferBufferViewTargets(gltf) { + // If bufferView elements are missing targets, we can infer their type from their use + var needsTarget = {}; + var shouldTraverse = 0; + ForEach.bufferView(gltf, function(bufferView, bufferViewId) { + if (!defined(bufferView.target)) { + needsTarget[bufferViewId] = true; + shouldTraverse++; } - }, - /** - * 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') - }); + }); + if (shouldTraverse > 0) { + var accessors = gltf.accessors; + var bufferViews = gltf.bufferViews; + ForEach.mesh(gltf, function (mesh) { + ForEach.meshPrimitive(mesh, function (primitive) { + var indices = primitive.indices; + if (defined(indices)) { + var accessor = accessors[indices]; + var bufferViewId = accessor.bufferView; + if (needsTarget[bufferViewId]) { + var bufferView = bufferViews[bufferViewId]; + if (defined(bufferView)) { + bufferView.target = WebGLConstants.ELEMENT_ARRAY_BUFFER; + needsTarget[bufferViewId] = false; + shouldTraverse--; + } + } + } + ForEach.meshPrimitiveAttribute(primitive, function (accessorId) { + var accessor = accessors[accessorId]; + var bufferViewId = accessor.bufferView; + if (needsTarget[bufferViewId]) { + var bufferView = bufferViews[bufferViewId]; + if (defined(bufferView)) { + bufferView.target = WebGLConstants.ARRAY_BUFFER; + needsTarget[bufferViewId] = false; + shouldTraverse--; + } + } + }); + ForEach.meshPrimitiveTargetAttribute(primitive, function (targetAttribute) { + var bufferViewId = accessors[targetAttribute].bufferView; + if (needsTarget[bufferViewId]) { + var bufferView = bufferViews[bufferViewId]; + if (defined(bufferView)) { + bufferView.target = WebGLConstants.ARRAY_BUFFER; + needsTarget[bufferViewId] = false; + shouldTraverse--; + } + } + }); + }); + if (shouldTraverse === 0) { + return true; + } + }); + } + } /** - * Gets the {@link Material} type at the provided time. + * Adds default glTF values if they don't exist. * - * @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. + * The glTF asset must be initialized for the pipeline. * - * @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 {Object} gltf A javascript object containing a glTF asset. + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.optimizeForCesium] Optimize the defaults for Cesium. Uses the Cesium sun as the default light source. + * @returns {Object} The modified glTF. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @see addPipelineExtras + * @see loadGltfUris */ - 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)); - }; + function addDefaults(gltf, options) { + options = defaultValue(options, {}); + addDefaultsFromTemplate(gltf, gltfTemplate); + addDefaultMaterial(gltf); + addDefaultTechnique(gltf); + addDefaultByteOffsets(gltf); + selectDefaultScene(gltf); + inferBufferViewTargets(gltf); + if (options.optimizeForCesium) { + optimizeForCesium(gltf); + } + return gltf; + } - return StripeMaterialProperty; + return addDefaults; }); -/*global define*/ -define('DataSources/TimeIntervalCollectionPositionProperty',[ - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/ReferenceFrame', - '../Core/TimeIntervalCollection', - './PositionProperty', - './Property' +define('ThirdParty/GltfPipeline/addPipelineExtras',[ + '../../Core/defaultValue', + '../../Core/defined' ], function( defaultValue, - defined, - defineProperties, - DeveloperError, - Event, - ReferenceFrame, - TimeIntervalCollection, - PositionProperty, - Property) { + defined) { 'use strict'; + // Objects with these ids should not have extras added + var exceptions = { + attributes: true, + uniforms: true, + extensions: true, + values: true, + samplers: true + }; + /** - * A {@link TimeIntervalCollectionProperty} which is also a {@link PositionProperty}. - * - * @alias TimeIntervalCollectionPositionProperty - * @constructor + * Adds extras._pipeline to each object that can have extras in the glTF asset. * - * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined. + * @param {Object} gltf A javascript object containing a glTF asset. + * @returns {Object} The glTF asset with the added pipeline extras. */ - 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; + function addPipelineExtras(gltf) { + var objectStack = []; + for (var rootArrayId in gltf) { + if (gltf.hasOwnProperty(rootArrayId)) { + var rootArray = gltf[rootArrayId]; + var rootArrayLength = rootArray.length; + for (var i = 0; i < rootArrayLength; i++) { + var rootObject = rootArray[i]; + if (defined(rootObject) && typeof rootObject === 'object') { + rootObject.extras = defaultValue(rootObject.extras, {}); + rootObject.extras._pipeline = defaultValue(rootObject.extras._pipeline, {}); + objectStack.push(rootObject); + } + } } - }, - /** - * Gets the reference frame in which the position is defined. - * @memberof TimeIntervalCollectionPositionProperty.prototype - * @type {ReferenceFrame} - * @default ReferenceFrame.FIXED; - */ - referenceFrame : { - get : function() { - return this._referenceFrame; + } + while (objectStack.length > 0) { + var object = objectStack.pop(); + for (var propertyId in object) { + if (object.hasOwnProperty(propertyId)) { + var property = object[propertyId]; + if (defined(property) && typeof property === 'object' && propertyId !== 'extras') { + objectStack.push(property); + if (!exceptions[propertyId] && !Array.isArray(property)) { + property.extras = defaultValue(property.extras, {}); + property.extras._pipeline = defaultValue(property.extras._pipeline, {}); + } + } + } } } - }); + gltf.extras = defaultValue(gltf.extras, {}); + gltf.extras._pipeline = defaultValue(gltf.extras._pipeline, {}); + gltf.asset = defaultValue(gltf.asset, {}); + gltf.asset.extras = defaultValue(gltf.asset.extras, {}); + if (defined(gltf.asset.extras) && typeof(gltf.asset.extras) !== 'object') { + gltf.asset.extras = { + extras : gltf.asset.extras + }; + } + gltf.asset.extras._pipeline = defaultValue(gltf.asset.extras._pipeline, {}); + return gltf; + } + return addPipelineExtras; +}); + +define('ThirdParty/GltfPipeline/byteLengthForComponentType',[ + '../../Core/WebGLConstants' + ], function( + WebGLConstants) { + 'use strict'; /** - * Gets the value of the property at the provided time in the fixed frame. + * Utility function for retrieving the byte length of a component type. + * As per the spec: + * 5120 (BYTE) : 1 + * 5121 (UNSIGNED_BYTE) : 1 + * 5122 (SHORT) : 2 + * 5123 (UNSIGNED_SHORT) : 2 + * 5126 (FLOAT) : 4 + * 5125 (UNSIGNED_INT) : 4 * - * @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. + * @param {Number} [componentType] + * @returns {Number} The byte length of the component type. */ - TimeIntervalCollectionPositionProperty.prototype.getValue = function(time, result) { - return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result); - }; + function byteLengthForComponentType(componentType) { + switch (componentType) { + case WebGLConstants.BYTE: + case WebGLConstants.UNSIGNED_BYTE: + return 1; + case WebGLConstants.SHORT: + case WebGLConstants.UNSIGNED_SHORT: + return 2; + case WebGLConstants.FLOAT: + case WebGLConstants.UNSIGNED_INT: + return 4; + } + } + return byteLengthForComponentType; +}); + +define('ThirdParty/GltfPipeline/numberOfComponentsForType',[], function() { + 'use strict'; /** - * Gets the value of the property at the provided time and in the provided reference frame. + * Utility function for retrieving the number of components in a given type. + * As per the spec: + * 'SCALAR' : 1 + * 'VEC2' : 2 + * 'VEC3' : 3 + * 'VEC4' : 4 + * 'MAT2' : 4 + * 'MAT3' : 9 + * 'MAT4' : 16 * - * @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. + * @param {String} type glTF type + * @returns {Number} The number of components in that type. */ - TimeIntervalCollectionPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { - - var position = this._intervals.findDataForIntervalContainingDate(time); - if (defined(position)) { - return PositionProperty.convertToReferenceFrame(time, position, this._referenceFrame, referenceFrame, result); + function numberOfComponentsForType(type) { + switch (type) { + case 'SCALAR': + return 1; + case 'VEC2': + return 2; + case 'VEC3': + return 3; + case 'VEC4': + case 'MAT2': + return 4; + case 'MAT3': + return 9; + case 'MAT4': + return 16; } - return undefined; - }; + } + return numberOfComponentsForType; +}); + +define('ThirdParty/GltfPipeline/getAccessorByteStride',[ + './byteLengthForComponentType', + './numberOfComponentsForType', + '../../Core/defined' + ], function( + byteLengthForComponentType, + numberOfComponentsForType, + defined) { + 'use strict'; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * Returns the byte stride of the provided accessor. + * If the byteStride is 0, it is calculated based on type and componentType * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @param {Object} gltf A javascript object containing a glTF asset. + * @param {Object} accessor The accessor. + * @returns {Number} The byte stride of the accessor. */ - TimeIntervalCollectionPositionProperty.prototype.equals = function(other) { - return this === other || // - (other instanceof TimeIntervalCollectionPositionProperty && // - this._intervals.equals(other._intervals, Property.equals) && // - this._referenceFrame === other._referenceFrame); - }; + function getAccessorByteStride(gltf, accessor) { + var bufferView = gltf.bufferViews[accessor.bufferView]; + if (defined(bufferView.byteStride) && bufferView.byteStride > 0) { + return bufferView.byteStride; + } + return byteLengthForComponentType(accessor.componentType) * numberOfComponentsForType(accessor.type); + } + return getAccessorByteStride; +}); + +define('ThirdParty/GltfPipeline/removeExtensionsRequired',[ + '../../Core/defined' + ], function( + defined) { + 'use strict'; /** - * @private + * Removes an extension from gltf.extensionsRequired if it is present. + * + * @param {Object} gltf A javascript object containing a glTF asset. + * @param {String} extension The extension to remove. */ - TimeIntervalCollectionPositionProperty.prototype._intervalsChanged = function() { - this._definitionChanged.raiseEvent(this); - }; + function removeExtensionsRequired(gltf, extension) { + var extensionsRequired = gltf.extensionsRequired; + if (defined(extensionsRequired)) { + var index = extensionsRequired.indexOf(extension); + if (index >= 0) { + extensionsRequired.splice(index, 1); + } + if (extensionsRequired.length === 0) { + delete gltf.extensionsRequired; + } + } + } - return TimeIntervalCollectionPositionProperty; + return removeExtensionsRequired; }); -/*global define*/ -define('DataSources/TimeIntervalCollectionProperty',[ - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/TimeIntervalCollection', - './Property' +define('ThirdParty/GltfPipeline/removeExtensionsUsed',[ + './removeExtensionsRequired', + '../../Core/defined' ], function( - defined, - defineProperties, - DeveloperError, - Event, - TimeIntervalCollection, - Property) { + removeExtensionsRequired, + defined) { '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 + * Removes an extension from gltf.extensionsUsed and gltf.extensionsRequired if it is present. * - * @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) - * })); + * @param {Object} gltf A javascript object containing a glTF asset. + * @param {String} extension The extension to remove. */ - 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; + function removeExtensionsUsed(gltf, extension) { + var extensionsUsed = gltf.extensionsUsed; + if (defined(extensionsUsed)) { + var index = extensionsUsed.indexOf(extension); + if (index >= 0) { + extensionsUsed.splice(index, 1); } - }, - /** - * Gets the interval collection. - * @memberof TimeIntervalCollectionProperty.prototype - * - * @type {TimeIntervalCollection} - */ - intervals : { - get : function() { - return this._intervals; + removeExtensionsRequired(gltf, extension); + if (extensionsUsed.length === 0) { + delete gltf.extensionsUsed; } } - }); + } + return removeExtensionsUsed; +}); + +define('ThirdParty/GltfPipeline/addExtensionsUsed',[ + '../../Core/defined' + ], function( + defined) { + 'use strict'; /** - * Gets the value of the property at the provided time. + * Adds an extension to gltf.extensionsUsed if it does not already exist. + * Initializes extensionsUsed if it is not defined. * - * @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. + * @param {Object} gltf A javascript object containing a glTF asset. + * @param {String} extension The extension to add. */ - TimeIntervalCollectionProperty.prototype.getValue = function(time, result) { - - var value = this._intervals.findDataForIntervalContainingDate(time); - if (defined(value) && (typeof value.clone === 'function')) { - return value.clone(result); + function addExtensionsUsed(gltf, extension) { + var extensionsUsed = gltf.extensionsUsed; + if (!defined(extensionsUsed)) { + extensionsUsed = []; + gltf.extensionsUsed = extensionsUsed; } - return value; - }; + if (extensionsUsed.indexOf(extension) < 0) { + extensionsUsed.push(extension); + } + } + return addExtensionsUsed; +}); - /** - * 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)); - }; +define('ThirdParty/GltfPipeline/addExtensionsRequired',[ + './addExtensionsUsed', + '../../Core/defined' + ], function( + addExtensionsUsed, + defined) { + 'use strict'; /** - * @private + * Adds an extension to gltf.extensionsRequired if it does not already exist. + * Initializes extensionsRequired if it is not defined. + * + * @param {Object} gltf A javascript object containing a glTF asset. + * @param {String} extension The extension to add. */ - TimeIntervalCollectionProperty.prototype._intervalsChanged = function() { - this._definitionChanged.raiseEvent(this); - }; - - return TimeIntervalCollectionProperty; + function addExtensionsRequired(gltf, extension) { + var extensionsRequired = gltf.extensionsRequired; + if (!defined(extensionsRequired)) { + extensionsRequired = []; + gltf.extensionsRequired = extensionsRequired; + } + if (extensionsRequired.indexOf(extension) < 0) { + extensionsRequired.push(extension); + } + addExtensionsUsed(gltf, extension); + } + return addExtensionsRequired; }); -/*global define*/ -define('DataSources/VelocityVectorProperty',[ - '../Core/Cartesian3', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/JulianDate', - './Property' +define('ThirdParty/GltfPipeline/updateVersion',[ + './addExtensionsRequired', + './addToArray', + './ForEach', + './getAccessorByteStride', + '../../Core/Cartesian3', + '../../Core/Math', + '../../Core/clone', + '../../Core/defaultValue', + '../../Core/defined', + '../../Core/Quaternion', + '../../Core/WebGLConstants' ], function( + addExtensionsRequired, + addToArray, + ForEach, + getAccessorByteStride, Cartesian3, + CesiumMath, + clone, defaultValue, defined, - defineProperties, - DeveloperError, - Event, - JulianDate, - Property) { + Quaternion, + WebGLConstants) { 'use strict'; + var updateFunctions = { + '0.8' : glTF08to10, + '1.0' : glTF10to20, + '2.0' : undefined + }; + /** - * 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. + * Update the glTF version to the latest version (2.0), or targetVersion if specified. + * Applies changes made to the glTF spec between revisions so that the core library + * only has to handle the latest version. * - * @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 - * } - * })); + * @param {Object} gltf A javascript object containing a glTF asset. + * @param {Object} [options] Options for updating the glTF. + * @param {String} [options.targetVersion] The glTF will be upgraded until it hits the specified version. + * @returns {Object} The updated glTF asset. */ - function VelocityVectorProperty(position, normalize) { - this._position = undefined; - this._subscription = undefined; - this._definitionChanged = new Event(); - this._normalize = defaultValue(normalize, true); + function updateVersion(gltf, options) { + options = defaultValue(options, {}); + var targetVersion = options.targetVersion; + var version = gltf.version; - this.position = position; + gltf.asset = defaultValue(gltf.asset, { + version: '1.0' + }); + + version = defaultValue(version, gltf.asset.version); + // invalid version + if (!updateFunctions.hasOwnProperty(version)) { + // try truncating trailing version numbers, could be a number as well if it is 0.8 + if (defined(version)) { + version = ('' + version).substring(0, 3); + } + // default to 1.0 if it cannot be determined + if (!updateFunctions.hasOwnProperty(version)) { + version = '1.0'; + } + } + + var updateFunction = updateFunctions[version]; + + while (defined(updateFunction)) { + if (version === targetVersion) { + break; + } + updateFunction(gltf); + version = gltf.asset.version; + updateFunction = updateFunctions[version]; + } + return gltf; } - 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); + function updateInstanceTechniques(gltf) { + var materials = gltf.materials; + for (var materialId in materials) { + if (materials.hasOwnProperty(materialId)) { + var material = materials[materialId]; + var instanceTechnique = material.instanceTechnique; + if (defined(instanceTechnique)) { + material.technique = instanceTechnique.technique; + material.values = instanceTechnique.values; + delete material.instanceTechnique; + } } - }, - /** - * 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; + } + } + + function setPrimitiveModes(gltf) { + var meshes = gltf.meshes; + for (var meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var mesh = meshes[meshId]; + var primitives = mesh.primitives; + if (defined(primitives)) { + var primitivesLength = primitives.length; + for (var i = 0; i < primitivesLength; i++) { + var primitive = primitives[i]; + var defaultMode = defaultValue(primitive.primitive, WebGLConstants.TRIANGLES); + primitive.mode = defaultValue(primitive.mode, defaultMode); + delete primitive.primitive; + } + } } - }, - /** - * 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(); + } + } + + function updateNodes(gltf) { + var nodes = gltf.nodes; + var axis = new Cartesian3(); + var quat = new Quaternion(); + for (var nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + var node = nodes[nodeId]; + if (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]; + } + var instanceSkin = node.instanceSkin; + if (defined(instanceSkin)) { + node.skeletons = instanceSkin.skeletons; + node.skin = instanceSkin.skin; + node.meshes = instanceSkin.meshes; + delete node.instanceSkin; + } + } + } + } + + function removeTechniquePasses(gltf) { + var techniques = gltf.techniques; + for (var techniqueId in techniques) { + if (techniques.hasOwnProperty(techniqueId)) { + var technique = techniques[techniqueId]; + 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); } + delete technique.passes; + delete technique.pass; + } + } + } + } - this._position = value; + function glTF08to10(gltf) { + if (!defined(gltf.asset)) { + gltf.asset = {}; + } + var asset = gltf.asset; + asset.version = '1.0'; + // profile should be an object, not a string + if (!defined(asset.profile) || (typeof asset.profile === 'string')) { + asset.profile = {}; + } + // version property should be in asset, not on the root element + if (defined(gltf.version)) { + delete gltf.version; + } + // material.instanceTechnique properties should be directly on the material + updateInstanceTechniques(gltf); + // primitive.primitive should be primitive.mode + setPrimitiveModes(gltf); + // node rotation should be quaternion, not axis-angle + // node.instanceSkin is deprecated + updateNodes(gltf); + // technique.pass and techniques.passes are deprecated + removeTechniquePasses(gltf); + // gltf.lights -> khrMaterialsCommon.lights + if (defined(gltf.lights)) { + var extensions = defaultValue(gltf.extensions, {}); + gltf.extensions = extensions; + var materialsCommon = defaultValue(extensions.KHR_materials_common, {}); + extensions.KHR_materials_common = materialsCommon; + materialsCommon.lights = gltf.lights; + delete gltf.lights; + } + // gltf.allExtensions -> extensionsUsed + if (defined(gltf.allExtensions)) { + gltf.extensionsUsed = gltf.allExtensions; + gltf.allExtensions = undefined; + } + } - if (defined(value)) { - this._subscription = value._definitionChanged.addEventListener(function() { - this._definitionChanged.raiseEvent(this); - }, this); + function removeAnimationSamplersIndirection(gltf) { + var animations = gltf.animations; + for (var animationId in animations) { + if (animations.hasOwnProperty(animationId)) { + var animation = animations[animationId]; + var parameters = animation.parameters; + if (defined(parameters)) { + var samplers = animation.samplers; + for (var samplerId in samplers) { + if (samplers.hasOwnProperty(samplerId)) { + var sampler = samplers[samplerId]; + sampler.input = parameters[sampler.input]; + sampler.output = parameters[sampler.output]; + } } + delete animation.parameters; + } + } + } + } - this._definitionChanged.raiseEvent(this); + function objectToArray(object, mapping) { + var array = []; + for (var id in object) { + if (object.hasOwnProperty(id)) { + var value = object[id]; + mapping[id] = array.length; + array.push(value); + if (!defined(value.name) && typeof(value) === 'object') { + value.name = id; } } - }, - /** - * 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; + } + return array; + } + + function objectsToArrays(gltf) { + var i; + var globalMapping = { + accessors: {}, + animations: {}, + bufferViews: {}, + buffers: {}, + cameras: {}, + materials: {}, + meshes: {}, + nodes: {}, + programs: {}, + shaders: {}, + skins: {}, + techniques: {} + }; + + // Map joint names to id names + var jointName; + var jointNameToId = {}; + var nodes = gltf.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + jointName = nodes[id].jointName; + if (defined(jointName)) { + jointNameToId[jointName] = id; + } + } + } + + // Convert top level objects to arrays + for (var topLevelId in gltf) { + if (gltf.hasOwnProperty(topLevelId) && topLevelId !== 'extras' && topLevelId !== 'asset' && topLevelId !== 'extensions') { + var objectMapping = {}; + var object = gltf[topLevelId]; + if (typeof(object) === 'object' && !Array.isArray(object)) { + gltf[topLevelId] = objectToArray(object, objectMapping); + globalMapping[topLevelId] = objectMapping; + if (topLevelId === 'animations') { + objectMapping = {}; + object.samplers = objectToArray(object.samplers, objectMapping); + globalMapping[topLevelId].samplers = objectMapping; + } + } + } + } + + // Remap joint names to array indexes + for (jointName in jointNameToId) { + if (jointNameToId.hasOwnProperty(jointName)) { + jointNameToId[jointName] = globalMapping.nodes[jointNameToId[jointName]]; + } + } + + // Fix references + if (defined(gltf.scene)) { + gltf.scene = globalMapping.scenes[gltf.scene]; + } + ForEach.bufferView(gltf, function(bufferView) { + if (defined(bufferView.buffer)) { + bufferView.buffer = globalMapping.buffers[bufferView.buffer]; + } + }); + ForEach.accessor(gltf, function(accessor) { + if (defined(accessor.bufferView)) { + accessor.bufferView = globalMapping.bufferViews[accessor.bufferView]; + } + }); + ForEach.shader(gltf, function(shader) { + var extensions = shader.extensions; + if (defined(extensions)) { + var binaryGltf = extensions.KHR_binary_glTF; + if (defined(binaryGltf)) { + shader.bufferView = globalMapping.bufferViews[binaryGltf.bufferView]; + delete extensions.KHR_binary_glTF; + } + if (Object.keys(extensions).length === 0) { + delete shader.extensions; + } + } + }); + ForEach.program(gltf, function(program) { + if (defined(program.vertexShader)) { + program.vertexShader = globalMapping.shaders[program.vertexShader]; + } + if (defined(program.fragmentShader)) { + program.fragmentShader = globalMapping.shaders[program.fragmentShader]; + } + }); + ForEach.technique(gltf, function(technique) { + if (defined(technique.program)) { + technique.program = globalMapping.programs[technique.program]; + } + ForEach.techniqueParameter(technique, function(parameter) { + if (defined(parameter.node)) { + parameter.node = globalMapping.nodes[parameter.node]; + } + var value = parameter.value; + if (defined(value)) { + if (Array.isArray(value)) { + if (value.length === 1) { + var textureId = value[0]; + if (typeof textureId === 'string') { + value[0] = globalMapping.textures[textureId]; + } + } + } + else if (typeof value === 'string') { + parameter.value = [globalMapping.textures[value]]; + } + } + }); + }); + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + if (defined(primitive.indices)) { + primitive.indices = globalMapping.accessors[primitive.indices]; + } + ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) { + primitive.attributes[semantic] = globalMapping.accessors[accessorId]; + }); + if (defined(primitive.material)) { + primitive.material = globalMapping.materials[primitive.material]; + } + }); + }); + ForEach.node(gltf, function(node) { + var children = node.children; + if (defined(children)) { + var childrenLength = children.length; + for (i = 0; i < childrenLength; i++) { + children[i] = globalMapping.nodes[children[i]]; + } + } + if (defined(node.meshes)) { + // Split out meshes on nodes + var meshes = node.meshes; + var meshesLength = meshes.length; + if (meshesLength > 0) { + node.mesh = globalMapping.meshes[meshes[0]]; + for (i = 1; i < meshesLength; i++) { + var meshNode = { + mesh: globalMapping.meshes[meshes[i]], + extras: { + _pipeline: {} + } + }; + var meshNodeId = addToArray(gltf.nodes, meshNode); + if (!defined(children)) { + children = []; + node.children = children; + } + children.push(meshNodeId); + } } + delete node.meshes; + } + if (defined(node.camera)) { + node.camera = globalMapping.cameras[node.camera]; + } + if (defined(node.skeletons)) { + // Assign skeletons to skins + var skeletons = node.skeletons; + var skeletonsLength = skeletons.length; + if ((skeletonsLength > 0) && defined(node.skin)) { + var skin = gltf.skins[globalMapping.skins[node.skin]]; + skin.skeleton = globalMapping.nodes[skeletons[0]]; + } + delete node.skeletons; + } + if (defined(node.skin)) { + node.skin = globalMapping.skins[node.skin]; + } + if (defined(node.jointName)) { + delete(node.jointName); + } + }); + ForEach.skin(gltf, function(skin) { + if (defined(skin.inverseBindMatrices)) { + skin.inverseBindMatrices = globalMapping.accessors[skin.inverseBindMatrices]; + } + var joints = []; + var jointNames = skin.jointNames; + if (defined(jointNames)) { + for (i = 0; i < jointNames.length; i++) { + joints[i] = jointNameToId[jointNames[i]]; + } + skin.joints = joints; + delete skin.jointNames; + } + }); + ForEach.scene(gltf, function(scene) { + var sceneNodes = scene.nodes; + if (defined(sceneNodes)) { + var sceneNodesLength = sceneNodes.length; + for (i = 0; i < sceneNodesLength; i++) { + sceneNodes[i] = globalMapping.nodes[sceneNodes[i]]; + } + } + }); + ForEach.animation(gltf, function(animation) { + var samplerMapping = {}; + animation.samplers = objectToArray(animation.samplers, samplerMapping); + ForEach.animationSampler(animation, function(sampler) { + sampler.input = globalMapping.accessors[sampler.input]; + sampler.output = globalMapping.accessors[sampler.output]; + }); + var channels = animation.channels; + if (defined(channels)) { + var channelsLength = channels.length; + for (i = 0; i < channelsLength; i++) { + var channel = channels[i]; + channel.sampler = samplerMapping[channel.sampler]; + var target = channel.target; + if (defined(target)) { + target.node = globalMapping.nodes[target.id]; + delete target.id; + } + } + } + }); + ForEach.material(gltf, function(material) { + if (defined(material.technique)) { + material.technique = globalMapping.techniques[material.technique]; + } + ForEach.materialValue(material, function(value, name) { + if (Array.isArray(value)) { + if (value.length === 1) { + var textureId = value[0]; + if (typeof textureId === 'string') { + value[0] = globalMapping.textures[textureId]; + } + } + } + else if (typeof value === 'string') { + material.values[name] = { + index : globalMapping.textures[value] + }; + } + }); + var extensions = material.extensions; + if (defined(extensions)) { + var materialsCommon = extensions.KHR_materials_common; + if (defined(materialsCommon)) { + ForEach.materialValue(materialsCommon, function(value, name) { + if (Array.isArray(value)) { + if (value.length === 1) { + var textureId = value[0]; + if (typeof textureId === 'string') { + value[0] = globalMapping.textures[textureId]; + } + } + } + else if (typeof value === 'string') { + materialsCommon.values[name] = { + index: globalMapping.textures[value] + }; + } + }); + } + } + }); + ForEach.image(gltf, function(image) { + var extensions = image.extensions; + if (defined(extensions)) { + var binaryGltf = extensions.KHR_binary_glTF; + if (defined(binaryGltf)) { + image.bufferView = globalMapping.bufferViews[binaryGltf.bufferView]; + image.mimeType = binaryGltf.mimeType; + delete extensions.KHR_binary_glTF; + } + if (Object.keys(extensions).length === 0) { + delete image.extensions; + } + } + if (defined(image.extras)) { + var compressedImages = image.extras.compressedImage3DTiles; + for (var type in compressedImages) { + if (compressedImages.hasOwnProperty(type)) { + var compressedImage = compressedImages[type]; + var compressedExtensions = compressedImage.extensions; + if (defined(compressedExtensions)) { + var compressedBinaryGltf = compressedExtensions.KHR_binary_glTF; + if (defined(compressedBinaryGltf)) { + compressedImage.bufferView = globalMapping.bufferViews[compressedBinaryGltf.bufferView]; + compressedImage.mimeType = compressedBinaryGltf.mimeType; + delete compressedExtensions.KHR_binary_glTF; + } + if (Object.keys(compressedExtensions).length === 0) { + delete compressedImage.extensions; + } + } + } + } + } + }); + ForEach.texture(gltf, function(texture) { + if (defined(texture.sampler)) { + texture.sampler = globalMapping.samplers[texture.sampler]; + } + if (defined(texture.source)) { + texture.source = globalMapping.images[texture.source]; + } + }); + } + + function stripProfile(gltf) { + var asset = gltf.asset; + delete asset.profile; + } + + var knownExtensions = { + CESIUM_RTC : true, + KHR_materials_common : true, + WEB3D_quantized_attributes : true + }; + function requireKnownExtensions(gltf) { + var extensionsUsed = gltf.extensionsUsed; + gltf.extensionsRequired = defaultValue(gltf.extensionsRequired, []); + if (defined(extensionsUsed)) { + var extensionsUsedLength = extensionsUsed.length; + for (var i = 0; i < extensionsUsedLength; i++) { + var extension = extensionsUsed[i]; + if (defined(knownExtensions[extension])) { + gltf.extensionsRequired.push(extension); + } + } + } + } + + function removeBufferType(gltf) { + ForEach.buffer(gltf, function(buffer) { + delete buffer.type; + }); + } + + function requireAttributeSetIndex(gltf) { + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) { + if (semantic === 'TEXCOORD') { + primitive.attributes.TEXCOORD_0 = accessorId; + } else if (semantic === 'COLOR') { + primitive.attributes.COLOR_0 = accessorId; + } + }); + delete primitive.attributes.TEXCOORD; + delete primitive.attributes.COLOR; + }); + }); + ForEach.technique(gltf, function(technique) { + ForEach.techniqueParameter(technique, function(parameter) { + var semantic = parameter.semantic; + if (defined(semantic)) { + if (semantic === 'TEXCOORD') { + parameter.semantic = 'TEXCOORD_0'; + } else if (semantic === 'COLOR') { + parameter.semantic = 'COLOR_0'; + } + } + }); + }); + } + + var knownSemantics = { + POSITION: true, + NORMAL: true, + TEXCOORD: true, + COLOR: true, + JOINT: true, + WEIGHT: true + }; + function underscoreApplicationSpecificSemantics(gltf) { + var mappedSemantics = {}; + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + /* jshint unused:vars */ + ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) { + if (semantic.charAt(0) !== '_') { + var setIndex = semantic.search(/_[0-9]+/g); + var strippedSemantic = semantic; + if (setIndex >= 0) { + strippedSemantic = semantic.substring(0, setIndex); + } + if (!defined(knownSemantics[strippedSemantic])) { + var newSemantic = '_' + semantic; + mappedSemantics[semantic] = newSemantic; + } + } + }); + for (var semantic in mappedSemantics) { + if (mappedSemantics.hasOwnProperty(semantic)) { + var mappedSemantic = mappedSemantics[semantic]; + var accessorId = primitive.attributes[semantic]; + if (defined(accessorId)) { + delete primitive.attributes[semantic]; + primitive.attributes[mappedSemantic] = accessorId; + } + } + } + }); + }); + ForEach.technique(gltf, function(technique) { + ForEach.techniqueParameter(technique, function(parameter) { + var mappedSemantic = mappedSemantics[parameter.semantic]; + if (defined(mappedSemantic)) { + parameter.semantic = mappedSemantic; + } + }); + }); + } + + function removeScissorFromTechniques(gltf) { + ForEach.technique(gltf, function(technique) { + var techniqueStates = technique.states; + if (defined(techniqueStates)) { + var techniqueFunctions = techniqueStates.functions; + if (defined(techniqueFunctions)) { + delete techniqueFunctions.scissor; + } + var enableStates = techniqueStates.enable; + if (defined(enableStates)) { + var scissorIndex = enableStates.indexOf(WebGLConstants.SCISSOR_TEST); + if (scissorIndex >= 0) { + enableStates.splice(scissorIndex, 1); + } + } + } + }); + } + + function clampTechniqueFunctionStates(gltf) { + ForEach.technique(gltf, function(technique) { + var techniqueStates = technique.states; + if (defined(techniqueStates)) { + var functions = techniqueStates.functions; + if (defined(functions)) { + var blendColor = functions.blendColor; + if (defined(blendColor)) { + for (var i = 0; i < 4; i++) { + blendColor[i] = CesiumMath.clamp(blendColor[i], 0.0, 1.0); + } + } + var depthRange = functions.depthRange; + if (defined(depthRange)) { + depthRange[1] = CesiumMath.clamp(depthRange[1], 0.0, 1.0); + depthRange[0] = CesiumMath.clamp(depthRange[0], 0.0, depthRange[1]); + } + } + } + }); + } + + function clampCameraParameters(gltf) { + ForEach.camera(gltf, function(camera) { + var perspective = camera.perspective; + if (defined(perspective)) { + var aspectRatio = perspective.aspectRatio; + if (defined(aspectRatio) && aspectRatio === 0.0) { + delete perspective.aspectRatio; + } + var yfov = perspective.yfov; + if (defined(yfov) && yfov === 0.0) { + perspective.yfov = 1.0; + } + } + }); + } + + function requireByteLength(gltf) { + ForEach.buffer(gltf, function(buffer) { + if (!defined(buffer.byteLength)) { + buffer.byteLength = buffer.extras._pipeline.source.length; + } + }); + ForEach.bufferView(gltf, function(bufferView) { + if (!defined(bufferView.byteLength)) { + var bufferViewBufferId = bufferView.buffer; + var bufferViewBuffer = gltf.buffers[bufferViewBufferId]; + bufferView.byteLength = bufferViewBuffer.byteLength; + } + }); + } + + function moveByteStrideToBufferView(gltf) { + var bufferViews = gltf.bufferViews; + var bufferViewsToDelete = {}; + ForEach.accessor(gltf, function(accessor) { + var oldBufferViewId = accessor.bufferView; + if (defined(oldBufferViewId)) { + if (!defined(bufferViewsToDelete[oldBufferViewId])) { + bufferViewsToDelete[oldBufferViewId] = true; + } + var bufferView = clone(bufferViews[oldBufferViewId]); + var accessorByteStride = (defined(accessor.byteStride) && accessor.byteStride !== 0) ? accessor.byteStride : getAccessorByteStride(gltf, accessor); + if (defined(accessorByteStride)) { + bufferView.byteStride = accessorByteStride; + if (bufferView.byteStride !== 0) { + bufferView.byteLength = accessor.count * accessorByteStride; + } + bufferView.byteOffset += accessor.byteOffset; + accessor.byteOffset = 0; + delete accessor.byteStride; + } + accessor.bufferView = addToArray(bufferViews, bufferView); + } + }); + + var bufferViewShiftMap = {}; + var bufferViewRemovalCount = 0; + /* jshint unused:vars */ + ForEach.bufferView(gltf, function(bufferView, bufferViewId) { + if (defined(bufferViewsToDelete[bufferViewId])) { + bufferViewRemovalCount++; + } else { + bufferViewShiftMap[bufferViewId] = bufferViewId - bufferViewRemovalCount; + } + }); + + var removedCount = 0; + for (var bufferViewId in bufferViewsToDelete) { + if (defined(bufferViewId)) { + var index = parseInt(bufferViewId) - removedCount; + bufferViews.splice(index, 1); + removedCount++; + } + } + + ForEach.accessor(gltf, function(accessor) { + var accessorBufferView = accessor.bufferView; + if (defined(accessorBufferView)) { + accessor.bufferView = bufferViewShiftMap[accessorBufferView]; + } + }); + + ForEach.shader(gltf, function(shader) { + var shaderBufferView = shader.bufferView; + if (defined(shaderBufferView)) { + shader.bufferView = bufferViewShiftMap[shaderBufferView]; + } + }); - this._normalize = value; - this._definitionChanged.raiseEvent(this); + ForEach.image(gltf, function(image) { + var imageBufferView = image.bufferView; + if (defined(imageBufferView)) { + image.bufferView = bufferViewShiftMap[imageBufferView]; + } + if (defined(image.extras)) { + var compressedImages = image.extras.compressedImage3DTiles; + for (var type in compressedImages) { + if (compressedImages.hasOwnProperty(type)) { + var compressedImage = compressedImages[type]; + var compressedImageBufferView = compressedImage.bufferView; + if (defined(compressedImageBufferView)) { + compressedImage.bufferView = bufferViewShiftMap[compressedImageBufferView]; + } + } + } } + }); + } + + function stripTechniqueAttributeValues(gltf) { + ForEach.technique(gltf, function(technique) { + ForEach.techniqueAttribute(technique, function(attribute) { + var parameter = technique.parameters[attribute]; + if (defined(parameter.value)) { + delete parameter.value; + } + }); + }); + } + + function stripTechniqueParameterCount(gltf) { + ForEach.technique(gltf, function(technique) { + ForEach.techniqueParameter(technique, function(parameter) { + if (defined(parameter.count)) { + var semantic = parameter.semantic; + if (!defined(semantic) || (semantic !== 'JOINTMATRIX' && semantic.indexOf('_') !== 0)) { + delete parameter.count; + } + } + }); + }); + } + + function addKHRTechniqueExtension(gltf) { + var techniques = gltf.techniques; + if (defined(techniques) && techniques.length > 0) { + addExtensionsRequired(gltf, 'KHR_technique_webgl'); } - }); + } - var position1Scratch = new Cartesian3(); - var position2Scratch = new Cartesian3(); - var timeScratch = new JulianDate(); - var step = 1.0 / 60.0; + function glTF10to20(gltf) { + if (!defined(gltf.asset)) { + gltf.asset = {}; + } + var asset = gltf.asset; + asset.version = '2.0'; + // material.instanceTechnique properties should be directly on the material. instanceTechnique is a gltf 0.8 property but is seen in some 1.0 models. + updateInstanceTechniques(gltf); + // animation.samplers now refers directly to accessors and animation.parameters should be removed + removeAnimationSamplersIndirection(gltf); + // top-level objects are now arrays referenced by index instead of id + objectsToArrays(gltf); + // asset.profile no longer exists + stripProfile(gltf); + // move known extensions from extensionsUsed to extensionsRequired + requireKnownExtensions(gltf); + // bufferView.byteLength and buffer.byteLength are required + requireByteLength(gltf); + // byteStride moved from accessor to bufferView + moveByteStrideToBufferView(gltf); + // buffer.type is unnecessary and should be removed + removeBufferType(gltf); + // TEXCOORD and COLOR attributes must be written with a set index (TEXCOORD_#) + requireAttributeSetIndex(gltf); + // Add underscores to application-specific parameters + underscoreApplicationSpecificSemantics(gltf); + // remove scissor from techniques + removeScissorFromTechniques(gltf); + // clamp technique function states to min/max + clampTechniqueFunctionStates(gltf); + // clamp camera parameters + clampCameraParameters(gltf); + // a technique parameter specified as an attribute cannot have a value + stripTechniqueAttributeValues(gltf); + // only techniques with a JOINTMATRIX or application specific semantic may have a defined count property + stripTechniqueParameterCount(gltf); + // add KHR_technique_webgl extension + addKHRTechniqueExtension(gltf); + } + return updateVersion; +}); + +define('ThirdParty/GltfPipeline/parseBinaryGltf',[ + './addPipelineExtras', + './removeExtensionsUsed', + './updateVersion', + '../../Core/ComponentDatatype', + '../../Core/defined', + '../../Core/DeveloperError', + '../../Core/getMagic', + '../../Core/getStringFromTypedArray', + '../../Core/WebGLConstants' + ], function( + addPipelineExtras, + removeExtensionsUsed, + updateVersion, + ComponentDatatype, + defined, + DeveloperError, + getMagic, + getStringFromTypedArray, + WebGLConstants) { + 'use strict'; /** - * Gets the value of the property at the provided time. + * Parses a binary glTF buffer into glTF JSON. * - * @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. + * @param {Uint8Array} data The binary glTF data to parse. + * @returns {Object} The parsed binary glTF. */ - VelocityVectorProperty.prototype.getValue = function(time, result) { - return this._getValue(time, result); - }; + function parseBinaryGltf(data) { + var headerView = ComponentDatatype.createArrayBufferView(WebGLConstants.INT, data.buffer, data.byteOffset, 5); - /** - * @private - */ - VelocityVectorProperty.prototype._getValue = function(time, velocityResult, positionResult) { - - if (!defined(velocityResult)) { - velocityResult = new Cartesian3(); + // Check that the magic string is present + var magic = getMagic(data); + if (magic !== 'glTF') { + throw new DeveloperError('File is not valid binary glTF'); } - var property = this._position; - if (Property.isConstant(property)) { - return this._normalize ? undefined : Cartesian3.clone(Cartesian3.ZERO, velocityResult); + // Check that the version is 1 or 2 + var version = headerView[1]; + if (version !== 1 && version !== 2) { + throw new DeveloperError('Binary glTF version is not 1 or 2'); } - var position1 = property.getValue(time, position1Scratch); - var position2 = property.getValue(JulianDate.addSeconds(time, step, timeScratch), position2Scratch); + var gltf; + var buffers; + var length; + // Load binary glTF version 1 + if (version === 1) { + length = headerView[2]; + var contentLength = headerView[3]; + var contentFormat = headerView[4]; - //If we don't have a position for now, return undefined. - if (!defined(position1)) { - return undefined; - } + // Check that the content format is 0, indicating that it is JSON + if (contentFormat !== 0) { + throw new DeveloperError('Binary glTF scene format is not JSON'); + } - //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); + var jsonStart = 20; + var binaryStart = jsonStart + contentLength; - if (!defined(position1)) { - return undefined; - } - } + var contentString = getStringFromTypedArray(data, jsonStart, contentLength); + gltf = JSON.parse(contentString); - if (Cartesian3.equals(position1, position2)) { - return this._normalize ? undefined : Cartesian3.clone(Cartesian3.ZERO, velocityResult); - } + var binaryData = data.subarray(binaryStart, length); - if (defined(positionResult)) { - position1.clone(positionResult); + buffers = gltf.buffers; + if (defined(buffers) && Object.keys(buffers).length > 0) { + var binaryGltfBuffer = buffers.binary_glTF; + // In some older models, the binary glTF buffer is named KHR_binary_glTF + if (!defined(binaryGltfBuffer)) { + binaryGltfBuffer = buffers.KHR_binary_glTF; + } + if (defined(binaryGltfBuffer)) { + binaryGltfBuffer.extras = { + _pipeline: { + source: binaryData + } + }; + } + } + // Update to glTF 2.0 + updateVersion(gltf); + // Remove the KHR_binary_glTF extension + removeExtensionsUsed(gltf, 'KHR_binary_glTF'); + addPipelineExtras(gltf); } - var velocity = Cartesian3.subtract(position2, position1, velocityResult); - if (this._normalize) { - return Cartesian3.normalize(velocity, velocityResult); - } else { - return Cartesian3.divideByScalar(velocity, step, velocityResult); + // Load binary glTF version 2 + if (version === 2) { + length = headerView[2]; + var byteOffset = 12; + var binaryBuffer; + while (byteOffset < length) { + var chunkHeaderView = ComponentDatatype.createArrayBufferView(WebGLConstants.INT, data.buffer, data.byteOffset + byteOffset, 2); + var chunkLength = chunkHeaderView[0]; + var chunkType = chunkHeaderView[1]; + byteOffset += 8; + var chunkBuffer = data.subarray(byteOffset, byteOffset + chunkLength); + byteOffset += chunkLength; + // Load JSON chunk + if (chunkType === 0x4E4F534A) { + var jsonString = getStringFromTypedArray(chunkBuffer); + gltf = JSON.parse(jsonString); + addPipelineExtras(gltf); + } + // Load Binary chunk + else if (chunkType === 0x004E4942) { + binaryBuffer = chunkBuffer; + } + } + if (defined(gltf) && defined(binaryBuffer)) { + buffers = gltf.buffers; + if (defined(buffers) && buffers.length > 0) { + var buffer = buffers[0]; + buffer.extras._pipeline.source = binaryBuffer; + } + } } - }; + return gltf; + } + return parseBinaryGltf; +}); + +define('ThirdParty/GltfPipeline/techniqueParameterForSemantic',[ + '../../Core/defined' + ], function( + defined) { + 'use strict'; /** - * Compares this property to the provided property and returns - * true if they are equal, false otherwise. + * Retrieves the technique parameter that has a matching semantic. * - * @param {Property} [other] The other property. - * @returns {Boolean} true if left and right are equal, false otherwise. + * @param {Object} technique A javascript object containing a glTF technique. + * @param {String} semantic The search string for semantics. + * @returns {String} The technique parameter with matching semantic. + * + * @private */ - VelocityVectorProperty.prototype.equals = function(other) { - return this === other ||// - (other instanceof VelocityVectorProperty && - Property.equals(this._position, other._position)); - }; - - return VelocityVectorProperty; + function techniqueParameterForSemantic(technique, semantic) { + var parameters = technique.parameters; + for (var parameterName in parameters) { + if (parameters.hasOwnProperty(parameterName)) { + var parameter = parameters[parameterName]; + var parameterSemantic = parameter.semantic; + if (defined(parameterSemantic) && parameterSemantic === semantic) { + return parameterName; + } + } + } + } + return techniqueParameterForSemantic; }); -/*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', - './PolylineDashMaterialProperty', - './PolylineGlowMaterialProperty', - './PolylineGraphics', - './PolylineOutlineMaterialProperty', - './PositionPropertyArray', - './PropertyArray', - './PropertyBag', - './RectangleGraphics', - './ReferenceProperty', - './Rotation', - './SampledPositionProperty', - './SampledProperty', - './StripeMaterialProperty', - './StripeOrientation', - './TimeIntervalCollectionPositionProperty', - './TimeIntervalCollectionProperty', - './VelocityVectorProperty', - './WallGraphics' +define('ThirdParty/GltfPipeline/webGLConstantToGlslType',[ + '../../Core/WebGLConstants' ], 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, - PolylineDashMaterialProperty, - PolylineGlowMaterialProperty, - PolylineGraphics, - PolylineOutlineMaterialProperty, - PositionPropertyArray, - PropertyArray, - PropertyBag, - RectangleGraphics, - ReferenceProperty, - Rotation, - SampledPositionProperty, - SampledProperty, - StripeMaterialProperty, - StripeOrientation, - TimeIntervalCollectionPositionProperty, - TimeIntervalCollectionProperty, - VelocityVectorProperty, - WallGraphics) { + WebGLConstants) { 'use strict'; - var currentId; - - function makeReference(collection, referenceString) { - if (referenceString[0] === '#') { - referenceString = currentId + referenceString; + 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'; + case WebGLConstants.BOOL: + return 'bool'; } - return ReferenceProperty.fromString(collection, referenceString); } + return webGLConstantToGlslType; +}); - var scratchCartesian = new Cartesian3(); - var scratchSpherical = new Spherical(); - var scratchCartographic = new Cartographic(); - var scratchTimeInterval = new TimeInterval(); +define('ThirdParty/GltfPipeline/glslTypeToWebGLConstant',[ + '../../Core/WebGLConstants' + ], function( + WebGLConstants) { + 'use strict'; - function unwrapColorInterval(czmlInterval) { - var rgbaf = czmlInterval.rgbaf; - if (defined(rgbaf)) { - return rgbaf; - } + function glslTypeToWebGLConstant(glslType) { + switch (glslType) { + case 'float': + return WebGLConstants.FLOAT; + case 'vec2': + return WebGLConstants.FLOAT_VEC2; + case 'vec3': + return WebGLConstants.FLOAT_VEC3; + case 'vec4': + return WebGLConstants.FLOAT_VEC4; + case 'mat2': + return WebGLConstants.FLOAT_MAT2; + case 'mat3': + return WebGLConstants.FLOAT_MAT3; + case 'mat4': + return WebGLConstants.FLOAT_MAT4; + case 'sampler2D': + return WebGLConstants.SAMPLER_2D; + } + } + return glslTypeToWebGLConstant; +}); + +define('ThirdParty/GltfPipeline/processModelMaterialsCommon',[ + './addToArray', + './ForEach', + './numberOfComponentsForType', + './techniqueParameterForSemantic', + './webGLConstantToGlslType', + './glslTypeToWebGLConstant', + '../../Core/clone', + '../../Core/defined', + '../../Core/defaultValue', + '../../Core/WebGLConstants' + ], function( + addToArray, + ForEach, + numberOfComponentsForType, + techniqueParameterForSemantic, + webGLConstantToGlslType, + glslTypeToWebGLConstant, + clone, + defined, + defaultValue, + WebGLConstants) { + 'use strict'; - var rgba = czmlInterval.rgba; - if (!defined(rgba)) { + /** + * @private + */ + function processModelMaterialsCommon(gltf, options) { + options = defaultValue(options, {}); + + if (!defined(gltf)) { 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])]; + var hasExtension = false; + var extensionsRequired = gltf.extensionsRequired; + var extensionsUsed = gltf.extensionsUsed; + if (defined(extensionsUsed)) { + var index = extensionsUsed.indexOf('KHR_materials_common'); + if (index >= 0) { + extensionsUsed.splice(index, 1); + hasExtension = true; + } + if (defined(extensionsRequired)) { + index = extensionsRequired.indexOf('KHR_materials_common'); + if (index >= 0) { + extensionsRequired.splice(index, 1); + } + } } - 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]); + if (hasExtension) { + if (!defined(gltf.programs)) { + gltf.programs = []; + } + if (!defined(gltf.shaders)) { + gltf.shaders = []; + } + if (!defined(gltf.techniques)) { + gltf.techniques = []; + } + lightDefaults(gltf); + + var lightParameters = generateLightParameters(gltf); + + // Pre-processing to assign skinning info and address incompatibilities + splitIncompatibleSkins(gltf); + + var techniques = {}; + ForEach.material(gltf, function(material) { + if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) { + var khrMaterialsCommon = material.extensions.KHR_materials_common; + var techniqueKey = getTechniqueKey(khrMaterialsCommon); + var technique = techniques[techniqueKey]; + if (!defined(technique)) { + technique = generateTechnique(gltf, khrMaterialsCommon, lightParameters, options); + techniques[techniqueKey] = technique; + } + + // Take advantage of the fact that we generate techniques that use the + // same parameter names as the extension values. + material.values = {}; + var values = khrMaterialsCommon.values; + for (var valueName in values) { + if (values.hasOwnProperty(valueName)) { + var value = values[valueName]; + material.values[valueName] = value; + } + } + + material.technique = technique; + + delete material.extensions.KHR_materials_common; + if (Object.keys(material.extensions).length === 0) { + delete material.extensions; + } + } + }); + + if (defined(gltf.extensions)) { + delete gltf.extensions.KHR_materials_common; + if (Object.keys(gltf.extensions).length === 0) { + delete gltf.extensions; + } + } + + // If any primitives have semantics that aren't declared in the generated + // shaders, we want to preserve them. + ensureSemanticExistence(gltf); } - return rgbaf; + + return gltf; } - function unwrapUriInterval(czmlInterval, sourceUri) { - var result = defaultValue(czmlInterval.uri, czmlInterval); - if (defined(sourceUri)) { - result = getAbsoluteUri(result, getAbsoluteUri(sourceUri)); + 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 unwrapRectangleInterval(czmlInterval) { - var wsen = czmlInterval.wsen; - if (defined(wsen)) { - return wsen; - } + function generateTechnique(gltf, khrMaterialsCommon, lightParameters, options) { + var optimizeForCesium = defaultValue(options.optimizeForCesium, false); + var hasCesiumRTCExtension = defined(gltf.extensions) && defined(gltf.extensions.CESIUM_RTC); + var addBatchIdToGeneratedShaders = defaultValue(options.addBatchIdToGeneratedShaders, false); - var wsenDegrees = czmlInterval.wsenDegrees; - if (!defined(wsenDegrees)) { - return undefined; + 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 length = wsenDegrees.length; - if (length === Rectangle.packedLength) { - return [CesiumMath.toRadians(wsenDegrees[0]), CesiumMath.toRadians(wsenDegrees[1]), CesiumMath.toRadians(wsenDegrees[2]), CesiumMath.toRadians(wsenDegrees[3])]; + var parameterValues = khrMaterialsCommon.values; + if (defined(khrMaterialsCommon.transparent)) { + parameterValues.transparent = khrMaterialsCommon.transparent; } + if (defined(khrMaterialsCommon.doubleSided)) { + parameterValues.doubleSided = khrMaterialsCommon.doubleSided; + } + var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0); - 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]); + var hasSkinning = jointCount > 0; + var skinningInfo = {}; + if (hasSkinning) { + skinningInfo = khrMaterialsCommon.extras._pipeline.skinning; } - 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]; + var vertexShader = 'precision highp float;\n'; + var fragmentShader = 'precision highp float;\n'; - scratchSpherical.clock = unitSpherical[i + 1]; - scratchSpherical.cone = unitSpherical[i + 2]; - Cartesian3.fromSpherical(scratchSpherical, scratchCartesian); + var hasNormals = (lightingModel !== 'CONSTANT'); - result[j + 1] = scratchCartesian.x; - result[j + 2] = scratchCartesian.y; - result[j + 3] = scratchCartesian.z; + // Add techniques + var techniqueParameters = { + // Add matrices + modelViewMatrix: { + semantic: hasCesiumRTCExtension ? 'CESIUM_RTC_MODELVIEW' : 'MODELVIEW', + type: WebGLConstants.FLOAT_MAT4 + }, + projectionMatrix: { + semantic: 'PROJECTION', + type: WebGLConstants.FLOAT_MAT4 } - 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]; + if (hasNormals) { + techniqueParameters.normalMatrix = { + semantic: 'MODELVIEWINVERSETRANSPOSE', + type: WebGLConstants.FLOAT_MAT3 + }; + } - scratchSpherical.clock = spherical[i + 1]; - scratchSpherical.cone = spherical[i + 2]; - scratchSpherical.magnitude = spherical[i + 3]; - Cartesian3.fromSpherical(scratchSpherical, scratchCartesian); + if (hasSkinning) { + techniqueParameters.jointMatrix = { + count: jointCount, + semantic: 'JOINTMATRIX', + type: WebGLConstants.FLOAT_MAT4 + }; + } - result[i + 1] = scratchCartesian.x; - result[i + 2] = scratchCartesian.y; - result[i + 3] = scratchCartesian.z; + // 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 + }; } - 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]; + // Give the diffuse uniform a semantic to support color replacement in 3D Tiles + if (defined(techniqueParameters.diffuse) && optimizeForCesium) { + techniqueParameters.diffuse.semantic = '_3DTILESDIFFUSE'; + } - scratchCartographic.longitude = cartographicRadians[i + 1]; - scratchCartographic.latitude = cartographicRadians[i + 2]; - scratchCartographic.height = cartographicRadians[i + 3]; - Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian); + // Copy light parameters into technique parameters + if (defined(lightParameters)) { + for (var lightParamName in lightParameters) { + if (lightParameters.hasOwnProperty(lightParamName)) { + techniqueParameters[lightParamName] = lightParameters[lightParamName]; + } + } + } - result[i + 1] = scratchCartesian.x; - result[i + 2] = scratchCartesian.y; - result[i + 3] = scratchCartesian.z; + // Generate uniforms object before attributes are added + var techniqueUniforms = {}; + for (var paramName in techniqueParameters) { + if (techniqueParameters.hasOwnProperty(paramName) && paramName !== 'extras') { + 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'; + } } - 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]; + // Add attributes with semantics + var vertexShaderMain = ''; + if (hasSkinning) { + var i, j; + var numberOfComponents = numberOfComponentsForType(skinningInfo.type); + var matrix = false; + if (skinningInfo.type.indexOf('MAT') === 0) { + matrix = true; + numberOfComponents = Math.sqrt(numberOfComponents); + } + if (!matrix) { + for (i = 0; i < numberOfComponents; i++) { + if (i === 0) { + vertexShaderMain += ' mat4 skinMat = '; + } else { + vertexShaderMain += ' skinMat += '; + } + vertexShaderMain += 'a_weight[' + i + '] * u_jointMatrix[int(a_joint[' + i + '])];\n'; + } + } else { + for (i = 0; i < numberOfComponents; i++) { + for (j = 0; j < numberOfComponents; j++) { + if (i === 0 && j === 0) { + vertexShaderMain += ' mat4 skinMat = '; + } else { + vertexShaderMain += ' skinMat += '; + } + vertexShaderMain += 'a_weight[' + i + '][' + j + '] * u_jointMatrix[int(a_joint[' + i + '][' + j + '])];\n'; + } + } + } + } - scratchCartographic.longitude = CesiumMath.toRadians(cartographicDegrees[i + 1]); - scratchCartographic.latitude = CesiumMath.toRadians(cartographicDegrees[i + 2]); - scratchCartographic.height = cartographicDegrees[i + 3]; - Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian); + // 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'; - result[i + 1] = scratchCartesian.x; - result[i + 2] = scratchCartesian.y; - result[i + 3] = scratchCartesian.z; + // 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'; } - return result; + + fragmentShader += 'varying vec3 v_normal;\n'; } - } - function unwrapCartesianInterval(czmlInterval) { - var cartesian = czmlInterval.cartesian; - if (defined(cartesian)) { - return cartesian; + // 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'; } - var cartesianVelocity = czmlInterval.cartesianVelocity; - if (defined(cartesianVelocity)) { - return cartesianVelocity; + if (hasSkinning) { + techniqueAttributes.a_joint = 'joint'; + var attributeType = getShaderVariable(skinningInfo.type); + var webGLConstant = glslTypeToWebGLConstant(attributeType); + + techniqueParameters.joint = { + semantic: 'JOINT', + type: webGLConstant + }; + techniqueAttributes.a_weight = 'weight'; + techniqueParameters.weight = { + semantic: 'WEIGHT', + type: webGLConstant + }; + + vertexShader += 'attribute ' + attributeType + ' a_joint;\n'; + vertexShader += 'attribute ' + attributeType + ' a_weight;\n'; } - var unitCartesian = czmlInterval.unitCartesian; - if (defined(unitCartesian)) { - return unitCartesian; + if (addBatchIdToGeneratedShaders) { + techniqueAttributes.a_batchId = 'batchId'; + techniqueParameters.batchId = { + semantic: '_BATCHID', + type: WebGLConstants.FLOAT + }; + vertexShader += 'attribute float a_batchId;\n'; } - var unitSpherical = czmlInterval.unitSpherical; - if (defined(unitSpherical)) { - return convertUnitSphericalToCartesian(unitSpherical); + var hasSpecular = hasNormals && ((lightingModel === 'BLINN') || (lightingModel === 'PHONG')) && + defined(techniqueParameters.specular) && defined(techniqueParameters.shininess) && + (techniqueParameters.shininess > 0.0); + + // 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'; + } } - var spherical = czmlInterval.spherical; - if (defined(spherical)) { - return convertSphericalToCartesian(spherical); + if (!hasAmbientLights) { + // Add an ambient light if we don't have one + fragmentLightingBlock += ' ambientLight += vec3(0.2, 0.2, 0.2);\n'; } - var cartographicRadians = czmlInterval.cartographicRadians; - if (defined(cartographicRadians)) { - return convertCartographicRadiansToCartesian(cartographicRadians); + if (!hasNonAmbientLights && (lightingModel !== 'CONSTANT')) { + if (optimizeForCesium) { + fragmentLightingBlock += ' vec3 l = normalize(czm_sunDirectionEC);\n'; + } else { + fragmentLightingBlock += ' vec3 l = vec3(0.0, 0.0, 1.0);\n'; + } + var minimumLighting = optimizeForCesium ? 0.2 : 0.0; + fragmentLightingBlock += ' diffuseLight += vec3(1.0, 1.0, 1.0) * max(dot(normal,l), ' + minimumLighting + ');\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'; + } } - var cartographicDegrees = czmlInterval.cartographicDegrees; - if (defined(cartographicDegrees)) { - return convertCartographicDegreesToCartesian(cartographicDegrees); + 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'; + } } - throw new RuntimeError(JSON.stringify(czmlInterval) + ' is not a valid CZML interval.'); - } + 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'; + } - function normalizePackedQuaternionArray(array, startingIndex) { - var x = array[startingIndex]; - var y = array[startingIndex + 1]; - var z = array[startingIndex + 2]; - var w = array[startingIndex + 3]; + 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'; + } - 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; - } + if (defined(techniqueParameters.transparency)) { + finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a * u_transparency, 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, u_transparency);\n'; + } else { + finalColorComputation = ' gl_FragColor = vec4(color, 1.0);\n'; + } + } - function unwrapQuaternionInterval(czmlInterval) { - var unitQuaternion = czmlInterval.unitQuaternion; - if (defined(unitQuaternion)) { - if (unitQuaternion.length === 4) { - normalizePackedQuaternionArray(unitQuaternion, 0); - return unitQuaternion; + 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'; + } - for (var i = 1; i < unitQuaternion.length; i += 5) { - normalizePackedQuaternionArray(unitQuaternion, i); + 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'; } - return unitQuaternion; - } + fragmentShader += ' vec3 viewDir = -normalize(v_positionEC);\n'; + fragmentShader += ' vec3 ambientLight = vec3(0.0, 0.0, 0.0);\n'; - function getPropertyType(czmlInterval) { - // The associations in this function need to be kept in sync with the - // associations in unwrapInterval. + // Add in light computations + fragmentShader += fragmentLightingBlock; - // Intentionally omitted due to conficts in CZML property names: - // * Image (conflicts with Uri) - // * Rotation (conflicts with Number) - // - // cartesianVelocity is also omitted due to incomplete support for - // derivative information in CZML properties. - // (Currently cartesianVelocity is hacked directly into the position processing code) - if (typeof czmlInterval === 'boolean') { - return Boolean; - } else if (typeof czmlInterval === 'number') { - return Number; - } else if (typeof czmlInterval === 'string') { - return String; - } else if (czmlInterval.hasOwnProperty('array')) { - return Array; - } else if (czmlInterval.hasOwnProperty('boolean')) { - return Boolean; - } else if (czmlInterval.hasOwnProperty('boundingRectangle')) { - return BoundingRectangle; - } else if (czmlInterval.hasOwnProperty('cartesian2')) { - return Cartesian2; - } else if (czmlInterval.hasOwnProperty('cartesian') || - czmlInterval.hasOwnProperty('unitCartesian') || - czmlInterval.hasOwnProperty('unitSpherical') || - czmlInterval.hasOwnProperty('spherical') || - czmlInterval.hasOwnProperty('cartographicRadians') || - czmlInterval.hasOwnProperty('cartographicDegrees')) { - return Cartesian3; - } else if (czmlInterval.hasOwnProperty('rgba') || - czmlInterval.hasOwnProperty('rgbaf')) { - return Color; - } else if (czmlInterval.hasOwnProperty('colorBlendMode')) { - return ColorBlendMode; - } else if (czmlInterval.hasOwnProperty('cornerType')) { - return CornerType; - } else if (czmlInterval.hasOwnProperty('heightReference')) { - return HeightReference; - } else if (czmlInterval.hasOwnProperty('horizontalOrigin')) { - return HorizontalOrigin; - } else if (czmlInterval.hasOwnProperty('date')) { - return JulianDate; - } else if (czmlInterval.hasOwnProperty('labelStyle')) { - return LabelStyle; - } else if (czmlInterval.hasOwnProperty('number')) { - return Number; - } else if (czmlInterval.hasOwnProperty('nearFarScalar')) { - return NearFarScalar; - } else if (czmlInterval.hasOwnProperty('object') || - czmlInterval.hasOwnProperty('value')) { - return Object; - } else if (czmlInterval.hasOwnProperty('unitQuaternion')) { - return Quaternion; - } else if (czmlInterval.hasOwnProperty('shadowMode')) { - return ShadowMode; - } else if (czmlInterval.hasOwnProperty('string')) { - return String; - } else if (czmlInterval.hasOwnProperty('stripeOrientation')) { - return StripeOrientation; - } else if (czmlInterval.hasOwnProperty('wsen') || - czmlInterval.hasOwnProperty('wsenDegrees')) { - return Rectangle; - } else if (czmlInterval.hasOwnProperty('uri')) { - return Uri; - } else if (czmlInterval.hasOwnProperty('verticalOrigin')) { - return VerticalOrigin; + fragmentShader += colorCreationBlock; + fragmentShader += finalColorComputation; + fragmentShader += '}\n'; + + var techniqueStates; + if (parameterValues.transparent) { + techniqueStates = { + enable: [ + WebGLConstants.DEPTH_TEST, + WebGLConstants.BLEND + ], + functions: { + depthMask : [false], + 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 + ] + }; + } + + // Add shaders + var vertexShaderId = addToArray(shaders, { + type: WebGLConstants.VERTEX_SHADER, + extras: { + _pipeline: { + source: vertexShader, + extension: '.glsl' + } + } + }); + + var fragmentShaderId = addToArray(shaders, { + type: WebGLConstants.FRAGMENT_SHADER, + extras: { + _pipeline: { + source: fragmentShader, + extension: '.glsl' + } + } + }); + + // Add program + var programAttributes = Object.keys(techniqueAttributes); + var programId = addToArray(programs, { + attributes: programAttributes, + fragmentShader: fragmentShaderId, + vertexShader: vertexShaderId + }); + + var techniqueId = addToArray(techniques, { + attributes: techniqueAttributes, + parameters: techniqueParameters, + program: programId, + states: techniqueStates, + uniforms: techniqueUniforms + }); + + 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 if (defined(paramValue.index)) { + value = [paramValue.index]; } else { - // fallback case - return Object; + value = paramValue; } - } - function unwrapInterval(type, czmlInterval, sourceUri) { - // The associations in this function need to be kept in sync with the - // associations in getPropertyType - 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 Object: - return defaultValue(defaultValue(czmlInterval.object, czmlInterval.value), czmlInterval); - case Quaternion: - return unwrapQuaternionInterval(czmlInterval); - case Rotation: - return defaultValue(czmlInterval.number, czmlInterval); - case ShadowMode: - return ShadowMode[defaultValue(defaultValue(czmlInterval.shadowMode, 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); + switch (paramName) { + case 'ambient': + return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4; + case 'diffuse': + return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4; + case 'emission': + return value.length === 1 ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4; + case 'specular': + return value.length === 1 ? 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; } } - var interpolators = { - HERMITE : HermitePolynomialApproximation, - LAGRANGE : LagrangePolynomialApproximation, - LINEAR : LinearApproximation - }; + function getTechniqueKey(khrMaterialsCommon) { + var techniqueKey = ''; + techniqueKey += 'technique:' + khrMaterialsCommon.technique + ';'; - function updateInterpolationSettings(packetData, property) { - var interpolationAlgorithm = packetData.interpolationAlgorithm; - if (defined(interpolationAlgorithm) || defined(packetData.interpolationDegree)) { - property.setInterpolationOptions({ - interpolationAlgorithm : interpolators[interpolationAlgorithm], - interpolationDegree : packetData.interpolationDegree - }); + var values = khrMaterialsCommon.values; + var keys = Object.keys(values).sort(); + var keysCount = keys.length; + for (var i = 0; i < keysCount; ++i) { + var name = keys[i]; + if (values.hasOwnProperty(name)) { + techniqueKey += name + ':' + getKHRMaterialsCommonValueType(name, values[name]); + techniqueKey += ';'; + } } - var forwardExtrapolationType = packetData.forwardExtrapolationType; - if (defined(forwardExtrapolationType)) { - property.forwardExtrapolationType = ExtrapolationType[forwardExtrapolationType]; + var doubleSided = defaultValue(khrMaterialsCommon.doubleSided, defaultValue(khrMaterialsCommon.values.doubleSided, false)); + techniqueKey += doubleSided.toString() + ';'; + var transparent = defaultValue(khrMaterialsCommon.transparent, defaultValue(khrMaterialsCommon.values.transparent, false)); + techniqueKey += transparent.toString() + ';'; + var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0); + techniqueKey += jointCount.toString() + ';'; + if (jointCount > 0) { + var skinningInfo = khrMaterialsCommon.extras._pipeline.skinning; + techniqueKey += skinningInfo.type + ';'; } - var forwardExtrapolationDuration = packetData.forwardExtrapolationDuration; - if (defined(forwardExtrapolationDuration)) { - property.forwardExtrapolationDuration = forwardExtrapolationDuration; - } + return techniqueKey; + } - var backwardExtrapolationType = packetData.backwardExtrapolationType; - if (defined(backwardExtrapolationType)) { - property.backwardExtrapolationType = ExtrapolationType[backwardExtrapolationType]; + function lightDefaults(gltf) { + if (!defined(gltf.extensions)) { + gltf.extensions = {}; } + var extensions = gltf.extensions; - var backwardExtrapolationDuration = packetData.backwardExtrapolationDuration; - if (defined(backwardExtrapolationDuration)) { - property.backwardExtrapolationDuration = backwardExtrapolationDuration; + if (!defined(extensions.KHR_materials_common)) { + extensions.KHR_materials_common = {}; } - } + var khrMaterialsCommon = extensions.KHR_materials_common; - 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; + if (!defined(khrMaterialsCommon.lights)) { + khrMaterialsCommon.lights = {}; } + var lights = khrMaterialsCommon.lights; - var packedLength; - var isSampled; - var unwrappedInterval; - var unwrappedIntervalLength; - var isReference = defined(packetData.reference); - var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL); + var lightsLength = lights.length; + for (var lightId = 0; lightId < lightsLength; lightId++) { + var light = lights[lightId]; + if (light.type === 'ambient') { + if (!defined(light.ambient)) { + light.ambient = {}; + } + var ambientLight = light.ambient; - 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) && (type !== Object); - } + 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; - //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; + 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; - //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; - } + if (!defined(pointLight.color)) { + pointLight.color = [1.0, 1.0, 1.0]; + } - var property = object[propertyName]; + 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; - var epoch; - var packetEpoch = packetData.epoch; - if (defined(packetEpoch)) { - epoch = JulianDate.fromIso8601(packetEpoch); - } + if (!defined(spotLight.color)) { + spotLight.color = [1.0, 1.0, 1.0]; + } - //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; + 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); } - property.addSamplesPackedArray(unwrappedInterval, epoch); - updateInterpolationSettings(packetData, property); - return; } + } - var interval; + function getShaderVariable(type) { + if (type === 'SCALAR') { + return 'float'; + } + return type.toLowerCase(); + } - //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; - } + function ensureSemanticExistenceForPrimitive(gltf, primitive) { + var accessors = gltf.accessors; + var materials = gltf.materials; + var techniques = gltf.techniques; + var programs = gltf.programs; + var shaders = gltf.shaders; - //If no property exists, simply use a new interval collection - if (!defined(property)) { - if (isReference) { - property = new CompositeProperty(); - } else { - property = new TimeIntervalCollectionProperty(); + var attributes = primitive.attributes; + var material = materials[primitive.material]; + var technique = techniques[material.technique]; + var program = programs[technique.program]; + var vertexShader = shaders[program.vertexShader]; + + for (var semantic in attributes) { + if (attributes.hasOwnProperty(semantic)) { + if (!defined(techniqueParameterForSemantic(technique, semantic))) { + var accessorId = attributes[semantic]; + var accessor = accessors[accessorId]; + var lowerCase = semantic.toLowerCase(); + if (lowerCase.charAt(0) === '_') { + lowerCase = lowerCase.slice(1); + } + var attributeName = 'a_' + lowerCase; + technique.parameters[lowerCase] = { + semantic: semantic, + type: accessor.componentType + }; + technique.attributes[attributeName] = lowerCase; + program.attributes.push(attributeName); + var pipelineExtras = vertexShader.extras._pipeline; + var shaderText = pipelineExtras.source; + shaderText = 'attribute ' + getShaderVariable(accessor.type) + ' ' + attributeName + ';\n' + shaderText; + pipelineExtras.source = shaderText; } - 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. + function ensureSemanticExistence(gltf) { + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + ensureSemanticExistenceForPrimitive(gltf, primitive); + }); + }); + } - //Put the old property in an infinite interval. - interval = Iso8601.MAXIMUM_INTERVAL.clone(); - interval.data = property; + function splitIncompatibleSkins(gltf) { + var accessors = gltf.accessors; + var materials = gltf.materials; + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + var materialId = primitive.material; + var material = materials[materialId]; + + if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) { + var khrMaterialsCommon = material.extensions.KHR_materials_common; + var jointAccessorId = primitive.attributes.JOINT; + var componentType; + var type; + if (defined(jointAccessorId)) { + var jointAccessor = accessors[jointAccessorId]; + componentType = jointAccessor.componentType; + type = jointAccessor.type; + } + var isSkinned = defined(jointAccessorId); + + var skinningInfo = khrMaterialsCommon.extras._pipeline.skinning; + if (!defined(skinningInfo)) { + khrMaterialsCommon.extras._pipeline.skinning = { + skinned: isSkinned, + componentType: componentType, + type: type + }; + } else if ((skinningInfo.skinned !== isSkinned) || (skinningInfo.type !== type)) { + // This primitive uses the same material as another one that either isn't skinned or uses a different type to store joints and weights + var clonedMaterial = clone(material, true); + clonedMaterial.extensions.KHR_materials_common.extras._pipeline.skinning = { + skinned: isSkinned, + componentType: componentType, + type: type + }; + // Split this off as a separate material + materialId = addToArray(materials, clonedMaterial); + primitive.material = materialId; + } + } + }); + }); + } - //Create the composite. - property = new CompositeProperty(); - object[propertyName] = property; + return processModelMaterialsCommon; +}); - //add the old property interval - property.intervals.addInterval(interval); +define('ThirdParty/GltfPipeline/processPbrMetallicRoughness',[ + './addToArray', + './ForEach', + './numberOfComponentsForType', + './techniqueParameterForSemantic', + './webGLConstantToGlslType', + './glslTypeToWebGLConstant', + '../../Core/clone', + '../../Core/defined', + '../../Core/defaultValue', + '../../Core/WebGLConstants' + ], function( + addToArray, + ForEach, + numberOfComponentsForType, + techniqueParameterForSemantic, + webGLConstantToGlslType, + glslTypeToWebGLConstant, + clone, + defined, + defaultValue, + WebGLConstants) { + 'use strict'; - //Change the new data to a ConstantProperty and add it. - combinedInterval.data = isReference ? combinedInterval.data : new ConstantProperty(combinedInterval.data); - property.intervals.addInterval(combinedInterval); + /** + * @private + */ + function processPbrMetallicRoughness(gltf, options) { + options = defaultValue(options, {}); + + var hasPbrMetallicRoughness = false; + ForEach.material(gltf, function(material) { + if (defined(material.pbrMetallicRoughness)) { + hasPbrMetallicRoughness = true; } + }); - return; - } + if (hasPbrMetallicRoughness) { + if (!defined(gltf.programs)) { + gltf.programs = []; + } + if (!defined(gltf.shaders)) { + gltf.shaders = []; + } + if (!defined(gltf.techniques)) { + gltf.techniques = []; + } - //isSampled && hasInterval - if (!defined(property)) { - property = new CompositeProperty(); - object[propertyName] = property; - } + // Pre-processing to assign skinning info and address incompatibilities + splitIncompatibleSkins(gltf); - //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; + if (!defined(gltf.techniques)) { + gltf.techniques = []; + } + var materials = []; + ForEach.material(gltf, function(material) { + if (defined(material.pbrMetallicRoughness)) { + var pbrMetallicRoughness = material.pbrMetallicRoughness; + var technique = generateTechnique(gltf, material, options); - //Create the composite. - property = new CompositeProperty(); - object[propertyName] = property; + var newMaterial = { + values : pbrMetallicRoughness, + technique : technique + }; + materials.push(newMaterial); + } + }); + gltf.materials = materials; - //add the old property interval - property.intervals.addInterval(interval); + // If any primitives have semantics that aren't declared in the generated + // shaders, we want to preserve them. + ensureSemanticExistence(gltf); } - //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); + return gltf; } - function processPacketData(type, object, propertyName, packetData, interval, sourceUri, entityCollection) { - if (!defined(packetData)) { - return; - } + function generateTechnique(gltf, material, options) { + var optimizeForCesium = defaultValue(options.optimizeForCesium, false); + var hasCesiumRTCExtension = defined(gltf.extensions) && defined(gltf.extensions.CESIUM_RTC); + var addBatchIdToGeneratedShaders = defaultValue(options.addBatchIdToGeneratedShaders, false); - 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); - } - } + var techniques = gltf.techniques; + var shaders = gltf.shaders; + var programs = gltf.programs; - 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); + var parameterValues = material.pbrMetallicRoughness; + for (var additional in material) { + if (material.hasOwnProperty(additional) && ((additional.indexOf('Texture') >= 0) || additional.indexOf('Factor') >= 0) || additional === 'doubleSided') { + parameterValues[additional] = material[additional]; } - } 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); + var vertexShader = 'precision highp float;\n'; + var fragmentShader = 'precision highp float;\n'; - 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; + var skin; + if (defined(gltf.skins)) { + skin = gltf.skins[0]; } + var joints = (defined(skin)) ? skin.joints : []; + var jointCount = joints.length; + var skinningInfo = material.extras._pipeline.skinning; + var hasSkinning = defined(skinningInfo.type); - //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); + var hasNormals = true; + var hasTangents = false; + var hasMorphTargets = false; + var morphTargets; + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + var targets = primitive.targets; + if (!hasMorphTargets && defined(targets)) { + hasMorphTargets = true; + morphTargets = targets; + } + var attributes = primitive.attributes; + for (var attribute in attributes) { + if (attribute.indexOf('TANGENT') >= 0) { + hasTangents = true; + } + } + }); + }); + + // Add techniques + var techniqueParameters = { + // Add matrices + modelViewMatrix : { + semantic : hasCesiumRTCExtension ? 'CESIUM_RTC_MODELVIEW' : 'MODELVIEW', + type : WebGLConstants.FLOAT_MAT4 + }, + projectionMatrix : { + semantic : 'PROJECTION', + type : WebGLConstants.FLOAT_MAT4 } - return; + }; + + if (hasNormals) { + techniqueParameters.normalMatrix = { + semantic : 'MODELVIEWINVERSETRANSPOSE', + type : WebGLConstants.FLOAT_MAT3 + }; } - var property = object[propertyName]; + if (hasSkinning) { + techniqueParameters.jointMatrix = { + count : jointCount, + semantic : 'JOINTMATRIX', + type : WebGLConstants.FLOAT_MAT4 + }; + } - var epoch; - var packetEpoch = packetData.epoch; - if (defined(packetEpoch)) { - epoch = JulianDate.fromIso8601(packetEpoch); + if (hasMorphTargets) { + techniqueParameters.morphWeights = { + count : morphTargets.length, + semantic : 'MORPHWEIGHTS', + type : WebGLConstants.FLOAT + }; } - //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; + // Add material parameters + var hasTexCoords = false; + for (var name in parameterValues) { + //generate shader parameters + if (parameterValues.hasOwnProperty(name)) { + var valType = getPBRValueType(name); + if (!hasTexCoords && (valType === WebGLConstants.SAMPLER_2D)) { + hasTexCoords = true; + } + techniqueParameters[name] = { + type : valType + }; } - property.addSamplesPackedArray(unwrappedInterval, epoch); - updateInterpolationSettings(packetData, property); - return; } - var interval; + // Generate uniforms object before attributes are added + var techniqueUniforms = {}; + for (var paramName in techniqueParameters) { + if (techniqueParameters.hasOwnProperty(paramName) && paramName !== 'extras') { + 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) && (paramName !== 'morphWeights')) || + param.useInFragment) { + fragmentShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n'; + delete param.useInFragment; + } else { + vertexShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n'; + } + } + } - //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); + // Add attributes with semantics + var vertexShaderMain = ''; + if (hasSkinning) { + var i, j; + var numberOfComponents = numberOfComponentsForType(skinningInfo.type); + var matrix = false; + if (skinningInfo.type.indexOf('MAT') === 0) { + matrix = true; + numberOfComponents = Math.sqrt(numberOfComponents); + } + if (!matrix) { + for (i = 0; i < numberOfComponents; i++) { + if (i === 0) { + vertexShaderMain += ' mat4 skinMatrix = '; + } else { + vertexShaderMain += ' skinMatrix += '; + } + vertexShaderMain += 'a_weight[' + i + '] * u_jointMatrix[int(a_joint[' + i + '])];\n'; + } } else { - combinedInterval.data = Cartesian3.unpack(unwrappedInterval); + for (i = 0; i < numberOfComponents; i++) { + for (j = 0; j < numberOfComponents; j++) { + if (i === 0 && j === 0) { + vertexShaderMain += ' mat4 skinMatrix = '; + } else { + vertexShaderMain += ' skinMatrix += '; + } + vertexShaderMain += 'a_weight[' + i + '][' + j + '] * u_jointMatrix[int(a_joint[' + i + '][' + j + '])];\n'; + } + } } + } - //If no property exists, simply use a new interval collection - if (!defined(property)) { - if (isReference) { - property = new CompositePositionProperty(referenceFrame); - } else { - property = new TimeIntervalCollectionPositionProperty(referenceFrame); + // 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 (optimizeForCesium) { + vertexShader += 'varying vec3 v_positionWC;\n'; + } + + // Morph Target Weighting + vertexShaderMain += ' vec3 weightedPosition = a_position;\n'; + if (hasNormals) { + vertexShaderMain += ' vec3 weightedNormal = a_normal;\n'; + } + if (hasTangents) { + vertexShaderMain += ' vec3 weightedTangent = a_tangent;\n'; + } + if (hasMorphTargets) { + for (var k = 0; k < morphTargets.length; k++) { + var targetAttributes = morphTargets[k]; + for (var targetAttribute in targetAttributes) { + if (targetAttributes.hasOwnProperty(targetAttribute) && targetAttribute !== 'extras') { + var attributeLower = targetAttribute.toLowerCase() + '_' + k; + techniqueAttributes['a_' + attributeLower] = attributeLower; + techniqueParameters[attributeLower] = { + semantic : targetAttribute + '_' + k, + type : WebGLConstants.FLOAT_VEC3 + }; + vertexShader += 'attribute vec3 a_' + attributeLower + ';\n'; + if (targetAttribute === 'POSITION') { + vertexShaderMain += ' weightedPosition += u_morphWeights[' + k + '] * a_' + attributeLower + ';\n'; + } else if (targetAttribute === 'NORMAL') { + vertexShaderMain += ' weightedNormal += u_morphWeights[' + k + '] * a_' + attributeLower + ';\n'; + } else if (targetAttribute === 'TANGENT') { + vertexShaderMain += ' weightedTangent += u_morphWeights[' + k + '] * a_' + attributeLower + ';\n'; + } + } } - 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); + // Final position computation + if (hasSkinning) { + vertexShaderMain += ' vec4 position = skinMatrix * vec4(weightedPosition, 1.0);\n'; + } else { + vertexShaderMain += ' vec4 position = vec4(weightedPosition, 1.0);\n'; + } + if (optimizeForCesium) { + vertexShaderMain += ' v_positionWC = (czm_model * position).xyz;\n'; + } + vertexShaderMain += ' position = u_modelViewMatrix * position;\n'; + vertexShaderMain += ' v_positionEC = position.xyz;\n'; + vertexShaderMain += ' gl_Position = u_projectionMatrix * position;\n'; + fragmentShader += 'varying vec3 v_positionEC;\n'; + if (optimizeForCesium) { + fragmentShader += 'varying vec3 v_positionWC;\n'; + } + + // Final normal computation + 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(skinMatrix) * weightedNormal;\n'; } else { - //Otherwise, create a CompositePositionProperty but preserve the existing data. + vertexShaderMain += ' v_normal = u_normalMatrix * weightedNormal;\n'; + } - //Put the old property in an infinite interval. - interval = Iso8601.MAXIMUM_INTERVAL.clone(); - interval.data = property; + fragmentShader += 'varying vec3 v_normal;\n'; + } - //Create the composite. - property = new CompositePositionProperty(property.referenceFrame); - object[propertyName] = property; + // Read tangents if available + if (hasTangents) { + techniqueAttributes.a_tangent = 'tangent'; + techniqueParameters.tangent = { + semantic : 'TANGENT', + type : WebGLConstants.FLOAT_VEC3 + }; + vertexShader += 'attribute vec3 a_tangent;\n'; + vertexShader += 'varying vec3 v_tangent;\n'; + vertexShaderMain += ' v_tangent = (u_modelViewMatrix * vec4(weightedTangent, 1.0)).xyz;\n'; - //add the old property interval - property.intervals.addInterval(interval); + fragmentShader += 'varying vec3 v_tangent;\n'; + } - //Change the new data to a ConstantPositionProperty and add it. - combinedInterval.data = isReference ? combinedInterval.data : new ConstantPositionProperty(combinedInterval.data, referenceFrame); - property.intervals.addInterval(combinedInterval); - } + // 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 + }; - return; + 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'; } - //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; + // Add skinning information if available + if (hasSkinning) { + techniqueAttributes.a_joint = 'joint'; + var attributeType = getShaderVariable(skinningInfo.type); + var webGLConstant = glslTypeToWebGLConstant(attributeType); - //Create the composite. - property = new CompositePositionProperty(property.referenceFrame); - object[propertyName] = property; + techniqueParameters.joint = { + semantic : 'JOINTS_0', + type : webGLConstant + }; + techniqueAttributes.a_weight = 'weight'; + techniqueParameters.weight = { + semantic : 'WEIGHTS_0', + type : webGLConstant + }; - //add the old property interval - property.intervals.addInterval(interval); + vertexShader += 'attribute ' + attributeType + ' a_joint;\n'; + vertexShader += 'attribute ' + attributeType + ' a_weight;\n'; } - //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); + if (addBatchIdToGeneratedShaders) { + techniqueAttributes.a_batchId = 'batchId'; + techniqueParameters.batchId = { + semantic: '_BATCHID', + type: WebGLConstants.FLOAT + }; + vertexShader += 'attribute float a_batchId;\n'; } - interval.data.addSamplesPackedArray(unwrappedInterval, epoch); - updateInterpolationSettings(packetData, interval.data); - } - function processPositionPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection) { - if (!defined(packetData)) { - return; - } + vertexShader += 'void main(void) \n{\n'; + vertexShader += vertexShaderMain; + vertexShader += '}\n'; - 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); - } - } + // Fragment shader lighting + fragmentShader += 'const float M_PI = 3.141592653589793;\n'; - 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; - } + fragmentShader += 'vec3 lambertianDiffuse(vec3 baseColor) \n' + + '{\n' + + ' return baseColor / M_PI;\n' + + '}\n\n'; - var property = object[propertyName]; - var existingMaterial; - var existingInterval; + fragmentShader += 'vec3 fresnelSchlick2(vec3 f0, vec3 f90, float VdotH) \n' + + '{\n' + + ' return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);\n' + + '}\n\n'; - 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; + fragmentShader += 'vec3 fresnelSchlick(float metalness, float VdotH) \n' + + '{\n' + + ' return metalness + (vec3(1.0) - metalness) * pow(1.0 - VdotH, 5.0);\n' + + '}\n\n'; + + fragmentShader += 'float smithVisibilityG1(float NdotV, float roughness) \n' + + '{\n' + + ' float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;\n' + + ' return NdotV / (NdotV * (1.0 - k) + k);\n' + + '}\n\n'; + + fragmentShader += 'float smithVisibilityGGX(float roughness, float NdotL, float NdotV) \n' + + '{\n' + + ' return smithVisibilityG1(NdotL, roughness) * smithVisibilityG1(NdotV, roughness);\n' + + '}\n\n'; + + fragmentShader += 'float GGX(float roughness, float NdotH) \n' + + '{\n' + + ' float roughnessSquared = roughness * roughness;\n' + + ' float f = (NdotH * roughnessSquared - NdotH) * NdotH + 1.0;\n' + + ' return roughnessSquared / (M_PI * f * f);\n' + + '}\n\n'; + + fragmentShader += 'void main(void) \n{\n'; + + // Add normal mapping to fragment shader + if (hasNormals) { + fragmentShader += ' vec3 ng = normalize(v_normal);\n'; + if (defined(parameterValues.normalTexture)) { + if (hasTangents) { + // Read tangents from varying + fragmentShader += ' vec3 t = normalize(v_tangent);\n'; + fragmentShader += ' vec3 b = normalize(cross(ng, t));\n'; + fragmentShader += ' mat3 tbn = mat3(t, b, ng);\n'; + fragmentShader += ' vec3 n = texture2D(u_normalTexture, ' + v_texcoord + ').rgb;\n'; + fragmentShader += ' n = normalize(tbn * (2.0 * n - 1.0));\n'; + } else { + // Add standard derivatives extension + fragmentShader = '#ifdef GL_OES_standard_derivatives\n' + + '#extension GL_OES_standard_derivatives : enable\n' + + '#endif\n' + + fragmentShader; + // Compute tangents + fragmentShader += '#ifdef GL_OES_standard_derivatives\n'; + fragmentShader += ' vec3 pos_dx = dFdx(v_positionEC);\n'; + fragmentShader += ' vec3 pos_dy = dFdy(v_positionEC);\n'; + fragmentShader += ' vec3 tex_dx = dFdx(vec3(' + v_texcoord + ',0.0));\n'; + fragmentShader += ' vec3 tex_dy = dFdy(vec3(' + v_texcoord + ',0.0));\n'; + fragmentShader += ' vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t);\n'; + fragmentShader += ' t = normalize(t - ng * dot(ng, t));\n'; + fragmentShader += ' vec3 b = normalize(cross(ng, t));\n'; + fragmentShader += ' mat3 tbn = mat3(t, b, ng);\n'; + fragmentShader += ' vec3 n = texture2D(u_normalTexture, ' + v_texcoord + ').rgb;\n'; + fragmentShader += ' n = normalize(tbn * (2.0 * n - 1.0));\n'; + fragmentShader += '#else\n'; + fragmentShader += ' vec3 n = ng;\n'; + fragmentShader += '#endif\n'; + } } else { - //If not, create it. - existingInterval = combinedInterval.clone(); - thisIntervals.addInterval(existingInterval); + fragmentShader += ' vec3 n = ng;\n'; + } + if (parameterValues.doubleSided) { + fragmentShader += ' if (!gl_FrontFacing)\n'; + fragmentShader += ' {\n'; + fragmentShader += ' n = -n;\n'; + fragmentShader += ' }\n'; } - } 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(); + // Add base color to fragment shader + if (defined(parameterValues.baseColorTexture)) { + fragmentShader += ' vec4 baseColorWithAlpha = texture2D(u_baseColorTexture, ' + v_texcoord + ');\n'; + if (defined(parameterValues.baseColorFactor)) { + fragmentShader += ' baseColorWithAlpha *= u_baseColorFactor;\n'; } - 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(); + } else { + if (defined(parameterValues.baseColorFactor)) { + fragmentShader += ' vec4 baseColorWithAlpha = u_baseColorFactor;\n'; + } else { + fragmentShader += ' vec4 baseColorWithAlpha = vec4(1.0);\n'; } - 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(); + } + fragmentShader += ' vec3 baseColor = baseColorWithAlpha.rgb;\n'; + // Add metallic-roughness to fragment shader + if (defined(parameterValues.metallicRoughnessTexture)) { + fragmentShader += ' vec3 metallicRoughness = texture2D(u_metallicRoughnessTexture, ' + v_texcoord + ').rgb;\n'; + fragmentShader += ' float metalness = clamp(metallicRoughness.b, 0.0, 1.0);\n'; + fragmentShader += ' float roughness = clamp(metallicRoughness.g, 0.04, 1.0);\n'; + if (defined(parameterValues.metallicFactor)) { + fragmentShader += ' metalness *= u_metallicFactor;\n'; } - 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(); + if (defined(parameterValues.roughnessFactor)) { + fragmentShader += ' roughness *= u_roughnessFactor;\n'; } - 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(); + } else { + if (defined(parameterValues.metallicFactor)) { + fragmentShader += ' float metalness = clamp(u_metallicFactor, 0.0, 1.0);\n'; + } else { + fragmentShader += ' float metalness = 1.0;\n'; } - materialData = packetData.polylineArrow; - processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection); - } else if (defined(packetData.polylineDash)) { - if (!(existingMaterial instanceof PolylineDashMaterialProperty)) { - existingMaterial = new PolylineDashMaterialProperty(); + if (defined(parameterValues.roughnessFactor)) { + fragmentShader += ' float roughness = clamp(u_roughnessFactor, 0.04, 1.0);\n'; + } else { + fragmentShader += ' float roughness = 1.0;\n'; } - materialData = packetData.polylineDash; - processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection); - processPacketData(Color, existingMaterial, 'gapColor', materialData.gapColor, undefined, undefined, entityCollection); - processPacketData(Number, existingMaterial, 'dashLength', materialData.dashLength, undefined, sourceUri, entityCollection); - processPacketData(Number, existingMaterial, 'dashPattern', materialData.dashPattern, undefined, sourceUri, entityCollection); } + fragmentShader += ' vec3 v = -normalize(v_positionEC);\n'; - if (defined(existingInterval)) { - existingInterval.data = existingMaterial; + // Generate fragment shader's lighting block + fragmentShader += ' vec3 lightColor = vec3(1.0, 1.0, 1.0);\n'; + if (optimizeForCesium) { + fragmentShader += ' vec3 l = normalize(czm_sunDirectionEC);\n'; } else { - object[propertyName] = existingMaterial; + fragmentShader += ' vec3 l = vec3(0.0, 0.0, 1.0);\n'; + } + fragmentShader += ' vec3 h = normalize(v + l);\n'; + if (optimizeForCesium) { + fragmentShader += ' vec3 r = normalize(czm_inverseViewRotation * normalize(reflect(v, n)));\n'; + // Figure out if the reflection vector hits the ellipsoid + fragmentShader += ' czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC();\n'; + fragmentShader += ' float vertexRadius = length(v_positionWC);\n'; + fragmentShader += ' float horizonDotNadir = 1.0 - ellipsoid.radii.x / vertexRadius;\n'; + fragmentShader += ' float reflectionDotNadir = dot(r, normalize(v_positionWC));\n'; + // Flipping the X vector is a cheap way to get the inverse of czm_temeToPseudoFixed, since that's a rotation about Z. + fragmentShader += ' r.x = -r.x;\n'; + fragmentShader += ' r = -normalize(czm_temeToPseudoFixed * r);\n'; + fragmentShader += ' r.x = -r.x;\n'; + } else { + fragmentShader += ' vec3 r = normalize(reflect(v, n));\n'; + } + fragmentShader += ' float NdotL = clamp(dot(n, l), 0.001, 1.0);\n'; + fragmentShader += ' float NdotV = abs(dot(n, v)) + 0.001;\n'; + fragmentShader += ' float NdotH = clamp(dot(n, h), 0.0, 1.0);\n'; + fragmentShader += ' float LdotH = clamp(dot(l, h), 0.0, 1.0);\n'; + fragmentShader += ' float VdotH = clamp(dot(v, h), 0.0, 1.0);\n'; + + fragmentShader += ' vec3 f0 = vec3(0.04);\n'; + fragmentShader += ' float alpha = roughness * roughness;\n'; + fragmentShader += ' vec3 diffuseColor = baseColor * (1.0 - metalness);\n'; + fragmentShader += ' vec3 specularColor = mix(f0, baseColor, metalness);\n'; + fragmentShader += ' float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);\n'; + fragmentShader += ' vec3 r90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0));\n'; + fragmentShader += ' vec3 r0 = specularColor.rgb;\n'; + + fragmentShader += ' vec3 F = fresnelSchlick2(r0, r90, VdotH);\n'; + fragmentShader += ' float G = smithVisibilityGGX(alpha, NdotL, NdotV);\n'; + fragmentShader += ' float D = GGX(alpha, NdotH);\n'; + + fragmentShader += ' vec3 diffuseContribution = (1.0 - F) * lambertianDiffuse(baseColor);\n'; + fragmentShader += ' vec3 specularContribution = F * G * D / (4.0 * NdotL * NdotV);\n'; + fragmentShader += ' vec3 color = NdotL * lightColor * (diffuseContribution + specularContribution);\n'; + + if (optimizeForCesium) { + fragmentShader += ' float inverseRoughness = 1.0 - roughness;\n'; + fragmentShader += ' inverseRoughness *= inverseRoughness;\n'; + fragmentShader += ' vec3 sceneSkyBox = textureCube(czm_environmentMap, r).rgb * inverseRoughness;\n'; + + fragmentShader += ' float atmosphereHeight = 0.05;\n'; + fragmentShader += ' float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - horizonDotNadir);\n'; + fragmentShader += ' float farAboveHorizon = clamp(horizonDotNadir - blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; + fragmentShader += ' float aroundHorizon = clamp(horizonDotNadir + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; + fragmentShader += ' float farBelowHorizon = clamp(horizonDotNadir + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999);\n'; + fragmentShader += ' float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir);\n'; + fragmentShader += ' float lightScale = smoothstepHeight * 1.5 + 1.0;\n'; + + fragmentShader += ' vec3 diffuseIrradiance = mix(vec3(0.5), vec3(0.05), smoothstepHeight);\n'; + fragmentShader += ' vec3 belowHorizonColor = mix(vec3(0.1, 0.2, 0.4), vec3(0.2, 0.5, 0.7), smoothstepHeight);\n'; + fragmentShader += ' vec3 nadirColor = belowHorizonColor * 0.5;\n'; + fragmentShader += ' vec3 aboveHorizonColor = vec3(0.8, 0.9, 0.95);\n'; + fragmentShader += ' vec3 blueSkyColor = mix(vec3(0.09, 0.13, 0.24), aboveHorizonColor, reflectionDotNadir * inverseRoughness * 0.5 + 0.5);\n'; + fragmentShader += ' vec3 zenithColor = mix(blueSkyColor, sceneSkyBox, smoothstepHeight);\n'; + + fragmentShader += ' vec3 specularIrradiance = mix(zenithColor, aboveHorizonColor, smoothstep(farAboveHorizon, aroundHorizon, reflectionDotNadir) * inverseRoughness);\n'; + fragmentShader += ' specularIrradiance = mix(specularIrradiance, belowHorizonColor, smoothstep(aroundHorizon, farBelowHorizon, reflectionDotNadir) * inverseRoughness);\n'; + fragmentShader += ' specularIrradiance = mix(specularIrradiance, nadirColor, smoothstep(farBelowHorizon, 1.0, reflectionDotNadir) * inverseRoughness);\n'; + + fragmentShader += ' vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg;\n'; + fragmentShader += ' vec3 IBLColor = (diffuseIrradiance * diffuseColor) + (specularIrradiance * (specularColor * brdfLut.x + brdfLut.y));\n'; + fragmentShader += ' color = color * lightScale + IBLColor;\n'; + } + + if (defined(parameterValues.occlusionTexture)) { + fragmentShader += ' color *= texture2D(u_occlusionTexture, ' + v_texcoord + ').r;\n'; + } + if (defined(parameterValues.emissiveTexture)) { + fragmentShader += ' vec3 emissive = texture2D(u_emissiveTexture, ' + v_texcoord + ').rgb;\n'; + if (defined(parameterValues.emissiveFactor)) { + fragmentShader += ' emissive *= u_emissiveFactor;\n'; + } + fragmentShader += ' color += emissive;\n'; } - } - - function processMaterialPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection) { - if (!defined(packetData)) { - return; + else { + if (defined(parameterValues.emissiveFactor)) { + fragmentShader += ' color += u_emissiveFactor;\n'; + } } - if (isArray(packetData)) { - for (var i = 0, len = packetData.length; i < len; i++) { - processMaterialProperty(object, propertyName, packetData[i], interval, sourceUri, entityCollection); + // Final color + var alphaMode = material.alphaMode; + if (defined(alphaMode)) { + if (alphaMode === 'MASK') { + var alphaCutoff = material.alphaCutoff; + if (defined(alphaCutoff)) { + fragmentShader += ' gl_FragColor = vec4(color, int(baseColorWithAlpha.a >= ' + alphaCutoff + '));\n'; + } else { + fragmentShader += ' gl_FragColor = vec4(color, 1.0);\n'; + } + } else if (alphaMode === 'BLEND') { + fragmentShader += ' gl_FragColor = vec4(color, baseColorWithAlpha.a);\n'; + } else { + fragmentShader += ' gl_FragColor = vec4(color, 1.0);\n'; } } else { - processMaterialProperty(object, propertyName, packetData, interval, sourceUri, entityCollection); + fragmentShader += ' gl_FragColor = vec4(color, 1.0);\n'; } - } - - function processName(entity, packet, entityCollection, sourceUri) { - entity.name = defaultValue(packet.name, entity.name); - } + fragmentShader += '}\n'; - function processDescription(entity, packet, entityCollection, sourceUri) { - var descriptionData = packet.description; - if (defined(descriptionData)) { - processPacketData(String, entity, 'description', descriptionData, undefined, sourceUri, entityCollection); + var techniqueStates; + if (defined(alphaMode) && alphaMode !== 'OPAQUE') { + techniqueStates = { + enable: [ + WebGLConstants.DEPTH_TEST, + WebGLConstants.BLEND + ], + functions: { + depthMask : [false], + blendEquationSeparate: [ + WebGLConstants.FUNC_ADD, + WebGLConstants.FUNC_ADD + ], + blendFuncSeparate: [ + WebGLConstants.ONE, + WebGLConstants.ONE_MINUS_SRC_ALPHA, + WebGLConstants.ONE, + WebGLConstants.ONE_MINUS_SRC_ALPHA + ] + } + }; + } else if (parameterValues.doubleSided) { + techniqueStates = { + enable : [ + WebGLConstants.DEPTH_TEST + ] + }; + } else { + techniqueStates = { + enable : [ + WebGLConstants.CULL_FACE, + WebGLConstants.DEPTH_TEST + ] + }; } - } - function processPosition(entity, packet, entityCollection, sourceUri) { - var positionData = packet.position; - if (defined(positionData)) { - processPositionPacketData(entity, 'position', positionData, undefined, sourceUri, entityCollection); - } + // Add shaders + var vertexShaderId = addToArray(shaders, { + type : WebGLConstants.VERTEX_SHADER, + extras : { + _pipeline : { + source : vertexShader, + extension : '.glsl' + } + } + }); + + var fragmentShaderId = addToArray(shaders, { + type : WebGLConstants.FRAGMENT_SHADER, + extras : { + _pipeline : { + source : fragmentShader, + extension : '.glsl' + } + } + }); + + // Add program + var programAttributes = Object.keys(techniqueAttributes); + var programId = addToArray(programs, { + attributes : programAttributes, + fragmentShader : fragmentShaderId, + vertexShader : vertexShaderId + }); + + var techniqueId = addToArray(techniques, { + attributes : techniqueAttributes, + parameters : techniqueParameters, + program : programId, + states : techniqueStates, + uniforms : techniqueUniforms + }); + + return techniqueId; } - function processViewFrom(entity, packet, entityCollection, sourceUri) { - var viewFromData = packet.viewFrom; - if (defined(viewFromData)) { - processPacketData(Cartesian3, entity, 'viewFrom', viewFromData, undefined, sourceUri, entityCollection); + function getPBRValueType(paramName) { + switch (paramName) { + case 'baseColorFactor': + return WebGLConstants.FLOAT_VEC4; + case 'metallicFactor': + return WebGLConstants.FLOAT; + case 'roughnessFactor': + return WebGLConstants.FLOAT; + case 'baseColorTexture': + return WebGLConstants.SAMPLER_2D; + case 'metallicRoughnessTexture': + return WebGLConstants.SAMPLER_2D; + case 'normalTexture': + return WebGLConstants.SAMPLER_2D; + case 'occlusionTexture': + return WebGLConstants.SAMPLER_2D; + case 'emissiveTexture': + return WebGLConstants.SAMPLER_2D; + case 'emissiveFactor': + return WebGLConstants.FLOAT_VEC3; + case 'doubleSided': + return WebGLConstants.BOOL; } } - function processOrientation(entity, packet, entityCollection, sourceUri) { - var orientationData = packet.orientation; - if (defined(orientationData)) { - processPacketData(Quaternion, entity, 'orientation', orientationData, undefined, sourceUri, entityCollection); + function getShaderVariable(type) { + if (type === 'SCALAR') { + return 'float'; } + return type.toLowerCase(); } - function processProperties(entity, packet, entityCollection, sourceUri) { - var propertiesData = packet.properties; - if (defined(propertiesData)) { - if (!defined(entity.properties)) { - entity.properties = new PropertyBag(); - } - //We cannot simply call processPacketData(entity, 'properties', propertyData, undefined, sourceUri, entityCollection) - //because each property of "properties" may vary separately. - //The properties will be accessible as entity.properties.myprop.getValue(time). + function ensureSemanticExistenceForPrimitive(gltf, primitive) { + var accessors = gltf.accessors; + var materials = gltf.materials; + var techniques = gltf.techniques; + var programs = gltf.programs; + var shaders = gltf.shaders; + var targets = primitive.targets; - for (var key in propertiesData) { - if (propertiesData.hasOwnProperty(key)) { - if (!entity.properties.hasProperty(key)) { - entity.properties.addProperty(key); + var attributes = primitive.attributes; + for (var target in targets) { + if (defined(target)) { + var targetAttributes = targets[target]; + for (var attribute in targetAttributes) { + if (attribute !== 'extras') { + attributes[attribute + '_' + target] = targetAttributes[attribute]; } + } + } + } + var material = materials[primitive.material]; + var technique = techniques[material.technique]; + var program = programs[technique.program]; + var vertexShader = shaders[program.vertexShader]; - var propertyData = propertiesData[key]; - if (isArray(propertyData)) { - for (var i = 0, len = propertyData.length; i < len; i++) { - processProperty(getPropertyType(propertyData[i]), entity.properties, key, propertyData[i], undefined, sourceUri, entityCollection); - } - } else { - processProperty(getPropertyType(propertyData), entity.properties, key, propertyData, undefined, sourceUri, entityCollection); + for (var semantic in attributes) { + if (attributes.hasOwnProperty(semantic)) { + if (!defined(techniqueParameterForSemantic(technique, semantic))) { + var accessorId = attributes[semantic]; + var accessor = accessors[accessorId]; + if (semantic.charAt(0) === '_') { + semantic = semantic.slice(1); } + var attributeName = 'a_' + semantic; + technique.parameters[semantic] = { + semantic : semantic, + type : accessor.componentType + }; + technique.attributes[attributeName] = semantic; + program.attributes.push(attributeName); + var pipelineExtras = vertexShader.extras._pipeline; + var shaderText = pipelineExtras.source; + shaderText = 'attribute ' + getShaderVariable(accessor.type) + ' ' + attributeName + ';\n' + shaderText; + pipelineExtras.source = shaderText; } } } } - function processArrayPacketData(object, propertyName, packetData, entityCollection) { - var references = packetData.references; - if (defined(references)) { - var properties = references.map(function(reference) { - return makeReference(entityCollection, reference); + function ensureSemanticExistence(gltf) { + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + ensureSemanticExistenceForPrimitive(gltf, primitive); }); + }); + } - 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; + function splitIncompatibleSkins(gltf) { + var accessors = gltf.accessors; + var materials = gltf.materials; + ForEach.mesh(gltf, function(mesh) { + ForEach.meshPrimitive(mesh, function(primitive) { + var materialId = primitive.material; + var material = materials[materialId]; + + var jointAccessorId = primitive.attributes.JOINTS_0; + var componentType; + var type; + if (defined(jointAccessorId)) { + var jointAccessor = accessors[jointAccessorId]; + componentType = jointAccessor.componentType; + type = jointAccessor.type; + } + var isSkinned = defined(jointAccessorId); + + var skinningInfo = material.extras._pipeline.skinning; + if (!defined(skinningInfo)) { + material.extras._pipeline.skinning = { + skinned : isSkinned, + componentType : componentType, + type : type + }; + } else if ((skinningInfo.skinned !== isSkinned) || (skinningInfo.type !== type)) { + // This primitive uses the same material as another one that either isn't skinned or uses a different type to store joints and weights + var clonedMaterial = clone(material, true); + clonedMaterial.material.extras._pipeline.skinning = { + skinned : isSkinned, + componentType : componentType, + type : type + }; + // Split this off as a separate material + materialId = addToArray(materials, clonedMaterial); + primitive.material = materialId; + } + }); + }); + } + + return processPbrMetallicRoughness; +}); + +define('ThirdParty/GltfPipeline/removePipelineExtras',[ + '../../Core/defined' + ], function( + defined) { + 'use strict'; + + /** + * Iterate through the objects within each glTF object and delete their pipeline extras object. + * + * @param {Object} object Root object to remove pipeline extras. + * @returns {Object} glTF with no pipeline extras. + */ + function removePipelineExtras(object) { + if (defined(object) && typeof object === 'object') { + if (defined(object.extras) && defined(object.extras._pipeline)) { + delete object.extras._pipeline; + // Also delete extras if extras._pipeline is the only extras object + if (Object.keys(object.extras).length === 0) { + delete object.extras; + } + } + + //Recursively check subproperties for extras + for (var propertyId in object) { + if (object.hasOwnProperty(propertyId)) { + removePipelineExtras(object[propertyId]); } - } else { - object[propertyName] = new PropertyArray(properties); } - } else { - processPacketData(Array, object, propertyName, packetData, undefined, undefined, entityCollection); } + + return object; } + return removePipelineExtras; +}); - function processArray(object, propertyName, packetData, entityCollection) { - if (!defined(packetData)) { - return; +define('Scene/AttributeType',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * An enum describing the attribute type for glTF and 3D Tiles. + * + * @exports AttributeType + * + * @private + */ + var AttributeType = { + /** + * The attribute is a single component. + * + * @type {String} + * @constant + */ + SCALAR : 'SCALAR', + + /** + * The attribute is a two-component vector. + * + * @type {String} + * @constant + */ + VEC2 : 'VEC2', + + /** + * The attribute is a three-component vector. + * + * @type {String} + * @constant + */ + VEC3 : 'VEC3', + + /** + * The attribute is a four-component vector. + * + * @type {String} + * @constant + */ + VEC4 : 'VEC4', + + /** + * The attribute is a 2x2 matrix. + * + * @type {String} + * @constant + */ + MAT2 : 'MAT2', + + /** + * The attribute is a 3x3 matrix. + * + * @type {String} + * @constant + */ + MAT3 : 'MAT3', + + /** + * The attribute is a 4x4 matrix. + * + * @type {String} + * @constant + */ + MAT4 : 'MAT4' + }; + + return freezeObject(AttributeType); +}); + +define('Scene/Axis',[ + '../Core/Check', + '../Core/freezeObject', + '../Core/Math', + '../Core/Matrix3', + '../Core/Matrix4' + ], function( + Check, + freezeObject, + CesiumMath, + Matrix3, + Matrix4) { + 'use strict'; + + /** + * An enum describing the x, y, and z axes and helper conversion functions. + * + * @exports Axis + * @private + */ + var Axis = { + /** + * Denotes the x-axis. + * + * @type {Number} + * @constant + */ + X : 0, + + /** + * Denotes the y-axis. + * + * @type {Number} + * @constant + */ + Y : 1, + + /** + * Denotes the z-axis. + * + * @type {Number} + * @constant + */ + Z : 2, + + /** + * Matrix used to convert from y-up to z-up + * + * @type {Matrix4} + * @constant + */ + Y_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from z-up to y-up + * + * @type {Matrix4} + * @constant + */ + Z_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(-CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from x-up to z-up + * + * @type {Matrix4} + * @constant + */ + X_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(-CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from z-up to x-up + * + * @type {Matrix4} + * @constant + */ + Z_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from x-up to y-up + * + * @type {Matrix4} + * @constant + */ + X_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(CesiumMath.PI_OVER_TWO)), + + /** + * Matrix used to convert from y-up to x-up + * + * @type {Matrix4} + * @constant + */ + Y_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(-CesiumMath.PI_OVER_TWO)), + + /** + * Gets the axis by name + * + * @param {String} name The name of the axis. + * @returns {Number} The axis enum. + */ + fromName : function(name) { + + return Axis[name]; } + }; - if (isArray(packetData)) { - for (var i = 0, length = packetData.length; i < length; ++i) { - processArrayPacketData(object, propertyName, packetData[i], entityCollection); + return freezeObject(Axis); +}); + +define('Scene/getAttributeOrUniformBySemantic',[ + '../Core/defined' + ], function( + defined) { + 'use strict'; + + /** + * Return the uniform or attribute that has the given semantic. + * + * @private + */ + function getAttributeOrUniformBySemantic(gltf, semantic) { + var techniques = gltf.techniques; + var parameter; + 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)) { + parameter = parameters[attributes[attributeName]]; + if (defined(parameter) && parameter.semantic === semantic) { + return attributeName; + } + } + } + for (var uniformName in uniforms) { + if (uniforms.hasOwnProperty(uniformName)) { + parameter = parameters[uniforms[uniformName]]; + if (defined(parameter) && parameter.semantic === semantic) { + return uniformName; + } + } + } } - } else { - processArrayPacketData(object, propertyName, packetData, entityCollection); } + return undefined; } - function processPositionsPacketData(object, propertyName, positionsData, entityCollection) { - if (defined(positionsData.references)) { - var properties = positionsData.references.map(function(reference) { - return makeReference(entityCollection, reference); - }); + return getAttributeOrUniformBySemantic; +}); - 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); - } +define('Scene/JobType',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * @private + */ + var JobType = { + TEXTURE : 0, + PROGRAM : 1, + BUFFER : 2, + NUMBER_OF_JOB_TYPES : 3 + }; + + return freezeObject(JobType); +}); + +define('Scene/ModelAnimationCache',[ + './AttributeType', + '../Core/Cartesian3', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/LinearSpline', + '../Core/Matrix4', + '../Core/Quaternion', + '../Core/QuaternionSpline', + '../Core/WebGLConstants', + '../Core/WeightSpline', + '../ThirdParty/GltfPipeline/getAccessorByteStride', + '../ThirdParty/GltfPipeline/numberOfComponentsForType' + ], function( + AttributeType, + Cartesian3, + ComponentDatatype, + defaultValue, + defined, + LinearSpline, + Matrix4, + Quaternion, + QuaternionSpline, + WebGLConstants, + WeightSpline, + getAccessorByteStride, + numberOfComponentsForType) { + 'use strict'; - if (defined(positionsData.array)) { - processPacketData(Array, object, propertyName, positionsData, undefined, undefined, entityCollection); - } - } + /** + * @private + */ + function ModelAnimationCache() { } - function processPositions(object, propertyName, positionsData, entityCollection) { - if (!defined(positionsData)) { - return; - } + var dataUriRegex = /^data\:/i; - 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 getAccessorKey(model, accessor) { + var gltf = model.gltf; + var buffers = gltf.buffers; + var bufferViews = gltf.bufferViews; - function processAvailability(entity, packet, entityCollection, sourceUri) { - var interval; - var packetData = packet.availability; - if (!defined(packetData)) { - return; - } + var bufferView = bufferViews[accessor.bufferView]; + var buffer = buffers[bufferView.buffer]; - 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 byteOffset = bufferView.byteOffset + accessor.byteOffset; + var byteLength = accessor.count * numberOfComponentsForType(accessor.type); + + var uriKey = dataUriRegex.test(buffer.uri) ? '' : buffer.uri; + return model.cacheKey + '//' + uriKey + '/' + byteOffset + '/' + byteLength; } - var iso8601Scratch = { - iso8601 : undefined + var cachedAnimationParameters = { }; - function processAlignedAxis(billboard, packetData, interval, sourceUri, entityCollection) { - if (!defined(packetData)) { - return; - } + ModelAnimationCache.getAnimationParameterValues = function(model, accessor) { + var key = getAccessorKey(model, accessor); + var values = cachedAnimationParameters[key]; - if (defined(packetData.velocityReference)) { - billboard.alignedAxis = new VelocityVectorProperty(makeReference(entityCollection, packetData.velocityReference), true); - } else { - processPacketData(Cartesian3, billboard, 'alignedAxis', packetData, interval, sourceUri, entityCollection); - } - } + if (!defined(values)) { + // Cache miss + var gltf = model.gltf; - function processBillboard(entity, packet, entityCollection, sourceUri) { - var billboardData = packet.billboard; - if (!defined(billboardData)) { - return; - } + var buffers = gltf.buffers; + var bufferViews = gltf.bufferViews; - var interval; - var intervalString = billboardData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + var bufferView = bufferViews[accessor.bufferView]; + var bufferId = bufferView.buffer; + var buffer = buffers[bufferId]; + var source = buffer.extras._pipeline.source; - var billboard = entity.billboard; - if (!defined(billboard)) { - entity.billboard = billboard = new BillboardGraphics(); - } + var componentType = accessor.componentType; + var type = accessor.type; + var numberOfComponents = numberOfComponentsForType(type); + var count = accessor.count; + var byteStride = getAccessorByteStride(gltf, accessor); + + values = new Array(count); + var accessorByteOffset = defaultValue(accessor.byteOffset, 0); + var byteOffset = bufferView.byteOffset + accessorByteOffset; + for (var i = 0; i < count; i++) { + var typedArrayView = ComponentDatatype.createArrayBufferView(componentType, source.buffer, source.byteOffset + byteOffset, numberOfComponents); + if (type === 'SCALAR') { + values[i] = typedArrayView[0]; + } else if (type === 'VEC3') { + values[i] = Cartesian3.fromArray(typedArrayView); + } else if (type === 'VEC4') { + values[i] = Quaternion.unpack(typedArrayView); + } + byteOffset += byteStride; + } + // GLTF_SPEC: Support more parameter types when glTF supports targeting materials. https://github.com/KhronosGroup/glTF/issues/142 - 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; + if (defined(model.cacheKey)) { + // Only cache when we can create a unique id + cachedAnimationParameters[key] = values; + } } - var interval; - var intervalString = boxData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + return values; + }; - var box = entity.box; - if (!defined(box)) { - entity.box = box = new BoxGraphics(); - } + var cachedAnimationSplines = { + }; - 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 getAnimationSplineKey(model, animationName, samplerName) { + return model.cacheKey + '//' + animationName + '/' + samplerName; } - function processCorridor(entity, packet, entityCollection, sourceUri) { - var corridorData = packet.corridor; - if (!defined(corridorData)) { - return; - } + function ConstantSpline(value) { + this._value = value; + } + ConstantSpline.prototype.evaluate = function(time, result) { + return this._value; + }; - var interval; - var intervalString = corridorData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + ModelAnimationCache.getAnimationSpline = function(model, animationName, animation, samplerName, sampler, input, path, output) { + var key = getAnimationSplineKey(model, animationName, samplerName); + var spline = cachedAnimationSplines[key]; - var corridor = entity.corridor; - if (!defined(corridor)) { - entity.corridor = corridor = new CorridorGraphics(); - } + if (!defined(spline)) { + var times = input; + var controlPoints = output; - 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; - } + if ((times.length === 1) && (controlPoints.length === 1)) { + spline = new ConstantSpline(controlPoints[0]); + } else if (sampler.interpolation === 'LINEAR') { + if (path === 'translation' || path === 'scale') { + spline = new LinearSpline({ + times : times, + points : controlPoints + }); + } else if (path === 'rotation') { + spline = new QuaternionSpline({ + times : times, + points : controlPoints + }); + } else if (path === 'weights') { + spline = new WeightSpline({ + times : times, + weights : controlPoints + }); + } + // GLTF_SPEC: Support more parameter types when glTF supports targeting materials. https://github.com/KhronosGroup/glTF/issues/142 + } - var interval; - var intervalString = cylinderData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); + if (defined(model.cacheKey)) { + // Only cache when we can create a unique id + cachedAnimationSplines[key] = spline; + } } - var cylinder = entity.cylinder; - if (!defined(cylinder)) { - entity.cylinder = cylinder = new CylinderGraphics(); - } + return spline; + }; - 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); - } + var cachedSkinInverseBindMatrices = { + }; - 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; - } - } - } + ModelAnimationCache.getSkinInverseBindMatrices = function(model, accessor) { + var key = getAccessorKey(model, accessor); + var matrices = cachedSkinInverseBindMatrices[key]; - 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.'); - } + if (!defined(matrices)) { + // Cache miss + var gltf = model.gltf; + var buffers = gltf.buffers; + var bufferViews = gltf.bufferViews; - var documentPacket = dataSource._documentPacket; + var bufferViewId = accessor.bufferView; + var bufferView = bufferViews[bufferViewId]; + var bufferId = bufferView.buffer; + var buffer = buffers[bufferId]; + var source = buffer.extras._pipeline.source; - if (defined(packet.name)) { - documentPacket.name = packet.name; - } + var componentType = accessor.componentType; + var type = accessor.type; + var count = accessor.count; + var byteStride = getAccessorByteStride(gltf, accessor); + var byteOffset = bufferView.byteOffset + accessor.byteOffset; + var numberOfComponents = numberOfComponentsForType(type); - 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); + matrices = new Array(count); + + if ((componentType === WebGLConstants.FLOAT) && (type === AttributeType.MAT4)) { + for (var i = 0; i < count; ++i) { + var typedArrayView = ComponentDatatype.createArrayBufferView(componentType, source.buffer, source.byteOffset + byteOffset, numberOfComponents); + matrices[i] = Matrix4.fromArray(typedArrayView); + byteOffset += byteStride; + } } - } - } - function processEllipse(entity, packet, entityCollection, sourceUri) { - var ellipseData = packet.ellipse; - if (!defined(ellipseData)) { - return; + cachedSkinInverseBindMatrices[key] = matrices; } - var interval; - var intervalString = ellipseData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + return matrices; + }; - var ellipse = entity.ellipse; - if (!defined(ellipse)) { - entity.ellipse = ellipse = new EllipseGraphics(); - } + return ModelAnimationCache; +}); - 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; - } +define('Scene/ModelAnimationLoop',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; - var interval; - var intervalString = ellipsoidData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + /** + * 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, - var ellipsoid = entity.ellipsoid; - if (!defined(ellipsoid)) { - entity.ellipsoid = ellipsoid = new EllipsoidGraphics(); - } + /** + * Loop the animation playing it from the start immediately after it stops. + * + * @type {Number} + * @constant + */ + REPEAT : 1, - 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); - } + /** + * Loop the animation. First, playing it forward, then in reverse, then forward, and so on. + * + * @type {Number} + * @constant + */ + MIRRORED_REPEAT : 2 + }; - function processLabel(entity, packet, entityCollection, sourceUri) { - var labelData = packet.label; - if (!defined(labelData)) { - return; - } + return freezeObject(ModelAnimationLoop); +}); - var interval; - var intervalString = labelData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } +define('Scene/ModelAnimationState',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; - var label = entity.label; - if (!defined(label)) { - entity.label = label = new LabelGraphics(); - } + /** + * @private + */ + return freezeObject({ + STOPPED : 0, + ANIMATING : 1 + }); +}); - 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; - } +define('Scene/ModelAnimation',[ + '../Core/defaultValue', + '../Core/defineProperties', + '../Core/Event', + '../Core/JulianDate', + './ModelAnimationLoop', + './ModelAnimationState' + ], function( + defaultValue, + defineProperties, + Event, + JulianDate, + ModelAnimationLoop, + ModelAnimationState) { + 'use strict'; - var interval; - var intervalString = modelData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + /** + * 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 = runtimeAnimation.name; + this._startTime = JulianDate.clone(options.startTime); + this._delay = defaultValue(options.delay, 0.0); // in seconds + this._stopTime = options.stopTime; - var model = entity.model; - if (!defined(model)) { - entity.model = model = new ModelGraphics(); - } + /** + * 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); - 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); + this._speedup = defaultValue(options.speedup, 1.0); + this._reverse = defaultValue(options.reverse, false); + this._loop = defaultValue(options.loop, ModelAnimationLoop.NONE); - 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); - } - } - } + /** + * 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(); - 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; - } + /** + * 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(); - var nodeTransformations = model.nodeTransformations; - var nodeNames = Object.keys(nodeTransformationsData); - for (var i = 0, len = nodeNames.length; i < len; ++i) { - var nodeName = nodeNames[i]; + /** + * 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(); - if (nodeName === 'interval') { - continue; - } + this._state = ModelAnimationState.STOPPED; + this._runtimeAnimation = runtimeAnimation; - var nodeTransformationData = nodeTransformationsData[nodeName]; + // Set during animation update + this._computedStartTime = undefined; + this._duration = undefined; - if (!defined(nodeTransformationData)) { - continue; - } + // 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); + }; + } - if (!defined(nodeTransformations)) { - model.nodeTransformations = nodeTransformations = new PropertyBag(); + defineProperties(ModelAnimation.prototype, { + /** + * The glTF animation name that identifies this animation. + * + * @memberof ModelAnimation.prototype + * + * @type {String} + * @readonly + */ + name : { + get : function() { + return this._name; } + }, - if (!nodeTransformations.hasProperty(nodeName)) { - nodeTransformations.addProperty(nodeName); + /** + * 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; } + }, - var nodeTransformation = nodeTransformations[nodeName]; - if (!defined(nodeTransformation)) { - nodeTransformations[nodeName] = nodeTransformation = new NodeTransformationProperty(); + /** + * 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; } + }, - 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); - } - } + /** + * 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; + } + }, - function processPath(entity, packet, entityCollection, sourceUri) { - var pathData = packet.path; - if (!defined(pathData)) { - return; - } + /** + * 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; + } + }, - var interval; - var intervalString = pathData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + /** + * When true, the animation is played in reverse. + * + * @memberof ModelAnimation.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + reverse : { + get : function() { + return this._reverse; + } + }, - var path = entity.path; - if (!defined(path)) { - entity.path = path = new PathGraphics(); + /** + * Determines if and how the animation is looped. + * + * @memberof ModelAnimation.prototype + * + * @type {ModelAnimationLoop} + * @readonly + * + * @default {@link ModelAnimationLoop.NONE} + */ + loop : { + get : function() { + return this._loop; + } } + }); - 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); - } + return ModelAnimation; +}); - function processPoint(entity, packet, entityCollection, sourceUri) { - var pointData = packet.point; - if (!defined(pointData)) { - return; - } +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'; - var interval; - var intervalString = pointData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + /** + * 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(); - var point = entity.point; - if (!defined(point)) { - entity.point = point = new PointGraphics(); - } + /** + * 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(); - 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); + this._model = model; + this._scheduledAnimations = []; + this._previousTime = undefined; } - function processPolygon(entity, packet, entityCollection, sourceUri) { - var polygonData = packet.polygon; - if (!defined(polygonData)) { - return; + defineProperties(ModelAnimationCollection.prototype, { + /** + * The number of animations in the collection. + * + * @memberof ModelAnimationCollection.prototype + * + * @type {Number} + * @readonly + */ + length : { + get : function() { + return this._scheduledAnimations.length; + } } + }); - var interval; - var intervalString = polygonData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + function add(collection, index, options) { + var model = collection._model; + var animations = model._runtime.animations; + var animation = animations[index]; + var scheduledAnimation = new ModelAnimation(options, model, animation); + collection._scheduledAnimations.push(scheduledAnimation); + collection.animationAdded.raiseEvent(model, scheduledAnimation); + return scheduledAnimation; + } - var polygon = entity.polygon; - if (!defined(polygon)) { - entity.polygon = polygon = new PolygonGraphics(); - } + /** + * 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. Must be defined if options.id is undefined. + * @param {Number} [options.index] The glTF animation index that identifies the animation. Must be defined if options.name is undefined. + * @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.index must be a valid animation index. + * @exception {DeveloperError} Either options.name or options.index must be defined. + * @exception {DeveloperError} options.speedup must be greater than zero. + * + * @example + * // Example 1. Add an animation by name + * model.activeAnimations.add({ + * name : 'animation name' + * }); + * + * // Example 2. Add an animation by index + * model.activeAnimations.add({ + * index : 0 + * }); + * + * @example + * // Example 3. 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); - 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 model = this._model; + var animations = model._runtime.animations; - var interval; - var intervalString = polylineData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); + + if (defined(options.index)) { + return add(this, options.index, options); } - var polyline = entity.polyline; - if (!defined(polyline)) { - entity.polyline = polyline = new PolylineGraphics(); + // Find the index of the animation with the given name + var index; + var length = animations.length; + for (var i = 0; i < length; ++i) { + if (animations[i].name === options.name) { + index = i; + break; + } } - 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; - } + + return add(this, index, options); + }; - var interval; - var intervalString = rectangleData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + /** + * 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); - var rectangle = entity.rectangle; - if (!defined(rectangle)) { - entity.rectangle = rectangle = new RectangleGraphics(); + + var scheduledAnimations = []; + var model = this._model; + var animations = model._runtime.animations; + var length = animations.length; + for (var i = 0; i < length; ++i) { + scheduledAnimations.push(add(this, i, options)); } + return scheduledAnimations; + }; - 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; + /** + * 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; + } } - var interval; - var intervalString = wallData.interval; - if (defined(intervalString)) { - iso8601Scratch.iso8601 = intervalString; - interval = TimeInterval.fromIso8601(iso8601Scratch); - } + return false; + }; - var wall = entity.wall; - if (!defined(wall)) { - entity.wall = wall = new WallGraphics(); - } + /** + * 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; - 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); - } + this._scheduledAnimations = []; - function processCzmlPacket(packet, entityCollection, updaterFunctions, sourceUri, dataSource) { - var objectId = packet.id; - if (!defined(objectId)) { - objectId = createGuid(); + for (var i = 0; i < length; ++i) { + this.animationRemoved.raiseEvent(model, animations[i]); } + }; - currentId = objectId; - - if (!defined(dataSource._version) && objectId !== 'document') { - throw new RuntimeError('The first CZML packet is required to be the document object.'); + /** + * 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); } - if (packet['delete'] === true) { - entityCollection.removeById(objectId); - } else if (objectId === 'document') { - processDocument(packet, dataSource); - } else { - var entity = entityCollection.getOrCreateEntity(objectId); + return false; + }; - var parentId = packet.parent; - if (defined(parentId)) { - entity.parent = entityCollection.getOrCreateEntity(parentId); - } + /** + * 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) { + + return this._scheduledAnimations[index]; + }; - for (var i = updaterFunctions.length - 1; i > -1; i--) { - updaterFunctions[i](entity, packet, entityCollection, sourceUri); - } + function animateChannels(runtimeAnimation, localAnimationTime) { + var channelEvaluators = runtimeAnimation.channelEvaluators; + var length = channelEvaluators.length; + for (var i = 0; i < length; ++i) { + channelEvaluators[i](localAnimationTime); } - - 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); + var animationsToRemove = []; - 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; - } + function createAnimationRemovedFunction(modelAnimationCollection, model, animation) { + return function() { + modelAnimationCollection.animationRemoved.raiseEvent(model, animation); + }; + } - 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; + /** + * @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 (!clock.equals(dataSource._clock)) { - dataSource._clock = clock.clone(dataSource._clock); - return true; + 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); - return false; - } + var animationOccured = false; + var sceneTime = frameState.time; + var model = this._model; - function load(dataSource, czml, options, clear) { - - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + for (var i = 0; i < length; ++i) { + var scheduledAnimation = scheduledAnimations[i]; + var runtimeAnimation = scheduledAnimation._runtimeAnimation; - var promise = czml; - var sourceUri = options.sourceUri; - if (typeof czml === 'string') { - promise = loadJson(czml); - sourceUri = defaultValue(sourceUri, czml); - } + if (!defined(scheduledAnimation._computedStartTime)) { + scheduledAnimation._computedStartTime = JulianDate.addSeconds(defaultValue(scheduledAnimation.startTime, sceneTime), scheduledAnimation.delay, new JulianDate()); + } - DataSource.setLoading(dataSource, true); + if (!defined(scheduledAnimation._duration)) { + scheduledAnimation._duration = runtimeAnimation.stopTime * (1.0 / scheduledAnimation.speedup); + } - 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); - }); - } + var startTime = scheduledAnimation._computedStartTime; + var duration = scheduledAnimation._duration; + var stopTime = scheduledAnimation.stopTime; - function loadCzml(dataSource, czml, sourceUri, clear) { - DataSource.setLoading(dataSource, true); - var entityCollection = dataSource._entityCollection; + // [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); - if (clear) { - dataSource._version = undefined; - dataSource._documentPacket = new DocumentPacket(); - entityCollection.removeAll(); - } + // 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. - CzmlDataSource._processCzml(czml, entityCollection, sourceUri, undefined, dataSource); + var repeat = ((scheduledAnimation.loop === ModelAnimationLoop.REPEAT) || + (scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT)); - var raiseChangedEvent = updateClock(dataSource); + var play = (pastStartTime || (repeat && !defined(scheduledAnimation.startTime))) && + ((delta <= 1.0) || repeat) && + (!defined(stopTime) || JulianDate.lessThanOrEquals(sceneTime, stopTime)); - 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; + 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 if (pastStartTime && (scheduledAnimation._state === ModelAnimationState.ANIMATING)) { + // ANIMATING -> STOPPED state transition? + scheduledAnimation._state = ModelAnimationState.STOPPED; + if (scheduledAnimation.stop.numberOfListeners > 0) { + frameState.afterRender.push(scheduledAnimation._raiseStopEvent); + } + + if (scheduledAnimation.removeOnStop) { + animationsToRemove.push(scheduledAnimation); + } + } } - DataSource.setLoading(dataSource, false); - if (raiseChangedEvent) { - dataSource._changed.raiseEvent(dataSource); + // 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 dataSource; - } + return animationOccured; + }; - function DocumentPacket() { - this.name = undefined; - this.clock = undefined; - } + return ModelAnimationCollection; +}); + +define('Scene/ModelMaterial',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError' + ], function( + defined, + defineProperties, + DeveloperError) { + 'use strict'; /** - * A {@link DataSource} which processes {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/CZML-Guide|CZML}. - * @alias CzmlDataSource - * @constructor + * 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. + *

    * - * @param {String} [name] An optional name for the data source. This value will be overwritten if a loaded document contains a name. + * @alias ModelMaterial + * @internalConstructor * - * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=CZML.html|Cesium Sandcastle CZML Demo} + * @see Model#getMaterial */ - 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(); + function ModelMaterial(model, material, id) { + this._name = material.name; + this._id = id; + this._uniformMap = model._uniformMaps[id]; } - /** - * Creates a Promise to a new instance loaded with the provided CZML 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 the new instance once the data is processed. - */ - CzmlDataSource.load = function(czml, options) { - return new CzmlDataSource().load(czml, options); - }; - - defineProperties(CzmlDataSource.prototype, { + defineProperties(ModelMaterial.prototype, { /** - * Gets a human-readable name for this instance. - * @memberof CzmlDataSource.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; } }, + /** - * 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} + * 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 */ - clock : { + id : { get : function() { - return this._clock; + 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) { + + var v = this._uniformMap.values[name]; + + + 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) { + + var v = this._uniformMap.values[name]; + + if (!defined(v)) { + return undefined; + } + + return v.value; + }; + + return ModelMaterial; +}); + +define('Scene/ModelMesh',[ + '../Core/defineProperties' + ], function( + defineProperties) { + 'use strict'; + + /** + * A model's mesh and its materials. + *

    + * 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, { /** - * Gets the collection of {@link Entity} instances. - * @memberof CzmlDataSource.prototype - * @type {EntityCollection} + * 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 */ - entities : { + name : { get : function() { - return this._entityCollection; + return this._name; } }, + /** - * Gets a value indicating if the data source is currently loading data. - * @memberof CzmlDataSource.prototype - * @type {Boolean} + * 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 */ - isLoading : { + id : { get : function() { - return this._isLoading; + return this._id; } }, + /** - * Gets an event that will be raised when the underlying data changes. - * @memberof CzmlDataSource.prototype - * @type {Event} + * An array of {@link ModelMaterial} instances indexed by the mesh's + * primitive indices. + * + * @memberof ModelMesh.prototype + * + * @type {ModelMaterial[]} + * @readonly */ - changedEvent : { + materials : { get : function() { - return this._changed; + return this._materials; } - }, + } + }); + + return ModelMesh; +}); + +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; + /** - * Gets an event that will be raised if an error is encountered during processing. - * @memberof CzmlDataSource.prototype - * @type {Event} + * @private */ - errorEvent : { + 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._error; + return this._name; } }, + /** - * Gets an event that will be raised when the data source either starts or stops loading. - * @memberof CzmlDataSource.prototype - * @type {Event} + * 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 */ - loadingEvent : { + id : { get : function() { - return this._loading; + return this._id; } }, + /** - * Gets whether or not this data source should be displayed. - * @memberof CzmlDataSource.prototype + * Determines if this node and its children will be shown. + * + * @memberof ModelNode.prototype * @type {Boolean} + * + * @default true */ show : { get : function() { - return this._entityCollection.show; + return this._show; }, set : function(value) { - this._entityCollection.show = value; + if (this._show !== value) { + this._show = value; + this._model._perNodeShowDirty = true; + } } }, /** - * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * 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 CzmlDataSource.prototype - * @type {EntityCluster} + * @memberof ModelNode.prototype + * @type {Matrix4} */ - clustering : { + matrix : { get : function() { - return this._entityCluster; + return this._matrix; }, set : function(value) { - this._entityCluster = value; + this._matrix = Matrix4.clone(value, this._matrix); + this.useMatrix = true; + + var model = this._model; + model._cesiumAnimationsDirty = true; + this._runtimeNode.dirtyNumber = model._maxDirtyNumber; } } }); /** - * 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, // - processProperties, // - 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. + * @private */ - 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); - } + 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 CzmlDataSource; + return ModelNode; }); -/*global define*/ -define('DataSources/DataSourceCollection',[ +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/Event', - '../ThirdParty/when' + '../Core/DistanceDisplayCondition', + '../Core/FeatureDetection', + '../Core/getAbsoluteUri', + '../Core/getBaseUri', + '../Core/getMagic', + '../Core/getStringFromTypedArray', + '../Core/IndexDatatype', + '../Core/joinUrls', + '../Core/loadArrayBuffer', + '../Core/loadCRN', + '../Core/loadImage', + '../Core/loadImageFromTypedArray', + '../Core/loadKTX', + '../Core/loadText', + '../Core/Math', + '../Core/Matrix2', + '../Core/Matrix3', + '../Core/Matrix4', + '../Core/PixelFormat', + '../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/GltfPipeline/addDefaults', + '../ThirdParty/GltfPipeline/addPipelineExtras', + '../ThirdParty/GltfPipeline/ForEach', + '../ThirdParty/GltfPipeline/getAccessorByteStride', + '../ThirdParty/GltfPipeline/numberOfComponentsForType', + '../ThirdParty/GltfPipeline/parseBinaryGltf', + '../ThirdParty/GltfPipeline/processModelMaterialsCommon', + '../ThirdParty/GltfPipeline/processPbrMetallicRoughness', + '../ThirdParty/GltfPipeline/removePipelineExtras', + '../ThirdParty/GltfPipeline/updateVersion', + '../ThirdParty/Uri', + '../ThirdParty/when', + './AttributeType', + './Axis', + './BlendingState', + './ColorBlendMode', + './getAttributeOrUniformBySemantic', + './HeightReference', + './JobType', + './ModelAnimationCache', + './ModelAnimationCollection', + './ModelMaterial', + './ModelMesh', + './ModelNode', + './SceneMode', + './ShadowMode' ], function( + BoundingSphere, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, + clone, + Color, + combine, defaultValue, defined, defineProperties, destroyObject, DeveloperError, - Event, - when) { + DistanceDisplayCondition, + FeatureDetection, + getAbsoluteUri, + getBaseUri, + getMagic, + getStringFromTypedArray, + IndexDatatype, + joinUrls, + loadArrayBuffer, + loadCRN, + loadImage, + loadImageFromTypedArray, + loadKTX, + loadText, + CesiumMath, + Matrix2, + Matrix3, + Matrix4, + PixelFormat, + PrimitiveType, + Quaternion, + Queue, + RuntimeError, + Transforms, + WebGLConstants, + Buffer, + BufferUsage, + DrawCommand, + Pass, + RenderState, + Sampler, + ShaderProgram, + ShaderSource, + Texture, + TextureMinificationFilter, + TextureWrap, + VertexArray, + addDefaults, + addPipelineExtras, + ForEach, + getAccessorByteStride, + numberOfComponentsForType, + parseBinaryGltf, + processModelMaterialsCommon, + processPbrMetallicRoughness, + removePipelineExtras, + updateVersion, + Uri, + when, + AttributeType, + Axis, + BlendingState, + ColorBlendMode, + getAttributeOrUniformBySemantic, + HeightReference, + JobType, + ModelAnimationCache, + ModelAnimationCollection, + ModelMaterial, + ModelMesh, + ModelNode, + SceneMode, + ShadowMode) { 'use strict'; - /** - * A collection of {@link DataSource} instances. - * @alias DataSourceCollection - * @constructor - */ - function DataSourceCollection() { - this._dataSources = []; - this._dataSourceAdded = new Event(); - this._dataSourceRemoved = new Event(); + // 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 {}; } - 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; - } - } - }); + var boundingSphereCartesian3Scratch = new Cartesian3(); - /** - * 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) { - - 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; - }); + var ModelState = { + NEEDS_LOAD : 0, + LOADING : 1, + LOADED : 2, // Renderable, but textures can still be pending when incrementallyLoadTextures is true. + FAILED : 3 }; - /** - * 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); + // glTF MIME types discussed in https://github.com/KhronosGroup/glTF/issues/412 and https://github.com/KhronosGroup/glTF/issues/943 + var defaultModelAccept = 'model/gltf-binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01'; - var index = this._dataSources.indexOf(dataSource); - if (index !== -1) { - this._dataSources.splice(index, 1); - this._dataSourceRemoved.raiseEvent(this, dataSource); + function LoadResources() { + this.vertexBuffersToCreate = new Queue(); + this.indexBuffersToCreate = new Queue(); + this.buffers = {}; + this.pendingBufferLoads = 0; - if (destroy && typeof dataSource.destroy === 'function') { - dataSource.destroy(); - } + this.programsToCreate = new Queue(); + this.shaders = {}; + this.pendingShaderLoads = 0; - return true; - } + this.texturesToCreate = new Queue(); + this.pendingTextureLoads = 0; - return false; - }; + this.texturesToCreateFromBufferView = new Queue(); + this.pendingBufferViewToImage = 0; - /** - * 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); + this.createSamplers = true; + this.createSkins = true; + this.createRuntimeAnimations = true; + this.createVertexArrays = true; + this.createRenderStates = true; + this.createUniformMaps = true; + this.createRuntimeNodes = true; - var dataSources = this._dataSources; - for (var i = 0, len = dataSources.length; i < len; ++i) { - var dataSource = dataSources[i]; - this._dataSourceRemoved.raiseEvent(this, dataSource); + this.skinnedNodesIds = []; + } - if (destroy && typeof dataSource.destroy === 'function') { - dataSource.destroy(); - } - } - this._dataSources = []; + LoadResources.prototype.getBuffer = function(bufferView) { + return getSubarray(this.buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength); }; - /** - * 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; + LoadResources.prototype.finishedPendingBufferLoads = function() { + return (this.pendingBufferLoads === 0); }; - /** - * 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); + LoadResources.prototype.finishedBuffersCreation = function() { + return ((this.pendingBufferLoads === 0) && + (this.vertexBuffersToCreate.length === 0) && + (this.indexBuffersToCreate.length === 0)); }; - /** - * Gets a data source by index from the collection. - * - * @param {Number} index the index to retrieve. - * @returns {DataSource} The data source at the specified index. - */ - DataSourceCollection.prototype.get = function(index) { - - return this._dataSources[index]; + LoadResources.prototype.finishedProgramCreation = function() { + return ((this.pendingShaderLoads === 0) && (this.programsToCreate.length === 0)); }; - /** - * 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; + LoadResources.prototype.finishedTextureCreation = function() { + var finishedPendingLoads = (this.pendingTextureLoads === 0); + var finishedResourceCreation = + (this.texturesToCreate.length === 0) && + (this.texturesToCreateFromBufferView.length === 0); + + return finishedPendingLoads && finishedResourceCreation; }; - /** - * 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); + LoadResources.prototype.finishedEverythingButTextureCreation = function() { + var finishedPendingLoads = + (this.pendingBufferLoads === 0) && + (this.pendingShaderLoads === 0); + var finishedResourceCreation = + (this.vertexBuffersToCreate.length === 0) && + (this.indexBuffersToCreate.length === 0) && + (this.programsToCreate.length === 0) && + (this.pendingBufferViewToImage === 0); + + return finishedPendingLoads && finishedResourceCreation; }; - return DataSourceCollection; -}); + LoadResources.prototype.finished = function() { + return this.finishedTextureCreation() && this.finishedEverythingButTextureCreation(); + }; -/*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'; + /////////////////////////////////////////////////////////////////////////// + + function setCachedGltf(model, cachedGltf) { + model._cachedGltf = 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 = options.gltf; + this.ready = options.ready; + this.modelsToLoad = []; + this.count = 0; + } + + defineProperties(CachedGltf.prototype, { + gltf : { + set : function(value) { + this._gltf = value; + }, + + get : function() { + return this._gltf; + } + } + }); + + CachedGltf.prototype.makeReady = function(gltfJson) { + this.gltf = gltfJson; + + 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; + }; - 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(); + var gltfCache = {}; - 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 + * 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 {Entity} entity The entity containing the geometry to be visualized. - * @param {Scene} scene The scene where visualization is taking place. + * @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 EllipseGeometryUpdater(entity, scene) { - - 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); + function Model(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this._onEntityPropertyChanged(entity, 'ellipse', entity.ellipse, undefined); - } + var cacheKey = options.cacheKey; + this._cacheKey = cacheKey; + this._cachedGltf = undefined; + this._releaseGltfJson = defaultValue(options.releaseGltfJson, false); + + 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 parsedGltf = parseBinaryGltf(gltf); + + cachedGltf = new CachedGltf({ + gltf : parsedGltf, + 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 baseUri = getBaseUri(document.location.href); + this._baseUri = joinUrls(baseUri, this._basePath); - defineProperties(EllipseGeometryUpdater, { /** - * Gets the type of Appearance to use for simple color-based geometry. - * @memberof EllipseGeometryUpdater - * @type {Appearance} + * Determines if the model primitive will be shown. + * + * @type {Boolean} + * + * @default true */ - perInstanceColorAppearanceType : { - value : PerInstanceColorAppearance - }, + this.show = defaultValue(options.show, true); + /** - * Gets the type of Appearance to use for material-based geometry. - * @memberof EllipseGeometryUpdater - * @type {Appearance} + * The silhouette color. + * + * @type {Color} + * + * @default Color.RED */ - materialAppearanceType : { - value : MaterialAppearance - } - }); + this.silhouetteColor = defaultValue(options.silhouetteColor, Color.RED); + this._silhouetteColor = new Color(); + this._silhouetteColorPreviousAlpha = 1.0; + this._normalAttributeName = undefined; - defineProperties(EllipseGeometryUpdater.prototype, { /** - * Gets the entity associated with this geometry. - * @memberof EllipseGeometryUpdater.prototype + * The size of the silhouette in pixels. * - * @type {Entity} - * @readonly + * @type {Number} + * + * @default 0.0 */ - entity : { - get : function() { - return this._entity; - } - }, + this.silhouetteSize = defaultValue(options.silhouetteSize, 0.0); + /** - * Gets a value indicating if the geometry has a fill component. - * @memberof EllipseGeometryUpdater.prototype + * 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 {Boolean} - * @readonly + * @type {Matrix4} + * + * @default {@link Matrix4.IDENTITY} + * + * @example + * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0); + * m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin); */ - fillEnabled : { - get : function() { - return this._fillEnabled; - } - }, + this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + this._modelMatrix = Matrix4.clone(this.modelMatrix); + this._clampedModelMatrix = undefined; + /** - * Gets a value indicating if fill visibility varies with simulation time. - * @memberof EllipseGeometryUpdater.prototype + * 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 {Boolean} - * @readonly + * @type {Number} + * + * @default 1.0 */ - hasConstantFill : { - get : function() { - return !this._fillEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._fillProperty)); - } - }, + this.scale = defaultValue(options.scale, 1.0); + this._scale = this.scale; + /** - * Gets the material property used to fill the geometry. - * @memberof EllipseGeometryUpdater.prototype + * 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 {MaterialProperty} - * @readonly + * @type {Number} + * + * @default 0.0 */ - fillMaterialProperty : { - get : function() { - return this._materialProperty; - } - }, + this.minimumPixelSize = defaultValue(options.minimumPixelSize, 0.0); + this._minimumPixelSize = this.minimumPixelSize; + /** - * Gets a value indicating if the geometry has an outline component. - * @memberof EllipseGeometryUpdater.prototype + * 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 {Boolean} - * @readonly + * @type {Number} */ - outlineEnabled : { - get : function() { - return this._outlineEnabled; - } - }, + this.maximumScale = options.maximumScale; + this._maximumScale = this.maximumScale; + /** - * Gets a value indicating if outline visibility varies with simulation time. - * @memberof EllipseGeometryUpdater.prototype + * User-defined object returned when the model is picked. * - * @type {Boolean} - * @readonly + * @type Object + * + * @default undefined + * + * @see Scene#pick */ - hasConstantOutline : { - get : function() { - return !this._outlineEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._showOutlineProperty)); - } - }, + this.id = options.id; + this._id = options.id; + /** - * Gets the {@link Color} property for the geometry outline. - * @memberof EllipseGeometryUpdater.prototype + * Returns the height reference of the model * - * @type {Property} - * @readonly + * @memberof Model.prototype + * + * @type {HeightReference} + * + * @default HeightReference.NONE */ - outlineColorProperty : { - get : function() { - return this._outlineColorProperty; - } - }, + 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) && defined(scene.terrainProviderChanged)) { + this._terrainProviderChangedCallback = scene.terrainProviderChanged.addEventListener(function() { + this._heightChanged = true; + }, this); + } + /** - * Gets the constant with of the geometry outline, in pixels. - * This value is only valid if isDynamic is false. - * @memberof EllipseGeometryUpdater.prototype + * Used for picking primitives that wrap a model. * - * @type {Number} - * @readonly + * @private */ - outlineWidth : { - get : function() { - return this._outlineWidth; - } - }, + this._pickObject = options.pickObject; + this._allowPicking = defaultValue(options.allowPicking, true); + + this._ready = false; + this._readyPromise = when.defer(); + /** - * Gets the property specifying whether the geometry - * casts or receives shadows from each light source. - * @memberof EllipseGeometryUpdater.prototype + * The currently playing glTF animations. * - * @type {Property} - * @readonly + * @type {ModelAnimationCollection} */ - shadowsProperty : { - get : function() { - return this._shadowsProperty; - } - }, + this.activeAnimations = new ModelAnimationCollection(this); + + this._defaultTexture = undefined; + this._incrementallyLoadTextures = defaultValue(options.incrementallyLoadTextures, true); + this._asynchronous = defaultValue(options.asynchronous, true); + /** - * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. - * @memberof EllipseGeometryUpdater.prototype + * Determines whether the model casts or receives shadows from each light source. * - * @type {Property} - * @readonly + * @type {ShadowMode} + * + * @default ShadowMode.ENABLED */ - distanceDisplayConditionProperty : { - get : function() { - return this._distanceDisplayConditionProperty; - } - }, + this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); + this._shadows = this.shadows; + /** - * 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 + * A color that blends with the model's rendered color. * - * @type {Boolean} - * @readonly + * @type {Color} + * + * @default Color.WHITE */ - isDynamic : { - get : function() { - return this._dynamic; - } - }, + this.color = defaultValue(options.color, Color.WHITE); + this._color = new Color(); + this._colorPreviousAlpha = 1.0; + /** - * Gets a value indicating if the geometry is closed. - * This property is only valid for static geometry. - * @memberof EllipseGeometryUpdater.prototype + * Defines how the color blends with the model. * - * @type {Boolean} - * @readonly + * @type {ColorBlendMode} + * + * @default ColorBlendMode.HIGHLIGHT */ - isClosed : { - get : function() { - return this._isClosed; - } - }, + this.colorBlendMode = defaultValue(options.colorBlendMode, ColorBlendMode.HIGHLIGHT); + /** - * Gets a value indicating if the geometry should be drawn on terrain. - * @memberof EllipseGeometryUpdater.prototype + * 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 {Boolean} - * @readonly + * @type {Number} + * + * @default 0.5 */ - onTerrain : { - get : function() { - return this._onTerrain; - } - }, + this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5); + /** - * Gets an event that is raised whenever the public properties - * of this updater change. - * @memberof EllipseGeometryUpdater.prototype + * 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} - * @readonly + * + * @default false */ - 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) { - - 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) { - - 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; + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + this._debugShowBoundingVolume = false; - if (!defined(ellipse)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + /** + * 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; - var fillProperty = ellipse.fill; - var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + this._distanceDisplayCondition = options.distanceDisplayCondition; - var outlineProperty = ellipse.outline; - var outlineEnabled = defined(outlineProperty); - if (outlineEnabled && outlineProperty.isConstant) { - outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); - } + // Undocumented options + this._addBatchIdToGeneratedShaders = options.addBatchIdToGeneratedShaders; + 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); + this._requestType = options.requestType; + this._upAxis = defaultValue(options.upAxis, Axis.Y); - if (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + /** + * @private + * @readonly + */ + this.cull = defaultValue(options.cull, true); - var position = this._entity.position; - var semiMajorAxis = ellipse.semiMajorAxis; - var semiMinorAxis = ellipse.semiMinorAxis; + /** + * @private + * @readonly + */ + this.opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); - 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; - } + 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; - 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); + this._mode = undefined; - 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); + 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 - if (outlineEnabled && onTerrain) { - oneTimeWarning(oneTimeWarning.geometryOutlines); - outlineEnabled = false; - } + 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._fillEnabled = fillEnabled; - this._onTerrain = onTerrain; - this._isClosed = defined(extrudedHeight) || onTerrain; - this._outlineEnabled = outlineEnabled; + this._uniformMaps = {}; // Not cached since it can be targeted by glTF animation + this._extensionsUsed = undefined; // Cached used glTF extensions + this._extensionsRequired = undefined; // Cached required glTF extensions + 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._updatedGltfVersion = false; - 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); - } - }; + this._cachedGeometryByteLength = 0; + this._cachedTexturesByteLength = 0; + this._geometryByteLength = 0; + this._texturesByteLength = 0; + this._trianglesLength = 0; - /** - * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true. - * - * @param {PrimitiveCollection} primitives The primitive collection to use. - * @param {PrimitiveCollection} groundPrimitives The ground primitives 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) { - - return new DynamicGeometryUpdater(primitives, groundPrimitives, this); - }; + this._nodeCommands = []; + this._pickIds = []; - /** - * @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); + // CESIUM_RTC extension + this._rtcCenter = undefined; // reference to either 3D or 2D + this._rtcCenterEye = undefined; // in eye coordinates + this._rtcCenter3D = undefined; // in world coordinates + this._rtcCenter2D = undefined; // in projected world coordinates } - DynamicGeometryUpdater.prototype.update = function(time) { - - 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 - })); + 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; } - } - - 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) { - - 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} + * 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 */ - perInstanceColorAppearanceType : { - value : PerInstanceColorAppearance + releaseGltfJson : { + get : function() { + return this._releaseGltfJson; + } }, + /** - * Gets the type of Appearance to use for material-based geometry. - * @memberof EllipsoidGeometryUpdater - * @type {Appearance} + * 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 */ - materialAppearanceType : { - value : MaterialAppearance - } - }); + cacheKey : { + get : function() { + return this._cacheKey; + } + }, - defineProperties(EllipsoidGeometryUpdater.prototype, { /** - * Gets the entity associated with this geometry. - * @memberof EllipsoidGeometryUpdater.prototype + * 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. * - * @type {Entity} + * @memberof Model.prototype + * + * @type {String} * @readonly + * + * @default '' */ - entity : { + basePath : { get : function() { - return this._entity; + return this._basePath; } }, + /** - * Gets a value indicating if the geometry has a fill component. - * @memberof EllipsoidGeometryUpdater.prototype + * 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}. * - * @type {Boolean} + * @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()); */ - fillEnabled : { + boundingSphere : { get : function() { - return this._fillEnabled; + + 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; } }, + /** - * Gets a value indicating if fill visibility varies with simulation time. - * @memberof EllipsoidGeometryUpdater.prototype + * 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 */ - hasConstantFill : { + ready : { get : function() { - return !this._fillEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._fillProperty)); + return this._ready; } }, + /** - * Gets the material property used to fill the geometry. - * @memberof EllipsoidGeometryUpdater.prototype + * 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. + *

    * - * @type {MaterialProperty} + * @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 */ - fillMaterialProperty : { + readyPromise : { get : function() { - return this._materialProperty; + return this._readyPromise.promise; } }, + /** - * Gets a value indicating if the geometry has an outline component. - * @memberof EllipsoidGeometryUpdater.prototype + * 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 */ - outlineEnabled : { + asynchronous : { get : function() { - return this._outlineEnabled; + return this._asynchronous; } }, + /** - * Gets a value indicating if outline visibility varies with simulation time. - * @memberof EllipsoidGeometryUpdater.prototype + * 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 */ - hasConstantOutline : { + allowPicking : { get : function() { - return !this._outlineEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._showOutlineProperty)); + return this._allowPicking; } }, + /** - * Gets the {@link Color} property for the geometry outline. - * @memberof EllipsoidGeometryUpdater.prototype + * Determine if textures may continue to stream in after the model is loaded. * - * @type {Property} + * @memberof Model.prototype + * + * @type {Boolean} * @readonly + * + * @default true */ - outlineColorProperty : { + incrementallyLoadTextures : { get : function() { - return this._outlineColorProperty; + return this._incrementallyLoadTextures; } }, + /** - * Gets the constant with of the geometry outline, in pixels. - * This value is only valid if isDynamic is false. - * @memberof EllipsoidGeometryUpdater.prototype + * Return the number of pending texture loads. + * + * @memberof Model.prototype * * @type {Number} * @readonly */ - outlineWidth : { + pendingTextureLoads : { get : function() { - return this._outlineWidth; + return defined(this._loadResources) ? this._loadResources.pendingTextureLoads : 0; } }, + /** - * Gets the property specifying whether the geometry - * casts or receives shadows from each light source. - * @memberof EllipsoidGeometryUpdater.prototype - * - * @type {Property} + * Returns true if the model was transformed this frame + * + * @memberof Model.prototype + * + * @type {Boolean} * @readonly + * + * @private */ - shadowsProperty : { + dirty : { get : function() { - return this._shadowsProperty; + return this._dirty; } }, + /** - * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. - * @memberof EllipsoidGeometryUpdater.prototype + * 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) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + } + }, + + extensionsUsed : { + get : function() { + if (!defined(this._extensionsUsed)) { + this._extensionsUsed = getUsedExtensions(this); + } + return this._extensionsUsed; + } + }, + + extensionsRequired : { + get : function() { + if (!defined(this._extensionsRequired)) { + this._extensionsRequired = getRequiredExtensions(this); + } + return this._extensionsRequired; + } + }, + + /** + * Gets the model's up-axis. + * By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up. * - * @type {Property} + * @memberof Model.prototype + * + * @type {Number} + * @default Axis.Y * @readonly + * + * @private */ - distanceDisplayConditionProperty : { + upAxis : { get : function() { - return this._distanceDisplayConditionProperty; + return this._upAxis; } }, + /** - * 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 + * Gets the model's triangle count. * - * @type {Boolean} - * @readonly + * @private */ - isDynamic : { + trianglesLength : { get : function() { - return this._dynamic; + return this._trianglesLength; } }, + /** - * Gets a value indicating if the geometry is closed. - * This property is only valid for static geometry. - * @memberof EllipsoidGeometryUpdater.prototype + * Gets the model's geometry memory in bytes. This includes all vertex and index buffers. * - * @type {Boolean} - * @readonly + * @private */ - isClosed : { - value : true + geometryByteLength : { + get : function() { + return this._geometryByteLength; + } }, + /** - * Gets an event that is raised whenever the public properties - * of this updater change. - * @memberof EllipsoidGeometryUpdater.prototype + * Gets the model's texture memory in bytes. * - * @type {Boolean} - * @readonly + * @private */ - geometryChanged : { + texturesByteLength : { get : function() { - return this._geometryChanged; + return this._texturesByteLength; + } + }, + + /** + * Gets the model's cached geometry memory in bytes. This includes all vertex and index buffers. + * + * @private + */ + cachedGeometryByteLength : { + get : function() { + return this._cachedGeometryByteLength; + } + }, + + /** + * Gets the model's cached texture memory in bytes. + * + * @private + */ + cachedTexturesByteLength : { + get : function() { + return this._cachedTexturesByteLength; } } }); + function silhouetteSupported(context) { + return context.stencilBuffer; + } + /** - * Checks if the geometry is outlined at the provided time. + * Determines if silhouettes are supported. * - * @param {JulianDate} time The time for which to retrieve visibility. - * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise. + * @param {Scene} scene The scene. + * @returns {Boolean} true if silhouettes are supported; otherwise, returns false */ - EllipsoidGeometryUpdater.prototype.isOutlineVisible = function(time) { - var entity = this._entity; - return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); + Model.silhouetteSupported = function(scene) { + return silhouetteSupported(scene.context); }; /** - * 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. + * This function differs from the normal subarray function + * because it takes offset and length, rather than begin and end. */ - EllipsoidGeometryUpdater.prototype.isFilled = function(time) { - var entity = this._entity; - return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time); - }; + function getSubarray(array, offset, length) { + return array.subarray(offset, offset + length); + } + + function containsGltfMagic(uint8Array) { + var magic = getMagic(uint8Array); + return magic === 'glTF'; + } /** - * Creates the geometry instance which represents the fill of the geometry. + *

    + * 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 {JulianDate} time The time to use when retrieving initial attribute values. - * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry. + * @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 {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 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. * - * @exception {DeveloperError} This instance does not represent a filled geometry. + * @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(); + * }); */ - EllipsoidGeometryUpdater.prototype.createFillGeometryInstance = function(time) { + Model.fromGltf = function(options) { - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + 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)); + var basePath = defaultValue(options.basePath, getBaseUri(url, true)); - var attributes; + options = clone(options); + if (defined(options.basePath) && !defined(options.cacheKey)) { + cacheKey += basePath; + } - 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 - }; + options.cacheKey = cacheKey; + options.basePath = basePath; + var model = new Model(options); + + options.headers = defined(options.headers) ? clone(options.headers) : {}; + if (!defined(options.headers.Accept)) { + options.headers.Accept = defaultModelAccept; } - return new GeometryInstance({ - id : entity, - geometry : new EllipsoidGeometry(this._options), - modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE), - attributes : attributes - }); + 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 parsedGltf = parseBinaryGltf(array); + // KHR_binary_glTF is from the beginning of the binary section + cachedGltf.makeReady(parsedGltf, 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; }; /** - * 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. + * For the unit tests to verify model caching. * - * @exception {DeveloperError} This instance does not represent an outlined geometry. + * @private */ - EllipsoidGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + Model._gltfCache = gltfCache; - var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); - var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + function getRuntime(model, runtimeName, name) { + + return (model._runtime[runtimeName])[name]; + } - 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 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 true if this object was destroyed; otherwise, false. + * Returns the glTF mesh with the given name property. * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * @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. */ - EllipsoidGeometryUpdater.prototype.isDestroyed = function() { - return false; + Model.prototype.getMesh = function(name) { + return getRuntime(this, 'meshesByName', name); }; /** - * Destroys and resources used by the object. Once an object is destroyed, it should not be used. + * Returns the glTF material with the given name property. * - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * @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. */ - EllipsoidGeometryUpdater.prototype.destroy = function() { - this._entitySubscription(); - destroyObject(this); + Model.prototype.getMaterial = function(name) { + return getRuntime(this, 'materialsByName', name); }; - EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { - if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'ellipsoid')) { - return; - } - - var ellipsoid = entity.ellipsoid; + var aMinScratch = new Cartesian3(); + var aMaxScratch = new Cartesian3(); - if (!defined(ellipsoid)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); + 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; } + return { + min : accessorMin, + max : accessorMax + }; + } - var fillProperty = ellipsoid.fill; - var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + function computeBoundingSphere(model) { + var gltf = model.gltf; + var gltfNodes = gltf.nodes; + var gltfMeshes = gltf.meshes; + var rootNodes = gltf.scenes[gltf.scene].nodes; + var rootNodesLength = rootNodes.length; - var outlineProperty = ellipsoid.outline; - var outlineEnabled = defined(outlineProperty); - if (outlineEnabled && outlineProperty.isConstant) { - outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); - } + var nodeStack = []; - if (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); + 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 meshId = n.mesh; + if (defined(meshId)) { + var mesh = gltfMeshes[meshId]; + var primitives = mesh.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; } - return; } - var position = entity.position; - var radii = ellipsoid.radii; + var boundingSphere = BoundingSphere.fromCornerPoints(min, max); + if (model._upAxis === Axis.Y) { + BoundingSphere.transformWithoutScale(boundingSphere, Axis.Y_UP_TO_Z_UP, boundingSphere); + } else if (model._upAxis === Axis.X) { + BoundingSphere.transformWithoutScale(boundingSphere, Axis.X_UP_TO_Z_UP, boundingSphere); + } + return boundingSphere; + } - 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); + /////////////////////////////////////////////////////////////////////////// + + function getFailedLoadFunction(model, type, path) { + return function() { + model._state = ModelState.FAILED; + model._readyPromise.reject(new RuntimeError('Failed to load ' + type + ': ' + path)); + }; + } + + function addBuffersToLoadResources(model) { + var gltf = model.gltf; + var loadResources = model._loadResources; + ForEach.buffer(gltf, function(buffer, id) { + loadResources.buffers[id] = buffer.extras._pipeline.source; + }); + } + + function bufferLoad(model, id) { + return function(arrayBuffer) { + var loadResources = model._loadResources; + var buffer = new Uint8Array(arrayBuffer); + --loadResources.pendingBufferLoads; + model.gltf.buffers[id].extras._pipeline.source = buffer; + }; + } + + function parseBuffers(model) { + var loadResources = model._loadResources; + // Iterate this way for compatibility with objects and arrays + var buffers = model.gltf.buffers; + for (var id in buffers) { + if (buffers.hasOwnProperty(id)) { + var buffer = buffers[id]; + buffer.extras = defaultValue(buffer.extras, {}); + buffer.extras._pipeline = defaultValue(buffer.extras._pipeline, {}); + if (defined(buffer.extras._pipeline.source)) { + loadResources.buffers[id] = buffer.extras._pipeline.source; + } else { + var bufferPath = joinUrls(model._baseUri, buffer.uri); + ++loadResources.pendingBufferLoads; + loadArrayBuffer(bufferPath).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); + } } - 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; + function parseBufferViews(model) { + var bufferViews = model.gltf.bufferViews; - var stackPartitions = ellipsoid.stackPartitions; - var slicePartitions = ellipsoid.slicePartitions; - var outlineWidth = ellipsoid.outlineWidth; - var subdivisions = ellipsoid.subdivisions; + var vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate; - 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); + // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below. + ForEach.bufferView(model.gltf, function(bufferView, id) { + if (bufferView.target === WebGLConstants.ARRAY_BUFFER) { + vertexBuffersToCreate.enqueue(id); } - } 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) { - - return new DynamicGeometryUpdater(primitives, this); - }; + var indexBuffersToCreate = model._loadResources.indexBuffersToCreate; + var indexBufferIds = {}; - /** - * @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) { - - 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; + // 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. + ForEach.accessor(model.gltf, function(accessor) { + var bufferViewId = accessor.bufferView; + var bufferView = bufferViews[bufferViewId]; + + if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) { + indexBufferIds[bufferViewId] = true; + indexBuffersToCreate.enqueue({ + id : bufferViewId, + componentType : accessor.componentType + }); } + }); + } - if (defined(this._outlinePrimitive)) { - this._outlinePrimitive.show = false; + function shaderLoad(model, type, id) { + return function(source) { + var loadResources = model._loadResources; + loadResources.shaders[id] = { + source : source, + type : type, + bufferView : undefined + }; + --loadResources.pendingShaderLoads; + model.gltf.shaders[id].extras._pipeline.source = source; + }; + } + + function parseShaders(model) { + var gltf = model.gltf; + var buffers = gltf.buffers; + var bufferViews = gltf.bufferViews; + ForEach.shader(gltf, function(shader, id) { + // Shader references either uri (external or base64-encoded) or bufferView + if (defined(shader.bufferView)) { + var bufferViewId = shader.bufferView; + var bufferView = bufferViews[bufferViewId]; + var bufferId = bufferView.buffer; + var buffer = buffers[bufferId]; + var source = getStringFromTypedArray(buffer.extras._pipeline.source, bufferView.byteOffset, bufferView.byteLength); + model._loadResources.shaders[id] = { + source : source, + bufferView : undefined + }; + shader.extras._pipeline.source = source; + } else if (defined(shader.extras._pipeline.source)) { + model._loadResources.shaders[id] = { + source : shader.extras._pipeline.source, + bufferView : undefined + }; + } else { + ++model._loadResources.pendingShaderLoads; + var shaderPath = joinUrls(model._baseUri, shader.uri); + loadText(shaderPath).then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); } - 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; + function parsePrograms(model) { + ForEach.program(model.gltf, function(program, id) { + model._loadResources.programsToCreate.enqueue(id); + }); + } + + function imageLoad(model, textureId, imageId) { + return function(image) { + var gltf = model.gltf; + var loadResources = model._loadResources; + --loadResources.pendingTextureLoads; + loadResources.texturesToCreate.enqueue({ + id : textureId, + image : image, + bufferView : image.bufferView, + width : image.width, + height : image.height, + internalFormat : image.internalFormat + }); + gltf.images[imageId].extras._pipeline.source = image; + }; + } + + var ktxRegex = /(^data:image\/ktx)|(\.ktx$)/i; + var crnRegex = /(^data:image\/crn)|(\.crn$)/i; + + function parseTextures(model, context) { + var gltf = model.gltf; + var images = gltf.images; + var uri; + ForEach.texture(gltf, function(texture, id) { + var imageId = texture.source; + var gltfImage = images[imageId]; + var extras = gltfImage.extras; + + var bufferViewId = gltfImage.bufferView; + var mimeType = gltfImage.mimeType; + uri = gltfImage.uri; + + // First check for a compressed texture + if (defined(extras) && defined(extras.compressedImage3DTiles)) { + var crunch = extras.compressedImage3DTiles.crunch; + var s3tc = extras.compressedImage3DTiles.s3tc; + var pvrtc = extras.compressedImage3DTiles.pvrtc1; + var etc1 = extras.compressedImage3DTiles.etc1; + + if (context.s3tc && defined(crunch)) { + mimeType = crunch.mimeType; + if (defined(crunch.bufferView)) { + bufferViewId = crunch.bufferView; + } else { + uri = crunch.uri; + } + } else if (context.s3tc && defined(s3tc)) { + mimeType = s3tc.mimeType; + if (defined(s3tc.bufferView)) { + bufferViewId = s3tc.bufferView; + } else { + uri = s3tc.uri; + } + } else if (context.pvrtc && defined(pvrtc)) { + mimeType = pvrtc.mimeType; + if (defined(pvrtc.bufferView)) { + bufferViewId = pvrtc.bufferView; + } else { + uri = pvrtc.uri; + } + } else if (context.etc1 && defined(etc1)) { + mimeType = etc1.mimeType; + if (defined(etc1.bufferView)) { + bufferViewId = etc1.bufferView; + } else { + uri = etc1.uri; + } + } } - if (defined(this._outlinePrimitive)) { - this._outlinePrimitive.show = false; + // Image references either uri (external or base64-encoded) or bufferView + if (defined(bufferViewId)) { + model._loadResources.texturesToCreateFromBufferView.enqueue({ + id : id, + image : undefined, + bufferView : bufferViewId, + mimeType : mimeType + }); + } else { + ++model._loadResources.pendingTextureLoads; + uri = new Uri(uri); + var imagePath = joinUrls(model._baseUri, uri); + + var promise; + if (ktxRegex.test(imagePath)) { + promise = loadKTX(imagePath); + } else if (crnRegex.test(imagePath)) { + promise = loadCRN(imagePath); + } else { + promise = loadImage(imagePath); + } + promise.then(imageLoad(model, id, imageId)).otherwise(getFailedLoadFunction(model, 'image', imagePath)); } - 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; + var nodeTranslationScratch = new Cartesian3(); + var nodeQuaternionScratch = new Quaternion(); + var nodeScaleScratch = new Cartesian3(); - // 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); + function getTransform(node) { + if (defined(node.matrix)) { + return Matrix4.fromArray(node.matrix); + } - //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; + return Matrix4.fromTranslationQuaternionRotationScale( + Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch), + Quaternion.unpack(node.rotation, 0, nodeQuaternionScratch), + Cartesian3.fromArray(node.scale, 0, nodeScaleScratch)); + } - var options = this._options; - - var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + function parseNodes(model) { + var runtimeNodes = {}; + var runtimeNodesByName = {}; + var skinnedNodes = []; - 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; + var skinnedNodesIds = model._loadResources.skinnedNodesIds; - 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; + ForEach.node(model.gltf, function(node, id) { + var runtimeNode = { + // Animation targets + matrix : undefined, + translation : undefined, + rotation : undefined, + scale : undefined, - options.stackPartitions = stackPartitions; - options.slicePartitions = slicePartitions; - options.subdivisions = subdivisions; - options.radii = in3D ? unitSphere : radii; + // Per-node show inherited from parent + computedShow : true, - appearance = new MaterialAppearance({ - material : material, - translucent : material.isTranslucent(), - closed : true - }); - options.vertexFormat = appearance.vertexFormat; + // Computed transforms + transformToRoot : new Matrix4(), + computedMatrix : new Matrix4(), + dirtyNumber : 0, // The frame this node was made dirty by an animation; for graph traversal - 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 - })); + // Rendering + commands : [], // empty for transform, light, and camera nodes - options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + // 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 - 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 - })); + // Joint node + jointName : node.jointName, // undefined when node is not a joint - 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; + weights : [], - primitive.show = true; - outlinePrimitive.show = true; + // Graph pointers + children : [], // empty for leaf nodes + parents : [], // empty for root nodes - appearance = primitive.appearance; - appearance.material = material; + // Publicly-accessible ModelNode instance to modify animation targets + publicNode : undefined + }; + runtimeNode.publicNode = new ModelNode(model, node, runtimeNode, id, getTransform(node)); - 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; + runtimeNodes[id] = runtimeNode; + runtimeNodesByName[node.name] = runtimeNode; + + if (defined(node.skin)) { + skinnedNodesIds.push(id); + skinnedNodes.push(runtimeNode); } + }); - var outlineAttributes = this._outlineAttributes; + model._runtime.nodes = runtimeNodes; + model._runtime.nodesByName = runtimeNodesByName; + model._runtime.skinnedNodes = skinnedNodes; + } - if (!defined(outlineAttributes)) { - outlineAttributes = outlinePrimitive.getGeometryInstanceAttributes(entity); - this._outlineAttributes = outlineAttributes; - } + function parseMaterials(model) { + var runtimeMaterialsByName = {}; + var runtimeMaterialsById = {}; + var uniformMaps = model._uniformMaps; - if (showOutline !== this._lastOutlineShow) { - outlineAttributes.show = ShowGeometryInstanceAttribute.toValue(showOutline, outlineAttributes.show); - this._lastOutlineShow = showOutline; - } + ForEach.material(model.gltf, function(material, id) { + // Allocated now so ModelMaterial can keep a reference to it. + uniformMaps[id] = { + uniformMap : undefined, + values : undefined, + jointMatrixUniformName : undefined, + morphWeightsUniformName : undefined + }; - if (!Color.equals(outlineColor, this._lastOutlineColor)) { - outlineAttributes.color = ColorGeometryInstanceAttribute.toValue(outlineColor, outlineAttributes.color); - Color.clone(outlineColor, this._lastOutlineColor); + 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; + + ForEach.mesh(model.gltf, function(mesh, id) { + runtimeMeshesByName[mesh.name] = new ModelMesh(mesh, runtimeMaterialsById, id); + if (defined(model.extensionsUsed.WEB3D_quantized_attributes)) { + // 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); + } } + }); - 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); + model._runtime.meshesByName = runtimeMeshesByName; + } + + function getUsedExtensions(model) { + var extensionsUsed = model.gltf.extensionsUsed; + var cachedExtensionsUsed = {}; + + if (defined(extensionsUsed)) { + var extensionsUsedLength = extensionsUsed.length; + for (var i = 0; i < extensionsUsedLength; i++) { + var extension = extensionsUsed[i]; + cachedExtensionsUsed[extension] = true; } } + return cachedExtensionsUsed; + } - 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); + function getRequiredExtensions(model) { + var extensionsRequired = model.gltf.extensionsRequired; + var cachedExtensionsRequired = {}; - modelMatrix = Matrix4.multiplyByScale(modelMatrix, radii, modelMatrix); - this._primitive.modelMatrix = modelMatrix; - this._outlinePrimitive.modelMatrix = modelMatrix; + if (defined(extensionsRequired)) { + var extensionsRequiredLength = extensionsRequired.length; + for (var i = 0; i < extensionsRequiredLength; i++) { + var extension = extensionsRequired[i]; + cachedExtensionsRequired[extension] = true; + } } - }; - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + return cachedExtensionsRequired; + } + + /////////////////////////////////////////////////////////////////////////// + + var CreateVertexBufferJob = function() { + this.id = undefined; + this.model = undefined; + this.context = undefined; }; - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; + CreateVertexBufferJob.prototype.set = function(id, model, context) { + this.id = id; + this.model = model; + this.context = context; }; - DynamicGeometryUpdater.prototype.destroy = function() { - var primitives = this._primitives; - primitives.removeAndDestroy(this._primitive); - primitives.removeAndDestroy(this._outlinePrimitive); - destroyObject(this); + CreateVertexBufferJob.prototype.execute = function() { + createVertexBuffer(this.id, this.model, this.context); }; - return EllipsoidGeometryUpdater; -}); - -/*global define*/ -define('DataSources/StaticGeometryColorBatch',[ - '../Core/AssociativeArray', - '../Core/Color', - '../Core/ColorGeometryInstanceAttribute', - '../Core/defined', - '../Core/DistanceDisplayCondition', - '../Core/DistanceDisplayConditionGeometryInstanceAttribute', - '../Core/ShowGeometryInstanceAttribute', - '../Scene/Primitive', - './BoundingSphereState', - './ColorMaterialProperty', - './MaterialProperty', - './Property' - ], function( - AssociativeArray, - Color, - ColorGeometryInstanceAttribute, - defined, - DistanceDisplayCondition, - DistanceDisplayConditionGeometryInstanceAttribute, - ShowGeometryInstanceAttribute, - Primitive, - BoundingSphereState, - ColorMaterialProperty, - MaterialProperty, - Property) { - 'use strict'; - - var colorScratch = new Color(); - var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + /////////////////////////////////////////////////////////////////////////// - function Batch(primitives, translucent, appearanceType, depthFailAppearanceType, depthFailMaterialProperty, closed, shadows) { - this.translucent = translucent; - this.appearanceType = appearanceType; - this.depthFailAppearanceType = depthFailAppearanceType; - this.depthFailMaterialProperty = depthFailMaterialProperty; - this.depthFailMaterial = undefined; - 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 = []; - this.invalidated = false; + function createVertexBuffer(bufferViewId, model, context) { + var loadResources = model._loadResources; + var bufferViews = model.gltf.bufferViews; + var bufferView = bufferViews[bufferViewId]; - var removeMaterialSubscription; - if (defined(depthFailMaterialProperty)) { - removeMaterialSubscription = depthFailMaterialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this); - } - this.removeMaterialSubscription = removeMaterialSubscription; + var vertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : loadResources.getBuffer(bufferView), + usage : BufferUsage.STATIC_DRAW + }); + vertexBuffer.vertexArrayDestroyable = false; + model._rendererResources.buffers[bufferViewId] = vertexBuffer; + model._geometryByteLength += vertexBuffer.sizeInBytes; } - Batch.prototype.onMaterialChanged = function() { - this.invalidated = true; - }; + /////////////////////////////////////////////////////////////////////////// - Batch.prototype.isMaterial = function(updater) { - var material = this.depthFailMaterialProperty; - var updaterMaterial = updater.depthFailMaterialProperty; - if (updaterMaterial === material) { - return true; - } - if (defined(material)) { - return material.equals(updaterMaterial); - } - return false; + var CreateIndexBufferJob = function() { + this.id = undefined; + this.componentType = undefined; + this.model = undefined; + this.context = undefined; }; - 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); - } - })); - } + CreateIndexBufferJob.prototype.set = function(id, componentType, model, context) { + this.id = id; + this.componentType = componentType; + this.model = model; + this.context = context; }; - 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); - } - } + CreateIndexBufferJob.prototype.execute = function() { + createIndexBuffer(this.id, this.componentType, this.model, this.context); }; - 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); - } - } + function createIndexBuffer(bufferViewId, componentType, model, context) { + var loadResources = model._loadResources; + var bufferViews = model.gltf.bufferViews; + var bufferView = bufferViews[bufferViewId]; - for (i = 0; i < geometriesLength; i++) { - var geometryItem = geometries[i]; - var originalAttributes = geometryItem.attributes; - attributes = this.attributes.get(geometryItem.id.id); + var indexBuffer = Buffer.createIndexBuffer({ + context : context, + typedArray : loadResources.getBuffer(bufferView), + usage : BufferUsage.STATIC_DRAW, + indexDatatype : componentType + }); + indexBuffer.vertexArrayDestroyable = false; + model._rendererResources.buffers[bufferViewId] = indexBuffer; + model._geometryByteLength += indexBuffer.sizeInBytes; + } - if (defined(attributes)) { - if (defined(originalAttributes.show)) { - originalAttributes.show.value = attributes.show; - } - if (defined(originalAttributes.color)) { - originalAttributes.color.value = attributes.color; - } - if (defined(originalAttributes.depthFailColor)) { - originalAttributes.depthFailColor.value = attributes.depthFailColor; - } - } - } + var scratchVertexBufferJob = new CreateVertexBufferJob(); + var scratchIndexBufferJob = new CreateIndexBufferJob(); - var depthFailAppearance; - if (defined(this.depthFailAppearanceType)) { - if (defined(this.depthFailMaterialProperty)) { - this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); - } - depthFailAppearance = new this.depthFailAppearanceType({ - material : this.depthFailMaterial, - translucent : this.translucent, - closed : this.closed - }); - } + function createBuffers(model, frameState) { + var loadResources = model._loadResources; - primitive = new Primitive({ - asynchronous : true, - geometryInstances : geometries, - appearance : new this.appearanceType({ - translucent : this.translucent, - closed : this.closed - }), - depthFailAppearance : depthFailAppearance, - shadows : this.shadows - }); - primitives.add(primitive); - isUpdated = false; - } else { - if (defined(primitive)) { - primitives.remove(primitive); - primitive = undefined; + if (loadResources.pendingBufferLoads !== 0) { + return; + } + + var context = frameState.context; + var vertexBuffersToCreate = loadResources.vertexBuffersToCreate; + var indexBuffersToCreate = loadResources.indexBuffersToCreate; + var i; + + if (model.asynchronous) { + while (vertexBuffersToCreate.length > 0) { + scratchVertexBufferJob.set(vertexBuffersToCreate.peek(), model, context); + if (!frameState.jobScheduler.execute(scratchVertexBufferJob, JobType.BUFFER)) { + break; } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; + vertexBuffersToCreate.dequeue(); + } + + while (indexBuffersToCreate.length > 0) { + i = indexBuffersToCreate.peek(); + scratchIndexBufferJob.set(i.id, i.componentType, model, context); + if (!frameState.jobScheduler.execute(scratchIndexBufferJob, JobType.BUFFER)) { + break; } + indexBuffersToCreate.dequeue(); + } + } else { + while (vertexBuffersToCreate.length > 0) { + createVertexBuffer(vertexBuffersToCreate.dequeue(), model, context); } - 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; + while (indexBuffersToCreate.length > 0) { + i = indexBuffersToCreate.dequeue(); + createIndexBuffer(i.id, i.componentType, model, context); } + } + } - if (defined(this.depthFailAppearanceType) && !(this.depthFailMaterialProperty instanceof ColorMaterialProperty)) { - this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); - this.primitive.depthFailAppearance.material = this.depthFailMaterial; + 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 (/pos/i.test(attribute)) { + attributes[i] = attributes[0]; + attributes[0] = attribute; + break; } + } - 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); + for (i = 0; i < length; ++i) { + attributeLocations[attributes[i]] = i; + } - attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } + return attributeLocations; + } - 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; + 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; + } - if (defined(this.depthFailAppearanceType) && this.depthFailAppearanceType instanceof ColorMaterialProperty && (!updater.depthFailMaterialProperty.isConstant || waitingOnCreate)) { - var depthFailColorProperty = updater.depthFailMaterialProperty.color; - depthFailColorProperty.getValue(time, colorScratch); - if (!Color.equals(attributes._lastDepthFailColor, colorScratch)) { - attributes._lastDepthFailColor = Color.clone(colorScratch, attributes._lastDepthFailColor); - attributes.depthFailColor = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.depthFailColor); - } - } + function modifyShaderForQuantizedAttributes(shader, programName, model, context) { + var quantizedUniforms = {}; + model._quantizedUniforms[programName] = quantizedUniforms; - 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 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 attributeVarName = getAttributeVariableName(model, primitive, attributeSemantic); + var accessorId = primitive.attributes[attributeSemantic]; - 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); + if (attributeSemantic.charAt(0) === '_') { + attributeSemantic = attributeSemantic.substring(1); + } + var decodeUniformVarName = 'gltf_u_dec_' + attributeSemantic.toLowerCase(); + + var decodeUniformVarNameScale = decodeUniformVarName + '_scale'; + var decodeUniformVarNameTranslate = decodeUniformVarName + '_translate'; + if (!defined(quantizedUniforms[decodeUniformVarName]) && !defined(quantizedUniforms[decodeUniformVarNameScale])) { + var quantizedAttributes = getQuantizedAttributes(model, accessorId); + if (defined(quantizedAttributes)) { + 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.updateShows(primitive); - this.waitingOnCreate = false; - } else if (defined(primitive) && !primitive.ready) { - isUpdated = false; } - this.itemsToRemove.length = removedCount; - return isUpdated; - }; + // This is not needed after the program is processed, free the memory + model._programPrimitives[programName] = undefined; + return shader; + } - 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); + function hasPremultipliedAlpha(model) { + var gltf = model.gltf; + return defined(gltf.asset) ? defaultValue(gltf.asset.premultipliedAlpha, false) : false; + } - var attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } + 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'; - var show = updater.entity.isShowing; - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); - } - } - this.showsUpdated.removeAll(); - }; + // Un-premultiply the alpha so that blending is correct. - Batch.prototype.contains = function(entity) { - return this.updaters.contains(entity.id); - }; + // Avoid divide-by-zero. The code below is equivalent to: + // if (gl_FragColor.a > 0.0) + // { + // gl_FragColor.rgb /= gl_FragColor.a; + // } - 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; + if (premultipliedAlpha) { + shader += + ' float alpha = 1.0 - ceil(gl_FragColor.a) + gl_FragColor.a; \n' + + ' gl_FragColor.rgb /= alpha; \n'; } - attributes.boundingSphere.clone(result); - return BoundingSphereState.DONE; - }; - Batch.prototype.removeAllPrimitives = function() { - var primitives = this.primitives; + 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'; - var primitive = this.primitive; - if (defined(primitive)) { - primitives.remove(primitive); - this.primitive = undefined; - this.geometry.removeAll(); - this.updaters.removeAll(); - } + return shader; + } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; + function modifyShader(shader, programName, callback) { + if (defined(callback)) { + shader = callback(shader, programName); } + return shader; + } + + var CreateProgramJob = function() { + this.id = undefined; + this.model = undefined; + this.context = undefined; }; - Batch.prototype.destroy = function() { - var primitive = this.primitive; - var primitives = this.primitives; - if (defined(primitive)) { - primitives.remove(primitive); - } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - } - if(defined(this.removeMaterialSubscription)) { - this.removeMaterialSubscription(); - } + CreateProgramJob.prototype.set = function(id, model, context) { + this.id = id; + this.model = model; + this.context = context; }; - /** - * @private - */ - function StaticGeometryColorBatch(primitives, appearanceType, depthFailAppearanceType, closed, shadows) { - this._solidItems = []; - this._translucentItems = []; - this._primitives = primitives; - this._appearanceType = appearanceType; - this._depthFailAppearanceType = depthFailAppearanceType; - this._closed = closed; - this._shadows = shadows; - } + CreateProgramJob.prototype.execute = function() { + createProgram(this.id, this.model, this.context); + }; - StaticGeometryColorBatch.prototype.add = function(time, updater) { - var items; - var translucent; - var instance = updater.createFillGeometryInstance(time); - if (instance.attributes.color.value[3] === 255) { - items = this._solidItems; - translucent = false; - } else { - items = this._translucentItems; - translucent = true; - } + /////////////////////////////////////////////////////////////////////////// - var length = items.length; - for (var i = 0; i < length; i++) { - var item = items[i]; - if (item.isMaterial(updater)) { - item.add(updater, instance); - return; - } - } - var batch = new Batch(this._primitives, translucent, this._appearanceType, this._depthFailAppearanceType, updater.depthFailMaterialProperty, this._closed, this._shadows); - batch.add(updater, instance); - items.push(batch); - }; + function createProgram(id, model, context) { + var programs = model.gltf.programs; + var shaders = model.gltf.shaders; + var program = programs[id]; - function removeItem(items, updater) { - 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(); - return true; + var attributeLocations = createAttributeLocations(model, program.attributes); + var vs = shaders[program.vertexShader].extras._pipeline.source; + var fs = shaders[program.fragmentShader].extras._pipeline.source; + + // 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++; } } } - return false; - } - StaticGeometryColorBatch.prototype.remove = function(updater) { - if (!removeItem(this._solidItems, updater)) { - removeItem(this._translucentItems, updater); + if (model.extensionsUsed.WEB3D_quantized_attributes) { + vs = modifyShaderForQuantizedAttributes(vs, id, model, context); } - }; - function moveItems(batch, items, time) { - var itemsMoved = false; - var length = items.length; - for (var i = 0; i < length; ++i) { - var item = items[i]; - var itemsToRemove = item.itemsToRemove; - var itemsToMoveLength = itemsToRemove.length; - if (itemsToMoveLength > 0) { - for (i = 0; i < itemsToMoveLength; i++) { - var updater = itemsToRemove[i]; - item.remove(updater); - batch.add(time, updater); - itemsMoved = true; - } - } - } - return itemsMoved; - } + var premultipliedAlpha = hasPremultipliedAlpha(model); + var blendFS = modifyShaderForColor(fs, premultipliedAlpha); - function updateItems(batch, items, time, isUpdated) { - 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++) { - batch.add(time, updaters[h]); - } - item.destroy(); + 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'); } - } - length = items.length; - for (var i = 0; i < length; ++i) { - isUpdated = items[i].update(time) && isUpdated; + model._rendererResources.pickPrograms[id] = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations + }); } - return isUpdated; } - StaticGeometryColorBatch.prototype.update = function(time) { - //Perform initial update - var isUpdated = updateItems(this, this._solidItems, time, true); - isUpdated = updateItems(this, this._translucentItems, time, isUpdated) && isUpdated; + var scratchCreateProgramJob = new CreateProgramJob(); - //If any items swapped between solid/translucent, we need to - //move them between batches - var solidsMoved = moveItems(this, this._solidItems, time); - var translucentsMoved = moveItems(this, this._translucentItems, time); + function createPrograms(model, frameState) { + var loadResources = model._loadResources; + var programsToCreate = loadResources.programsToCreate; - //If we moved anything around, we need to re-build the primitive - if (solidsMoved || translucentsMoved) { - isUpdated = updateItems(this, this._solidItems, time, isUpdated) && isUpdated; - isUpdated = updateItems(this, this._translucentItems, time, isUpdated)&& isUpdated; + if (loadResources.pendingShaderLoads !== 0) { + return; } - return isUpdated; - }; + // 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; + } - function getBoundingSphere(items, entity, result) { - var length = items.length; - for (var i = 0; i < length; i++) { - var item = items[i]; - if(item.contains(entity)){ - return item.getBoundingSphere(entity, result); + var context = frameState.context; + + if (model.asynchronous) { + while (programsToCreate.length > 0) { + scratchCreateProgramJob.set(programsToCreate.peek(), model, context); + if (!frameState.jobScheduler.execute(scratchCreateProgramJob, JobType.PROGRAM)) { + break; + } + programsToCreate.dequeue(); + } + } else { + // Create all loaded programs this frame + while (programsToCreate.length > 0) { + createProgram(programsToCreate.dequeue(), model, context); } } - return BoundingSphereState.FAILED; } - StaticGeometryColorBatch.prototype.getBoundingSphere = function(entity, result) { - var boundingSphere = getBoundingSphere(this._solidItems, entity, result); - if (boundingSphere === BoundingSphereState.FAILED) { - return getBoundingSphere(this._translucentItems, entity, result); - } - return boundingSphere; - }; + function getOnImageCreatedFromTypedArray(loadResources, gltfTexture) { + return function(image) { + loadResources.texturesToCreate.enqueue({ + id : gltfTexture.id, + image : image, + bufferView : undefined + }); - function removeAllPrimitives(items) { - var length = items.length; - for (var i = 0; i < length; i++) { - items[i].destroy(); - } - items.length = 0; + --loadResources.pendingBufferViewToImage; + }; } - StaticGeometryColorBatch.prototype.removeAllPrimitives = function() { - removeAllPrimitives(this._solidItems); - removeAllPrimitives(this._translucentItems); - }; + function loadTexturesFromBufferViews(model) { + var loadResources = model._loadResources; - return StaticGeometryColorBatch; -}); + if (loadResources.pendingBufferLoads !== 0) { + return; + } -/*global define*/ -define('DataSources/StaticGeometryPerMaterialBatch',[ - '../Core/AssociativeArray', - '../Core/Color', - '../Core/ColorGeometryInstanceAttribute', - '../Core/defined', - '../Core/DistanceDisplayCondition', - '../Core/DistanceDisplayConditionGeometryInstanceAttribute', - '../Core/ShowGeometryInstanceAttribute', - '../Scene/Primitive', - './BoundingSphereState', - './ColorMaterialProperty', - './MaterialProperty', - './Property' - ], function( - AssociativeArray, - Color, - ColorGeometryInstanceAttribute, - defined, - DistanceDisplayCondition, - DistanceDisplayConditionGeometryInstanceAttribute, - ShowGeometryInstanceAttribute, - Primitive, - BoundingSphereState, - ColorMaterialProperty, - MaterialProperty, - Property) { - 'use strict'; + while (loadResources.texturesToCreateFromBufferView.length > 0) { + var gltfTexture = loadResources.texturesToCreateFromBufferView.dequeue(); - var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + var gltf = model.gltf; + var bufferView = gltf.bufferViews[gltfTexture.bufferView]; + var imageId = gltf.textures[gltfTexture.id].source; - function Batch(primitives, appearanceType, materialProperty, depthFailAppearanceType, depthFailMaterialProperty, closed, shadows) { - this.primitives = primitives; - this.appearanceType = appearanceType; - this.materialProperty = materialProperty; - this.depthFailAppearanceType = depthFailAppearanceType; - this.depthFailMaterialProperty = depthFailMaterialProperty; - 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.depthFailMaterial = 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(); + var onerror = getFailedLoadFunction(model, 'image', 'id: ' + gltfTexture.id + ', bufferView: ' + gltfTexture.bufferView); + + if (gltfTexture.mimeType === 'image/ktx') { + loadKTX(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id, imageId)).otherwise(onerror); + ++model._loadResources.pendingTextureLoads; + } else if (gltfTexture.mimeType === 'image/crn') { + loadCRN(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id, imageId)).otherwise(onerror); + ++model._loadResources.pendingTextureLoads; + } else { + var onload = getOnImageCreatedFromTypedArray(loadResources, gltfTexture); + loadImageFromTypedArray(loadResources.getBuffer(bufferView), gltfTexture.mimeType) + .then(onload).otherwise(onerror); + ++loadResources.pendingBufferViewToImage; + } + } } - Batch.prototype.onMaterialChanged = function() { - this.invalidated = true; - }; + function createSamplers(model, context) { + var loadResources = model._loadResources; - Batch.prototype.isMaterial = function(updater) { - var material = this.materialProperty; - var updaterMaterial = updater.fillMaterialProperty; - var depthFailMaterial = this.depthFailMaterialProperty; - var updaterDepthFailMaterial = updater.depthFailMaterialProperty; + if (loadResources.createSamplers) { + loadResources.createSamplers = false; - if (updaterMaterial === material && updaterDepthFailMaterial === depthFailMaterial) { - return true; - } - var equals = defined(material) && material.equals(updaterMaterial); - equals = ((!defined(depthFailMaterial) && !defined(updaterDepthFailMaterial)) || (defined(depthFailMaterial) && depthFailMaterial.equals(updaterDepthFailMaterial))) && equals; - return equals; - }; + var rendererSamplers = model._rendererResources.samplers; + var samplers = model.gltf.samplers; + for (var id in samplers) { + if (samplers.hasOwnProperty(id)) { + var sampler = samplers[id]; - 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); + rendererSamplers[id] = new Sampler({ + wrapS : sampler.wrapS, + wrapT : sampler.wrapT, + minificationFilter : sampler.minFilter, + magnificationFilter : sampler.magFilter + }); } - })); + } } - this.createPrimitive = true; + } + + /////////////////////////////////////////////////////////////////////////// + + var CreateTextureJob = function() { + this.gltfTexture = undefined; + this.model = undefined; + this.context = undefined; }; - 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; + CreateTextureJob.prototype.set = function(gltfTexture, model, context) { + this.gltfTexture = gltfTexture; + this.model = model; + this.context = context; }; - var colorScratch = new Color(); + CreateTextureJob.prototype.execute = function() { + createTexture(this.gltfTexture, this.model, this.context); + }; - 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); - } - } + function createTexture(gltfTexture, model, context) { + var textures = model.gltf.textures; + var texture = textures[gltfTexture.id]; - for (i = 0; i < geometriesLength; i++) { - var geometry = geometries[i]; - var originalAttributes = geometry.attributes; - attributes = this.attributes.get(geometry.id.id); + var rendererSamplers = model._rendererResources.samplers; + var sampler = rendererSamplers[texture.sampler]; + sampler = defaultValue(sampler, new Sampler({ + wrapS : TextureWrap.REPEAT, + wrapT : TextureWrap.REPEAT + })); - if (defined(attributes)) { - if (defined(originalAttributes.show)) { - originalAttributes.show.value = attributes.show; - } - if (defined(originalAttributes.color)) { - originalAttributes.color.value = attributes.color; - } - if (defined(originalAttributes.depthFailColor)) { - originalAttributes.depthFailColor.value = attributes.depthFailColor; - } - } - } + var internalFormat = gltfTexture.internalFormat; - this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + var mipmap = + (!(defined(internalFormat) && PixelFormat.isCompressedFormat(internalFormat))) && + ((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 depthFailAppearance; - if (defined(this.depthFailMaterialProperty)) { - var translucent; - if (this.depthFailMaterialProperty instanceof MaterialProperty) { - this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); - translucent = this.depthFailMaterial.isTranslucent(); - } else { - translucent = this.material.isTranslucent(); - } - depthFailAppearance = new this.depthFailAppearanceType({ - material : this.depthFailMaterial, - translucent : translucent, - closed : this.closed - }); - } + var tx; + var source = gltfTexture.image; - primitive = new Primitive({ - asynchronous : true, - geometryInstances : geometries, - appearance : new this.appearanceType({ - material : this.material, - translucent : this.material.isTranslucent(), - closed : this.closed - }), - depthFailAppearance : depthFailAppearance, - shadows : this.shadows + if (defined(internalFormat) && texture.target === WebGLConstants.TEXTURE_2D) { + tx = new Texture({ + context : context, + source : { + arrayBufferView : gltfTexture.bufferView + }, + width : gltfTexture.width, + height : gltfTexture.height, + pixelFormat : internalFormat, + sampler : sampler + }); + } else if (defined(source)) { + 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; + } + + 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 - 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; + if (mipmap) { + tx.generateMipmap(); + } + } + + model._rendererResources.textures[gltfTexture.id] = tx; + model._texturesByteLength += tx.sizeInBytes; + } + + var scratchCreateTextureJob = new CreateTextureJob(); + + function createTextures(model, frameState) { + var context = frameState.context; + var texturesToCreate = model._loadResources.texturesToCreate; + + if (model.asynchronous) { + while (texturesToCreate.length > 0) { + scratchCreateTextureJob.set(texturesToCreate.peek(), model, context); + if (!frameState.jobScheduler.execute(scratchCreateTextureJob, JobType.TEXTURE)) { + break; } + texturesToCreate.dequeue(); } - - 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; + } else { + // Create all loaded textures this frame + while (texturesToCreate.length > 0) { + createTexture(texturesToCreate.dequeue(), model, context); } + } + } - this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); - this.primitive.appearance.material = this.material; + function getAttributeLocations(model, primitive) { + var gltf = model.gltf; + var techniques = gltf.techniques; + var materials = gltf.materials; - if (defined(this.depthFailAppearanceType) && !(this.depthFailMaterialProperty instanceof ColorMaterialProperty)) { - this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); - this.primitive.depthFailAppearance.material = this.depthFailMaterial; - } + // Retrieve the compiled shader program to assign index values to attributes + var attributeLocations = {}; - 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); + var location; + var index; + var technique = techniques[materials[primitive.material].technique]; + var parameters = technique.parameters; + var attributes = technique.attributes; + var program = model._rendererResources.programs[technique.program]; + var programVertexAttributes = program.vertexAttributes; + var programAttributeLocations = program._attributeLocations; - attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); + // Note: WebGL shader compiler may have optimized and removed some attributes from programVertexAttributes + for (location in programVertexAttributes) { + if (programVertexAttributes.hasOwnProperty(location)) { + var attribute = attributes[location]; + index = programVertexAttributes[location].index; + if (defined(attribute)) { + var parameter = parameters[attribute]; + attributeLocations[parameter.semantic] = index; } + } + } - if (defined(this.depthFailAppearanceType) && this.depthFailAppearanceType instanceof ColorMaterialProperty && !updater.depthFailMaterialProperty.isConstant) { - var depthFailColorProperty = updater.depthFailMaterialProperty.color; - depthFailColorProperty.getValue(time, colorScratch); - if (!Color.equals(attributes._lastDepthFailColor, colorScratch)) { - attributes._lastDepthFailColor = Color.clone(colorScratch, attributes._lastDepthFailColor); - attributes.depthFailColor = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.depthFailColor); - } + // Always add pre-created attributes. + // Some pre-created attributes, like per-instance pickIds, may be compiled out of the draw program + // but should be included in the list of attribute locations for the pick program. + // This is safe to do since programVertexAttributes and programAttributeLocations are equivalent except + // that programVertexAttributes optimizes out unused attributes. + var precreatedAttributes = model._precreatedAttributes; + if (defined(precreatedAttributes)) { + for (location in precreatedAttributes) { + if (precreatedAttributes.hasOwnProperty(location)) { + index = programAttributeLocations[location]; + attributeLocations[location] = index; } + } + } - 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); + return attributeLocations; + } + + function mapJointNames(forest, nodes) { + var length = forest.length; + var jointNodes = {}; + 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 (defined(n)) { + jointNodes[id] = id; } - 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); - } + var children = n.children; + var childrenLength = children.length; + for (var k = 0; k < childrenLength; ++k) { + stack.push(children[k]); } } - - this.updateShows(primitive); - } else if (defined(primitive) && !primitive.ready) { - isUpdated = false; } - return isUpdated; - }; + return jointNodes; + } - 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); + function createJoints(model, runtimeSkins) { + var gltf = model.gltf; + var skins = gltf.skins; + var nodes = gltf.nodes; + var runtimeNodes = model._runtime.nodes; - var attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); + 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 skin = skins[node.skin]; + if (defined(skin.skeleton)) { + forest.push(skin.skeleton); } - var show = entity.isShowing; - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + var mappedJointNames = mapJointNames(forest, nodes); + var gltfJointNames = skins[node.skin].joints; + var jointNamesLength = gltfJointNames.length; + for (var i = 0; i < jointNamesLength; ++i) { + var jointName = gltfJointNames[i]; + var nodeId = mappedJointNames[jointName]; + var jointNode = runtimeNodes[nodeId]; + skinnedNode.joints.push(jointNode); } } - this.showsUpdated.removeAll(); - }; + } - Batch.prototype.contains = function(entity) { - return this.updaters.contains(entity.id); - }; + function createSkins(model) { + var loadResources = model._loadResources; - 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; + if (loadResources.pendingBufferLoads !== 0) { + return; } - attributes.boundingSphere.clone(result); - return BoundingSphereState.DONE; - }; - Batch.prototype.destroy = function() { - var primitive = this.primitive; - var primitives = this.primitives; - if (defined(primitive)) { - primitives.remove(primitive); - } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); + if (!loadResources.createSkins) { + return; } - this.removeMaterialSubscription(); - }; + loadResources.createSkins = false; - /** - * @private - */ - function StaticGeometryPerMaterialBatch(primitives, appearanceType, depthFailAppearanceType, closed, shadows) { - this._items = []; - this._primitives = primitives; - this._appearanceType = appearanceType; - this._depthFailAppearanceType = depthFailAppearanceType; - this._closed = closed; - this._shadows = shadows; - } + var gltf = model.gltf; + var accessors = gltf.accessors; + var runtimeSkins = {}; - 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._depthFailAppearanceType, updater.depthFailMaterialProperty, this._closed, this._shadows); - batch.add(time, updater); - items.push(batch); - }; + ForEach.skin(gltf, function(skin, id) { + var accessor = accessors[skin.inverseBindMatrices]; - 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; + var bindShapeMatrix; + if (!Matrix4.equals(skin.bindShapeMatrix, Matrix4.IDENTITY)) { + bindShapeMatrix = Matrix4.clone(skin.bindShapeMatrix); } - } - }; - StaticGeometryPerMaterialBatch.prototype.update = function(time) { - var i; - var items = this._items; - var length = items.length; + runtimeSkins[id] = { + inverseBindMatrices : ModelAnimationCache.getSkinInverseBindMatrices(model, accessor), + bindShapeMatrix : bindShapeMatrix // not used when undefined + }; + }); - 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(); - } - } + createJoints(model, runtimeSkins); + } - var isUpdated = true; - for (i = 0; i < length; i++) { - //isUpdated = items[i].update(time) && isUpdated; - //acevedo - added condition - if (items[i]) - { - isUpdated = items[i].update(time) && isUpdated; - } - else - { - isUpdated = false; - } - } - return isUpdated; - }; + function getChannelEvaluator(model, runtimeNode, targetPath, spline) { + return function(localAnimationTime) { + // Workaround for https://github.com/KhronosGroup/glTF/issues/219 - 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); + //if (targetPath === 'translation') { + // return; + //} + if (defined(spline)) { + runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]); + runtimeNode.dirtyNumber = model._maxDirtyNumber; } + }; + } + + function createRuntimeAnimations(model) { + var loadResources = model._loadResources; + + if (!loadResources.finishedPendingBufferLoads()) { + return; } - 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(); + if (!loadResources.createRuntimeAnimations) { + return; } - this._items.length = 0; - }; + loadResources.createRuntimeAnimations = false; - return StaticGeometryPerMaterialBatch; -}); + model._runtime.animations = []; -/*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 runtimeNodes = model._runtime.nodes; + var animations = model.gltf.animations; + var accessors = model.gltf.accessors; - var colorScratch = new Color(); - var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + var length = animations.length; + for (var i = 0; i < length; ++i) { + var animation = animations[i]; + var channels = animation.channels; + var samplers = animation.samplers; - 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; + // Find start and stop time for the entire animation + var startTime = Number.MAX_VALUE; + var stopTime = -Number.MAX_VALUE; + + var channelsLength = channels.length; + var channelEvaluators = new Array(channelsLength); + + for (var j = 0; j < channelsLength; ++j) { + var channel = channels[j]; + var target = channel.target; + var path = target.path; + var sampler = samplers[channel.sampler]; + var input = ModelAnimationCache.getAnimationParameterValues(model, accessors[sampler.input]); + var output = ModelAnimationCache.getAnimationParameterValues(model, accessors[sampler.output]); + + startTime = Math.min(startTime, input[0]); + stopTime = Math.max(stopTime, input[input.length - 1]); + + var spline = ModelAnimationCache.getAnimationSpline(model, i, animation, channel.sampler, sampler, input, path, output); + + // GLTF_SPEC: Support more targets like materials. https://github.com/KhronosGroup/glTF/issues/142 + channelEvaluators[j] = getChannelEvaluator(model, runtimeNodes[target.node], target.path, spline); + } + + model._runtime.animations[i] = { + name : animation.name, + startTime : startTime, + stopTime : stopTime, + channelEvaluators : channelEvaluators + }; + } } - 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); - } - })); + function createVertexArrays(model, context) { + var loadResources = model._loadResources; + + if (!loadResources.finishedBuffersCreation() || !loadResources.finishedProgramCreation()) { + return; } - }; - 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); - } + if (!loadResources.createVertexArrays) { + return; } - }; + loadResources.createVertexArrays = false; - var scratchArray = new Array(4); + var rendererBuffers = model._rendererResources.buffers; + var rendererVertexArrays = model._rendererResources.vertexArrays; + var gltf = model.gltf; + var accessors = gltf.accessors; + var meshes = gltf.meshes; - Batch.prototype.update = function(time) { - var isUpdated = true; - var removedCount = 0; - var primitive = this.primitive; - var primitives = this.primitives; - var attributes; - var i; + for (var meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var primitives = meshes[meshId].primitives; + var primitivesLength = primitives.length; - 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 (var i = 0; i < primitivesLength; ++i) { + var primitive = primitives[i]; - for (i = 0; i < geometriesLength; i++) { - var geometryItem = geometries[i]; - var originalAttributes = geometryItem.attributes; - attributes = this.attributes.get(geometryItem.id.id); + // 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 - if (defined(attributes)) { - if (defined(originalAttributes.show)) { - originalAttributes.show.value = attributes.show; + 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]]; + var normalize = false; + if (defined(a.normalized) && a.normalized) { + normalize = true; + } + + attributes.push({ + index : attributeLocation, + vertexBuffer : rendererBuffers[a.bufferView], + componentsPerAttribute : numberOfComponentsForType(a.type), + componentDatatype : a.componentType, + normalize : normalize, + offsetInBytes : a.byteOffset, + strideInBytes : getAccessorByteStride(gltf, a) + }); + } } - if (defined(originalAttributes.color)) { - originalAttributes.color.value = attributes.color; + } + + // 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); + } + } } } - } - 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; + 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 + }); } } + } + } - 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); + 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; - attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } + var enable = states.enable; + var length = enable.length; + var i; + for (i = 0; i < length; ++i) { + booleanStates[enable[i]] = true; + } - if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) { - var colorProperty = updater.fillMaterialProperty.color; - colorProperty.getValue(time, colorScratch); + return booleanStates; + } - 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; - } - } - } + function createRenderStates(model, context) { + var loadResources = model._loadResources; + var techniques = model.gltf.techniques; - 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); + if (loadResources.createRenderStates) { + loadResources.createRenderStates = false; + for (var id in techniques) { + if (techniques.hasOwnProperty(id)) { + createRenderStateForTechnique(model, id, context); } + } + } + } - 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); - } - } + 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]); + + // 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; } + } - this.updateShows(primitive); - this.waitingOnCreate = false; - } else if (defined(primitive) && !primitive.ready) { - isUpdated = false; + 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] + }, + 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() { + if (defined(model._rtcCenter)) { + Matrix4.getTranslation(uniformState.model, scratchTranslationRtc); + Cartesian3.add(scratchTranslationRtc, model._rtcCenter, scratchTranslationRtc); + Matrix4.multiplyByPoint(uniformState.view, scratchTranslationRtc, scratchTranslationRtc); + return Matrix4.setTranslation(uniformState.modelView, scratchTranslationRtc, mvRtc); + } + return uniformState.modelView; + }; + }, + 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; + }; } - this.itemsToRemove.length = removedCount; - return isUpdated; + // JOINTMATRIX created in createCommand() }; - 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); + function getScalarUniformFunction(value, model) { + var that = { + value : value, + clone : function(source, result) { + return source; + }, + func : function() { + return that.value; } + }; + return that; + } - var show = updater.entity.isShowing; - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + function getVec2UniformFunction(value, model) { + var that = { + value : Cartesian2.fromArray(value), + clone : Cartesian2.clone, + func : function() { + return that.value; } - } - this.showsUpdated.removeAll(); - }; + }; + return that; + } - Batch.prototype.contains = function(entity) { - return this.updaters.contains(entity.id); - }; + function getVec3UniformFunction(value, model) { + var that = { + value : Cartesian3.fromArray(value), + clone : Cartesian3.clone, + func : function() { + return that.value; + } + }; + return that; + } - 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; - } + function getVec4UniformFunction(value, model) { + var that = { + value : Cartesian4.fromArray(value), + clone : Cartesian4.clone, + func : function() { + return that.value; + } + }; + return that; + } - bs.clone(result); - return BoundingSphereState.DONE; - }; + function getMat2UniformFunction(value, model) { + var that = { + value : Matrix2.fromColumnMajorArray(value), + clone : Matrix2.clone, + func : function() { + return that.value; + } + }; + return that; + } - Batch.prototype.removeAllPrimitives = function() { - var primitives = this.primitives; + function getMat3UniformFunction(value, model) { + var that = { + value : Matrix3.fromColumnMajorArray(value), + clone : Matrix3.clone, + func : function() { + return that.value; + } + }; + return that; + } - var primitive = this.primitive; - if (defined(primitive)) { - primitives.remove(primitive); - this.primitive = undefined; - this.geometry.removeAll(); - this.updaters.removeAll(); - } + function getMat4UniformFunction(value, model) { + var that = { + value : Matrix4.fromColumnMajorArray(value), + clone : Matrix4.clone, + func : function() { + return that.value; + } + }; + return that; + } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; - } - }; + /////////////////////////////////////////////////////////////////////////// - /** - * @private - */ - function StaticGroundGeometryColorBatch(primitives) { - this._batches = new AssociativeArray(); - this._primitives = primitives; + function DelayLoadedTextureUniform(value, model) { + this._value = undefined; + this._textureId = value.index; + this._model = model; } - 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; - }; + 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; + } + } - 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; + return this._value; + }, + set : function(value) { + this._value = value; } } + }); + + DelayLoadedTextureUniform.prototype.clone = function(source, result) { + return source; }; - StaticGroundGeometryColorBatch.prototype.update = function(time) { - var i; - var updater; + DelayLoadedTextureUniform.prototype.func = undefined; - //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; + /////////////////////////////////////////////////////////////////////////// + + 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; + }; } + }; - //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; - } + 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 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); - } + if (!loadResources.createUniformMaps) { + return; } + loadResources.createUniformMaps = false; - return isUpdated; - }; + var gltf = model.gltf; + var materials = gltf.materials; + var techniques = gltf.techniques; + var uniformMaps = model._uniformMaps; - 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); - } - } + for (var materialId in materials) { + if (materials.hasOwnProperty(materialId)) { + var material = materials[materialId]; + var instanceParameters; + instanceParameters = material.values; + var technique = techniques[material.technique]; + var parameters = technique.parameters; + var uniforms = technique.uniforms; - return BoundingSphereState.FAILED; - }; + var uniformMap = {}; + var uniformValues = {}; + var jointMatrixUniformName; + var morphWeightsUniformName; - StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() { - var batchesArray = this._batches.values; - var batchCount = batchesArray.length; - for (var i = 0; i < batchCount; ++i) { - batchesArray[i].removeAllPrimitives(); - } - }; + // Uniform parameters + for (var name in uniforms) { + if (uniforms.hasOwnProperty(name) && name !== 'extras') { + var parameterName = uniforms[name]; + var parameter = parameters[parameterName]; - return StaticGroundGeometryColorBatch; -}); + // GLTF_SPEC: This does not take into account uniform arrays, + // indicated by parameters with a count property. + // + // https://github.com/KhronosGroup/glTF/issues/258 -/*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'; + // 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 - 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); + 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') { + jointMatrixUniformName = name; + } else if (parameter.semantic === 'MORPHWEIGHTS') { + morphWeightsUniformName = name; + } else { + // Map glTF semantic to Cesium automatic uniform + uniformMap[name] = gltfSemanticUniforms[parameter.semantic](context.uniformState, model); + } + } 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; + } + } } - })); - } - }; - 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 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; + u.morphWeightsUniformName = morphWeightsUniformName; } } - }; + } - var colorScratch = new Color(); - var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + 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]]; + } - Batch.prototype.update = function(time) { - var isUpdated = true; - var removedCount = 0; - var primitive = this.primitive; - var primitives = this.primitives; - var attributes; - var i; + function translateFromMatrix5Array(matrix) { + return [matrix[20], matrix[21], matrix[22], matrix[23]]; + } - 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); - } - } + 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 (i = 0; i < geometriesLength; i++) { - var geometryItem = geometries[i]; - var originalAttributes = geometryItem.attributes; - attributes = this.attributes.get(geometryItem.id.id); + 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(attributes)) { - if (defined(originalAttributes.show)) { - originalAttributes.show.value = attributes.show; - } - if (defined(originalAttributes.color)) { - originalAttributes.color.value = attributes.color; - } - } + if (attribute.charAt(0) === '_') { + attribute = attribute.substring(1); } - primitive = new Primitive({ - asynchronous : true, - geometryInstances : geometries, - appearance : new PerInstanceColorAppearance({ - flat : true, - translucent : this.translucent, - renderState : { - lineWidth : this.width - } - }), - shadows : this.shadows - }); + if (defined(extensions)) { + var quantizedAttributes = extensions.WEB3D_quantized_attributes; + if (defined(quantizedAttributes)) { + var decodeMatrix = quantizedAttributes.decodeMatrix; + var uniformVariable = 'gltf_u_dec_' + attribute.toLowerCase(); - 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; + switch (a.type) { + case AttributeType.SCALAR: + uniformMap[uniformVariable] = getMat2UniformFunction(decodeMatrix, model).func; + setUniforms[uniformVariable] = true; + break; + case AttributeType.VEC2: + uniformMap[uniformVariable] = getMat3UniformFunction(decodeMatrix, model).func; + setUniforms[uniformVariable] = true; + break; + case AttributeType.VEC3: + uniformMap[uniformVariable] = getMat4UniformFunction(decodeMatrix, model).func; + setUniforms[uniformVariable] = true; + break; + case AttributeType.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; + } + } } } + } - 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; + // 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; } } - } - - 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); + if (defined(properties.vec)) { + if (properties.vec === 4) { + uniformMap[quantizedUniform] = getVec4UniformFunction([0, 0, 0, 0], model).func; + } } } } - - this.updateShows(primitive); - this.waitingOnCreate = false; - } else if (defined(primitive) && !primitive.ready) { - isUpdated = false; } + return uniformMap; + } - this.itemsToRemove.length = removedCount; - return isUpdated; - }; + function createPickColorFunction(color) { + return function() { + return color; + }; + } - 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); + function createJointMatricesFunction(runtimeNode) { + return function() { + return runtimeNode.computedJointMatrices; + }; + } - var attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } + function createMorphWeightsFunction(runtimeNode) { + return function() { + return runtimeNode.weights; + }; + } - var show = updater.entity.isShowing; - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); - } - } - this.showsUpdated.removeAll(); - }; + function createSilhouetteColorFunction(model) { + return function() { + return model.silhouetteColor; + }; + } - Batch.prototype.contains = function(entity) { - return this.updaters.contains(entity.id); - }; + function createSilhouetteSizeFunction(model) { + return function() { + return model.silhouetteSize; + }; + } - 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; - }; + function createColorFunction(model) { + return function() { + return model.color; + }; + } - Batch.prototype.removeAllPrimitives = function() { - var primitives = this.primitives; + function createColorBlendFunction(model) { + return function() { + return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount); + }; + } - var primitive = this.primitive; - if (defined(primitive)) { - primitives.remove(primitive); - this.primitive = undefined; - this.geometry.removeAll(); - this.updaters.removeAll(); + function triangleCountFromPrimitiveIndices(primitive, indicesCount) { + switch (primitive.mode) { + case PrimitiveType.TRIANGLES: + return (indicesCount / 3); + case PrimitiveType.TRIANGLE_STRIP: + case PrimitiveType.TRIANGLE_FAN: + return Math.max(indicesCount - 2, 0); + default: + return 0; } + } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; - } - }; + function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) { + var nodeCommands = model._nodeCommands; + var pickIds = model._pickIds; + var allowPicking = model.allowPicking; + var runtimeMeshesByName = model._runtime.meshesByName; - /** - * @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); - } - }; + var resources = model._rendererResources; + var rendererVertexArrays = resources.vertexArrays; + var rendererPrograms = resources.programs; + var rendererPickPrograms = resources.pickPrograms; + var rendererRenderStates = resources.renderStates; + var uniformMaps = model._uniformMaps; - StaticOutlineGeometryBatch.prototype.remove = function(updater) { - var i; + var gltf = model.gltf; + var accessors = gltf.accessors; + var gltfMeshes = gltf.meshes; + var techniques = gltf.techniques; + var materials = gltf.materials; - var solidBatches = this._solidBatches.values; - var solidBatchesLength = solidBatches.length; - for (i = 0; i < solidBatchesLength; i++) { - if (solidBatches[i].remove(updater)) { - return; - } - } + var id = gltfNode.mesh; + var mesh = gltfMeshes[id]; - var translucentBatches = this._translucentBatches.values; - var translucentBatchesLength = translucentBatches.length; - for (i = 0; i < translucentBatchesLength; i++) { - if (translucentBatches[i].remove(updater)) { - return; - } - } - }; + var primitives = mesh.primitives; + var length = primitives.length; - 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; + // 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. - do { - needUpdate = false; - for (x = 0; x < solidBatchesLength; x++) { - batch = solidBatches[x]; - //Perform initial update - isUpdated = batch.update(time); + 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; - //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); - } - } + 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)); } - 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); - } - } + 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; } - } while (needUpdate); - return isUpdated; - }; + // Update model triangle count using number of indices + model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count); - StaticOutlineGeometryBatch.prototype.getBoundingSphere = function(entity, result) { - var i; + var um = uniformMaps[primitive.material]; + var uniformMap = um.uniformMap; + if (defined(um.jointMatrixUniformName)) { + var jointUniformMap = {}; + jointUniformMap[um.jointMatrixUniformName] = createJointMatricesFunction(runtimeNode); - 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); + uniformMap = combine(uniformMap, jointUniformMap); } - } + if (defined(um.morphWeightsUniformName)) { + var morphWeightsUniformMap = {}; + morphWeightsUniformMap[um.morphWeightsUniformName] = createMorphWeightsFunction(runtimeNode); - 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); + uniformMap = combine(uniformMap, morphWeightsUniformMap); } - } - return BoundingSphereState.FAILED; - }; + uniformMap = combine(uniformMap, { + gltf_color : createColorFunction(model), + gltf_colorBlend : createColorBlendFunction(model) + }); - StaticOutlineGeometryBatch.prototype.removeAllPrimitives = function() { - var i; + // Allow callback to modify the uniformMap + if (defined(model._uniformMapLoaded)) { + uniformMap = model._uniformMapLoaded(uniformMap, programId, runtimeNode); + } - var solidBatches = this._solidBatches.values; - var solidBatchesLength = solidBatches.length; - for (i = 0; i < solidBatchesLength; i++) { - solidBatches[i].removeAllPrimitives(); - } + // Add uniforms for decoding quantized attributes if used + if (model.extensionsUsed.WEB3D_quantized_attributes) { + var quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive, context); + uniformMap = combine(uniformMap, quantizedUniformMap); + } - var translucentBatches = this._translucentBatches.values; - var translucentBatchesLength = translucentBatches.length; - for (i = 0; i < translucentBatchesLength; i++) { - translucentBatches[i].removeAllPrimitives(); - } - }; + var rs = rendererRenderStates[material.technique]; - return StaticOutlineGeometryBatch; -}); + // GLTF_SPEC: Offical means to determine translucency. https://github.com/KhronosGroup/glTF/issues/105 + var isTranslucent = rs.blending.enabled; -/*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 owner = model._pickObject; + if (!defined(owner)) { + owner = { + primitive : model, + id : model.id, + node : runtimeNode.publicNode, + mesh : runtimeMeshesByName[mesh.name] + }; + } - var emptyArray = []; + var castShadows = ShadowMode.castShadows(model._shadows); + var receiveShadows = ShadowMode.receiveShadows(model._shadows); - 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)); - }; + 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 : model.opaquePass + }); - 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(); - } - }; + var pickCommand; - 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; - }; + if (allowPicking) { + var pickUniformMap; - 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(); - }; + // 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); + } - 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; - }; + 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 : model.opaquePass + }); + } - 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); - } - } + var command2D; + var pickCommand2D; + if (!scene3DOnly) { + command2D = DrawCommand.shallowClone(command); + command2D.boundingVolume = new BoundingSphere(); // updated in update() + command2D.modelMatrix = new Matrix4(); // updated in update() - function insertUpdaterIntoBatch(that, time, updater) { - if (updater.isDynamic) { - that._dynamicBatch.add(time, updater); - return; - } + if (allowPicking) { + pickCommand2D = DrawCommand.shallowClone(pickCommand); + pickCommand2D.boundingVolume = new BoundingSphere(); // updated in update() + pickCommand2D.modelMatrix = new Matrix4(); // updated in update() + } + } - var shadows; - if (updater.outlineEnabled || updater.fillEnabled) { - shadows = updater.shadowsProperty.getValue(time); + 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); } - if (updater.outlineEnabled) { - that._outlineBatches[shadows].add(time, updater); - } + } - var multiplier = 0; - if (defined(updater.depthFailMaterialProperty)) { - multiplier = updater.depthFailMaterialProperty instanceof ColorMaterialProperty ? 1 : 2; - } + function createRuntimeNodes(model, context, scene3DOnly) { + var loadResources = model._loadResources; - var index; - if (defined(shadows)) { - index = shadows + multiplier * ShadowMode.NUMBER_OF_SHADOW_MODES; + if (!loadResources.finishedEverythingButTextureCreation()) { + return; } - if (updater.fillEnabled) { - if (updater.onTerrain) { - that._groundColorBatch.add(time, updater); - } else { - if (updater.isClosed) { - if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { - that._closedColorBatches[index].add(time, updater); - } else { - that._closedMaterialBatches[index].add(time, updater); - } - } else { - if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { - that._openColorBatches[index].add(time, updater); - } else { - that._openMaterialBatches[index].add(time, updater); - } - } - } + if (!loadResources.createRuntimeNodes) { + return; } - } - - /** - * 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) { - - 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 * 3); - this._closedMaterialBatches = new Array(numberOfShadowModes * 3); - this._openColorBatches = new Array(numberOfShadowModes * 3); - this._openMaterialBatches = new Array(numberOfShadowModes * 3); + loadResources.createRuntimeNodes = false; - for (var i = 0; i < numberOfShadowModes; ++i) { - this._outlineBatches[i] = new StaticOutlineGeometryBatch(primitives, scene, i); + var rootNodes = []; + var runtimeNodes = model._runtime.nodes; - this._closedColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, undefined, true, i); - this._closedMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, undefined, true, i); - this._openColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, undefined, false, i); - this._openMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, undefined, false, i); + var gltf = model.gltf; + var nodes = gltf.nodes; + var skins = gltf.skins; - this._closedColorBatches[i + numberOfShadowModes] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.perInstanceColorAppearanceType, true, i); - this._closedMaterialBatches[i + numberOfShadowModes] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.perInstanceColorAppearanceType, true, i); - this._openColorBatches[i + numberOfShadowModes] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.perInstanceColorAppearanceType, false, i); - this._openMaterialBatches[i + numberOfShadowModes] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.perInstanceColorAppearanceType, false, i); + var scene = gltf.scenes[gltf.scene]; + var sceneNodes = scene.nodes; + var length = sceneNodes.length; - this._closedColorBatches[i + numberOfShadowModes * 2] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.materialAppearanceType, true, i); - this._closedMaterialBatches[i + numberOfShadowModes * 2] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.materialAppearanceType, true, i); - this._openColorBatches[i + numberOfShadowModes * 2] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, type.materialAppearanceType, false, i); - this._openMaterialBatches[i + numberOfShadowModes * 2] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, type.materialAppearanceType, false, i); - } + var stack = []; + var seen = {}; - this._groundColorBatch = new StaticGroundGeometryColorBatch(groundPrimitives); - this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives); + for (var i = 0; i < length; ++i) { + stack.push({ + parentRuntimeNode : undefined, + gltfNode : nodes[sceneNodes[i]], + id : sceneNodes[i] + }); - this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatch, this._dynamicBatch); + var skeletonIds = []; + while (stack.length > 0) { + var n = stack.pop(); + seen[n.id] = true; + var parentRuntimeNode = n.parentRuntimeNode; + var gltfNode = n.gltfNode; - this._subscriptions = new AssociativeArray(); - this._updaters = new AssociativeArray(); + // 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); + } + } - this._entityCollection = entityCollection; - entityCollection.collectionChanged.addEventListener(GeometryVisualizer.prototype._onCollectionChanged, this); - this._onCollectionChanged(entityCollection, entityCollection.values, emptyArray); - } + if (defined(parentRuntimeNode)) { + parentRuntimeNode.children.push(runtimeNode); + runtimeNode.parents.push(parentRuntimeNode); + } else { + rootNodes.push(runtimeNode); + } - /** - * 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) { - - var addedObjects = this._addedObjects; - var added = addedObjects.values; - var removedObjects = this._removedObjects; - var removed = removedObjects.values; - var changedObjects = this._changedObjects; - var changed = changedObjects.values; + if (defined(gltfNode.mesh)) { + createCommand(model, gltfNode, runtimeNode, context, scene3DOnly); + } - var i; - var entity; - var id; - var updater; + var children = gltfNode.children; + var childrenLength = children.length; + for (var j = 0; j < childrenLength; j++) { + var childId = children[j]; + if (!seen[childId]) { + stack.push({ + parentRuntimeNode : runtimeNode, + gltfNode : nodes[childId], + id : children[j] + }); + } + } - for (i = changed.length - 1; i > -1; i--) { - entity = changed[i]; - id = entity.id; - updater = this._updaters.get(id); + var skin = gltfNode.skin; + if (defined(skin)) { + skeletonIds.push(skins[skin].skeleton); + } - //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); + if (stack.length === 0) { + for (var k = 0; k < skeletonIds.length; k++) { + var skeleton = skeletonIds[k]; + if (!seen[skeleton]) { + stack.push({ + parentRuntimeNode : undefined, + gltfNode : nodes[skeleton], + id : skeleton + }); + } + } + } } } - 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); - } + model._runtime.rootNodes = rootNodes; + model._runtime.nodes = runtimeNodes; + } - 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)); + function getGeometryByteLength(buffers) { + var memory = 0; + for (var id in buffers) { + if (buffers.hasOwnProperty(id)) { + memory += buffers[id].sizeInBytes; + } } + return memory; + } - 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; + function getTexturesByteLength(textures) { + var memory = 0; + for (var id in textures) { + if (textures.hasOwnProperty(id)) { + memory += textures[id].sizeInBytes; + } } + return memory; + } - return isUpdated; - }; - - var getBoundingSphereArrayScratch = []; - var getBoundingSphereBoundingSphereScratch = new BoundingSphere(); + function createResources(model, frameState) { + var context = frameState.context; + var scene3DOnly = frameState.scene3DOnly; - /** - * 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) { - - var boundingSpheres = getBoundingSphereArrayScratch; - var tmp = getBoundingSphereBoundingSphereScratch; + checkSupportedGlExtensions(model, context); + if (model._loadRendererResourcesFromCache) { + var resources = model._rendererResources; + var cachedResources = model._cachedRendererResources; - var count = 0; - var state = BoundingSphereState.DONE; - var batches = this._batches; - var batchesLength = batches.length; + 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; - 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++; + // Vertex arrays are unique to this model, create instead of using the cache. + if (defined(model._precreatedAttributes)) { + createVertexArrays(model, context); } - } - if (count === 0) { - return BoundingSphereState.FAILED; + model._cachedGeometryByteLength += getGeometryByteLength(cachedResources.buffers); + model._cachedTexturesByteLength += getTexturesByteLength(cachedResources.textures); + } else { + createBuffers(model, frameState); // using glTF bufferViews + createPrograms(model, frameState); + createSamplers(model, context); + loadTexturesFromBufferViews(model); + createTextures(model, frameState); } - 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(); - } + createSkins(model); + createRuntimeAnimations(model); - var subscriptions = this._subscriptions.values; - length = subscriptions.length; - for (i = 0; i < length; i++) { - subscriptions[i](); + 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. } - this._subscriptions.removeAll(); - return destroyObject(this); - }; - /** - * @private - */ - GeometryVisualizer._onGeometryChanged = function(updater) { - var removedObjects = this._removedObjects; - var changedObjects = this._changedObjects; + createUniformMaps(model, context); // using glTF materials/techniques + createRuntimeNodes(model, context, scene3DOnly); // using glTF scene + } - var entity = updater.entity; - var id = entity.id; + /////////////////////////////////////////////////////////////////////////// - if (!defined(removedObjects.get(id)) && !defined(changedObjects.get(id))) { - changedObjects.set(id, entity); + 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); } - }; + } - /** - * @private - */ - GeometryVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed) { - var addedObjects = this._addedObjects; - var removedObjects = this._removedObjects; - var changedObjects = this._changedObjects; + var scratchNodeStack = []; + var scratchComputedTranslation = new Cartesian4(); + var scratchComputedMatrixIn2D = new Matrix4(); - 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); - } - } + function updateNodeHierarchyModelMatrix(model, modelTransformChanged, justLoaded, projection) { + var maxDirtyNumber = model._maxDirtyNumber; + var allowPicking = model.allowPicking; - for (i = added.length - 1; i > -1; i--) { - entity = added[i]; - id = entity.id; - if (removedObjects.remove(id)) { - changedObjects.set(id, entity); + var rootNodes = model._runtime.rootNodes; + var length = rootNodes.length; + + var nodeStack = scratchNodeStack; + var computedModelMatrix = model._computedModelMatrix; + + if ((model._mode !== SceneMode.SCENE3D) && !model._ignoreCommands) { + var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation); + if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) { + computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D); + model._rtcCenter = model._rtcCenter3D; } else { - addedObjects.set(id, entity); + var center = model.boundingSphere.center; + var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); + computedModelMatrix = Matrix4.multiply(to2D, computedModelMatrix, scratchComputedMatrixIn2D); + + if (defined(model._rtcCenter)) { + Matrix4.setTranslation(computedModelMatrix, Cartesian4.UNIT_W, computedModelMatrix); + model._rtcCenter = model._rtcCenter2D; + } } } - }; - return GeometryVisualizer; -}); + for (var i = 0; i < length; ++i) { + var n = rootNodes[i]; -/*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'; + getNodeMatrix(n, n.transformToRoot); + nodeStack.push(n); - 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 defaultDisableDepthTestDistance = 0.0; + while (nodeStack.length > 0) { + n = nodeStack.pop(); + var transformToRoot = n.transformToRoot; + var commands = n.commands; - 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 scaleByDistance = new NearFarScalar(); - var distanceDisplayCondition = new DistanceDisplayCondition(); + 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); - function EntityData(entity) { - this.entity = entity; - this.label = undefined; - this.index = undefined; - } + // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation) + BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume); - /** - * 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) { - - entityCollection.collectionChanged.addEventListener(LabelVisualizer.prototype._onCollectionChanged, this); + if (defined(model._rtcCenter)) { + Cartesian3.add(model._rtcCenter, command.boundingVolume.center, command.boundingVolume.center); + } - this._cluster = entityCluster; - this._entityCollection = entityCollection; - this._items = new AssociativeArray(); + if (allowPicking) { + var pickCommand = primitiveCommand.pickCommand; + Matrix4.clone(command.modelMatrix, pickCommand.modelMatrix); + BoundingSphere.clone(command.boundingVolume, pickCommand.boundingVolume); + } - this._onCollectionChanged(entityCollection, entityCollection.values, [], []); - } + // 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); - /** - * 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) { - - var items = this._items.values; - var cluster = this._cluster; + if (allowPicking) { + var pickCommand2D = primitiveCommand.pickCommand2D; + Matrix4.clone(command.modelMatrix, pickCommand2D.modelMatrix); + BoundingSphere.clone(command.boundingVolume, pickCommand2D.boundingVolume); + } + } + } + } + } - 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); + var children = n.children; + var childrenLength = children.length; + for (var k = 0; k < childrenLength; ++k) { + var child = children[k]; - if (show) { - position = Property.getValueOrUndefined(entity._position, time, position); - text = Property.getValueOrUndefined(labelGraphics._text, time); - show = defined(position) && defined(text); - } + // 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 - if (!show) { - //don't bother creating or updating anything else - returnPrimitive(item, entity, cluster); - continue; - } + // 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 (!Property.isConstant(entity._position)) { - cluster._clusterDirty = true; - } + 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); + } - if (!defined(label)) { - label = cluster.getLabel(entity); - label.id = entity; - item.label = label; + nodeStack.push(child); + } } - - 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.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance); - label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition); - label.disableDepthTestDistance = Property.getValueOrDefault(labelGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); } - 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) { - - var item = this._items.get(entity.id); - if (!defined(item) || !defined(item.label)) { - return BoundingSphereState.FAILED; - } + ++model._maxDirtyNumber; + } - var label = item.label; - result.center = Cartesian3.clone(defaultValue(label._clampedPosition, label.position), result.center); - result.radius = 0; - return BoundingSphereState.DONE; - }; + var scratchObjectSpace = new Matrix4(); - /** - * 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; - }; + function applySkins(model) { + var skinnedNodes = model._runtime.skinnedNodes; + var length = skinnedNodes.length; - /** - * 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); - }; + for (var i = 0; i < length; ++i) { + var node = skinnedNodes[i]; - LabelVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { - var i; - var entity; - var items = this._items; - var cluster = this._cluster; + scratchObjectSpace = Matrix4.inverseTransformation(node.transformToRoot, scratchObjectSpace); - 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)); + 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]); + } } } + } - 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)); + 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); } - } 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 updatePickIds(model, context) { + var id = model.id; + if (model._id !== id) { + model._id = id; - function returnPrimitive(item, entity, cluster) { - if (defined(item)) { - item.label = undefined; - cluster.removeLabel(entity); + var pickIds = model._pickIds; + var length = pickIds.length; + for (var i = 0; i < length; ++i) { + pickIds[i].object.id = id; + } } } - return LabelVisualizer; -}); + function updateWireframe(model) { + if (model._debugWireframe !== model.debugWireframe) { + model._debugWireframe = model.debugWireframe; -/*global define*/ -define('ThirdParty/gltfDefaults',[ - '../Core/Cartesian3', - '../Core/defaultValue', - '../Core/defined', - '../Core/Quaternion', - '../Core/WebGLConstants' - ], function( - Cartesian3, - defaultValue, - defined, - Quaternion, - WebGLConstants) { - "use strict"; + // 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; - function accessorDefaults(gltf) { - if (!defined(gltf.accessors)) { - gltf.accessors = {}; + for (var i = 0; i < length; ++i) { + nodeCommands[i].command.primitiveType = primitiveType; + } } - var accessors = gltf.accessors; + } + + 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 name in accessors) { - if (accessors.hasOwnProperty(name)) { - var accessor = accessors[name]; - accessor.byteStride = defaultValue(accessor.byteStride, 0); + for (var i = 0; i < length; ++i) { + nodeCommands[i].command.debugShowBoundingVolume = debugShowBoundingVolume; } } } - function animationDefaults(gltf) { - if (!defined(gltf.animations)) { - gltf.animations = {}; - } - var animations = gltf.animations; + function updateShadows(model) { + if (model.shadows !== model._shadows) { + model._shadows = model.shadows; - for (var name in animations) { - if (animations.hasOwnProperty(name)) { - var animation = animations[name]; + var castShadows = ShadowMode.castShadows(model.shadows); + var receiveShadows = ShadowMode.receiveShadows(model.shadows); + var nodeCommands = model._nodeCommands; + var length = nodeCommands.length; - if (!defined(animation.channels)) { - animation.channels = []; - } + for (var i = 0; i < length; i++) { + var nodeCommand = nodeCommands[i]; + nodeCommand.command.castShadows = castShadows; + nodeCommand.command.receiveShadows = receiveShadows; + } + } + } - if (!defined(animation.parameters)) { - animation.parameters = {}; - } + function getTranslucentRenderState(renderState) { + var rs = clone(renderState, true); + rs.cull.enabled = false; + rs.depthTest.enabled = true; + rs.depthMask = false; + rs.blending = BlendingState.ALPHA_BLEND; - if (!defined(animation.samplers)) { - animation.samplers = {}; - } + return RenderState.fromCache(rs); + } - var samplers = animations.samplers; + function deriveTranslucentCommand(command) { + var translucentCommand = DrawCommand.shallowClone(command); + translucentCommand.pass = Pass.TRANSLUCENT; + translucentCommand.renderState = getTranslucentRenderState(command.renderState); + return translucentCommand; + } - for (var samplerName in samplers) { - if (samplers.hasOwnProperty(samplerName)) { - var sampler = samplers[samplerName]; - sampler.interpolation = defaultValue(sampler.interpolation, 'LINEAR'); + 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 assetDefaults(gltf) { - if (!defined(gltf.asset)) { - gltf.asset = {}; + function getProgramId(model, program) { + var programs = model._rendererResources.programs; + for (var id in programs) { + if (programs.hasOwnProperty(id)) { + if (programs[id] === program) { + return id; + } + } } - 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; + 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' + + '}'; - asset.premultipliedAlpha = defaultValue(asset.premultipliedAlpha, false); - profile.api = defaultValue(profile.api, 'WebGL'); - profile.version = defaultValue(profile.version, '1.0.2'); + var fs = + 'uniform vec4 gltf_silhouetteColor; \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragColor = gltf_silhouetteColor; \n' + + '}'; - 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(); - } + return ShaderProgram.fromCache({ + context : frameState.context, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); } - function bufferDefaults(gltf) { - if (!defined(gltf.buffers)) { - gltf.buffers = {}; - } - var buffers = gltf.buffers; + function hasSilhouette(model, frameState) { + return silhouetteSupported(frameState.context) && (model.silhouetteSize > 0.0) && (model.silhouetteColor.alpha > 0.0) && defined(model._normalAttributeName); + } - for (var name in buffers) { - if (buffers.hasOwnProperty(name)) { - var buffer = buffers[name]; - buffer.type = defaultValue(buffer.type, 'arraybuffer'); + 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 bufferViewDefaults(gltf) { - if (!defined(gltf.bufferViews)) { - gltf.bufferViews = {}; - } + function isTranslucent(model) { + return (model.color.alpha > 0.0) && (model.color.alpha < 1.0); } - function cameraDefaults(gltf) { - if (!defined(gltf.cameras)) { - gltf.cameras = {}; - } + function isInvisible(model) { + return (model.color.alpha === 0.0); } - function imageDefaults(gltf) { - if (!defined(gltf.images)) { - gltf.images = {}; - } + 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)); } - 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; + var silhouettesLength = 0; - 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; + 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 (!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 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; - 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; + // Create model command + var modelCommand = isTranslucent(model) ? nodeCommand.translucentCommand : command; + var silhouetteModelCommand = DrawCommand.shallowClone(modelCommand); + var renderState = clone(modelCommand.renderState); - if (!defined(pointLight.color)) { - pointLight.color = [1.0, 1.0, 1.0]; - } + // 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 + } + }; - 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 (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; - if (!defined(spotLight.color)) { - spotLight.color = [1.0, 1.0, 1.0]; - } + // 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; + } - 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); + // 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); - function materialDefaults(gltf) { - if (!defined(gltf.materials)) { - gltf.materials = {}; - } - var materials = gltf.materials; + // 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; + } - 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; + var silhouetteUniformMap = combine(command.uniformMap, { + gltf_silhouetteColor : createSilhouetteColorFunction(model), + gltf_silhouetteSize : createSilhouetteSizeFunction(model) + }); - delete material.instanceTechnique; - } + silhouetteColorCommand.renderState = renderState; + silhouetteColorCommand.shaderProgram = silhouetteProgram; + silhouetteColorCommand.uniformMap = silhouetteUniformMap; + silhouetteColorCommand.castShadows = false; + silhouetteColorCommand.receiveShadows = false; + nodeCommand.silhouetteColorCommand = silhouetteColorCommand; - 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 (!scene3DOnly) { + var command2D = nodeCommand.command2D; + var silhouetteModelCommand2D = DrawCommand.shallowClone(silhouetteModelCommand); + silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume; + silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix; + nodeCommand.silhouetteModelCommand2D = silhouetteModelCommand2D; - 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 = {}; - } - } + var silhouetteColorCommand2D = DrawCommand.shallowClone(silhouetteColorCommand); + silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume; + silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix; + nodeCommand.silhouetteColorCommand2D = silhouetteColorCommand2D; } } } - function meshDefaults(gltf) { - if (!defined(gltf.meshes)) { - gltf.meshes = {}; + 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 meshes = gltf.meshes; - - for (var name in meshes) { - if (meshes.hasOwnProperty(name)) { - var mesh = meshes[name]; - if (!defined(mesh.primitives)) { - mesh.primitives = []; - } + var nodeCommands = model._nodeCommands; + var dirty = alphaDirty(model.color.alpha, model._colorPreviousAlpha) || + alphaDirty(model.silhouetteColor.alpha, model._silhouetteColorPreviousAlpha) || + !defined(nodeCommands[0].silhouetteModelCommand); - var primitives = mesh.primitives.length; - var length = primitives.length; - for (var i = 0; i < length; ++i) { - var primitive = primitives[i]; + model._colorPreviousAlpha = model.color.alpha; + model._silhouetteColorPreviousAlpha = model.silhouetteColor.alpha; - if (!defined(primitive.attributes)) { - primitive.attributes = {}; - } + if (dirty) { + createSilhouetteCommands(model, frameState); + } + } - // Backwards compatibility for glTF 0.8. primitive was renamed to mode. - var defaultMode = defaultValue(primitive.primitive, WebGLConstants.TRIANGLES); + var scratchBoundingSphere = new BoundingSphere(); - primitive.mode = defaultValue(primitive.mode, defaultMode); - } - } - } + function scaleInPixels(positionWC, radius, frameState) { + scratchBoundingSphere.center = positionWC; + scratchBoundingSphere.radius = radius; + return frameState.camera.getPixelSize(scratchBoundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight); } - function nodeDefaults(gltf) { - if (!defined(gltf.nodes)) { - gltf.nodes = {}; - } - var nodes = gltf.nodes; - var hasAxisAngle = (parseFloat(gltf.asset.version) < 1.0); + var scratchPosition = new Cartesian3(); + var scratchCartographic = new Cartographic(); - var axis = new Cartesian3(); - var quat = new Quaternion(); - for (var name in nodes) { - if (nodes.hasOwnProperty(name)) { - var node = nodes[name]; + function getScale(model, frameState) { + var scale = model.scale; - if (!defined(node.children)) { - node.children = []; - } + 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 (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(model._rtcCenter)) { + Cartesian3.add(model._rtcCenter, scratchPosition, scratchPosition); + } - 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 (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); + } - if (!defined(node.rotation)) { - node.rotation = [0.0, 0.0, 0.0, 1.0]; - } + var radius = model.boundingSphere.radius; + var metersPerPixel = scaleInPixels(scratchPosition, radius, frameState); - if (!defined(node.scale)) { - node.scale = [1.0, 1.0, 1.0]; - } - } - } + // metersPerPixel is always > 0.0 + var pixelsPerMeter = 1.0 / metersPerPixel; + var diameterInPixels = Math.min(pixelsPerMeter * (2.0 * radius), maxPixelSize); - var instanceSkin = node.instanceSkin; - if (defined(instanceSkin)) { - node.skeletons = instanceSkin.skeletons; - node.skin = instanceSkin.skin; - node.meshes = instanceSkin.meshes; - delete node.instanceSkin; - } + // 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 programDefaults(gltf) { - if (!defined(gltf.programs)) { - gltf.programs = {}; + function releaseCachedGltf(model) { + if (defined(model._cacheKey) && defined(model._cachedGltf) && (--model._cachedGltf.count === 0)) { + delete gltfCache[model._cacheKey]; } - var programs = gltf.programs; + model._cachedGltf = undefined; + } - for (var name in programs) { - if (programs.hasOwnProperty(name)) { - var program = programs[name]; - if (!defined(program.attributes)) { - program.attributes = []; + function checkSupportedExtensions(model) { + var extensionsRequired = model.extensionsRequired; + for (var extension in extensionsRequired) { + if (extensionsRequired.hasOwnProperty(extension)) { + if (extension !== 'CESIUM_RTC' && + extension !== 'KHR_technique_webgl' && + extension !== 'KHR_binary_glTF' && + extension !== 'KHR_materials_common' && + extension !== 'WEB3D_quantized_attributes') { + throw new RuntimeError('Unsupported glTF Extension: ' + extension); } } } } - function samplerDefaults(gltf) { - if (!defined(gltf.samplers)) { - gltf.samplers = {}; + function checkSupportedGlExtensions(model, context) { + var glExtensionsUsed = model.gltf.glExtensionsUsed; + if (defined(glExtensionsUsed)) { + var glExtensionsUsedLength = glExtensionsUsed.length; + for (var i = 0; i < glExtensionsUsedLength; i++) { + var extension = glExtensionsUsed[i]; + if (extension !== 'OES_element_index_uint') { + throw new RuntimeError('Unsupported WebGL Extension: ' + extension); + } else if (!context.elementIndexUint) { + throw new RuntimeError('OES_element_index_uint WebGL extension is not enabled.'); + } + } } - var samplers = gltf.samplers; + } + + /////////////////////////////////////////////////////////////////////////// + + function CachedRendererResources(context, cacheKey) { + this.buffers = undefined; + this.vertexArrays = undefined; + this.programs = undefined; + this.pickPrograms = undefined; + this.silhouettePrograms = undefined; + this.textures = undefined; + this.samplers = undefined; + this.renderStates = undefined; + this.ready = false; + + this.context = context; + this.cacheKey = cacheKey; + this.count = 0; + } - 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 destroy(property) { + for (var name in property) { + if (property.hasOwnProperty(name)) { + property[name].destroy(); } } } - function sceneDefaults(gltf) { - if (!defined(gltf.scenes)) { - gltf.scenes = {}; - } - var scenes = gltf.scenes; + function destroyCachedRendererResources(resources) { + destroy(resources.buffers); + destroy(resources.vertexArrays); + destroy(resources.programs); + destroy(resources.pickPrograms); + destroy(resources.silhouettePrograms); + destroy(resources.textures); + } - for (var name in scenes) { - if (scenes.hasOwnProperty(name)) { - var scene = scenes[name]; - if (!defined(scene.node)) { - scene.node = []; - } + CachedRendererResources.prototype.release = function() { + if (--this.count === 0) { + if (defined(this.cacheKey)) { + // Remove if this was cached + delete this.context.cache.modelRendererResourceCache[this.cacheKey]; } + destroyCachedRendererResources(this); + return destroyObject(this); } + + return undefined; + }; + + /////////////////////////////////////////////////////////////////////////// + + function getUpdateHeightCallback(model, ellipsoid, cartoPosition) { + return function(clampedPosition) { + if (model.heightReference === HeightReference.RELATIVE_TO_GROUND) { + var clampedCart = ellipsoid.cartesianToCartographic(clampedPosition, scratchCartographic); + clampedCart.height += cartoPosition.height; + ellipsoid.cartographicToCartesian(clampedCart, clampedPosition); + } + + var clampedModelMatrix = model._clampedModelMatrix; + + // Modify clamped model matrix to use new height + Matrix4.clone(model.modelMatrix, clampedModelMatrix); + clampedModelMatrix[12] = clampedPosition.x; + clampedModelMatrix[13] = clampedPosition.y; + clampedModelMatrix[14] = clampedPosition.z; + + model._heightChanged = true; + }; } - function shaderDefaults(gltf) { - if (!defined(gltf.shaders)) { - gltf.shaders = {}; + function updateClamping(model) { + if (defined(model._removeUpdateHeightCallback)) { + model._removeUpdateHeightCallback(); + model._removeUpdateHeightCallback = undefined; } - } - function skinDefaults(gltf) { - if (!defined(gltf.skins)) { - gltf.skins = {}; + var scene = model._scene; + if (!defined(scene) || (model.heightReference === HeightReference.NONE)) { + model._clampedModelMatrix = undefined; + return; } - 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 - ]; - } - } + var globe = scene.globe; + var ellipsoid = globe.ellipsoid; + + // Compute cartographic position so we don't recompute every update + var modelMatrix = model.modelMatrix; + scratchPosition.x = modelMatrix[12]; + scratchPosition.y = modelMatrix[13]; + scratchPosition.z = modelMatrix[14]; + var cartoPosition = ellipsoid.cartesianToCartographic(scratchPosition); + + if (!defined(model._clampedModelMatrix)) { + model._clampedModelMatrix = Matrix4.clone(modelMatrix, new Matrix4()); } - } - function statesDefaults(states) { - if (!defined(states.enable)) { - states.enable = []; + // Install callback to handle updating of terrain tiles + var surface = globe._surface; + model._removeUpdateHeightCallback = surface.updateHeight(cartoPosition, getUpdateHeightCallback(model, ellipsoid, cartoPosition)); + + // Set the correct height now + var height = globe.getHeight(cartoPosition); + if (defined(height)) { + // Get callback with cartoPosition being the non-clamped position + var cb = getUpdateHeightCallback(model, ellipsoid, cartoPosition); + + // Compute the clamped cartesian and call updateHeight callback + Cartographic.clone(cartoPosition, scratchCartographic); + scratchCartographic.height = height; + ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition); + cb(scratchPosition); } + } - if (!defined(states.disable)) { - states.disable = []; + var scratchDisplayConditionCartesian = new Cartesian3(); + var scratchDistanceDisplayConditionCartographic = new Cartographic(); + + function distanceDisplayConditionVisible(model, frameState) { + var distance2; + var ddc = model.distanceDisplayCondition; + var nearSquared = ddc.near * ddc.near; + var farSquared = ddc.far * ddc.far; + + if (frameState.mode === SceneMode.SCENE2D) { + var frustum2DWidth = frameState.camera.frustum.right - frameState.camera.frustum.left; + distance2 = frustum2DWidth * 0.5; + distance2 = distance2 * distance2; + } else { + // Distance to center of primitive's reference frame + var position = Matrix4.getTranslation(model.modelMatrix, scratchDisplayConditionCartesian); + if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + var projection = frameState.mapProjection; + var ellipsoid = projection.ellipsoid; + var cartographic = ellipsoid.cartesianToCartographic(position, scratchDistanceDisplayConditionCartographic); + position = projection.project(cartographic, position); + Cartesian3.fromElements(position.z, position.x, position.y, position); + } + distance2 = Cartesian3.distanceSquared(position, frameState.camera.positionWC); } + + return (distance2 >= nearSquared) && (distance2 <= farSquared); } - function techniqueDefaults(gltf) { - if (!defined(gltf.techniques)) { - gltf.techniques = {}; + /** + * 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 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 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; + } - 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; + this._state = ModelState.LOADING; + if (this._state !== ModelState.FAILED) { + var extensions = this.gltf.extensions; + if (defined(extensions) && defined(extensions.CESIUM_RTC)) { + var center = Cartesian3.fromArray(extensions.CESIUM_RTC.center); + if (!Cartesian3.equals(center, Cartesian3.ZERO)) { + this._rtcCenter3D = center; - technique.attributes = defaultValue(technique.attributes, instanceProgram.attributes); - technique.program = defaultValue(technique.program, instanceProgram.program); - technique.uniforms = defaultValue(technique.uniforms, instanceProgram.uniforms); + var projection = frameState.mapProjection; + var ellipsoid = projection.ellipsoid; + var cartographic = ellipsoid.cartesianToCartographic(this._rtcCenter3D); + var projectedCart = projection.project(cartographic); + Cartesian3.fromElements(projectedCart.z, projectedCart.x, projectedCart.y, projectedCart); + this._rtcCenter2D = projectedCart; - technique.states = defaultValue(technique.states, pass.states); + this._rtcCenterEye = new Cartesian3(); + this._rtcCenter = this._rtcCenter3D; } - - technique.passes = undefined; - technique.pass = undefined; } - if (!defined(technique.attributes)) { - technique.attributes = {}; + this._loadResources = new LoadResources(); + if (!this._loadRendererResourcesFromCache) { + // Buffers are required to updateVersion + parseBuffers(this); } + } + } - if (!defined(technique.uniforms)) { - technique.uniforms = {}; - } + var loadResources = this._loadResources; + var incrementallyLoadTextures = this._incrementallyLoadTextures; + var justLoaded = false; - if (!defined(technique.states)) { - technique.states = {}; + if (this._state === ModelState.LOADING) { + // Transition from LOADING -> LOADED once resources are downloaded and created. + // Textures may continue to stream in while in the LOADED state. + if (loadResources.pendingBufferLoads === 0) { + if (!this._updatedGltfVersion) { + var options = { + optimizeForCesium: true, + addBatchIdToGeneratedShaders : this._addBatchIdToGeneratedShaders + }; + frameState.brdfLutGenerator.update(frameState); + updateVersion(this.gltf); + checkSupportedExtensions(this); + addPipelineExtras(this.gltf); + addDefaults(this.gltf); + processModelMaterialsCommon(this.gltf, options); + processPbrMetallicRoughness(this.gltf, options); + // We do this after to make sure that the ids don't change + addBuffersToLoadResources(this); + + if (!this._loadRendererResourcesFromCache) { + parseBufferViews(this); + parseShaders(this); + parsePrograms(this); + parseTextures(this, context); + } + parseMaterials(this); + parseMeshes(this); + parseNodes(this); + + this._boundingSphere = computeBoundingSphere(this); + this._initialRadius = this._boundingSphere.radius; + this._updatedGltfVersion = true; + } + if (this._updatedGltfVersion && loadResources.pendingShaderLoads === 0) { + createResources(this, frameState); } - statesDefaults(technique.states); + } + if (loadResources.finished() || + (incrementallyLoadTextures && loadResources.finishedEverythingButTextureCreation())) { + this._state = ModelState.LOADED; + justLoaded = true; } } - } - - 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); + // Incrementally stream textures. + if (defined(loadResources) && (this._state === ModelState.LOADED)) { + if (incrementallyLoadTextures && !justLoaded) { + createResources(this, frameState); } - } - } - /** - * Modifies gltf in place. - * - * @private - */ - var gltfDefaults = function(gltf) { - if (!defined(gltf)) { - return undefined; - } + if (loadResources.finished()) { + this._loadResources = undefined; // Clear CPU memory since WebGL resources were created. - if (defined(gltf.allExtensions)) { - gltf.extensionsUsed = gltf.allExtensions; - gltf.allExtensions = undefined; + 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); + } + } } - 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; - }; + 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); - return gltfDefaults; -}); + 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; -/*global define*/ -define('Scene/Axis',[ - '../Core/Check', - '../Core/freezeObject', - '../Core/Math', - '../Core/Matrix3', - '../Core/Matrix4' -], function( - Check, - freezeObject, - CesiumMath, - Matrix3, - Matrix4) { - 'use strict'; + var modeChanged = frameState.mode !== this._mode; + this._mode = frameState.mode; - /** - * An enum describing the x, y, and z axes and helper conversion functions. - * - * @exports Axis - * @private - */ - var Axis = { - /** - * Denotes the x-axis. - * - * @type {Number} - * @constant - */ - X : 0, + // 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; - /** - * Denotes the y-axis. - * - * @type {Number} - * @constant - */ - Y : 1, + if (modelTransformChanged || justLoaded) { + Matrix4.clone(modelMatrix, this._modelMatrix); - /** - * Denotes the z-axis. - * - * @type {Number} - * @constant - */ - Z : 2, + updateClamping(this); - /** - * Matrix used to convert from y-up to z-up - * - * @type {Matrix4} - * @constant - */ - Y_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO)), + if (defined(this._clampedModelMatrix)) { + modelMatrix = this._clampedModelMatrix; + } - /** - * Matrix used to convert from z-up to y-up - * - * @type {Matrix4} - * @constant - */ - Z_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(-CesiumMath.PI_OVER_TWO)), + this._scale = this.scale; + this._minimumPixelSize = this.minimumPixelSize; + this._maximumScale = this.maximumScale; + this._heightReference = this.heightReference; + this._heightChanged = false; - /** - * Matrix used to convert from x-up to z-up - * - * @type {Matrix4} - * @constant - */ - X_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(-CesiumMath.PI_OVER_TWO)), + var scale = getScale(this, frameState); + var computedModelMatrix = this._computedModelMatrix; + Matrix4.multiplyByUniformScale(modelMatrix, scale, computedModelMatrix); + if (this._upAxis === Axis.Y) { + Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix); + } else if (this._upAxis === Axis.X) { + Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix); + } + } - /** - * Matrix used to convert from z-up to x-up - * - * @type {Matrix4} - * @constant - */ - Z_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(CesiumMath.PI_OVER_TWO)), + // Update modelMatrix throughout the graph as needed + if (animated || modelTransformChanged || justLoaded) { + updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection); + this._dirty = true; - /** - * Matrix used to convert from x-up to y-up - * - * @type {Matrix4} - * @constant - */ - X_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(CesiumMath.PI_OVER_TWO)), + if (animated || justLoaded) { + // Apply skins if animation changed any node transforms + applySkins(this); + } + } - /** - * Matrix used to convert from y-up to x-up - * - * @type {Matrix4} - * @constant - */ - Y_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(-CesiumMath.PI_OVER_TWO)), + if (this._perNodeShowDirty) { + this._perNodeShowDirty = false; + updatePerNodeShow(this); + } + updatePickIds(this, context); + updateWireframe(this); + updateShowBoundingVolume(this); + updateShadows(this); + updateColor(this, frameState); + updateSilhouette(this, frameState); + } - /** - * Gets the axis by name - * - * @param {String} name The name of the axis. - * @returns {Number} The axis enum. - */ - fromName : function(name) { - - return Axis[name]; + if (justLoaded) { + // Called after modelMatrix update. + var model = this; + frameState.afterRender.push(function() { + model._ready = true; + model._readyPromise.resolve(model); + }); + return; } - }; - return freezeObject(Axis); -}); + // 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; -/*global define*/ -define('Scene/getAttributeOrUniformBySemantic',[], function() { - 'use strict'; + var idl2D = frameState.mapProjection.ellipsoid.maximumRadius * CesiumMath.PI; + var boundingVolume; - /** - * 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; + 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); } } } - for (var uniformName in uniforms) { - if (uniforms.hasOwnProperty(uniformName)) { - if (parameters[uniforms[uniformName]].semantic === semantic) { - return uniformName; + + 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); + } } } } } - } - 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'; + if (passes.pick && this.allowPicking) { + for (i = 0; i < length; ++i) { + nc = nodeCommands[i]; + if (nc.show) { + var pickCommand = nc.pickCommand; + commandList.push(pickCommand); - var ComponentsPerAttribute = { - SCALAR : 1, - VEC2 : 2, - VEC3 : 3, - VEC4 : 4, - MAT2 : 4, - MAT3 : 9, - MAT4 : 16 + boundingVolume = pickCommand.boundingVolume; + if (frameState.mode === SceneMode.SCENE2D && + (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) { + commandList.push(nc.pickCommand2D); + } + } + } + } + } }; - var ClassPerType = { - SCALAR : undefined, - VEC2 : Cartesian2, - VEC3 : Cartesian3, - VEC4 : Cartesian4, - MAT2 : Matrix2, - MAT3 : Matrix3, - MAT4 : Matrix4 + /** + * 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; }; /** - * @private + * 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 */ - function getBinaryAccessor(accessor) { - var componentType = accessor.componentType; - var componentDatatype; - if (typeof componentType === 'string') { - componentDatatype = ComponentDatatype.fromName(componentType); - } else { - componentDatatype = componentType; + Model.prototype.destroy = function() { + // Vertex arrays are unique to this model, destroy here. + if (defined(this._precreatedAttributes)) { + destroy(this._rendererResources.vertexArrays); } - 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); - } - }; - } + if (defined(this._removeUpdateHeightCallback)) { + this._removeUpdateHeightCallback(); + this._removeUpdateHeightCallback = undefined; + } - return getBinaryAccessor; -}); + if (defined(this._terrainProviderChangedCallback)) { + this._terrainProviderChangedCallback(); + this._terrainProviderChangedCallback = undefined; + } -/*global define*/ -define('Scene/JobType',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + this._rendererResources = undefined; + this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release(); - /** - * @private - */ - var JobType = { - TEXTURE : 0, - PROGRAM : 1, - BUFFER : 2, - NUMBER_OF_JOB_TYPES : 3 + var pickIds = this._pickIds; + var length = pickIds.length; + for (var i = 0; i < length; ++i) { + pickIds[i].destroy(); + } + + releaseCachedGltf(this); + + return destroyObject(this); }; - return freezeObject(JobType); + return Model; }); -/*global define*/ -define('Scene/ModelAnimationCache',[ - '../Core/Cartesian3', - '../Core/defaultValue', +define('DataSources/ModelVisualizer',[ + '../Core/AssociativeArray', + '../Core/BoundingSphere', + '../Core/Color', '../Core/defined', - '../Core/LinearSpline', + '../Core/destroyObject', + '../Core/DeveloperError', '../Core/Matrix4', - '../Core/Quaternion', - '../Core/QuaternionSpline', - '../Core/WebGLConstants', - './getBinaryAccessor' + '../Scene/ColorBlendMode', + '../Scene/HeightReference', + '../Scene/Model', + '../Scene/ModelAnimationLoop', + '../Scene/ShadowMode', + './BoundingSphereState', + './Property' ], function( - Cartesian3, - defaultValue, + AssociativeArray, + BoundingSphere, + Color, defined, - LinearSpline, + destroyObject, + DeveloperError, Matrix4, - Quaternion, - QuaternionSpline, - WebGLConstants, - getBinaryAccessor) { + 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 modelMatrixScratch = new Matrix4(); + var nodeMatrixScratch = new Matrix4(); + /** - * @private + * 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 ModelAnimationCache() { + function ModelVisualizer(scene, entityCollection) { + + 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, [], []); } - var dataUriRegex = /^data\:/i; + /** + * 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) { + + var entities = this._entitiesToVisualize.values; + var modelHash = this._modelHash; + var primitives = this._primitives; - function getAccessorKey(model, accessor) { - var gltf = model.gltf; - var buffers = gltf.buffers; - var bufferViews = gltf.bufferViews; + for (var i = 0, len = entities.length; i < len; i++) { + var entity = entities[i]; + var modelGraphics = entity._model; - var bufferView = bufferViews[accessor.bufferView]; - var buffer = buffers[bufferView.buffer]; + var uri; + var modelData = modelHash[entity.id]; + var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(modelGraphics._show, time, true); - var byteOffset = bufferView.byteOffset + accessor.byteOffset; - var byteLength = accessor.count * getBinaryAccessor(accessor).componentsPerAttribute; + var modelMatrix; + if (show) { + modelMatrix = entity.computeModelMatrix(time, modelMatrixScratch); + uri = Property.getValueOrUndefined(modelGraphics._uri, time); + show = defined(modelMatrix) && defined(uri); + } - var uriKey = dataUriRegex.test(buffer.uri) ? '' : buffer.uri; - return model.cacheKey + '//' + uriKey + '/' + byteOffset + '/' + byteLength; - } + if (!show) { + if (defined(modelData)) { + modelData.modelPrimitive.show = false; + } + continue; + } - var cachedAnimationParameters = { - }; + 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 + }); - var axisScratch = new Cartesian3(); + model.readyPromise.otherwise(onModelError); - ModelAnimationCache.getAnimationParameterValues = function(model, accessor) { - var key = getAccessorKey(model, accessor); - var values = cachedAnimationParameters[key]; + model.id = entity; + primitives.add(model); - if (!defined(values)) { - // Cache miss - var loadResources = model._loadResources; - var gltf = model.gltf; - var hasAxisAngle = (parseFloat(gltf.asset.version) < 1.0); + modelData = { + modelPrimitive : model, + uri : uri, + animationsRunning : false, + nodeTransformationsScratch : {}, + originalNodeMatrixHash : {} + }; + modelHash[entity.id] = modelData; + } - var bufferViews = gltf.bufferViews; + 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._silhouetteColor); + model.silhouetteSize = Property.getValueOrDefault(modelGraphics._silhouetteSize, time, defaultSilhouetteSize); + model.color = Property.getValueOrDefault(modelGraphics._color, time, defaultColor, model._color); + model.colorBlendMode = Property.getValueOrDefault(modelGraphics._colorBlendMode, time, defaultColorBlendMode); + model.colorBlendAmount = Property.getValueOrDefault(modelGraphics._colorBlendAmount, time, defaultColorBlendAmount); - var bufferView = bufferViews[accessor.bufferView]; + 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; + } - var componentType = accessor.componentType; - var type = accessor.type; - var count = accessor.count; + // 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]; - // 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; + var nodeTransformation = nodeTransformations[nodeName]; + if (!defined(nodeTransformation)) { + continue; + } - 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); + 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); } } } - // 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; + return true; }; - var cachedAnimationSplines = { + /** + * 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; }; - 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; + /** + * 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); }; - // 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]; + /** + * 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) { + + var modelData = this._modelHash[entity.id]; + if (!defined(modelData)) { + return BoundingSphereState.FAILED; + } -// 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; + var model = modelData.modelPrimitive; + if (!defined(model) || !model.show) { + return BoundingSphereState.FAILED; + } - 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 (!model.ready) { + return BoundingSphereState.PENDING; + } - if (defined(model.cacheKey)) { - // Only cache when we can create a unique id - cachedAnimationSplines[key] = spline; + 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 spline; - }; - - var cachedSkinInverseBindMatrices = { + return BoundingSphereState.DONE; }; - 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); + /** + * @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; - if ((componentType === WebGLConstants.FLOAT) && (type === 'MAT4')) { - for (var i = 0; i < count; ++i) { - matrices[i] = Matrix4.fromArray(typedArray, 16 * i); - } + for (i = added.length - 1; i > -1; i--) { + entity = added[i]; + if (defined(entity._model) && defined(entity._position)) { + entities.set(entity.id, entity); } + } - cachedSkinInverseBindMatrices[key] = matrices; + 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); + } } - return matrices; + for (i = removed.length - 1; i > -1; i--) { + entity = removed[i]; + removeModel(this, entity, modelHash, primitives); + entities.remove(entity.id); + } }; - 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, + function removeModel(visualizer, entity, modelHash, primitives) { + var modelData = modelHash[entity.id]; + if (defined(modelData)) { + primitives.removeAndDestroy(modelData.modelPrimitive); + delete modelHash[entity.id]; + } + } - /** - * Loop the animation playing it from the start immediately after it stops. - * - * @type {Number} - * @constant - */ - REPEAT : 1, + function clearNodeTransformationsScratch(entity, modelHash) { + var modelData = modelHash[entity.id]; + if (defined(modelData)) { + modelData.nodeTransformationsScratch = {}; + } + } - /** - * Loop the animation. First, playing it forward, then in reverse, then forward, and so on. - * - * @type {Number} - * @constant - */ - MIRRORED_REPEAT : 2 - }; + function onModelError(error) { + console.error(error); + } - return freezeObject(ModelAnimationLoop); + return ModelVisualizer; }); -/*global define*/ -define('Scene/ModelAnimationState',[ - '../Core/freezeObject' - ], function( - freezeObject) { +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/PolylineCommon',[],function() { 'use strict'; - - /** - * @private - */ - return freezeObject({ - STOPPED : 0, - ANIMATING : 1 - }); + 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\ +\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\ +\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\ + // t = (-plane distance - dot(plane normal, ray origin)) / dot(plane normal, ray direction)\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\ +\n\ + positionWC = czm_eyeToWindowCoordinates(vec4(p0, 1.0));\n\ +}\n\ +\n\ +vec4 getPolylineWindowCoordinates(vec4 position, vec4 previous, vec4 next, float expandDirection, float width, bool usePrevious, out float angle) {\n\ + vec4 endPointWC, p0, p1;\n\ + bool culledByNearPlane, clipped;\n\ +\n\ + vec4 positionEC = czm_modelViewRelativeToEye * position;\n\ + vec4 prevEC = czm_modelViewRelativeToEye * previous;\n\ + vec4 nextEC = czm_modelViewRelativeToEye * next;\n\ +\n\ + // Compute the window coordinates of the points.\n\ + vec4 positionWindow = czm_eyeToWindowCoordinates(positionEC);\n\ + vec4 previousWindow = czm_eyeToWindowCoordinates(prevEC);\n\ + vec4 nextWindow = czm_eyeToWindowCoordinates(nextEC);\n\ +\n\ +#ifdef POLYLINE_DASH\n\ + // Determine the relative screen space direction of the line.\n\ + vec2 lineDir;\n\ + if (usePrevious) {\n\ + lineDir = normalize(positionWindow.xy - previousWindow.xy);\n\ + }\n\ + else {\n\ + lineDir = normalize(nextWindow.xy - positionWindow.xy);\n\ + }\n\ + angle = atan(lineDir.x, lineDir.y) - 1.570796327; // precomputed atan(1,0)\n\ +\n\ + // Quantize the angle so it doesn't change rapidly between segments.\n\ + angle = floor(angle / czm_piOverFour + 0.5) * czm_piOverFour;\n\ +#endif\n\ +\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\ +\n\ + if (culledByNearPlane)\n\ + {\n\ + return vec4(0.0, 0.0, 0.0, 1.0);\n\ + }\n\ +\n\ + vec2 prevWC = normalize(p0.xy - endPointWC.xy);\n\ + vec2 nextWC = normalize(p1.xy - endPointWC.xy);\n\ +\n\ + float expandWidth = width * 0.5;\n\ + vec2 direction;\n\ +\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\ +\n\ + // The sine of the angle between the two vectors is given by the formula\n\ + // |a x b| = |a||b|sin(theta)\n\ + // which is\n\ + // float sinAngle = length(cross(vec3(direction, 0.0), vec3(nextWC, 0.0)));\n\ + // Because the z components of both vectors are zero, the x and y coordinate will be zero.\n\ + // Therefore, the sine of the angle is just the z component of the cross product.\n\ + float sinAngle = abs(direction.x * nextWC.y - direction.y * nextWC.x);\n\ + expandWidth = clamp(expandWidth / sinAngle, 0.0, width * 2.0);\n\ + }\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. +define('Shaders/PolylineFS',[],function() { + 'use strict'; + return "varying vec2 v_st;\n\ +\n\ +void main()\n\ +{\n\ + czm_materialInput materialInput;\n\ + \n\ + materialInput.s = v_st.s;\n\ + materialInput.st = v_st;\n\ + materialInput.str = vec3(v_st, 0.0);\n\ + \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. +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\ +\n\ +varying vec2 v_st;\n\ +varying float v_width;\n\ +varying vec4 czm_pickColor;\n\ +varying float v_polylineAngle;\n\ +\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\ +\n\ + vec2 widthAndShow = batchTable_getWidthAndShow(batchTableIndex);\n\ + float width = widthAndShow.x + 0.5;\n\ + float show = widthAndShow.y;\n\ +\n\ + if (width < 1.0)\n\ + {\n\ + show = 0.0;\n\ + }\n\ +\n\ + vec4 pickColor = batchTable_getPickColor(batchTableIndex);\n\ +\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\ +\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\ +\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\ +\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\ +\n\ + vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev, v_polylineAngle);\n\ + gl_Position = czm_viewportOrthographic * positionWC * show;\n\ +\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/ModelAnimation',[ +define('Scene/Polyline',[ + '../Core/arrayRemoveDuplicates', + '../Core/BoundingSphere', + '../Core/Cartesian3', + '../Core/Color', '../Core/defaultValue', + '../Core/defined', '../Core/defineProperties', - '../Core/Event', - '../Core/JulianDate', - './ModelAnimationLoop', - './ModelAnimationState' + '../Core/DeveloperError', + '../Core/DistanceDisplayCondition', + '../Core/Matrix4', + '../Core/PolylinePipeline', + './Material' ], function( + arrayRemoveDuplicates, + BoundingSphere, + Cartesian3, + Color, defaultValue, + defined, defineProperties, - Event, - JulianDate, - ModelAnimationLoop, - ModelAnimationState) { + DeveloperError, + DistanceDisplayCondition, + Matrix4, + PolylinePipeline, + Material) { '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}. - *

    + * A renderable polyline. Create this by calling {@link PolylineCollection#add} * - * @alias ModelAnimation + * @alias Polyline * @internalConstructor * - * @see ModelAnimationCollection#add + * @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. + * @param {PolylineCollection} polylineCollection The renderable polyline collection. + * + * @see PolylineCollection + * */ - 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; + function Polyline(options, polylineCollection) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - /** - * 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._show = defaultValue(options.show, true); + this._width = defaultValue(options.width, 1.0); + this._loop = defaultValue(options.loop, false); + this._distanceDisplayCondition = options.distanceDisplayCondition; - this._speedup = defaultValue(options.speedup, 1.0); - this._reverse = defaultValue(options.reverse, false); - this._loop = defaultValue(options.loop, ModelAnimationLoop.NONE); + 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) + }); + } - /** - * 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(); + var positions = options.positions; + if (!defined(positions)) { + positions = []; + } - /** - * 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(); + this._positions = positions; + this._actualPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); - /** - * 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(); + 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._state = ModelAnimationState.STOPPED; - this._runtimeAnimation = runtimeAnimation; + this._length = this._actualPositions.length; + this._id = options.id; - // Set during animation update - this._computedStartTime = undefined; - this._duration = undefined; + var modelMatrix; + if (defined(polylineCollection)) { + modelMatrix = Matrix4.clone(polylineCollection.modelMatrix); + } - // 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); - }; + this._modelMatrix = modelMatrix; + this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix); + + this._actualLength = undefined; + + this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES); //eslint-disable-line no-use-before-define + 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 } - defineProperties(ModelAnimation.prototype, { - /** - * The glTF animation name that identifies this animation. - * - * @memberof ModelAnimation.prototype - * - * @type {String} - * @readonly - */ - name : { - get : function() { - return this._name; - } - }, + 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, { /** - * 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 + * 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} */ - startTime : { - get : function() { - return this._startTime; + show: { + get: function() { + return this._show; + }, + set: function(value) { + + if (value !== this._show) { + this._show = value; + makeDirty(this, SHOW_INDEX); + } } }, /** - * The delay, in seconds, from {@link ModelAnimation#startTime} to start playing. - * - * @memberof ModelAnimation.prototype - * - * @type {Number} - * @readonly - * - * @default undefined + * 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 + * ]); */ - delay : { - get : function() { - return this._delay; + positions : { + get: function() { + return this._positions; + }, + set: function(value) { + + 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(); } }, /** - * 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 + * 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} */ - stopTime : { - get : function() { - return this._stopTime; + material: { + get: function() { + return this._material; + }, + set: function(material) { + + if (this._material !== material) { + this._material = material; + makeDirty(this, MATERIAL_INDEX); + } } }, /** - * 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 - * + * Gets or sets the width of the polyline. + * @memberof Polyline.prototype * @type {Number} - * @readonly - * - * @default 1.0 */ - speedup : { - get : function() { - return this._speedup; + width: { + get: function() { + return this._width; + }, + set: function(value) { + + var width = this._width; + if (value !== width) { + this._width = value; + makeDirty(this, WIDTH_INDEX); + } } }, /** - * When true, the animation is played in reverse. - * - * @memberof ModelAnimation.prototype - * + * Gets or sets whether a line segment will be added between the first and last polyline positions. + * @memberof Polyline.prototype * @type {Boolean} - * @readonly - * - * @default false */ - reverse : { - get : function() { - return this._reverse; + loop: { + get: function() { + return this._loop; + }, + set: function(value) { + + 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); + } } }, /** - * Determines if and how the animation is looped. - * - * @memberof ModelAnimation.prototype - * - * @type {ModelAnimationLoop} - * @readonly - * - * @default {@link ModelAnimationLoop.NONE} + * Gets or sets the user-defined object returned when the polyline is picked. + * @memberof Polyline.prototype + * @type {Object} */ - loop : { + id : { get : function() { - return this._loop; + return this._id; + }, + set : function(value) { + this._id = value; + if (defined(this._pickId)) { + this._pickId.object.id = value; + } } - } - }); - - 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 + * 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 */ - length : { + distanceDisplayCondition : { get : function() { - return this._scheduledAnimations.length; + return this._distanceDisplayCondition; + }, + set : function(value) { + if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { + this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); + makeDirty(this, DISTANCE_DISPLAY_CONDITION); + } } } }); /** - * 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; - - - var animation = animations[options.name]; - - - 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 - * }); + * @private */ - ModelAnimationCollection.prototype.addAll = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + Polyline.prototype.update = function() { + var modelMatrix = Matrix4.IDENTITY; + if (defined(this._polylineCollection)) { + modelMatrix = this._polylineCollection.modelMatrix; + } - - options = clone(options); + var segmentPositionsLength = this._segments.positions.length; + var segmentLengths = this._segments.lengths; - 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)); + 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); } - return scheduledAnimations; - }; + this._modelMatrix = Matrix4.clone(modelMatrix, this._modelMatrix); - /** - * 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; + 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; + } } } - - 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. - *

    + * @private */ - 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]); + Polyline.prototype.getPickId = function(context) { + if (!defined(this._pickId)) { + this._pickId = context.createPickId({ + primitive : this, + collection : this._polylineCollection, + id : this._id + }); } + return this._pickId; }; - /** - * 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); + Polyline.prototype._clean = function() { + this._dirty = false; + var properties = this._propertiesChanged; + for ( var k = 0; k < NUMBER_OF_PROPERTIES - 1; ++k) { + properties[k] = 0; } - - 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) { - - 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; + Polyline.prototype._destroy = function() { + this._pickId = this._pickId && this._pickId.destroy(); + this._material = this._material && this._material.destroy(); + this._polylineCollection = undefined; }; - return ModelAnimationCollection; + return Polyline; }); -/*global define*/ -define('Scene/ModelMaterial',[ +define('Scene/PolylineCollection',[ + '../Core/BoundingSphere', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Cartographic', + '../Core/Color', + '../Core/ComponentDatatype', + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/DeveloperError' + '../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, - DeveloperError) { + 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 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. - *

    + * A renderable collection of polylines. + *

    + *
    + *
    + * Example polylines + *
    + *

    + * Polylines are added and removed from the collection using {@link PolylineCollection#add} + * and {@link PolylineCollection#remove}. * - * @alias ModelMaterial - * @internalConstructor + * @alias PolylineCollection + * @constructor * - * @see Model#getMaterial + * @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 ModelMaterial(model, material, id) { - this._name = material.name; - this._id = id; - this._uniformMap = model._uniformMaps[id]; - } + function PolylineCollection(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); - 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 + * 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 {String} - * @readonly + * @type {Matrix4} + * @default {@link Matrix4.IDENTITY} */ - name : { - get : function() { - return this._name; - } - }, + this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY); /** - * 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. + * 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. + *

    * - * @memberof ModelMaterial.prototype + * @type {Boolean} * - * @type {String} - * @readonly + * @default false */ - id : { + 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() { - return this._id; + removePolylines(this); + return this._polylines.length; } } }); /** - * 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. + * 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 {String} name The name of the parameter. - * @param {Object} [value] The value to assign to the parameter. + * @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. * - * @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 + * // 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 */ - ModelMaterial.prototype.setValue = function(name, value) { - - var v = this._uniformMap.values[name]; - - - v.value = v.clone(value, v.value); + 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; }; /** - * 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. + * Removes a polyline from the collection. * - * @param {String} name The name of the parameter. - * @returns {Object} The value of the parameter or undefined if the parameter does not exist. + * @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 */ - ModelMaterial.prototype.getValue = function(name) { - - var v = this._uniformMap.values[name]; - - if (!defined(v)) { - return undefined; + 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 v.value; + return false; }; - 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; - } + /** + * 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; + }; - 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; - } - } - } + /** + * 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; + }; - // 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] - }; + /** + * 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) { + + removePolylines(this); + return this._polylines[index]; + }; - result[lightBaseName + 'FallOff'] = - { - type: WebGLConstants.FLOAT_VEC2, - value: [spot.fallOffAngle, spot.fallOffExponent] - }; - break; - } - ++lightCount; - } - } + function createBatchTable(collection, context) { + if (defined(collection._batchTable)) { + collection._batchTable.destroy(); } - return result; - } - - function getNextId(dictionary, baseName, startingCount) { - var count = defaultValue(startingCount, 0); - var nextId; - do { - nextId = baseName + (count++).toString(); - } while(defined(dictionary[nextId])); + 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 + }]; - return nextId; + collection._batchTable = new BatchTable(context, attributes, collection._polylines.length); } - var techniqueCount = 0; - var vertexShaderCount = 0; - var fragmentShaderCount = 0; - var programCount = 0; - function generateTechnique(gltf, khrMaterialsCommon, lightParameters, options) { - 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'); + var scratchUpdatePolylineEncodedCartesian = new EncodedCartesian3(); + var scratchUpdatePolylineCartesian4 = new Cartesian4(); + var scratchNearFarCartesian2 = new Cartesian2(); - // Add techniques - var techniqueParameters = { - // Add matrices - modelViewMatrix: { - semantic: options.useCesiumRTCMatrixInShaders ? 'CESIUM_RTC_MODELVIEW' : 'MODELVIEW', - type: WebGLConstants.FLOAT_MAT4 - }, - projectionMatrix: { - semantic: 'PROJECTION', - type: WebGLConstants.FLOAT_MAT4 - } - }; + /** + * 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 (hasNormals) { - techniqueParameters.normalMatrix = { - semantic: 'MODELVIEWINVERSETRANSPOSE', - type: WebGLConstants.FLOAT_MAT3 - }; + if (this._polylines.length === 0) { + return; } - if (hasSkinning) { - techniqueParameters.jointMatrix = { - count: jointCount, - semantic: 'JOINTMATRIX', - type: WebGLConstants.FLOAT_MAT4 - }; - } + updateMode(this, frameState); - // 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 - }; - } - } + var context = frameState.context; + var projection = frameState.mapProjection; + var polyline; + var properties = this._propertiesChanged; - // Copy light parameters into technique parameters - if (defined(lightParameters)) { - for (var lightParamName in lightParameters) { - if (lightParameters.hasOwnProperty(lightParamName)) { - techniqueParameters[lightParamName] = lightParameters[lightParamName]; - } + 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; } - // 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'; + 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(); } } - } - - // 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 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 (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 (properties[SHOW_INDEX] || properties[WIDTH_INDEX]) { + this._batchTable.setBatchedAttribute(polyline._index, 0, new Cartesian2(polyline._width, polyline._show)); } - 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'; - } + 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); + } - fragmentLightingBlock += ' diffuseLight += ' + lightColorName + '* max(dot(normal,l), 0.) * attenuation;\n'; + if (properties[DISTANCE_DISPLAY_CONDITION]) { + var nearFarCartesian = scratchNearFarCartesian2; + nearFarCartesian.x = 0.0; + nearFarCartesian.y = Number.MAX_VALUE; - 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'; + var distanceDisplayCondition = polyline.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + nearFarCartesian.x = distanceDisplayCondition.near; + nearFarCartesian.y = distanceDisplayCondition.far; + } + + this._batchTable.setBatchedAttribute(polyline._index, 4, nearFarCartesian); } - fragmentLightingBlock += ' specularLight += ' + lightColorName + ' * specularIntensity;\n'; } + + polyline._clean(); } - fragmentLightingBlock += ' }\n'; } + polylinesToUpdate.length = 0; + this._polylinesUpdated = false; } - if (!hasAmbientLights) { - // Add an ambient light if we don't have one - fragmentLightingBlock += ' ambientLight += vec3(0.2, 0.2, 0.2);\n'; + properties = this._propertiesChanged; + for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) { + properties[k] = 0; } - 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'; - } + var modelMatrix = Matrix4.IDENTITY; + if (frameState.mode === SceneMode.SCENE3D) { + modelMatrix = this.modelMatrix; } - vertexShader += 'void main(void) {\n'; - vertexShader += vertexShaderMain; - vertexShader += '}\n'; + var pass = frameState.passes; + var useDepthTest = (frameState.morphTime !== 0.0); - 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'; - } + if (!defined(this._opaqueRS) || this._opaqueRS.depthTest.enabled !== useDepthTest) { + this._opaqueRS = RenderState.fromCache({ + depthMask : useDepthTest, + depthTest : { + enabled : useDepthTest + } + }); } - 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'; + if (!defined(this._translucentRS) || this._translucentRS.depthTest.enabled !== useDepthTest) { + this._translucentRS = RenderState.fromCache({ + blending : BlendingState.ALPHA_BLEND, + depthMask : !useDepthTest, + depthTest : { + enabled : useDepthTest } - 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'; - } + this._batchTable.update(frameState); - 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 (pass.render) { + var colorList = this._colorCommands; + createCommandLists(this, frameState, colorList, modelMatrix, true); } - 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 (pass.pick) { + var pickList = this._pickCommands; + createCommandLists(this, frameState, pickList, modelMatrix, false); } + }; - 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'; + var boundingSphereScratch = new BoundingSphere(); + var boundingSphereScratch2 = new BoundingSphere(); - // Add in light computations - fragmentShader += fragmentLightingBlock; + function createCommandLists(polylineCollection, frameState, commands, modelMatrix, renderPass) { + var context = frameState.context; + var commandList = frameState.commandList; - fragmentShader += colorCreationBlock; - fragmentShader += finalColorComputation; - fragmentShader += '}\n'; + var commandsLength = commands.length; + var commandIndex = 0; + var cloneBoundingSphere = true; - 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 - }; + var vertexArrays = polylineCollection._vertexArrays; + var debugShowBoundingVolume = polylineCollection.debugShowBoundingVolume; - // Add shaders - shaders[vertexShaderId] = { - type: WebGLConstants.VERTEX_SHADER, - uri: '', - extras: { - source: vertexShader - } - }; - shaders[fragmentShaderId] = { - type: WebGLConstants.FRAGMENT_SHADER, - uri: '', - extras: { - source: fragmentShader - } - }; + var batchTable = polylineCollection._batchTable; + var uniformCallback = batchTable.getUniformMapCallback(); - // Add program - var programAttributes = Object.keys(techniqueAttributes); - programs[programId] = { - attributes: programAttributes, - fragmentShader: fragmentShaderId, - vertexShader: vertexShaderId - }; + var length = vertexArrays.length; + for ( var m = 0; m < length; ++m) { + var va = vertexArrays[m]; + var buckets = va.buckets; + var bucketLength = buckets.length; - return techniqueId; - } + for ( var n = 0; n < bucketLength; ++n) { + var bucketLocator = buckets[n]; - function getKHRMaterialsCommonValueType(paramName, paramValue) - { - var value; + var offset = bucketLocator.offset; + var sp = renderPass ? bucketLocator.bucket.shaderProgram : bucketLocator.bucket.pickShaderProgram; - // 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; - } + var polylines = bucketLocator.bucket.polylines; + var polylineLength = polylines.length; + var currentId; + var currentMaterial; + var count = 0; + var command; - 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; + 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(); - // 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; - } - } + if (commandIndex >= commandsLength) { + command = new DrawCommand({ + owner : polylineCollection + }); + commands.push(command); + } else { + command = commands[commandIndex]; + } - function getTechniqueKey(khrMaterialsCommon) { - var techniqueKey = ''; - techniqueKey += 'technique:' + khrMaterialsCommon.technique + ';'; + ++commandIndex; - var values = khrMaterialsCommon.values; - var keys = Object.keys(values).sort(); - var keysCount = keys.length; - for (var i=0;i 0) { + if (commandIndex >= commandsLength) { + command = new DrawCommand({ + owner : polylineCollection + }); + commands.push(command); + } else { + command = commands[commandIndex]; + } - var techniques = {}; - var materials = gltf.materials; - for (var name in materials) { - if (materials.hasOwnProperty(name)) { - var material = materials[name]; - if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) { - var khrMaterialsCommon = material.extensions.KHR_materials_common; - var techniqueKey = getTechniqueKey(khrMaterialsCommon); - var technique = techniques[techniqueKey]; - if (!defined(technique)) { - technique = generateTechnique(gltf, khrMaterialsCommon, lightParameters, { - useCesiumRTCMatrixInShaders : hasCesiumRTCExtension - }); - techniques[techniqueKey] = technique; - } + ++commandIndex; - // Take advantage of the fact that we generate techniques that use the - // same parameter names as the extension values. - material.values = {}; - var values = khrMaterialsCommon.values; - for (var valueName in values) { - if (values.hasOwnProperty(valueName)) { - var value = values[valueName]; - - // Backwards compatibility for COLLADA2GLTF v1.0-draft when it encoding - // materials using KHR_materials_common with explicit type/value members - if (defined(value.value)) { - material.values[valueName] = value.value; - } else { - material.values[valueName] = value; - } - } - } + 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; - material.technique = technique; + command.uniformMap = uniformCallback(currentMaterial._uniforms); + command.count = count; + command.offset = offset; - delete material.extensions.KHR_materials_common; - } + cloneBoundingSphere = true; + + commandList.push(command); } - } - if (defined(gltf.extensions)) { - delete gltf.extensions.KHR_materials_common; + currentId = undefined; } } - return gltf; + commands.length = commandIndex; } - return modelMaterialsCommon; -}); - -/*global define*/ -define('Scene/ModelMesh',[ - '../Core/defineProperties' - ], function( - defineProperties) { - 'use strict'; - /** - * A model's mesh and its materials. - *

    - * Use {@link Model#getMesh} to create an instance. - *

    + * 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. * - * @alias ModelMesh - * @internalConstructor + * @returns {Boolean} true if this object was destroyed; otherwise, false. * - * @see Model#getMesh + * @see PolylineCollection#destroy */ - 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'; + PolylineCollection.prototype.isDestroyed = function() { + return false; + }; /** - * 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. - *

    + * 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. * - * @alias ModelNode - * @internalConstructor + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example - * var node = model.getNode('LOD3sp'); - * node.matrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(5.0, 1.0, 1.0), node.matrix); + * polylines = polylines && polylines.destroy(); * - * @see Model#getNode + * @see PolylineCollection#isDestroyed */ - function ModelNode(model, node, runtimeNode, id, matrix) { - this._model = model; - this._runtimeNode = runtimeNode; - this._name = node.name; - this._id = id; + PolylineCollection.prototype.destroy = function() { + destroyVertexArrays(this); + releaseShaders(this); + destroyPolylines(this); + this._batchTable = this._batchTable && this._batchTable.destroy(); + return destroyObject(this); + }; - /** - * @private - */ - this.useMatrix = false; + 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--; + } + } - this._show = true; - this._matrix = Matrix4.clone(matrix); + return usageChanged; } - 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; - } - }, + var emptyVertexBuffer = [0.0, 0.0, 0.0]; - /** - * 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; - } - }, + function createVertexArrays(collection, context, projection) { + collection._createVertexArray = false; + releaseShaders(collection); + destroyVertexArrays(collection); + sortPolylinesIntoBuckets(collection); - /** - * 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; - } - } - }, + //stores all of the individual indices arrays. + var totalIndices = [[]]; + var indices = totalIndices[0]; - /** - * 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 batchTable = collection._batchTable; - var model = this._model; - model._cesiumAnimationsDirty = true; - this._runtimeNode.dirtyNumber = model._maxDirtyNumber; + //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; } } - }); - - /** - * @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/joinUrls', - '../Core/loadArrayBuffer', - '../Core/loadCRN', - '../Core/loadImage', - '../Core/loadImageFromTypedArray', - '../Core/loadKTX', - '../Core/loadText', - '../Core/Math', - '../Core/Matrix2', - '../Core/Matrix3', - '../Core/Matrix4', - '../Core/PixelFormat', - '../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', - './Axis', - './BlendingState', - './ColorBlendMode', - './getAttributeOrUniformBySemantic', - './getBinaryAccessor', - './HeightReference', - './JobType', - './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, - joinUrls, - loadArrayBuffer, - loadCRN, - loadImage, - loadImageFromTypedArray, - loadKTX, - loadText, - CesiumMath, - Matrix2, - Matrix3, - Matrix4, - PixelFormat, - PrimitiveType, - Quaternion, - Queue, - RuntimeError, - Transforms, - WebGLConstants, - Buffer, - BufferUsage, - DrawCommand, - Pass, - RenderState, - Sampler, - ShaderProgram, - ShaderSource, - Texture, - TextureMinificationFilter, - TextureWrap, - VertexArray, - gltfDefaults, - Uri, - when, - Axis, - BlendingState, - ColorBlendMode, - getAttributeOrUniformBySemantic, - getBinaryAccessor, - HeightReference, - JobType, - ModelAnimationCache, - ModelAnimationCollection, - ModelMaterial, - modelMaterialsCommon, - ModelMesh, - ModelNode, - SceneMode, - ShadowMode) { - 'use strict'; + if (totalLength > 0) { + var mode = collection._mode; - // 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 positionArray = new Float32Array(6 * totalLength * 3); + var texCoordExpandAndBatchIndexArray = new Float32Array(totalLength * 4); + var position3DArray; - var boundingSphereCartesian3Scratch = new Cartesian3(); + 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); - var ModelState = { - NEEDS_LOAD : 0, - LOADING : 1, - LOADED : 2, // Renderable, but textures can still be pending when incrementallyLoadTextures is true. - FAILED : 3 - }; + if (mode === SceneMode.MORPHING) { + if (!defined(position3DArray)) { + position3DArray = new Float32Array(6 * totalLength * 3); + } + bucket.writeForMorph(position3DArray, positionIndex); + } - // 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'; + var bucketLength = bucket.lengthOfPositions; + positionIndex += 6 * bucketLength * 3; + colorIndex += bucketLength * 4; + texCoordExpandAndBatchIndexIndex += bucketLength * 4; + offset = bucket.updateIndices(totalIndices, vertexBufferOffset, vertexArrayBuckets, offset); + } + } - function LoadResources() { - this.vertexBuffersToCreate = new Queue(); - this.indexBuffersToCreate = new Queue(); - this.buffers = {}; - this.pendingBufferLoads = 0; + var positionBufferUsage = collection._positionBufferUsage.bufferUsage; + var texCoordExpandAndBatchIndexBufferUsage = BufferUsage.STATIC_DRAW; - this.programsToCreate = new Queue(); - this.shaders = {}; - this.pendingShaderLoads = 0; + 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 + }); - this.texturesToCreate = new Queue(); - this.pendingTextureLoads = 0; + var positionSizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT; + var texCoordExpandAndBatchIndexSizeInBytes = 4 * Float32Array.BYTES_PER_ELEMENT; - this.texturesToCreateFromBufferView = new Queue(); - this.pendingBufferViewToImage = 0; + var vbo = 0; + var numberOfIndicesArrays = totalIndices.length; + for ( var k = 0; k < numberOfIndicesArrays; ++k) { + indices = totalIndices[k]; - this.createSamplers = true; - this.createSkins = true; - this.createRuntimeAnimations = true; - this.createVertexArrays = true; - this.createRenderStates = true; - this.createUniformMaps = true; - this.createRuntimeNodes = true; + 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 + }); - this.skinnedNodesIds = []; - } + vbo += vertexBufferOffset[k]; - LoadResources.prototype.getBuffer = function(bufferView) { - return getSubarray(this.buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength); - }; + 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; - LoadResources.prototype.finishedPendingBufferLoads = function() { - return (this.pendingBufferLoads === 0); - }; + 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 + }]; - LoadResources.prototype.finishedBuffersCreation = function() { - return ((this.pendingBufferLoads === 0) && - (this.vertexBuffersToCreate.length === 0) && - (this.indexBuffersToCreate.length === 0)); - }; + var buffer3D; + var bufferProperty3D; + var buffer2D; + var bufferProperty2D; - LoadResources.prototype.finishedProgramCreation = function() { - return ((this.pendingShaderLoads === 0) && (this.programsToCreate.length === 0)); - }; + 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'; + } - LoadResources.prototype.finishedTextureCreation = function() { - var finishedPendingLoads = (this.pendingTextureLoads === 0); - var finishedResourceCreation = - (this.texturesToCreate.length === 0) && - (this.texturesToCreateFromBufferView.length === 0); + 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; - return finishedPendingLoads && finishedResourceCreation; - }; + var va = new VertexArray({ + context : context, + attributes : attributes, + indexBuffer : indexBuffer + }); + collection._vertexArrays.push({ + va : va, + buckets : vertexArrayBuckets[k] + }); + } + } + } + } - LoadResources.prototype.finishedEverythingButTextureCreation = function() { - var finishedPendingLoads = - (this.pendingBufferLoads === 0) && - (this.pendingShaderLoads === 0); - var finishedResourceCreation = - (this.vertexBuffersToCreate.length === 0) && - (this.indexBuffersToCreate.length === 0) && - (this.programsToCreate.length === 0) && - (this.pendingBufferViewToImage === 0); + var scratchUniformArray = []; + function createMaterialId(material) { + var uniforms = Material._uniformList[material.type]; + var length = uniforms.length; + scratchUniformArray.length = 2.0 * length; - return finishedPendingLoads && finishedResourceCreation; - }; + 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; + } - LoadResources.prototype.finished = function() { - return this.finishedTextureCreation() && this.finishedEverythingButTextureCreation(); - }; + return material.type + ':' + JSON.stringify(scratchUniformArray); + } - /////////////////////////////////////////////////////////////////////////// + function sortPolylinesIntoBuckets(collection) { + var mode = collection._mode; + var modelMatrix = collection._modelMatrix; - function setCachedGltf(model, cachedGltf) { - model._cachedGltf = cachedGltf; - model._animationIds = getAnimationIds(cachedGltf); + 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); + } + } } - // 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; + 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; + } } - defineProperties(CachedGltf.prototype, { - gltf : { - set : function(value) { - this._gltf = modelMaterialsCommon(gltfDefaults(value)); - }, + function removePolylines(collection) { + if (collection._polylinesRemoved) { + collection._polylinesRemoved = false; - get : function() { - return this._gltf; - } - }, + var polylines = []; - bgltf : { - get : function() { - return this._bgltf; + 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); + } } - } - }); - - 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); - } + collection._polylines = polylines; } - 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); + 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(); } } } - - 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; + function destroyVertexArrays(collection) { + var length = collection._vertexArrays.length; + for ( var t = 0; t < length; ++t) { + collection._vertexArrays[t].va.destroy(); + } + collection._vertexArrays.length = 0; + } - if (defined(gltf)) { - if (gltf instanceof ArrayBuffer) { - gltf = new Uint8Array(gltf); - } + PolylineCollection.prototype._updatePolyline = function(polyline, propertyChanged) { + this._polylinesUpdated = true; + this._polylinesToUpdate.push(polyline); + ++this._propertiesChanged[propertyChanged]; + }; - if (gltf instanceof Uint8Array) { - // Binary glTF - var result = parseBinaryGltfHeader(gltf); + 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(); + } + } + } - // KHR_binary_glTF is from the beginning of the binary section - if (result.binaryOffset !== 0) { - gltf = gltf.subarray(result.binaryOffset); - } + function VertexArrayBucketLocator(count, offset, bucket) { + this.count = count; + this.offset = offset; + this.bucket = bucket; + } - cachedGltf = new CachedGltf({ - gltf : result.glTF, - bgltf : gltf, - ready : true - }); - } else { - // Normal glTF (JSON) - cachedGltf = new CachedGltf({ - gltf : options.gltf, - ready : true - }); - } + 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; + } - cachedGltf.count = 1; + PolylineBucket.prototype.addPolyline = function(p) { + var polylines = this.polylines; + polylines.push(p); + p._actualLength = this.getPolylinePositionsLength(p); + this.lengthOfPositions += p._actualLength; + p._bucket = this; + }; - if (defined(cacheKey)) { - gltfCache[cacheKey] = cachedGltf; - } - } + PolylineBucket.prototype.updateShader = function(context, batchTable) { + if (defined(this.shaderProgram)) { + return; } - setCachedGltf(this, cachedGltf); - this._basePath = defaultValue(options.basePath, ''); - var baseUri = getBaseUri(document.location.href); - this._baseUri = joinUrls(baseUri, this._basePath); + var defines = ['DISTANCE_DISPLAY_CONDITION']; - /** - * Determines if the model primitive will be shown. - * - * @type {Boolean} - * - * @default true - */ - this.show = defaultValue(options.show, true); + var fs = new ShaderSource({ + sources : [this.material.shaderSource, PolylineFS] + }); - /** - * The silhouette color. - * - * @type {Color} - * - * @default Color.RED - */ - this.silhouetteColor = defaultValue(options.silhouetteColor, Color.RED); - this._silhouetteColor = new Color(); - this._silhouetteColorPreviousAlpha = 1.0; - this._normalAttributeName = undefined; + // Check for use of v_polylineAngle in material shader + if (this.material.shaderSource.search(/varying\s+float\s+v_polylineAngle;/g) !== -1) { + defines.push('POLYLINE_DASH'); + } - /** - * The size of the silhouette in pixels. - * - * @type {Number} - * - * @default 0.0 - */ - this.silhouetteSize = defaultValue(options.silhouetteSize, 0.0); + var vsSource = batchTable.getVertexShaderCallback()(PolylineVS); + var vs = new ShaderSource({ + defines : defines, + sources : [PolylineCommon, vsSource] + }); - /** - * 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; + var fsPick = new ShaderSource({ + sources : fs.sources, + pickColorQualifier : 'varying' + }); - /** - * 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; + this.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); - /** - * 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; + this.pickShaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vs, + fragmentShaderSource : fsPick, + attributeLocations : attributeLocations + }); + }; - /** - * 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; + function intersectsIDL(polyline) { + return Cartesian3.dot(Cartesian3.UNIT_X, polyline._boundingVolume.center) < 0 || + polyline._boundingVolume.intersectPlane(Plane.ORIGIN_ZX_PLANE) === Intersect.INTERSECTING; + } - /** - * User-defined object returned when the model is picked. - * - * @type Object - * - * @default undefined - * - * @see Scene#pick - */ - this.id = options.id; - this._id = options.id; + PolylineBucket.prototype.getPolylinePositionsLength = function(polyline) { + var length; + if (this.mode === SceneMode.SCENE3D || !intersectsIDL(polyline)) { + length = polyline._actualPositions.length; + return length * 4.0 - 4.0; + } - /** - * 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); + 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; } - /** - * Used for picking primitives that wrap a model. - * - * @private - */ - this.pickPrimitive = options.pickPrimitive; + return count; + }; - this._allowPicking = defaultValue(options.allowPicking, true); + var scratchWritePosition = new Cartesian3(); + var scratchWritePrevPosition = new Cartesian3(); + var scratchWriteNextPosition = new Cartesian3(); + var scratchWriteVector = new Cartesian3(); + var scratchPickColorCartesian = new Cartesian4(); + var scratchWidthShowCartesian = new Cartesian2(); - this._ready = false; - this._readyPromise = when.defer(); + PolylineBucket.prototype.write = function(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection) { + var mode = this.mode; + var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI; - /** - * The currently playing glTF animations. - * - * @type {ModelAnimationCollection} - */ - this.activeAnimations = new ModelAnimationCollection(this); + 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; - this._defaultTexture = undefined; - this._incrementallyLoadTextures = defaultValue(options.incrementallyLoadTextures, true); - this._asynchronous = defaultValue(options.asynchronous, true); + var pickColor = polyline.getPickId(context).color; - /** - * 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; + var segmentIndex = 0; + var count = 0; + var position; - /** - * 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(); - this._colorPreviousAlpha = 1.0; + 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]; + } - /** - * Defines how the color blends with the model. - * - * @type {ColorBlendMode} - * - * @default ColorBlendMode.HIGHLIGHT - */ - this.colorBlendMode = defaultValue(options.colorBlendMode, ColorBlendMode.HIGHLIGHT); + Cartesian3.clone(position, scratchWritePrevPosition); + Cartesian3.clone(positions[j], scratchWritePosition); - /** - * 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); + 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]; + } - /** - * 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; + Cartesian3.clone(position, scratchWriteNextPosition); - /** - * 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; + var segmentLength = lengths[segmentIndex]; + if (j === count + segmentLength) { + count += segmentLength; + ++segmentIndex; + } - this._distanceDisplayCondition = options.distanceDisplayCondition; + var segmentStart = j - count === 0; + var segmentEnd = j === count + lengths[segmentIndex] - 1; - // 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); - this._upAxis = defaultValue(options.upAxis, Axis.Y); + if (mode === SceneMode.SCENE2D) { + scratchWritePrevPosition.z = 0.0; + scratchWritePosition.z = 0.0; + scratchWriteNextPosition.z = 0.0; + } - /** - * @private - * @readonly - */ - this.cull = defaultValue(options.cull, true); + 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); + } - 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; + if ((scratchWritePosition.x < 0.0 && scratchWriteNextPosition.x > 0.0) || + (scratchWritePosition.x > 0.0 && scratchWriteNextPosition.x < 0.0)) { + Cartesian3.clone(scratchWritePosition, scratchWriteNextPosition); + } + } + } - this._mode = undefined; + var startK = (segmentStart) ? 2 : 0; + var endK = (segmentEnd) ? 2 : 4; - 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 + for (var k = startK; k < endK; ++k) { + EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex); + EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6); + EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12); - 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 - }; + 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; - 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; + positionIndex += 6 * 3; + texCoordExpandAndBatchIndexIndex += 4; + } + } - this._cachedVertexMemorySizeInBytes = 0; - this._cachedTextureMemorySizeInBytes = 0; - this._vertexMemorySizeInBytes = 0; - this._textureMemorySizeInBytes = 0; - this._trianglesLength = 0; + 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); - this._nodeCommands = []; - this._pickIds = []; + var widthShowCartesian = scratchWidthShowCartesian; + widthShowCartesian.x = width; + widthShowCartesian.y = show ? 1.0 : 0.0; - // CESIUM_RTC extension - this._rtcCenter = undefined; // reference to either 3D or 2D - this._rtcCenterEye = undefined; // in eye coordinates - this._rtcCenter3D = undefined; // in world coordinates - this._rtcCenter2D = undefined; // in projected world coordinates - } + 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); - 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; - } - }, + var nearFarCartesian = scratchNearFarCartesian2; + nearFarCartesian.x = 0.0; + nearFarCartesian.y = Number.MAX_VALUE; - /** - * 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; + var distanceDisplayCondition = polyline.distanceDisplayCondition; + if (defined(distanceDisplayCondition)) { + nearFarCartesian.x = distanceDisplayCondition.near; + nearFarCartesian.y = distanceDisplayCondition.far; } - }, - /** - * 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; - } - }, + batchTable.setBatchedAttribute(polylineBatchIndex, 0, widthShowCartesian); + batchTable.setBatchedAttribute(polylineBatchIndex, 1, colorCartesian); - /** - * 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; + if (batchTable.attributes.length > 2) { + batchTable.setBatchedAttribute(polylineBatchIndex, 2, high); + batchTable.setBatchedAttribute(polylineBatchIndex, 3, low); + batchTable.setBatchedAttribute(polylineBatchIndex, 4, nearFarCartesian); } - }, + } + }; - /** - * 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() { - - var modelMatrix = this.modelMatrix; - if ((this.heightReference !== HeightReference.NONE) && this._clampedModelMatrix) { - modelMatrix = this._clampedModelMatrix; + 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]; } - var nonUniformScale = Matrix4.getScale(modelMatrix, boundingSphereCartesian3Scratch); - var scale = defined(this.maximumScale) ? Math.min(this.maximumScale, this.scale) : this.scale; - Cartesian3.multiplyByScalar(nonUniformScale, scale, nonUniformScale); + prevPosition = Matrix4.multiplyByPoint(modelMatrix, prevPosition, morphPrevPositionScratch); - var scaledBoundingSphere = this._scaledBoundingSphere; - scaledBoundingSphere.center = Cartesian3.multiplyComponents(this._boundingSphere.center, nonUniformScale, scaledBoundingSphere.center); - scaledBoundingSphere.radius = Cartesian3.maximumComponent(nonUniformScale) * this._initialRadius; + var position = Matrix4.multiplyByPoint(modelMatrix, positions[j], morphPositionScratch); - if (defined(this._rtcCenter)) { - Cartesian3.add(this._rtcCenter, scaledBoundingSphere.center, scaledBoundingSphere.center); + 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]; } - return scaledBoundingSphere; - } - }, + nextPosition = Matrix4.multiplyByPoint(modelMatrix, nextPosition, morphNextPositionScratch); - /** - * 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; - } - }, + var segmentLength = lengths[segmentIndex]; + if (j === count + segmentLength) { + count += segmentLength; + ++segmentIndex; + } - /** - * 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; - } - }, + var segmentStart = j - count === 0; + var segmentEnd = j === count + lengths[segmentIndex] - 1; - /** - * 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; - } - }, + var startK = (segmentStart) ? 2 : 0; + var endK = (segmentEnd) ? 2 : 4; - /** - * 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; - } - }, + for (var k = startK; k < endK; ++k) { + EncodedCartesian3.writeElements(position, positionArray, positionIndex); + EncodedCartesian3.writeElements(prevPosition, positionArray, positionIndex + 6); + EncodedCartesian3.writeElements(nextPosition, positionArray, positionIndex + 12); - /** - * 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; + positionIndex += 6 * 3; + } } - }, + } + }; - /** - * Return the number of pending texture loads. - * - * @memberof Model.prototype - * - * @type {Number} - * @readonly - */ - pendingTextureLoads : { - get : function() { - return defined(this._loadResources) ? this._loadResources.pendingTextureLoads : 0; - } - }, + var scratchSegmentLengths = new Array(1); - /** - * Returns true if the model was transformed this frame - * - * @memberof Model.prototype - * - * @type {Boolean} - * @readonly - * - * @private - */ - dirty : { - get : function() { - return this._dirty; - } - }, + 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) { - /** - * 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) { - this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); - } - }, + var polyline = polylines[i]; + polyline._locatorBuckets = []; - /** - * Gets the model's up-axis. - * By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up. - * - * @memberof Model.prototype - * - * @type {Number} - * @default Axis.Y - * @readonly - * - * @private - */ - upAxis : { - get : function() { - return this._upAxis; + 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; } - }, - /** - * Gets the model's triangle count. - * - * @private - */ - trianglesLength : { - get : function() { - return this._trianglesLength; - } - }, + 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) { + 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]; + } - /** - * Gets the model's vertex memory in bytes. This includes all vertex and index buffers. - * - * @private - */ - vertexMemorySizeInBytes : { - get : function() { - return this._vertexMemorySizeInBytes; - } - }, + indices.push(indicesCount, indicesCount + 2, indicesCount + 1); + indices.push(indicesCount + 1, indicesCount + 2, indicesCount + 3); - /** - * Gets the model's texture memory in bytes. - * - * @private - */ - textureMemorySizeInBytes : { - get : function() { - return this._textureMemorySizeInBytes; - } - }, + segmentIndexCount += 6; + count += 6; + offset += 6; + indicesCount += 4; + } + } - /** - * Gets the model's cached vertex memory in bytes. This includes all vertex and index buffers. - * - * @private - */ - cachedVertexMemorySizeInBytes : { - get : function() { - return this._cachedVertexMemorySizeInBytes; + polyline._locatorBuckets.push({ + locator : bucketLocator, + count : segmentIndexCount + }); + + if (indicesCount + 4 > CesiumMath.SIXTY_FOUR_KILOBYTES) { + 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; + }; - /** - * Gets the model's cached texture memory in bytes. - * - * @private - */ - cachedTextureMemorySizeInBytes : { - get : function() { - return this._cachedTextureMemorySizeInBytes; + 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 sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var scratchSegments = { + positions : undefined, + lengths : undefined + }; + var scratchLengths = new Array(1); + var pscratch = new Cartesian3(); + var scratchCartographic = new Cartographic(); - function silhouetteSupported(context) { - return context.stencilBuffer; - } + PolylineBucket.prototype.getSegments = function(polyline, projection) { + var positions = polyline._actualPositions; - /** - * 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); - }; + if (this.mode === SceneMode.SCENE3D) { + scratchLengths[0] = positions.length; + scratchSegments.positions = positions; + scratchSegments.lengths = scratchLengths; + return scratchSegments; + } - /** - * 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); - } + if (intersectsIDL(polyline)) { + positions = polyline._segments.positions; + } - function containsGltfMagic(uint8Array) { - var magic = getMagic(uint8Array); - return magic === 'glTF'; - } + var ellipsoid = projection.ellipsoid; + var newPositions = []; + var modelMatrix = this.modelMatrix; + var length = positions.length; + var position; + var p = pscratch; - function parseBinaryGltfHeader(uint8Array) { - - var view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); - var byteOffset = 0; + for ( var n = 0; n < length; ++n) { + position = positions[n]; + p = Matrix4.multiplyByPoint(modelMatrix, position, p); + newPositions.push(projection.project(ellipsoid.cartesianToCartographic(p, scratchCartographic))); + } - byteOffset += sizeOfUint32; // Skip magic number + 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); + } - byteOffset += sizeOfUint32; + scratchSegments.positions = newPositions; + scratchSegments.lengths = polyline._segments.lengths; + return scratchSegments; + }; - byteOffset += sizeOfUint32; // Skip length + var scratchPositionsArray; - var sceneLength = view.getUint32(byteOffset, true); - byteOffset += sizeOfUint32 + sizeOfUint32; // Skip sceneFormat + PolylineBucket.prototype.writeUpdate = function(index, polyline, positionBuffer, projection) { + var mode = this.mode; + var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI; - var sceneOffset = byteOffset; - var binOffset = sceneOffset + sceneLength; + var positionsLength = polyline._actualLength; + if (positionsLength) { + index += this.getPolylineStartIndex(polyline); - var json = getStringFromTypedArray(uint8Array, sceneOffset, sceneLength); - return { - glTF: JSON.parse(json), - binaryOffset: binOffset - }; - } + var positionArray = scratchPositionsArray; + var positionsArrayLength = 6 * positionsLength * 3; - /** - *

    - * 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) { - - 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)); + if (!defined(positionArray) || positionArray.length < positionsArrayLength) { + positionArray = scratchPositionsArray = new Float32Array(positionsArrayLength); + } else if (positionArray.length > positionsArrayLength) { + positionArray = new Float32Array(positionArray.buffer, 0, positionsArrayLength); + } - options = clone(options); - options.basePath = getBaseUri(url, true); - options.cacheKey = cacheKey; - var model = new Model(options); + var segments = this.getSegments(polyline, projection); + var positions = segments.positions; + var lengths = segments.lengths; - options.headers = defined(options.headers) ? clone(options.headers) : {}; - if (!defined(options.headers.Accept)) { - options.headers.Accept = defaultModelAccept; - } + var positionIndex = 0; + var segmentIndex = 0; + var count = 0; + var position; - 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; + 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]; + } - 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); + 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); } - cachedGltf.makeReady(result.glTF, array); } else { - // Load text (JSON) glTF - var json = getStringFromTypedArray(array); - cachedGltf.makeReady(JSON.parse(json)); + position = positions[i + 1]; } - }).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; + 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; +}); + +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'; + /** - * For the unit tests to verify model caching. - * + * 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 */ - Model._gltfCache = gltfCache; + function ScaledPositionProperty(value) { + this._definitionChanged = new Event(); + this._value = undefined; + this._removeSubscription = undefined; + this.setValue(value); + } - function getRuntime(model, runtimeName, name) { + 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) { - return (model._runtime[runtimeName])[name]; - } + if (!defined(this._value)) { + return undefined; + } - /** - * 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; + result = this._value.getValueInReferenceFrame(time, referenceFrame, result); + return defined(result) ? Ellipsoid.WGS84.scaleToGeodeticSurface(result, result) : 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); + ScaledPositionProperty.prototype.equals = function(other) { + return this === other || (other instanceof ScaledPositionProperty && this._value === other._value); }; - /** - * 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); + ScaledPositionProperty.prototype._raiseDefinitionChanged = function() { + this._definitionChanged.raiseEvent(this); }; - var aMinScratch = new Cartesian3(); - var aMaxScratch = new Cartesian3(); + return ScaledPositionProperty; +}); - 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 - }; +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 computeBoundingSphere(model, gltf) { - var gltfNodes = gltf.nodes; - var gltfMeshes = gltf.meshes; - var rootNodes = gltf.scenes[gltf.scene].nodes; - var rootNodesLength = rootNodes.length; + 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 nodeStack = []; + var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop); - 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); + //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; - for (var i = 0; i < rootNodesLength; ++i) { - var n = gltfNodes[rootNodes[i]]; - n._transformToRoot = getTransform(n); - nodeStack.push(n); + 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; + } + } - while (nodeStack.length > 0) { - n = nodeStack.pop(); - var transformToRoot = n._transformToRoot; + if (t < (len - 1)) { + if (maximumStep > 0 && !sampling) { + var next = times[t + 1]; + var secondsUntilNext = JulianDate.secondsDifference(next, current); + sampling = secondsUntilNext > maximumStep; - 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); - } - } - } + if (sampling) { + sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep); + sampleStepsTaken = 0; + sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2); + sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1); } } - 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); + if (sampling && sampleStepsTaken < sampleStepsToTake) { + current = JulianDate.addSeconds(current, sampleStepSize, new JulianDate()); + sampleStepsTaken++; + continue; } - delete n._transformToRoot; } + sampling = false; + t++; + current = times[t]; } - var boundingSphere = BoundingSphere.fromCornerPoints(min, max); - if (model._upAxis === Axis.Y) { - BoundingSphere.transformWithoutScale(boundingSphere, Axis.Y_UP_TO_Z_UP, boundingSphere); - } else if (model._upAxis === Axis.X) { - BoundingSphere.transformWithoutScale(boundingSphere, Axis.X_UP_TO_Z_UP, boundingSphere); + //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 boundingSphere; - } - - /////////////////////////////////////////////////////////////////////////// - function getFailedLoadFunction(model, type, path) { - return function() { - model._state = ModelState.FAILED; - model._readyPromise.reject(new RuntimeError('Failed to load ' + type + ': ' + path)); - }; + return r; } - function bufferLoad(model, id) { - return function(arrayBuffer) { - var loadResources = model._loadResources; - loadResources.buffers[id] = new Uint8Array(arrayBuffer); - --loadResources.pendingBufferLoads; - }; + 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 parseBuffers(model) { - var buffers = model.gltf.buffers; - for (var id in buffers) { - if (buffers.hasOwnProperty(id)) { - var buffer = buffers[id]; + function subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) { + subSampleIntervalPropertyScratch.start = start; + subSampleIntervalPropertyScratch.stop = stop; - // 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; + 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()); + } } - else if (buffer.type === 'arraybuffer') { - ++model._loadResources.pendingBufferLoads; - var bufferPath = joinUrls(model._baseUri, buffer.uri); - loadArrayBuffer(bufferPath).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); + var tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]); + if (defined(tmp)) { + result[index] = tmp; + index++; } } } + return index; } - function parseBufferViews(model) { - var bufferViews = model.gltf.bufferViews; - var id; - - var vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate; - - // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below. - for (id in bufferViews) { - if (bufferViews.hasOwnProperty(id)) { - if (bufferViews[id].target === WebGLConstants.ARRAY_BUFFER) { - vertexBuffersToCreate.enqueue(id); - } - } + 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; + } - var indexBuffersToCreate = model._loadResources.indexBuffersToCreate; - var indexBufferIds = {}; + function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) { + subSampleCompositePropertyScratch.start = start; + subSampleCompositePropertyScratch.stop = stop; - // 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 (id in accessors) { - if (accessors.hasOwnProperty(id)) { - var accessor = accessors[id]; - var bufferViewId = accessor.bufferView; - var bufferView = bufferViews[bufferViewId]; + 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; - if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) { - indexBufferIds[bufferViewId] = true; - indexBuffersToCreate.enqueue({ - id : bufferViewId, - // 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. - componentType : accessor.componentType - }); + 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 shaderLoad(model, type, id) { - return function(source) { - var loadResources = model._loadResources; - loadResources.shaders[id] = { - source : source, - type : type, - bufferView : undefined - }; - --loadResources.pendingShaderLoads; - }; - } + function reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, index, result) { + //Unwrap any references until we have the actual property. + while (property instanceof ReferenceProperty) { + property = property.resolvedProperty; + } - 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 shaderPath = joinUrls(model._baseUri, shader.uri); - loadText(shaderPath).then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); - } - } + if (property instanceof SampledPositionProperty) { + var times = property._property._times; + index = subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, index, result); + } else if (property instanceof CompositePositionProperty) { + index = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result); + } else if (property instanceof TimeIntervalCollectionPositionProperty) { + index = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result); + } else if (property instanceof ConstantPositionProperty || + (property instanceof ScaledPositionProperty && Property.isConstant(property))) { + 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 parsePrograms(model) { - var programs = model.gltf.programs; - for (var id in programs) { - if (programs.hasOwnProperty(id)) { - model._loadResources.programsToCreate.enqueue(id); - } + 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; } - function imageLoad(model, id) { - return function(image) { - var loadResources = model._loadResources; - --loadResources.pendingTextureLoads; - loadResources.texturesToCreate.enqueue({ - id : id, - image : image, - bufferView : image.bufferView, - width : image.width, - height : image.height, - internalFormat : image.internalFormat - }); - }; + 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); } - var ktxRegex = /(^data:image\/ktx)|(\.ktx$)/i; - var crnRegex = /(^data:image\/crn)|(\.crn$)/i; + 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); + } + }; - function parseTextures(model, context) { - 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]; - var extras = gltfImage.extras; - - var binary = undefined; - var uri = undefined; - - // First check for a compressed texture - if (defined(extras) && defined(extras.compressedImage3DTiles)) { - var crunch = extras.compressedImage3DTiles.crunch; - var s3tc = extras.compressedImage3DTiles.s3tc; - var pvrtc = extras.compressedImage3DTiles.pvrtc1; - var etc1 = extras.compressedImage3DTiles.etc1; - - if (context.s3tc && defined(crunch)) { - if (defined(crunch.extensions)&& defined(crunch.extensions.KHR_binary_glTF)) { - binary = crunch.extensions.KHR_binary_glTF; - } else { - uri = crunch.uri; - } - } else if (context.s3tc && defined(s3tc)) { - if (defined(s3tc.extensions)&& defined(s3tc.extensions.KHR_binary_glTF)) { - binary = s3tc.extensions.KHR_binary_glTF; - } else { - uri = s3tc.uri; - } - } else if (context.pvrtc && defined(pvrtc)) { - if (defined(pvrtc.extensions)&& defined(pvrtc.extensions.KHR_binary_glTF)) { - binary = pvrtc.extensions.KHR_binary_glTF; - } else { - uri = pvrtc.uri; - } - } else if (context.etc1 && defined(etc1)) { - if (defined(etc1.extensions)&& defined(etc1.extensions.KHR_binary_glTF)) { - binary = etc1.extensions.KHR_binary_glTF; - } else { - uri = etc1.uri; - } - } - } + PolylineUpdater.prototype.updateObject = function(time, item) { + var entity = item.entity; + var pathGraphics = entity._path; + var positionProperty = entity._position; - // No compressed texture, so image references either uri (external or base64-encoded) or bufferView - if (!defined(binary) && !defined(uri)) { - if (defined(gltfImage.extensions) && defined(gltfImage.extensions.KHR_binary_glTF)) { - binary = gltfImage.extensions.KHR_binary_glTF; - } else { - uri = new Uri(gltfImage.uri); - } + 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()); } - // Image references either uri (external or base64-encoded) or bufferView - if (defined(binary)) { - model._loadResources.texturesToCreateFromBufferView.enqueue({ - id : id, - image : undefined, - bufferView : binary.bufferView, - mimeType : binary.mimeType - }); - } else { - ++model._loadResources.pendingTextureLoads; - var imagePath = joinUrls(model._baseUri, gltfImage.uri); + if (hasAvailability) { + var start = availability.start; + var stop = availability.stop; - var promise; - if (ktxRegex.test(imagePath)) { - promise = loadKTX(imagePath); - } else if (crnRegex.test(imagePath)) { - promise = loadCRN(imagePath); - } else { - promise = loadImage(imagePath); + if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) { + sampleStart = start; + } + + if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) { + sampleStop = stop; } - promise.then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath)); } + show = JulianDate.lessThan(sampleStart, sampleStop); } } - } - var nodeTranslationScratch = new Cartesian3(); - var nodeQuaternionScratch = new Quaternion(); - var nodeScaleScratch = new Cartesian3(); + 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; + } - function getTransform(node) { - if (defined(node.matrix)) { - return Matrix4.fromArray(node.matrix); + 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; } - return Matrix4.fromTranslationQuaternionRotationScale( - Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch), - Quaternion.unpack(node.rotation, 0, nodeQuaternionScratch), - Cartesian3.fromArray(node.scale, 0 , nodeScaleScratch)); - } + var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution); - function parseNodes(model) { - var runtimeNodes = {}; - var runtimeNodesByName = {}; - var skinnedNodes = []; + 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); + }; - var skinnedNodesIds = model._loadResources.skinnedNodesIds; - var nodes = model.gltf.nodes; + 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; + } + }; - for (var id in nodes) { - if (nodes.hasOwnProperty(id)) { - var node = nodes[id]; + 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) { + + entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this); + + this._scene = scene; + this._updaters = {}; + this._entityCollection = entityCollection; + this._items = new AssociativeArray(); - var runtimeNode = { - // Animation targets - matrix : undefined, - translation : undefined, - rotation : undefined, - scale : undefined, + this._onCollectionChanged(entityCollection, entityCollection.values, [], []); + } - // Per-node show inherited from parent - computedShow : true, + /** + * 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) { + + var updaters = this._updaters; + for ( var key in updaters) { + if (updaters.hasOwnProperty(key)) { + updaters[key].update(time); + } + } - // Computed transforms - transformToRoot : new Matrix4(), - computedMatrix : new Matrix4(), - dirtyNumber : 0, // The frame this node was made dirty by an animation; for graph traversal + 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; - // Rendering - commands : [], // empty for transform, light, and camera nodes + var lastUpdater = item.updater; - // 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 + var frameToVisualize = ReferenceFrame.FIXED; + if (this._scene.mode === SceneMode.SCENE3D) { + frameToVisualize = positionProperty.referenceFrame; + } - // Joint node - jointName : node.jointName, // undefined when node is not a joint + var currentUpdater = this._updaters[frameToVisualize]; - // Graph pointers - children : [], // empty for leaf nodes - parents : [], // empty for root nodes + if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) { + currentUpdater.updateObject(time, item); + continue; + } - // Publicly-accessible ModelNode instance to modify animation targets - publicNode : undefined - }; - runtimeNode.publicNode = new ModelNode(model, node, runtimeNode, id, getTransform(node)); + if (defined(lastUpdater)) { + lastUpdater.removeObject(item); + } - runtimeNodes[id] = runtimeNode; - runtimeNodesByName[node.name] = runtimeNode; + if (!defined(currentUpdater)) { + currentUpdater = new PolylineUpdater(this._scene, frameToVisualize); + currentUpdater.update(time); + this._updaters[frameToVisualize] = currentUpdater; + } - if (defined(node.skin)) { - skinnedNodesIds.push(id); - skinnedNodes.push(runtimeNode); - } + item.updater = currentUpdater; + if (defined(currentUpdater)) { + currentUpdater.updateObject(time, item); } } + return true; + }; - 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; + /** + * 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; + }; - 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 - }; + /** + * Removes and destroys all primitives created by this instance. + */ + PathVisualizer.prototype.destroy = function() { + this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this); - var material = materials[id]; - var modelMaterial = new ModelMaterial(model, material, id); - runtimeMaterialsByName[material.name] = modelMaterial; - runtimeMaterialsById[id] = modelMaterial; + var updaters = this._updaters; + for ( var key in updaters) { + if (updaters.hasOwnProperty(key)) { + updaters[key].destroy(); } } - model._runtime.materialsByName = runtimeMaterialsByName; - model._runtime.materialsById = runtimeMaterialsById; - } + return destroyObject(this); + }; - 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); - } - } + 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)); } } - model._runtime.meshesByName = runtimeMeshesByName; - } - - function parse(model, context) { - if (!model._loadRendererResourcesFromCache) { - parseBuffers(model); - parseBufferViews(model); - parseShaders(model); - parsePrograms(model); - parseTextures(model, context); + 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); + } + } } - 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; + 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); } } - return defined(cachedExtensionsUsed[extension]); - } + }; - /////////////////////////////////////////////////////////////////////////// + //for testing + PathVisualizer._subSample = subSample; - var CreateVertexBufferJob = function() { - this.id = undefined; - this.model = undefined; - this.context = undefined; - }; + return PathVisualizer; +}); - CreateVertexBufferJob.prototype.set = function(id, model, context) { - this.id = id; - this.model = model; - this.context = context; - }; +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'; - CreateVertexBufferJob.prototype.execute = function() { - createVertexBuffer(this.id, this.model, this.context); - }; + var defaultColor = Color.WHITE; + var defaultOutlineColor = Color.BLACK; + var defaultOutlineWidth = 0.0; + var defaultPixelSize = 1.0; + var defaultDisableDepthTestDistance = 0.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 createVertexBuffer(bufferViewId, model, context) { - var loadResources = model._loadResources; - var bufferViews = model.gltf.bufferViews; - var bufferView = bufferViews[bufferViewId]; + function EntityData(entity) { + this.entity = entity; + this.pointPrimitive = undefined; + this.billboard = undefined; + this.color = undefined; + this.outlineColor = undefined; + this.pixelSize = undefined; + this.outlineWidth = undefined; + } - var vertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : loadResources.getBuffer(bufferView), - usage : BufferUsage.STATIC_DRAW - }); - vertexBuffer.vertexArrayDestroyable = false; - model._rendererResources.buffers[bufferViewId] = vertexBuffer; - model._vertexMemorySizeInBytes += vertexBuffer.sizeInBytes; + /** + * 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) { + + 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) { + + 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; + } - var CreateIndexBufferJob = function() { - this.id = undefined; - this.componentType = undefined; - this.model = undefined; - this.context = undefined; - }; + if (!Property.isConstant(entity._position)) { + cluster._clusterDirty = true; + } - CreateIndexBufferJob.prototype.set = function(id, componentType, model, context) { - this.id = id; - this.componentType = componentType; - this.model = model; - this.context = context; - }; + var needsRedraw = false; + if ((heightReference !== HeightReference.NONE) && !defined(billboard)) { + if (defined(pointPrimitive)) { + returnPrimitive(item, entity, cluster); + pointPrimitive = undefined; + } - CreateIndexBufferJob.prototype.execute = function() { - createIndexBuffer(this.id, this.componentType, this.model, this.context); - }; + 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; + } - function createIndexBuffer(bufferViewId, componentType, model, context) { - var loadResources = model._loadResources; - var bufferViews = model.gltf.bufferViews; - var bufferView = bufferViews[bufferViewId]; + 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); + pointPrimitive.disableDepthTestDistance = Property.getValueOrDefault(pointGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + } 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.disableDepthTestDistance = Property.getValueOrDefault(pointGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); + billboard.heightReference = heightReference; - var indexBuffer = Buffer.createIndexBuffer({ - context : context, - typedArray : loadResources.getBuffer(bufferView), - usage : BufferUsage.STATIC_DRAW, - indexDatatype : componentType - }); - indexBuffer.vertexArrayDestroyable = false; - model._rendererResources.buffers[bufferViewId] = indexBuffer; - model._vertexMemorySizeInBytes += indexBuffer.sizeInBytes; - } + 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))); - var scratchVertexBufferJob = new CreateVertexBufferJob(); - var scratchIndexBufferJob = new CreateIndexBufferJob(); + 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); + } - function createBuffers(model, frameState) { - var loadResources = model._loadResources; + 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) { + + 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; + }; - if (loadResources.pendingBufferLoads !== 0) { - return; + /** + * 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); + }; - var context = frameState.context; - var vertexBuffersToCreate = loadResources.vertexBuffersToCreate; - var indexBuffersToCreate = loadResources.indexBuffersToCreate; + PointVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { var i; + var entity; + var items = this._items; + var cluster = this._cluster; - if (model.asynchronous) { - while (vertexBuffersToCreate.length > 0) { - scratchVertexBufferJob.set(vertexBuffersToCreate.peek(), model, context); - if (!frameState.jobScheduler.execute(scratchVertexBufferJob, JobType.BUFFER)) { - break; - } - vertexBuffersToCreate.dequeue(); + 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)); } + } - while (indexBuffersToCreate.length > 0) { - i = indexBuffersToCreate.peek(); - scratchIndexBufferJob.set(i.id, i.componentType, model, context); - if (!frameState.jobScheduler.execute(scratchIndexBufferJob, JobType.BUFFER)) { - break; + 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)); } - indexBuffersToCreate.dequeue(); - } - } else { - while (vertexBuffersToCreate.length > 0) { - createVertexBuffer(vertexBuffersToCreate.dequeue(), model, context); + } else { + returnPrimitive(items.get(entity.id), entity, cluster); + items.remove(entity.id); } + } - while (indexBuffersToCreate.length > 0) { - i = indexBuffersToCreate.dequeue(); - createIndexBuffer(i.id, i.componentType, model, context); + 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 createAttributeLocations(model, attributes) { - var attributeLocations = {}; - var length = attributes.length; - var i; + function createCallback(centerAlpha, cssColor, cssOutlineColor, cssOutlineWidth, newPixelSize) { + return function(id) { + var canvas = document.createElement('canvas'); - // 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 (/pos/i.test(attribute)) { - attributes[i] = attributes[0]; - attributes[0] = attribute; - break; + 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(); + } } - } - for (i = 0; i < length; ++i) { - attributeLocations[attributes[i]] = i; - } + context2D.beginPath(); + context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true); + context2D.closePath(); + context2D.fillStyle = cssColor; + context2D.fill(); - return attributeLocations; + return canvas; + }; } - 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 PointVisualizer; +}); - return getStringFromTypedArray(loadResources.getBuffer(bufferView)); - } +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'; - 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; - }); + 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; } - 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; + /** + * 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) { + + 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); } - 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; + 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 } - 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; - } - } - } - } + 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; } } - return undefined; - } + }); - function modifyShaderForQuantizedAttributes(shader, programName, model, context) { - var quantizedUniforms = {}; - model._quantizedUniforms[programName] = quantizedUniforms; + /** + * 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); + }; - 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)); + /** + * 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); + }; - // 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'; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); - 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'; + var attributes; - quantizedUniforms[decodeUniformVarName] = {mat : size}; - } - shader = ShaderSource.replaceMain(shader, newMain); - shader += decode; - } - } - } - } + 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 + }; } - // 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; - } + return new GeometryInstance({ + id : entity, + geometry : new PolygonGeometry(this._options), + attributes : attributes + }); + }; - 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'; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - // Un-premultiply the alpha so that blending is correct. + 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) + } + }); + }; - // Avoid divide-by-zero. The code below is equivalent to: - // if (gl_FragColor.a > 0.0) - // { - // gl_FragColor.rgb /= gl_FragColor.a; - // } + /** + * 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; + }; - if (premultipliedAlpha) { - shader += - ' float alpha = 1.0 - ceil(gl_FragColor.a) + gl_FragColor.a; \n' + - ' gl_FragColor.rgb /= alpha; \n'; - } + /** + * 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); + }; - 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'; + PolygonGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'polygon')) { + return; + } - return shader; - } + var polygon = this._entity.polygon; - function modifyShader(shader, programName, callback) { - if (defined(callback)) { - shader = callback(shader, programName); + if (!defined(polygon)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; } - return shader; - } - - var CreateProgramJob = function() { - this.id = undefined; - this.model = undefined; - this.context = undefined; - }; - CreateProgramJob.prototype.set = function(id, model, context) { - this.id = id; - this.model = model; - this.context = context; - }; + var fillProperty = polygon.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - CreateProgramJob.prototype.execute = function() { - createProgram(this.id, this.model, this.context); - }; + 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); + } - function createProgram(id, model, context) { - var programs = model.gltf.programs; - var shaders = model._loadResources.shaders; - var program = programs[id]; + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; + } - var attributeLocations = createAttributeLocations(model, program.attributes); - var vs = getShaderSource(model, shaders[program.vertexShader]); - var fs = getShaderSource(model, shaders[program.fragmentShader]); + var hierarchy = polygon.hierarchy; - // 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++; - } + 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; } - if (usesExtension(model, 'WEB3D_quantized_attributes')) { - vs = modifyShaderForQuantizedAttributes(vs, id, model, context); + 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 premultipliedAlpha = hasPremultipliedAlpha(model); - var blendFS = modifyShaderForColor(fs, premultipliedAlpha); + var perPositionHeight = polygon.perPositionHeight; + var closeTop = polygon.closeTop; + var closeBottom = polygon.closeBottom; - var drawVS = modifyShader(vs, id, model._vertexShaderLoaded); - var drawFS = modifyShader(blendFS, id, model._fragmentShaderLoaded); + this._fillEnabled = fillEnabled; + this._onTerrain = onTerrain; + this._outlineEnabled = outlineEnabled; - model._rendererResources.programs[id] = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : drawVS, - fragmentShaderSource : drawFS, - attributeLocations : attributeLocations - }); + 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 (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 (!this._dynamic) { + this._dynamic = true; + this._geometryChanged.raiseEvent(this); + } + } else { + var options = this._options; + options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat; - if (!model._pickFragmentShaderLoaded) { - pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); + var hierarchyValue = hierarchy.getValue(Iso8601.MINIMUM_VALUE); + if (isArray(hierarchyValue)) { + hierarchyValue = new PolygonHierarchy(hierarchyValue); } - model._rendererResources.pickPrograms[id] = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : pickVS, - fragmentShaderSource : pickFS, - attributeLocations : attributeLocations - }); + 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. + * @param {PrimitiveCollection} groundPrimitives The ground 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) { + + 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); } - var scratchCreateProgramJob = new CreateProgramJob(); + DynamicGeometryUpdater.prototype.update = function(time) { + + var geometryUpdater = this._geometryUpdater; + var onTerrain = geometryUpdater._onTerrain; - function createPrograms(model, frameState) { - var loadResources = model._loadResources; - var programsToCreate = loadResources.programsToCreate; + 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; - if (loadResources.pendingShaderLoads !== 0) { + var entity = geometryUpdater._entity; + var polygon = entity.polygon; + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polygon.show, time, true)) { 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) { + var options = this._options; + var hierarchy = Property.getValueOrUndefined(polygon.hierarchy, time); + if (!defined(hierarchy)) { return; } - var context = frameState.context; - - if (model.asynchronous) { - while (programsToCreate.length > 0) { - scratchCreateProgramJob.set(programsToCreate.peek(), model, context); - if (!frameState.jobScheduler.execute(scratchCreateProgramJob, JobType.PROGRAM)) { - break; - } - programsToCreate.dequeue(); - } + if (isArray(hierarchy)) { + options.polygonHierarchy = new PolygonHierarchy(hierarchy); } else { - // Create all loaded programs this frame - while (programsToCreate.length > 0) { - createProgram(programsToCreate.dequeue(), model, context); - } + options.polygonHierarchy = hierarchy; } - } - - 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; + var closeTopValue = Property.getValueOrDefault(polygon.closeTop, time, true); + var closeBottomValue = Property.getValueOrDefault(polygon.closeBottom, time, true); - if (loadResources.pendingBufferLoads !== 0) { - return; - } + 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; - while (loadResources.texturesToCreateFromBufferView.length > 0) { - var gltfTexture = loadResources.texturesToCreateFromBufferView.dequeue(); + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - var gltf = model.gltf; - var bufferView = gltf.bufferViews[gltfTexture.bufferView]; + if (Property.getValueOrDefault(polygon.fill, time, true)) { + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); + this._material = material; - var onerror = getFailedLoadFunction(model, 'image', 'id: ' + gltfTexture.id + ', bufferView: ' + gltfTexture.bufferView); + if (onTerrain) { + var currentColor = Color.WHITE; + if (defined(fillMaterialProperty.color)) { + currentColor = fillMaterialProperty.color.getValue(time); + } - if (gltfTexture.mimeType === 'image/ktx') { - loadKTX(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id)).otherwise(onerror); - ++model._loadResources.pendingTextureLoads; - } else if (gltfTexture.mimeType === 'image/crn') { - loadCRN(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id)).otherwise(onerror); - ++model._loadResources.pendingTextureLoads; + 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 onload = getOnImageCreatedFromTypedArray(loadResources, gltfTexture); - loadImageFromTypedArray(loadResources.getBuffer(bufferView), gltfTexture.mimeType) - .then(onload).otherwise(onerror); - ++loadResources.pendingBufferViewToImage; + 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 + })); } } - } - - function createSamplers(model, context) { - var loadResources = model._loadResources; - if (loadResources.createSamplers) { - loadResources.createSamplers = false; + if (!onTerrain && Property.getValueOrDefault(polygon.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - var rendererSamplers = model._rendererResources.samplers; - var samplers = model.gltf.samplers; - for (var id in samplers) { - if (samplers.hasOwnProperty(id)) { - var sampler = samplers[id]; + 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; - rendererSamplers[id] = new Sampler({ - wrapS : sampler.wrapS, - wrapT : sampler.wrapT, - minificationFilter : sampler.minFilter, - magnificationFilter : sampler.magFilter - }); - } - } + 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 + })); } - } - - /////////////////////////////////////////////////////////////////////////// - - var CreateTextureJob = function() { - this.gltfTexture = undefined; - this.model = undefined; - this.context = undefined; }; - CreateTextureJob.prototype.set = function(gltfTexture, model, context) { - this.gltfTexture = gltfTexture; - this.model = model; - this.context = context; + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); }; - CreateTextureJob.prototype.execute = function() { - createTexture(this.gltfTexture, this.model, this.context); + 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); + }; - function createTexture(gltfTexture, model, context) { - var textures = model.gltf.textures; - var texture = textures[gltfTexture.id]; + return PolygonGeometryUpdater; +}); - var rendererSamplers = model._rendererResources.samplers; - var sampler = rendererSamplers[texture.sampler]; +//This file is automatically rebuilt by the Cesium build process. +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\ +\n\ +varying vec4 v_color;\n\ +\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\ +\n\ + vec4 p = czm_computePosition();\n\ + vec4 prev = czm_computePrevPosition();\n\ + vec4 next = czm_computeNextPosition();\n\ +\n\ + v_color = color;\n\ +\n\ + float angle;\n\ + vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev, angle);\n\ + gl_Position = czm_viewportOrthographic * positionWC;\n\ +}\n\ +"; +}); +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 internalFormat = gltfTexture.internalFormat; + var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineColorAppearanceVS; + var defaultFragmentShaderSource = PerInstanceFlatColorAppearanceFS; - var mipmap = - (!(defined(internalFormat) && PixelFormat.isCompressedFormat(internalFormat))) && - ((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); + /** + * 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 tx; - var source = gltfTexture.image; + var translucent = defaultValue(options.translucent, true); + var closed = false; + var vertexFormat = PolylineColorAppearance.VERTEX_FORMAT; - if (defined(internalFormat) && texture.target === WebGLConstants.TEXTURE_2D) { - tx = new Texture({ - context : context, - source : { - arrayBufferView : gltfTexture.bufferView - }, - width : gltfTexture.width, - height : gltfTexture.height, - pixelFormat : internalFormat, - sampler : sampler - }); - } else if (defined(source)) { - var npot = !CesiumMath.isPowerOfTwo(source.width) || !CesiumMath.isPowerOfTwo(source.height); + /** + * 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; - 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; - } + /** + * When true, the geometry is expected to appear translucent so + * {@link PolylineColorAppearance#renderState} has alpha blending enabled. + * + * @type {Boolean} + * + * @default true + */ + this.translucent = translucent; - 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 + this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource); + this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource); + this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState); + this._closed = closed; - if (mipmap) { - tx.generateMipmap(); - } - } + // Non-derived members - model._rendererResources.textures[gltfTexture.id] = tx; - model._textureMemorySizeInBytes += tx.sizeInBytes; + this._vertexFormat = vertexFormat; } - var scratchCreateTextureJob = new CreateTextureJob(); - - function createTextures(model, frameState) { - var context = frameState.context; - var texturesToCreate = model._loadResources.texturesToCreate; - - if (model.asynchronous) { - while (texturesToCreate.length > 0) { - scratchCreateTextureJob.set(texturesToCreate.peek(), model, context); - if (!frameState.jobScheduler.execute(scratchCreateTextureJob, JobType.TEXTURE)) { - break; - } - texturesToCreate.dequeue(); - } - } else { - // Create all loaded textures this frame - while (texturesToCreate.length > 0) { - createTexture(texturesToCreate.dequeue(), model, context); + defineProperties(PolylineColorAppearance.prototype, { + /** + * The GLSL source code for the vertex shader. + * + * @memberof PolylineColorAppearance.prototype + * + * @type {String} + * @readonly + */ + vertexShaderSource : { + get : function() { + return this._vertexShaderSource; } - } - } - - 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 = {}; + /** + * The GLSL source code for the fragment shader. + * + * @memberof PolylineColorAppearance.prototype + * + * @type {String} + * @readonly + */ + fragmentShaderSource : { + get : function() { + return this._fragmentShaderSource; + } + }, - var location; - var index; - var technique = techniques[materials[primitive.material].technique]; - var parameters = technique.parameters; - var attributes = technique.attributes; - var program = model._rendererResources.programs[technique.program]; - var programVertexAttributes = program.vertexAttributes; - var programAttributeLocations = program._attributeLocations; + /** + * 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; + } + }, - // Note: WebGL shader compiler may have optimized and removed some attributes from programVertexAttributes - for (location in programVertexAttributes){ - if (programVertexAttributes.hasOwnProperty(location)) { - var attribute = attributes[location]; - index = programVertexAttributes[location].index; - if (defined(attribute)) { - var parameter = parameters[attribute]; - attributeLocations[parameter.semantic] = index; - } + /** + * 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; } - } + }, - // Always add pre-created attributes. - // Some pre-created attributes, like per-instance pickIds, may be compiled out of the draw program - // but should be included in the list of attribute locations for the pick program. - // This is safe to do since programVertexAttributes and programAttributeLocations are equivalent except - // that programVertexAttributes optimizes out unused attributes. - var precreatedAttributes = model._precreatedAttributes; - if (defined(precreatedAttributes)) { - for (location in precreatedAttributes) { - if (precreatedAttributes.hasOwnProperty(location)) { - index = programAttributeLocations[location]; - attributeLocations[location] = index; - } + /** + * 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; } } + }); - 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]; + /** + * 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; - if (n.jointName === jointName) { - return id; - } + /** + * Procedurally creates the full GLSL fragment shader source. + * + * @function + * + * @returns {String} The full GLSL fragment shader source. + */ + PolylineColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource; - var children = n.children; - var childrenLength = children.length; - for (var k = 0; k < childrenLength; ++k) { - stack.push(children[k]); - } - } - } + /** + * 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; - // This should never happen; the skeleton should have a node for all joints in the skin. - return undefined; - } + /** + * 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; - function createJoints(model, runtimeSkins) { - var gltf = model.gltf; - var skins = gltf.skins; - var nodes = gltf.nodes; - var runtimeNodes = model._runtime.nodes; + return PolylineColorAppearance; +}); - 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]; +//This file is automatically rebuilt by the Cesium build process. +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\ +\n\ +varying float v_width;\n\ +varying vec2 v_st;\n\ +varying float v_polylineAngle;\n\ +\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\ +\n\ + vec4 p = czm_computePosition();\n\ + vec4 prev = czm_computePrevPosition();\n\ + vec4 next = czm_computeNextPosition();\n\ +\n\ + v_width = width;\n\ + v_st = st;\n\ +\n\ + vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev, v_polylineAngle);\n\ + gl_Position = czm_viewportOrthographic * positionWC;\n\ +}\n\ +"; +}); +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 runtimeSkin = runtimeSkins[node.skin]; - skinnedNode.inverseBindMatrices = runtimeSkin.inverseBindMatrices; - skinnedNode.bindShapeMatrix = runtimeSkin.bindShapeMatrix; + var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineMaterialAppearanceVS; + var defaultFragmentShaderSource = PolylineFS; - // 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]); - } + /** + * 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 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); - } - } - } + var translucent = defaultValue(options.translucent, true); + var closed = false; + var vertexFormat = PolylineMaterialAppearance.VERTEX_FORMAT; - function createSkins(model) { - var loadResources = model._loadResources; + /** + * 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); - if (loadResources.pendingBufferLoads !== 0) { - return; - } + /** + * When true, the geometry is expected to appear translucent so + * {@link PolylineMaterialAppearance#renderState} has alpha blending enabled. + * + * @type {Boolean} + * + * @default true + */ + this.translucent = translucent; - if (!loadResources.createSkins) { - return; - } - loadResources.createSkins = false; + this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource); + this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource); + this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState); + this._closed = closed; - var gltf = model.gltf; - var accessors = gltf.accessors; - var skins = gltf.skins; - var runtimeSkins = {}; + // Non-derived members - for (var id in skins) { - if (skins.hasOwnProperty(id)) { - var skin = skins[id]; - var accessor = accessors[skin.inverseBindMatrices]; + this._vertexFormat = vertexFormat; + } - var bindShapeMatrix; - if (!Matrix4.equals(skin.bindShapeMatrix, Matrix4.IDENTITY)) { - bindShapeMatrix = Matrix4.clone(skin.bindShapeMatrix); + defineProperties(PolylineMaterialAppearance.prototype, { + /** + * The GLSL source code for the vertex shader. + * + * @memberof PolylineMaterialAppearance.prototype + * + * @type {String} + * @readonly + */ + vertexShaderSource : { + get : function() { + var vs = this._vertexShaderSource; + if (this.material.shaderSource.search(/varying\s+float\s+v_polylineAngle;/g) !== -1) { + vs = '#define POLYLINE_DASH\n' + vs; } - - runtimeSkins[id] = { - inverseBindMatrices : ModelAnimationCache.getSkinInverseBindMatrices(model, accessor), - bindShapeMatrix : bindShapeMatrix // not used when undefined - }; + return vs; } - } - - createJoints(model, runtimeSkins); - } + }, - function getChannelEvaluator(model, runtimeNode, targetPath, spline) { - return function(localAnimationTime) { - // Workaround for https://github.com/KhronosGroup/glTF/issues/219 + /** + * The GLSL source code for the fragment shader. + * + * @memberof PolylineMaterialAppearance.prototype + * + * @type {String} + * @readonly + */ + fragmentShaderSource : { + get : function() { + return this._fragmentShaderSource; + } + }, - //if (targetPath === 'translation') { - // return; - //} - runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]); - runtimeNode.dirtyNumber = model._maxDirtyNumber; - }; - } + /** + * 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; + } + }, - function createRuntimeAnimations(model) { - var loadResources = model._loadResources; + /** + * 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; + } + }, - if (!loadResources.finishedPendingBufferLoads()) { - return; + /** + * 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; + } } + }); - if (!loadResources.createRuntimeAnimations) { - return; - } - loadResources.createRuntimeAnimations = false; + /** + * 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; - model._runtime.animations = { - }; + /** + * 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; - var runtimeNodes = model._runtime.nodes; - var animations = model.gltf.animations; - var accessors = model.gltf.accessors; + /** + * 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; - 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; + /** + * 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; - var parameterValues = {}; + return PolylineMaterialAppearance; +}); - for (var name in parameters) { - if (parameters.hasOwnProperty(name)) { - parameterValues[name] = ModelAnimationCache.getAnimationParameterValues(model, accessors[parameters[name]]); - } - } +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'; - // Find start and stop time for the entire animation - var startTime = Number.MAX_VALUE; - var stopTime = -Number.MAX_VALUE; + //We use this object to create one polyline collection per-scene. + var polylineCollections = {}; - var length = channels.length; - var channelEvaluators = new Array(length); + var defaultMaterial = new ColorMaterialProperty(Color.WHITE); + var defaultShow = new ConstantProperty(true); + var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); + var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); - 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]; + function GeometryOptions(entity) { + this.id = entity; + this.vertexFormat = undefined; + this.positions = undefined; + this.width = undefined; + this.followSurface = undefined; + this.granularity = undefined; + } - startTime = Math.min(startTime, times[0]); - stopTime = Math.max(stopTime, times[times.length - 1]); + /** + * 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) { + + 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._depthFailMaterialProperty = undefined; + this._options = new GeometryOptions(entity); + this._onEntityPropertyChanged(entity, 'polyline', entity.polyline, undefined); + } - 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); - } + 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 + } + }); - model._runtime.animations[animationId] = { - startTime : startTime, - stopTime : stopTime, - channelEvaluators : channelEvaluators - }; + 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 the material property used to fill the geometry when it fails the depth test. + * @memberof PolylineGeometryUpdater.prototype + * + * @type {MaterialProperty} + * @readonly + */ + depthFailMaterialProperty : { + get : function() { + return this._depthFailMaterialProperty; + } + }, + /** + * 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; } } - } + }); - function createVertexArrays(model, context) { - var loadResources = model._loadResources; + /** + * 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; + }; - if (!loadResources.finishedBuffersCreation() || !loadResources.finishedProgramCreation()) { - return; - } + /** + * 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); + }; - if (!loadResources.createVertexArrays) { - return; - } - loadResources.createVertexArrays = false; + /** + * 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) { + + 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); - var rendererBuffers = model._rendererResources.buffers; - var rendererVertexArrays = model._rendererResources.vertexArrays; - var gltf = model.gltf; - var accessors = gltf.accessors; - var meshes = gltf.meshes; + var attributes = { + show : show, + distanceDisplayCondition : distanceDisplayConditionAttribute + }; - for (var meshId in meshes) { - if (meshes.hasOwnProperty(meshId)) { - var primitives = meshes[meshId].primitives; - var primitivesLength = primitives.length; + var currentColor; + if (this._materialProperty instanceof ColorMaterialProperty) { + currentColor = Color.WHITE; + if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { + currentColor = this._materialProperty.color.getValue(time); + } + attributes.color = ColorGeometryInstanceAttribute.fromColor(currentColor); + } - for (var i = 0; i < primitivesLength; ++i) { - var primitive = primitives[i]; + if (defined(this._depthFailMaterialProperty) && this._depthFailMaterialProperty instanceof ColorMaterialProperty) { + currentColor = Color.WHITE; + if (defined(this._depthFailMaterialProperty.color) && (this._depthFailMaterialProperty.color.isConstant || isAvailable)) { + currentColor = this._depthFailMaterialProperty.color.getValue(time); + } + attributes.depthFailColor = ColorGeometryInstanceAttribute.fromColor(currentColor); + } - // 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 + return new GeometryInstance({ + id : entity, + geometry : new PolylineGeometry(this._options), + attributes : attributes + }); + }; - 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]]; + /** + * 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) { + }; - attributes.push({ - index : attributeLocation, - vertexBuffer : rendererBuffers[a.bufferView], - componentsPerAttribute : getBinaryAccessor(a).componentsPerAttribute, - componentDatatype : a.componentType, - normalize : false, - offsetInBytes : a.byteOffset, - strideInBytes : a.byteStride - }); - } - } - } + /** + * 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; + }; - // 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); - } - } - } - } + /** + * 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); + }; - 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 - }); - } - } + PolylineGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'polyline')) { + return; } - } - 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 polyline = this._entity.polyline; - var enable = states.enable; - var length = enable.length; - var i; - for (i = 0; i < length; ++i) { - booleanStates[enable[i]] = true; + if (!defined(polyline)) { + if (this._fillEnabled) { + this._fillEnabled = false; + this._geometryChanged.raiseEvent(this); + } + return; } - return booleanStates; - } - - function createRenderStates(model, context) { - var loadResources = model._loadResources; - var techniques = model.gltf.techniques; + var positionsProperty = polyline.positions; - if (loadResources.createRenderStates) { - loadResources.createRenderStates = false; - for (var id in techniques) { - if (techniques.hasOwnProperty(id)) { - createRenderStateForTechnique(model, id, context); - } + 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; } - } - function createRenderStateForTechnique(model, id, context) { - var rendererRenderStates = model._rendererResources.renderStates; - var techniques = model.gltf.techniques; - var technique = techniques[id]; - var states = technique.states; + var material = defaultValue(polyline.material, defaultMaterial); + var isColorMaterial = material instanceof ColorMaterialProperty; + this._materialProperty = material; + this._depthFailMaterialProperty = polyline.depthFailMaterial; + this._showProperty = defaultValue(show, defaultShow); + this._shadowsProperty = defaultValue(polyline.shadows, defaultShadows); + this._distanceDisplayConditionProperty = defaultValue(polyline.distanceDisplayCondition, defaultDistanceDisplayCondition); + this._fillEnabled = true; - 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]); + var width = polyline.width; + var followSurface = polyline.followSurface; + var granularity = polyline.granularity; - // 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; + 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); - 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] + //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); } - }, - 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] + return; } - }); - } - // 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() { - if (defined(model._rtcCenter)) { - Matrix4.getTranslation(uniformState.model, scratchTranslationRtc); - Cartesian3.add(scratchTranslationRtc, model._rtcCenter, scratchTranslationRtc); - Matrix4.multiplyByPoint(uniformState.view, scratchTranslationRtc, scratchTranslationRtc); - return Matrix4.setTranslation(uniformState.modelView, scratchTranslationRtc, mvRtc); - } - return uniformState.modelView; - }; - }, - 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; - }; + var vertexFormat; + if (isColorMaterial && (!defined(this._depthFailMaterialProperty) || this._depthFailMaterialProperty instanceof ColorMaterialProperty)) { + vertexFormat = PolylineColorAppearance.VERTEX_FORMAT; + } else { + vertexFormat = PolylineMaterialAppearance.VERTEX_FORMAT; + } + + options.vertexFormat = vertexFormat; + 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); } - // JOINTMATRIX created in createCommand() }; - /////////////////////////////////////////////////////////////////////////// + /** + * 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) { + + return new DynamicGeometryUpdater(primitives, this); + }; - function getScalarUniformFunction(value, model) { - var that = { - value : value, - clone : function(source, result) { - return source; - }, - func : function() { - return that.value; - } - }; - return that; - } + /** + * @private + */ + var generateCartesianArcOptions = { + positions : undefined, + granularity : undefined, + height : undefined, + ellipsoid : undefined + }; - function getVec2UniformFunction(value, model) { - var that = { - value : Cartesian2.fromArray(value), - clone : Cartesian2.clone, - func : function() { - return that.value; - } - }; - return that; - } + function DynamicGeometryUpdater(primitives, geometryUpdater) { + var sceneId = geometryUpdater._scene.id; - function getVec3UniformFunction(value, model) { - var that = { - value : Cartesian3.fromArray(value), - clone : Cartesian3.clone, - func : function() { - return that.value; - } - }; - return that; - } + 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); + } - function getVec4UniformFunction(value, model) { - var that = { - value : Cartesian4.fromArray(value), - clone : Cartesian4.clone, - func : function() { - return that.value; - } - }; - return that; - } + var line = polylineCollection.add(); + line.id = geometryUpdater._entity; - function getMat2UniformFunction(value, model) { - var that = { - value : Matrix2.fromColumnMajorArray(value), - clone : Matrix2.clone, - func : function() { - return that.value; - } - }; - return that; - } + this._line = line; + this._primitives = primitives; + this._geometryUpdater = geometryUpdater; + this._positions = []; - function getMat3UniformFunction(value, model) { - var that = { - value : Matrix3.fromColumnMajorArray(value), - clone : Matrix3.clone, - func : function() { - return that.value; - } - }; - return that; } + DynamicGeometryUpdater.prototype.update = function(time) { + var geometryUpdater = this._geometryUpdater; + var entity = geometryUpdater._entity; + var polyline = entity.polyline; + var line = this._line; - function getMat4UniformFunction(value, model) { - var that = { - value : Matrix4.fromColumnMajorArray(value), - clone : Matrix4.clone, - func : function() { - return that.value; - } - }; - return that; - } + 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; + } - function DelayLoadedTextureUniform(value, model) { - this._value = undefined; - this._textureId = value; - this._model = model; - } + var followSurface = Property.getValueOrDefault(polyline._followSurface, time, true); + var globe = geometryUpdater._scene.globe; + if (followSurface && defined(globe)) { + generateCartesianArcOptions.ellipsoid = globe.ellipsoid; + generateCartesianArcOptions.positions = positions; + generateCartesianArcOptions.granularity = Property.getValueOrUndefined(polyline._granularity, time); + generateCartesianArcOptions.height = PolylinePipeline.extractHeights(positions, globe.ellipsoid); + positions = PolylinePipeline.generateCartesianArc(generateCartesianArcOptions); + } - 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; - } - } + 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); + }; - return this._value; - }, - set : function(value) { - this._value = value; - } + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + + var line = this._line; + if (line.show && line.positions.length > 0) { + BoundingSphere.fromPoints(line.positions, result); + return BoundingSphereState.DONE; } - }); + return BoundingSphereState.FAILED; + }; - DelayLoadedTextureUniform.prototype.clone = function(source, result) { - return source; + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; }; - DelayLoadedTextureUniform.prototype.func = undefined; + 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; +}); - 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; +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; } - 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 + /** + * 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) { + + 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); + } - var gltfUniformsFromNode = { - MODEL : function(uniformState, model, runtimeNode) { - return function() { - return runtimeNode.computedMatrix; - }; + defineProperties(PolylineVolumeGeometryUpdater, { + /** + * Gets the type of appearance to use for simple color-based geometry. + * @memberof PolylineVolumeGeometryUpdater + * @type {Appearance} + */ + perInstanceColorAppearanceType : { + value : PerInstanceColorAppearance }, - VIEW : function(uniformState, model, runtimeNode) { - return function() { - return uniformState.view; - }; + /** + * 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; + } }, - PROJECTION : function(uniformState, model, runtimeNode) { - return function() { - return uniformState.projection; - }; + /** + * Gets a value indicating if the geometry has a fill component. + * @memberof PolylineVolumeGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + fillEnabled : { + get : function() { + return this._fillEnabled; + } }, - MODELVIEW : function(uniformState, model, runtimeNode) { - var mv = new Matrix4(); - return function() { - return Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv); - }; + /** + * 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)); + } }, - 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); - }; + /** + * Gets the material property used to fill the geometry. + * @memberof PolylineVolumeGeometryUpdater.prototype + * + * @type {MaterialProperty} + * @readonly + */ + fillMaterialProperty : { + get : function() { + return this._materialProperty; + } }, - 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); - }; + /** + * Gets a value indicating if the geometry has an outline component. + * @memberof PolylineVolumeGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + outlineEnabled : { + get : function() { + return this._outlineEnabled; + } }, - MODELINVERSE : function(uniformState, model, runtimeNode) { - var mInverse = new Matrix4(); - return function() { - return Matrix4.inverse(runtimeNode.computedMatrix, mInverse); - }; + /** + * 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)); + } }, - VIEWINVERSE : function(uniformState, model) { - return function() { - return uniformState.inverseView; - }; + /** + * Gets the {@link Color} property for the geometry outline. + * @memberof PolylineVolumeGeometryUpdater.prototype + * + * @type {Property} + * @readonly + */ + outlineColorProperty : { + get : function() { + return this._outlineColorProperty; + } }, - PROJECTIONINVERSE : function(uniformState, model, runtimeNode) { - return function() { - return uniformState.inverseProjection; - }; + /** + * 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; + } }, - 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); - }; + /** + * 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; + } }, - 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); - }; + /** + * 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; + } }, - 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); - }; + /** + * 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; + } }, - 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); - }; + /** + * 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 }, - VIEWPORT : function(uniformState, model, runtimeNode) { - return function() { - return uniformState.viewportCartesian4; - }; + /** + * 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); }; - function getUniformFunctionFromSource(source, model, semantic, uniformState) { - var runtimeNode = model._runtime.nodes[source]; - return gltfUniformsFromNode[semantic](uniformState, model, runtimeNode); - } + /** + * 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); + }; - function createUniformMaps(model, context) { - var loadResources = model._loadResources; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); - if (!loadResources.finishedProgramCreation()) { - return; - } + var attributes; - if (!loadResources.createUniformMaps) { - return; + 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 + }; } - loadResources.createUniformMaps = false; - var gltf = model.gltf; - var materials = gltf.materials; - var techniques = gltf.techniques; - var uniformMaps = model._uniformMaps; + return new GeometryInstance({ + id : entity, + geometry : new PolylineVolumeGeometry(this._options), + attributes : attributes + }); + }; - 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; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - var uniformMap = {}; - var uniformValues = {}; - var jointMatrixUniformName; + 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) + } + }); + }; - // Uniform parameters - for (var name in uniforms) { - if (uniforms.hasOwnProperty(name)) { - var parameterName = uniforms[name]; - var parameter = parameters[parameterName]; + /** + * 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; + }; - // GLTF_SPEC: This does not take into account uniform arrays, - // indicated by parameters with a count property. - // - // https://github.com/KhronosGroup/glTF/issues/258 + /** + * 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); + }; - // 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 + PolylineVolumeGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'polylineVolume')) { + return; + } - 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 polylineVolume = this._entity.polylineVolume; - 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; + if (!defined(polylineVolume)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - } - - 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; + var fillProperty = polylineVolume.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - if (defined(extensions)) { - var quantizedAttributes = extensions.WEB3D_quantized_attributes; - if (defined(quantizedAttributes)) { - var decodeMatrix = quantizedAttributes.decodeMatrix; - var uniformVariable = 'gltf_u_dec_' + attribute.toLowerCase(); + var outlineProperty = polylineVolume.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); + } - 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 (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - // 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; - } - } - } + 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; } - return uniformMap; - } - function createPickColorFunction(color) { - return function() { - return color; - }; - } + 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); - function createJointMatricesFunction(runtimeNode) { - return function() { - return runtimeNode.computedJointMatrices; - }; - } + var granularity = polylineVolume.granularity; + var outlineWidth = polylineVolume.outlineWidth; + var cornerType = polylineVolume.cornerType; - function createSilhouetteColorFunction(model) { - return function() { - return model.silhouetteColor; - }; - } + this._fillEnabled = fillEnabled; + this._outlineEnabled = outlineEnabled; - function createSilhouetteSizeFunction(model) { - return function() { - return model.silhouetteSize; - }; - } + 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); + } + }; - function createColorFunction(model) { - return function() { - return model.color; - }; - } + /** + * 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) { + + return new DynamicGeometryUpdater(primitives, this); + }; - function createColorBlendFunction(model) { - return function() { - return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount); - }; + /** + * @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) { + + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + this._primitive = undefined; + this._outlinePrimitive = undefined; - function triangleCountFromPrimitiveIndices(primitive, indicesCount) { - switch (primitive.mode) { - case PrimitiveType.TRIANGLES: - return (indicesCount / 3); - case PrimitiveType.TRIANGLE_STRIP: - case PrimitiveType.TRIANGLE_FAN: - return Math.max(indicesCount - 2, 0); - default: - return 0; + 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; } - } - - 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; + 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; + } - for (var j = 0; j < meshesLength; ++j) { - var id = meshes[j]; - var mesh = gltfMeshes[id]; - var primitives = mesh.primitives; - var length = primitives.length; + options.polylinePositions = positions; + options.shapePositions = shape; + options.granularity = Property.getValueOrUndefined(polylineVolume.granularity, time); + options.cornerType = Property.getValueOrUndefined(polylineVolume.cornerType, time); - // 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. + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - 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; + if (!defined(polylineVolume.fill) || polylineVolume.fill.getValue(time)) { + var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); + this._material = material; - 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 appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : true + }); + options.vertexFormat = appearance.vertexFormat; - 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; - } + this._primitive = primitives.add(new Primitive({ + geometryInstances : new GeometryInstance({ + id : entity, + geometry : new PolylineVolumeGeometry(options) + }), + appearance : appearance, + asynchronous : false, + shadows : shadows + })); + } - // Update model triangle count using number of indices - model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count); + if (defined(polylineVolume.outline) && polylineVolume.outline.getValue(time)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - var um = uniformMaps[primitive.material]; - var uniformMap = um.uniformMap; - if (defined(um.jointMatrixUniformName)) { - var jointUniformMap = {}; - jointUniformMap[um.jointMatrixUniformName] = createJointMatricesFunction(runtimeNode); + 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; - uniformMap = combine(uniformMap, jointUniformMap); - } + 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 + })); + } + }; - uniformMap = combine(uniformMap, { - gltf_color : createColorFunction(model), - gltf_colorBlend : createColorBlendFunction(model) - }); + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + }; - // Allow callback to modify the uniformMap - if (defined(model._uniformMapLoaded)) { - uniformMap = model._uniformMapLoaded(uniformMap, programId, runtimeNode); - } + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; - // Add uniforms for decoding quantized attributes if used - if (usesExtension(model, 'WEB3D_quantized_attributes')) { - var quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive, context); - uniformMap = combine(uniformMap, quantizedUniformMap); - } + DynamicGeometryUpdater.prototype.destroy = function() { + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + destroyObject(this); + }; - var rs = rendererRenderStates[material.technique]; + return PolylineVolumeGeometryUpdater; +}); - // 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] - }; +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 castShadows = ShadowMode.castShadows(model._shadows); - var receiveShadows = ShadowMode.receiveShadows(model._shadows); + 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(); - 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 - }); + 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; + } - var pickCommand; + /** + * 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) { + + 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); - if (allowPicking) { - var pickUniformMap; + this._onEntityPropertyChanged(entity, 'rectangle', entity.rectangle, undefined); + } - // 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 - }); - } + 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 + } + }); - 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); + 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; } } - } + }); - function createRuntimeNodes(model, context, scene3DOnly) { - var loadResources = model._loadResources; + /** + * 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); + }; - if (!loadResources.finishedEverythingButTextureCreation()) { - return; - } + /** + * 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); + }; - if (!loadResources.createRuntimeNodes) { - return; - } - loadResources.createRuntimeNodes = false; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); - var rootNodes = []; - var runtimeNodes = model._runtime.nodes; + var attributes; - var gltf = model.gltf; - var nodes = gltf.nodes; + 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 + }; + } - var scene = gltf.scenes[gltf.scene]; - var sceneNodes = scene.nodes; - var length = sceneNodes.length; + return new GeometryInstance({ + id : entity, + geometry : new RectangleGeometry(this._options), + attributes : attributes + }); + }; - var stack = []; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - for (var i = 0; i < length; ++i) { - stack.push({ - parentRuntimeNode : undefined, - gltfNode : nodes[sceneNodes[i]], - id : sceneNodes[i] - }); + 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) + } + }); + }; - while (stack.length > 0) { - var n = stack.pop(); - var parentRuntimeNode = n.parentRuntimeNode; - var gltfNode = n.gltfNode; + /** + * 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; + }; - // 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); - } - } + /** + * 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); + }; - if (defined(parentRuntimeNode)) { - parentRuntimeNode.children.push(runtimeNode); - runtimeNode.parents.push(parentRuntimeNode); - } else { - rootNodes.push(runtimeNode); - } + RectangleGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'rectangle')) { + return; + } - if (defined(gltfNode.meshes)) { - createCommand(model, gltfNode, runtimeNode, context, scene3DOnly); - } + var rectangle = this._entity.rectangle; - 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] - }); - } + if (!defined(rectangle)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - model._runtime.rootNodes = rootNodes; - model._runtime.nodes = runtimeNodes; - } + var fillProperty = rectangle.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - function getVertexMemorySizeInBytes(buffers) { - var memory = 0; - for (var id in buffers) { - if (buffers.hasOwnProperty(id)) { - memory += buffers[id].sizeInBytes; - } + var outlineProperty = rectangle.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); } - return memory; - } - function getTextureMemorySizeInBytes(textures) { - var memory = 0; - for (var id in textures) { - if (textures.hasOwnProperty(id)) { - memory += textures[id].sizeInBytes; + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - return memory; - } - function createResources(model, frameState) { - var context = frameState.context; - var scene3DOnly = frameState.scene3DOnly; + var coordinates = rectangle.coordinates; - if (model._loadRendererResourcesFromCache) { - var resources = model._rendererResources; - var cachedResources = model._cachedRendererResources; + 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; + } - 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; + 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); - // Vertex arrays are unique to this model, create instead of using the cache. - if (defined(model._precreatedAttributes)) { - createVertexArrays(model, context); - } + 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); - model._cachedVertexMemorySizeInBytes += getVertexMemorySizeInBytes(cachedResources.buffers); - model._cachedTextureMemorySizeInBytes += getTextureMemorySizeInBytes(cachedResources.textures); - } else { - createBuffers(model, frameState); // using glTF bufferViews - createPrograms(model, frameState); - createSamplers(model, context); - loadTexturesFromBufferViews(model); - createTextures(model, frameState); + if (outlineEnabled && onTerrain) { + oneTimeWarning(oneTimeWarning.geometryOutlines); + outlineEnabled = false; } - createSkins(model); - createRuntimeAnimations(model); + this._fillEnabled = fillEnabled; + this._onTerrain = onTerrain; + this._outlineEnabled = outlineEnabled; - 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. + 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); } + }; - createUniformMaps(model, context); // using glTF materials/techniques - createRuntimeNodes(model, context, scene3DOnly); // using glTF scene + /** + * 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) { + + 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) { + + 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; - function getNodeMatrix(node, result) { - var publicNode = node.publicNode; - var publicMatrix = publicNode.matrix; + var entity = geometryUpdater._entity; + var rectangle = entity.rectangle; + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(rectangle.show, time, true)) { + return; + } - 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 options = this._options; + var coordinates = Property.getValueOrUndefined(rectangle.coordinates, time, options.rectangle); + if (!defined(coordinates)) { + return; } - } - var scratchNodeStack = []; - var scratchComputedTranslation = new Cartesian4(); - var scratchComputedMatrixIn2D = new Matrix4(); + 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); - function updateNodeHierarchyModelMatrix(model, modelTransformChanged, justLoaded, projection) { - var maxDirtyNumber = model._maxDirtyNumber; - var allowPicking = model.allowPicking; + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - var rootNodes = model._runtime.rootNodes; - var length = rootNodes.length; + if (Property.getValueOrDefault(rectangle.fill, time, true)) { + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); + this._material = material; - var nodeStack = scratchNodeStack; - var computedModelMatrix = model._computedModelMatrix; + if (onTerrain) { + var currentColor = Color.WHITE; + if (defined(fillMaterialProperty.color)) { + currentColor = fillMaterialProperty.color.getValue(time); + } - if ((model._mode !== SceneMode.SCENE3D) && !model._ignoreCommands) { - var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation); - if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) { - computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D); - model._rtcCenter = model._rtcCenter3D; + 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 center = model.boundingSphere.center; - var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); - computedModelMatrix = Matrix4.multiply(to2D, computedModelMatrix, scratchComputedMatrixIn2D); + var appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : defined(options.extrudedHeight) + }); - if (defined(model._rtcCenter)) { - Matrix4.setTranslation(computedModelMatrix, Cartesian4.UNIT_W, computedModelMatrix); - model._rtcCenter = model._rtcCenter2D; - } + 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 + })); } } - for (var i = 0; i < length; ++i) { - var n = rootNodes[i]; - - getNodeMatrix(n, n.transformToRoot); - nodeStack.push(n); + if (!onTerrain && Property.getValueOrDefault(rectangle.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - while (nodeStack.length > 0) { - n = nodeStack.pop(); - var transformToRoot = n.transformToRoot; - var commands = n.commands; + 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; - 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); + 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 + })); + } + }; - // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation) - BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume); + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + }; - if (defined(model._rtcCenter)) { - Cartesian3.add(model._rtcCenter, command.boundingVolume.center, command.boundingVolume.center); - } + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; - if (allowPicking) { - var pickCommand = primitiveCommand.pickCommand; - Matrix4.clone(command.modelMatrix, pickCommand.modelMatrix); - BoundingSphere.clone(command.boundingVolume, pickCommand.boundingVolume); - } + 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); + }; - // 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); + return RectangleGeometryUpdater; +}); - if (allowPicking) { - var pickCommand2D = primitiveCommand.pickCommand2D; - Matrix4.clone(command.modelMatrix, pickCommand2D.modelMatrix); - BoundingSphere.clone(command.boundingVolume, pickCommand2D.boundingVolume); - } - } - } - } - } +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 children = n.children; - var childrenLength = children.length; - for (var k = 0; k < childrenLength; ++k) { - var child = children[k]; + 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(); - // 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 + function GeometryOptions(entity) { + this.id = entity; + this.vertexFormat = undefined; + this.positions = undefined; + this.minimumHeights = undefined; + this.maximumHeights = undefined; + this.granularity = undefined; + } - // 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); + /** + * 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) { + + 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); + } - 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); - } + 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 + } + }); - nodeStack.push(child); - } + 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; } } + }); - ++model._maxDirtyNumber; - } - - var scratchObjectSpace = new Matrix4(); - - function applySkins(model) { - var skinnedNodes = model._runtime.skinnedNodes; - var length = skinnedNodes.length; + /** + * 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); + }; - for (var i = 0; i < length; ++i) { - var node = skinnedNodes[i]; + /** + * 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); + }; - scratchObjectSpace = Matrix4.inverseTransformation(node.transformToRoot, scratchObjectSpace); + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); - var computedJointMatrices = node.computedJointMatrices; - var joints = node.joints; - var bindShapeMatrix = node.bindShapeMatrix; - var inverseBindMatrices = node.inverseBindMatrices; - var inverseBindMatricesLength = inverseBindMatrices.length; + var attributes; - 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]); - } + 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 + }; } - } - 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; + return new GeometryInstance({ + id : entity, + geometry : new WallGeometry(this._options), + attributes : attributes + }); + }; - var nodeStack = scratchNodeStack; + /** + * 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) { + + var entity = this._entity; + var isAvailable = entity.isAvailable(time); + var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); + var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); - for (var i = 0; i < length; ++i) { - var n = rootNodes[i]; - n.computedShow = n.publicNode.show; - nodeStack.push(n); + 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) + } + }); + }; - while (nodeStack.length > 0) { - n = nodeStack.pop(); - var show = n.computedShow; + /** + * 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; + }; - 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 + /** + * 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); + }; - 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); - } - } + WallGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { + if (!(propertyName === 'availability' || propertyName === 'wall')) { + return; } - } - function updatePickIds(model, context) { - var id = model.id; - if (model._id !== id) { - model._id = id; + var wall = this._entity.wall; - var pickIds = model._pickIds; - var length = pickIds.length; - for (var i = 0; i < length; ++i) { - pickIds[i].object.id = id; + if (!defined(wall)) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - } - function updateWireframe(model) { - if (model._debugWireframe !== model.debugWireframe) { - model._debugWireframe = model.debugWireframe; + var fillProperty = wall.fill; + var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; - // 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; + var outlineProperty = wall.outline; + var outlineEnabled = defined(outlineProperty); + if (outlineEnabled && outlineProperty.isConstant) { + outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); + } - for (var i = 0; i < length; ++i) { - nodeCommands[i].command.primitiveType = primitiveType; + if (!fillEnabled && !outlineEnabled) { + if (this._fillEnabled || this._outlineEnabled) { + this._fillEnabled = false; + this._outlineEnabled = false; + this._geometryChanged.raiseEvent(this); } + return; } - } - - function updateShowBoundingVolume(model) { - if (model.debugShowBoundingVolume !== model._debugShowBoundingVolume) { - model._debugShowBoundingVolume = model.debugShowBoundingVolume; - var debugShowBoundingVolume = model.debugShowBoundingVolume; - var nodeCommands = model._nodeCommands; - var length = nodeCommands.length; + var positions = wall.positions; - for (var i = 0; i < length; ++i) { - nodeCommands[i].command.debugShowBoundingVolume = debugShowBoundingVolume; + 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; } - } - function updateShadows(model) { - if (model.shadows !== model._shadows) { - model._shadows = model.shadows; + 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 castShadows = ShadowMode.castShadows(model.shadows); - var receiveShadows = ShadowMode.receiveShadows(model.shadows); - var nodeCommands = model._nodeCommands; - var length = nodeCommands.length; + var minimumHeights = wall.minimumHeights; + var maximumHeights = wall.maximumHeights; + var outlineWidth = wall.outlineWidth; + var granularity = wall.granularity; - for (var i = 0; i < length; i++) { - var nodeCommand = nodeCommands[i]; - nodeCommand.command.castShadows = castShadows; - nodeCommand.command.receiveShadows = receiveShadows; + 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); } - } - - 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); - } + /** + * 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) { + + return new DynamicGeometryUpdater(primitives, this); + }; - function deriveTranslucentCommand(command) { - var translucentCommand = DrawCommand.shallowClone(command); - translucentCommand.pass = Pass.TRANSLUCENT; - translucentCommand.renderState = getTranslucentRenderState(command.renderState); - return translucentCommand; + /** + * @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) { + + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + this._primitive = undefined; + this._outlinePrimitive = undefined; - 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); - } - } - } + 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; } - } - function getProgramId(model, program) { - var programs = model._rendererResources.programs; - for (var id in programs) { - if (programs.hasOwnProperty(id)) { - if (programs[id] === program) { - return id; - } - } + var options = this._options; + var positions = Property.getValueOrUndefined(wall.positions, time, options.positions); + if (!defined(positions)) { + return; } - } - 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' + - '}'; + 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 fs = - 'uniform vec4 gltf_silhouetteColor; \n' + - 'void main() \n' + - '{ \n' + - ' gl_FragColor = gltf_silhouetteColor; \n' + - '}'; + var shadows = this._geometryUpdater.shadowsProperty.getValue(time); - return ShaderProgram.fromCache({ - context : frameState.context, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } + if (Property.getValueOrDefault(wall.fill, time, true)) { + var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); + this._material = material; - function hasSilhouette(model, frameState) { - return silhouetteSupported(frameState.context) && (model.silhouetteSize > 0.0) && (model.silhouetteColor.alpha > 0.0) && defined(model._normalAttributeName); - } + var appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : defined(options.extrudedHeight) + }); + options.vertexFormat = appearance.vertexFormat; - 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; - } + this._primitive = primitives.add(new Primitive({ + geometryInstances : new GeometryInstance({ + id : entity, + geometry : new WallGeometry(options) + }), + appearance : appearance, + asynchronous : false, + shadows : shadows + })); } - return false; - } - function isTranslucent(model) { - return (model.color.alpha > 0.0) && (model.color.alpha < 1.0); - } + if (Property.getValueOrDefault(wall.outline, time, false)) { + options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; - function isInvisible(model) { - return (model.color.alpha === 0.0); - } + 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; - 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)); - } + 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 + })); + } + }; - var silhouettesLength = 0; + DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { + return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); + }; - 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; + DynamicGeometryUpdater.prototype.isDestroyed = function() { + return false; + }; - // 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; + DynamicGeometryUpdater.prototype.destroy = function() { + var primitives = this._primitives; + primitives.removeAndDestroy(this._primitive); + primitives.removeAndDestroy(this._outlinePrimitive); + destroyObject(this); + }; - // Create model command - var modelCommand = isTranslucent(model) ? nodeCommand.translucentCommand : command; - var silhouetteModelCommand = DrawCommand.shallowClone(modelCommand); - var renderState = clone(modelCommand.renderState); + return WallGeometryUpdater; +}); - // 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 - } - }; +define('DataSources/DataSourceDisplay',[ + '../Core/BoundingSphere', + '../Core/Check', + '../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, + Check, + 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'; - 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; + /** + * 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) { + + GroundPrimitive.initializeTerrainHeights(); - // 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; - } + var scene = options.scene; + var dataSourceCollection = options.dataSourceCollection; - // 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); + this._eventHelper = new EventHelper(); + this._eventHelper.add(dataSourceCollection.dataSourceAdded, this._onDataSourceAdded, this); + this._eventHelper.add(dataSourceCollection.dataSourceRemoved, this._onDataSourceRemoved, this); - // 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; - } + this._dataSourceCollection = dataSourceCollection; + this._scene = scene; + this._visualizersCallback = defaultValue(options.visualizersCallback, DataSourceDisplay.defaultVisualizersCallback); - var silhouetteUniformMap = combine(command.uniformMap, { - gltf_silhouetteColor : createSilhouetteColorFunction(model), - gltf_silhouetteSize : createSilhouetteSizeFunction(model) - }); + for (var i = 0, len = dataSourceCollection.length; i < len; i++) { + this._onDataSourceAdded(dataSourceCollection, dataSourceCollection.get(i)); + } - silhouetteColorCommand.renderState = renderState; - silhouetteColorCommand.shaderProgram = silhouetteProgram; - silhouetteColorCommand.uniformMap = silhouetteUniformMap; - silhouetteColorCommand.castShadows = false; - silhouetteColorCommand.receiveShadows = false; - nodeCommand.silhouetteColorCommand = silhouetteColorCommand; + var defaultDataSource = new CustomDataSource(); + this._onDataSourceAdded(undefined, defaultDataSource); + this._defaultDataSource = defaultDataSource; - if (!scene3DOnly) { - var command2D = nodeCommand.command2D; - var silhouetteModelCommand2D = DrawCommand.shallowClone(silhouetteModelCommand); - silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume; - silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix; - nodeCommand.silhouetteModelCommand2D = silhouetteModelCommand2D; + this._ready = false; + } - var silhouetteColorCommand2D = DrawCommand.shallowClone(silhouetteColorCommand); - silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume; - silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix; - nodeCommand.silhouetteColorCommand2D = silhouetteColorCommand2D; + /** + * 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; + } + }, - 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; + /** + * 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; + } } + }); - var nodeCommands = model._nodeCommands; - var dirty = alphaDirty(model.color.alpha, model._colorPreviousAlpha) || - alphaDirty(model.silhouetteColor.alpha, model._silhouetteColorPreviousAlpha) || - !defined(nodeCommands[0].silhouetteModelCommand); + /** + * 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; + }; - model._colorPreviousAlpha = model.color.alpha; - model._silhouetteColorPreviousAlpha = model.silhouetteColor.alpha; + /** + * 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(); - if (dirty) { - createSilhouetteCommands(model, frameState); + var dataSourceCollection = this._dataSourceCollection; + for (var i = 0, length = dataSourceCollection.length; i < length; ++i) { + this._onDataSourceRemoved(this._dataSourceCollection, dataSourceCollection.get(i)); } - } - - 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); - } + this._onDataSourceRemoved(undefined, this._defaultDataSource); - var scratchPosition = new Cartesian3(); - var scratchCartographic = new Cartographic(); + return destroyObject(this); + }; - function getScale(model, frameState) { - var scale = model.scale; + /** + * 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 (!GroundPrimitive._initialized) { + this._ready = false; + return false; + } - 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]; + var result = true; - if (defined(model._rtcCenter)) { - Cartesian3.add(model._rtcCenter, scratchPosition, scratchPosition); + 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; } - 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); + visualizers = dataSource._visualizers; + vLength = visualizers.length; + for (x = 0; x < vLength; x++) { + result = visualizers[x].update(time) && result; } + } - var radius = model.boundingSphere.radius; - var metersPerPixel = scaleInPixels(scratchPosition, radius, frameState); + visualizers = this._defaultDataSource._visualizers; + vLength = visualizers.length; + for (x = 0; x < vLength; x++) { + result = visualizers[x].update(time) && result; + } - // metersPerPixel is always > 0.0 - var pixelsPerMeter = 1.0 / metersPerPixel; - var diameterInPixels = Math.min(pixelsPerMeter * (2.0 * radius), maxPixelSize); + this._ready = result; - // Maintain model's minimum pixel size - if (diameterInPixels < model.minimumPixelSize) { - scale = (model.minimumPixelSize * metersPerPixel) / (2.0 * model._initialRadius); - } - } + return result; + }; - return defined(model.maximumScale) ? Math.min(model.maximumScale, scale) : scale; - } + var getBoundingSphereArrayScratch = []; + var getBoundingSphereBoundingSphereScratch = new BoundingSphere(); - function releaseCachedGltf(model) { - if (defined(model._cacheKey) && defined(model._cachedGltf) && (--model._cachedGltf.count === 0)) { - delete gltfCache[model._cacheKey]; + /** + * 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 (!this._ready) { + return BoundingSphereState.PENDING; } - model._cachedGltf = undefined; - } - function checkSupportedExtensions(model) { - var extensionsUsed = model.gltf.extensionsUsed; - if (defined(extensionsUsed)) { - var extensionsUsedCount = extensionsUsed.length; - for (var index = 0; index < extensionsUsedCount; ++index) { - var extension = extensionsUsed[index]; + var i; + var length; + var dataSource = this._defaultDataSource; + if (!dataSource.entities.contains(entity)) { + dataSource = undefined; - if (extension !== 'CESIUM_RTC' && extension !== 'KHR_binary_glTF' && - extension !== 'KHR_materials_common' && extension !== 'WEB3D_quantized_attributes') { - throw new RuntimeError('Unsupported glTF Extension: ' + extension); + 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; + } - function CachedRendererResources(context, cacheKey) { - this.buffers = undefined; - this.vertexArrays = undefined; - this.programs = undefined; - this.pickPrograms = undefined; - this.silhouettePrograms = undefined; - this.textures = undefined; - this.samplers = undefined; - this.renderStates = undefined; - this.ready = false; + var boundingSpheres = getBoundingSphereArrayScratch; + var tmp = getBoundingSphereBoundingSphereScratch; - this.context = context; - this.cacheKey = cacheKey; - this.count = 0; - } + var count = 0; + var state = BoundingSphereState.DONE; + var visualizers = dataSource._visualizers; + var visualizersLength = visualizers.length; - function destroy(property) { - for (var name in property) { - if (property.hasOwnProperty(name)) { - property[name].destroy(); + 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++; + } } } - } - - function destroyCachedRendererResources(resources) { - destroy(resources.buffers); - destroy(resources.vertexArrays); - destroy(resources.programs); - destroy(resources.pickPrograms); - destroy(resources.silhouettePrograms); - destroy(resources.textures); - } - CachedRendererResources.prototype.release = function() { - if (--this.count === 0) { - if (defined(this.cacheKey)) { - // Remove if this was cached - delete this.context.cache.modelRendererResourceCache[this.cacheKey]; - } - destroyCachedRendererResources(this); - return destroyObject(this); + if (count === 0) { + return BoundingSphereState.FAILED; } - return undefined; + boundingSpheres.length = count; + BoundingSphere.fromBoundingSpheres(boundingSpheres, result); + return BoundingSphereState.DONE; }; - /////////////////////////////////////////////////////////////////////////// - - function getUpdateHeightCallback(model, ellipsoid, cartoPosition) { - return function (clampedPosition) { - if (model.heightReference === HeightReference.RELATIVE_TO_GROUND) { - var clampedCart = ellipsoid.cartesianToCartographic(clampedPosition, scratchCartographic); - clampedCart.height += cartoPosition.height; - ellipsoid.cartographicToCartesian(clampedCart, clampedPosition); - } + DataSourceDisplay.prototype._onDataSourceAdded = function(dataSourceCollection, dataSource) { + var scene = this._scene; - var clampedModelMatrix = model._clampedModelMatrix; + var entityCluster = dataSource.clustering; + entityCluster._initialize(scene); - // Modify clamped model matrix to use new height - Matrix4.clone(model.modelMatrix, clampedModelMatrix); - clampedModelMatrix[12] = clampedPosition.x; - clampedModelMatrix[13] = clampedPosition.y; - clampedModelMatrix[14] = clampedPosition.z; + scene.primitives.add(entityCluster); - model._heightChanged = true; - }; - } + dataSource._visualizers = this._visualizersCallback(scene, entityCluster, dataSource); + }; - function updateClamping(model) { - if (defined(model._removeUpdateHeightCallback)) { - model._removeUpdateHeightCallback(); - model._removeUpdateHeightCallback = undefined; - } + DataSourceDisplay.prototype._onDataSourceRemoved = function(dataSourceCollection, dataSource) { + var scene = this._scene; + var entityCluster = dataSource.clustering; + scene.primitives.remove(entityCluster); - var scene = model._scene; - if (!defined(scene) || (model.heightReference === HeightReference.NONE)) { - model._clampedModelMatrix = undefined; - return; + var visualizers = dataSource._visualizers; + var length = visualizers.length; + for (var i = 0; i < length; i++) { + visualizers[i].destroy(); } - var globe = scene.globe; - var ellipsoid = globe.ellipsoid; - - // Compute cartographic position so we don't recompute every update - var modelMatrix = model.modelMatrix; - scratchPosition.x = modelMatrix[12]; - scratchPosition.y = modelMatrix[13]; - scratchPosition.z = modelMatrix[14]; - var cartoPosition = ellipsoid.cartesianToCartographic(scratchPosition); + dataSource._visualizers = undefined; + }; - if (!defined(model._clampedModelMatrix)) { - model._clampedModelMatrix = Matrix4.clone(modelMatrix, new Matrix4()); - } + /** + * 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)]; + * } + */ - // Install callback to handle updating of terrain tiles - var surface = globe._surface; - model._removeUpdateHeightCallback = surface.updateHeight(cartoPosition, getUpdateHeightCallback(model, ellipsoid, cartoPosition)); + return DataSourceDisplay; +}); - // Set the correct height now - var height = globe.getHeight(cartoPosition); - if (defined(height)) { - // Get callback with cartoPosition being the non-clamped position - var cb = getUpdateHeightCallback(model, ellipsoid, cartoPosition); +define('DataSources/DynamicGeometryUpdater',[ + '../Core/DeveloperError' + ], function( + DeveloperError) { + 'use strict'; - // Compute the clamped cartesian and call updateHeight callback - Cartographic.clone(cartoPosition, scratchCartographic); - scratchCartographic.height = height; - ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition); - cb(scratchPosition); - } + /** + * 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(); } - var scratchDisplayConditionCartesian = new Cartesian3(); - var scratchDistanceDisplayConditionCartographic = new Cartographic(); - - function distanceDisplayConditionVisible(model, frameState) { - var distance2; - var ddc = model.distanceDisplayCondition; - var nearSquared = ddc.near * ddc.near; - var farSquared = ddc.far * ddc.far; + /** + * Updates the geometry to the specified time. + * @memberof DynamicGeometryUpdater + * @function + * + * @param {JulianDate} time The current time. + */ + DynamicGeometryUpdater.prototype.update = DeveloperError.throwInstantiationError; - if (frameState.mode === SceneMode.SCENE2D) { - var frustum2DWidth = frameState.camera.frustum.right - frameState.camera.frustum.left; - distance2 = frustum2DWidth * 0.5; - distance2 = distance2 * distance2; - } else { - // Distance to center of primitive's reference frame - var position = Matrix4.getTranslation(model.modelMatrix, scratchDisplayConditionCartesian); - if (frameState.mode === SceneMode.COLUMBUS_VIEW) { - var projection = frameState.mapProjection; - var ellipsoid = projection.ellipsoid; - var cartographic = ellipsoid.cartesianToCartographic(position, scratchDistanceDisplayConditionCartographic); - position = projection.project(cartographic, position); - Cartesian3.fromElements(position.z, position.x, position.y, position); - } - distance2 = Cartesian3.distanceSquared(position, frameState.camera.positionWC); - } + /** + * 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; - return (distance2 >= nearSquared) && (distance2 <= farSquared); - } + /** + * 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; /** - * 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: - *

    + * Destroys and resources used by the object. Once an object is destroyed, it should not be used. + * @memberof DynamicGeometryUpdater + * @function * - * @exception {RuntimeError} Failed to load external reference. + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. */ - Model.prototype.update = function(frameState) { - if (frameState.mode === SceneMode.MORPHING) { - return; - } + DynamicGeometryUpdater.prototype.destroy = DeveloperError.throwInstantiationError; - var context = frameState.context; - this._defaultTexture = context.defaultTexture; + return DynamicGeometryUpdater; +}); - 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; +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'; - 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; - } + 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 - ++cachedRendererResources.count; - this._loadRendererResourcesFromCache = true; - } else { - cachedRendererResources = new CachedRendererResources(context, cacheKey); - cachedRendererResources.count = 1; - modelCaches[this.cacheKey] = cachedRendererResources; + 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 invertVelocity = 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. + JulianDate.addSeconds(time, 0.001, deltaTime); + var deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1); + + // If no valid position at (time + 0.001), sample at (time - 0.001) and invert the vector + if (!defined(deltaCartesian)) { + JulianDate.addSeconds(time, -0.001, deltaTime); + deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1); + invertVelocity = true; } - this._cachedRendererResources = cachedRendererResources; - } else { - cachedRendererResources = new CachedRendererResources(context); - cachedRendererResources.count = 1; - this._cachedRendererResources = cachedRendererResources; - } - this._state = ModelState.LOADING; + if (defined(deltaCartesian)) { + var toInertial = Transforms.computeFixedToIcrfMatrix(time, updateTransformMatrix3Scratch1); + var toInertialDelta = Transforms.computeFixedToIcrfMatrix(deltaTime, updateTransformMatrix3Scratch2); + var toFixed; - this._boundingSphere = computeBoundingSphere(this, this.gltf); - this._initialRadius = this._boundingSphere.radius; + 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); + } - checkSupportedExtensions(this); - if (this._state !== ModelState.FAILED) { - var extensions = this.gltf.extensions; - if (defined(extensions) && defined(extensions.CESIUM_RTC)) { - var center = Cartesian3.fromArray(extensions.CESIUM_RTC.center); - if (!Cartesian3.equals(center, Cartesian3.ZERO)) { - this._rtcCenter3D = center; + var inertialCartesian = Matrix3.multiplyByVector(toInertial, cartesian, updateTransformCartesian3Scratch5); + var inertialDeltaCartesian = Matrix3.multiplyByVector(toInertialDelta, deltaCartesian, updateTransformCartesian3Scratch6); - var projection = frameState.mapProjection; - var ellipsoid = projection.ellipsoid; - var cartographic = ellipsoid.cartesianToCartographic(this._rtcCenter3D); - var projectedCart = projection.project(cartographic); - Cartesian3.fromElements(projectedCart.z, projectedCart.x, projectedCart.y, projectedCart); - this._rtcCenter2D = projectedCart; + Cartesian3.subtract(inertialCartesian, inertialDeltaCartesian, updateTransformCartesian3Scratch4); + var inertialVelocity = Cartesian3.magnitude(updateTransformCartesian3Scratch4) * 1000.0; // meters/sec - this._rtcCenterEye = new Cartesian3(); - this._rtcCenter = this._rtcCenter3D; - } - } + // http://en.wikipedia.org/wiki/Standard_gravitational_parameter + // Consider adding this to Cesium.Ellipsoid? + var mu = 3.986004418e14; // m^3 / sec^2 - this._loadResources = new LoadResources(); - parse(this, context); - } - } + var semiMajorAxis = -mu / (inertialVelocity * inertialVelocity - (2 * mu / Cartesian3.magnitude(inertialCartesian))); - var loadResources = this._loadResources; - var incrementallyLoadTextures = this._incrementallyLoadTextures; - var justLoaded = false; + if (semiMajorAxis < 0 || semiMajorAxis > northUpAxisFactor * ellipsoid.maximumRadius) { + // North-up viewing from deep space. - if (this._state === ModelState.LOADING) { - // Create WebGL resources as buffers/shaders/textures are downloaded - createResources(this, frameState); + // X along the nadir + xBasis = updateTransformCartesian3Scratch2; + Cartesian3.normalize(cartesian, xBasis); + Cartesian3.negate(xBasis, xBasis); - // 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; - } - } + // Z is North + zBasis = Cartesian3.clone(Cartesian3.UNIT_Z, updateTransformCartesian3Scratch3); - // 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); - } + // 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); - if (loadResources.finished()) { - this._loadResources = undefined; // Clear CPU memory since WebGL resources were created. + zBasis = Cartesian3.cross(xBasis, yBasis, updateTransformCartesian3Scratch3); + Cartesian3.normalize(zBasis, zBasis); - var resources = this._rendererResources; - var cachedResources = this._cachedRendererResources; + hasBasis = true; + } + } else if (!Cartesian3.equalsEpsilon(cartesian, deltaCartesian, CesiumMath.EPSILON7)) { + // Approximation of VVLH (Vehicle Velocity Local Horizontal) with the Z-axis flipped. - 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; + // Z along the position + zBasis = updateTransformCartesian3Scratch2; + Cartesian3.normalize(inertialCartesian, zBasis); + Cartesian3.normalize(inertialDeltaCartesian, inertialDeltaCartesian); - // The normal attribute name is required for silhouettes, so get it before the gltf JSON is released - this._normalAttributeName = getAttributeOrUniformBySemantic(this.gltf, 'NORMAL'); + // Y is along the angular momentum vector (e.g. "orbit normal") + yBasis = Cartesian3.cross(zBasis, inertialDeltaCartesian, updateTransformCartesian3Scratch3); - // Vertex arrays are unique to this model, do not store in cache. - if (defined(this._precreatedAttributes)) { - cachedResources.vertexArrays = {}; - } + if(invertVelocity) { + yBasis = Cartesian3.multiplyByScalar(yBasis, -1, yBasis); + } - if (this.releaseGltfJson) { - releaseCachedGltf(this); + 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; + } + } } } - } - 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 (defined(that.boundingSphere)) { + cartesian = that.boundingSphere.center; + } - 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 position; + var direction; + var up; - var modeChanged = frameState.mode !== this._mode; - this._mode = frameState.mode; + if (saveCamera) { + position = Cartesian3.clone(camera.position, updateTransformCartesian3Scratch4); + direction = Cartesian3.clone(camera.direction, updateTransformCartesian3Scratch5); + up = Cartesian3.clone(camera.up, updateTransformCartesian3Scratch6); + } - // 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; + 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); + } - if (modelTransformChanged || justLoaded) { - Matrix4.clone(modelMatrix, this._modelMatrix); + camera._setTransform(transform); - updateClamping(this); + if (saveCamera) { + Cartesian3.clone(position, camera.position); + Cartesian3.clone(direction, camera.direction); + Cartesian3.clone(up, camera.up); + Cartesian3.cross(direction, up, camera.right); + } + } - if (defined(this._clampedModelMatrix)) { - modelMatrix = this._clampedModelMatrix; - } + if (updateLookAt) { + var offset = (mode === SceneMode.SCENE2D || Cartesian3.equals(that._offset3D, Cartesian3.ZERO)) ? undefined : that._offset3D; + camera.lookAtTransform(camera.transform, offset); + } + } - this._scale = this.scale; - this._minimumPixelSize = this.minimumPixelSize; - this._maximumScale = this.maximumScale; - this._heightReference = this.heightReference; - this._heightChanged = false; + /** + * 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) { - var scale = getScale(this, frameState); - var computedModelMatrix = this._computedModelMatrix; - Matrix4.multiplyByUniformScale(modelMatrix, scale, computedModelMatrix); - if (this._upAxis === Axis.Y) { - Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix); - } else if (this._upAxis === Axis.X) { - Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix); - } - } + /** + * The entity to track with the camera. + * @type {Entity} + */ + this.entity = entity; - // Update modelMatrix throughout the graph as needed - if (animated || modelTransformChanged || justLoaded) { - updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection); - this._dirty = true; + /** + * The scene in which to track the object. + * @type {Scene} + */ + this.scene = scene; - if (animated || justLoaded) { - // Apply skins if animation changed any node transforms - applySkins(this); - } - } + /** + * The ellipsoid to use for orienting the camera. + * @type {Ellipsoid} + */ + this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - if (this._perNodeShowDirty) { - this._perNodeShowDirty = false; - updatePerNodeShow(this); + /** + * 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()); } - 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); - }); + // 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} boundingSphere bounding sphere of the object. + * + */ + EntityView.prototype.update = function(time, boundingSphere) { + var scene = this.scene; + var entity = this.entity; + var ellipsoid = this.ellipsoid; + + + var sceneMode = scene.mode; + if (sceneMode === SceneMode.MORPHING) { 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 passes = frameState.passes; - var nodeCommands = this._nodeCommands; - var length = nodeCommands.length; - var i; - var nc; + var positionProperty = entity.position; + var objectChanged = entity !== this._lastEntity; + var sceneModeChanged = sceneMode !== this._mode; - var idl2D = frameState.mapProjection.ellipsoid.maximumRadius * CesiumMath.PI; - var boundingVolume; + var offset3D = this._offset3D; + var camera = scene.camera; - 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; - frameState.addCommand(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; - frameState.addCommand(command2D); - } - } - } + var updateLookAt = objectChanged || sceneModeChanged; + var saveCamera = true; - if (silhouette) { - // Render second silhouette pass - for (i = 0; i < length; ++i) { - nc = nodeCommands[i]; - if (nc.show) { - frameState.addCommand(nc.silhouetteColorCommand); - boundingVolume = nc.command.boundingVolume; - if (frameState.mode === SceneMode.SCENE2D && - (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) { - frameState.addCommand(nc.silhouetteColorCommand2D); - } - } - } + if (objectChanged) { + var viewFromProperty = entity.viewFrom; + var hasViewFrom = defined(viewFromProperty); + + if (!hasViewFrom && defined(boundingSphere)) { + //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); + } - if (passes.pick && this.allowPicking) { - for (i = 0; i < length; ++i) { - nc = nodeCommands[i]; - if (nc.show) { - var pickCommand = nc.pickCommand; - frameState.addCommand(pickCommand); + this._lastEntity = entity; + this._mode = scene.mode !== SceneMode.MORPHING ? scene.mode : this._mode; - boundingVolume = pickCommand.boundingVolume; - if (frameState.mode === SceneMode.SCENE2D && - (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) { - frameState.addCommand(nc.pickCommand2D); - } - } - } - } + if (scene.mode !== SceneMode.MORPHING) { + updateTransform(this, camera, updateLookAt, saveCamera, positionProperty, time, 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 Model#destroy - */ - Model.prototype.isDestroyed = function() { - return false; - }; + return EntityView; +}); - /** - * 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); - } +/** +@license +topojson - https://github.com/mbostock/topojson - this._rendererResources = undefined; - this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release(); +Copyright (c) 2012, Michael Bostock +All rights reserved. - var pickIds = this._pickIds; - var length = pickIds.length; - for (var i = 0; i < length; ++i) { - pickIds[i].destroy(); - } +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: - releaseCachedGltf(this); +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. - return destroyObject(this); - }; +* 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. - return Model; -}); +* The name Michael Bostock may not be used to endorse or promote products + derived from this software without specific prior written permission. -/*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'; +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. +*/ - 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; +!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 + }; - var modelMatrixScratch = new Matrix4(); - var nodeMatrixScratch = new Matrix4(); + function stitchArcs(topology, arcs) { + var stitchedArcs = {}, + fragmentByStart = {}, + fragmentByEnd = {}, + fragments = [], + emptyIndex = -1; - /** - * 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) { - - entityCollection.collectionChanged.addEventListener(ModelVisualizer.prototype._onCollectionChanged, this); + // 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; + } + }); - this._scene = scene; - this._primitives = scene.primitives; - this._entityCollection = entityCollection; - this._modelHash = {}; - this._entitiesToVisualize = new AssociativeArray(); - this._onCollectionChanged(entityCollection, entityCollection.values, [], []); - } + arcs.forEach(function(i) { + var e = ends(i), + start = e[0], + end = e[1], + f, g; - /** - * 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) { - - var entities = this._entitiesToVisualize.values; - var modelHash = this._modelHash; - var primitives = this._primitives; + 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; + } + }); - for (var i = 0, len = entities.length; i < len; i++) { - var entity = entities[i]; - var modelGraphics = entity._model; + 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]; + } - var uri; - var modelData = modelHash[entity.id]; - var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(modelGraphics._show, time, true); + 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); + } + } - var modelMatrix; - if (show) { - modelMatrix = entity._getModelMatrix(time, modelMatrixScratch); - uri = Property.getValueOrUndefined(modelGraphics._uri, time); - show = defined(modelMatrix) && defined(uri); - } + flush(fragmentByEnd, fragmentByStart); + flush(fragmentByStart, fragmentByEnd); + arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); }); - if (!show) { - if (defined(modelData)) { - modelData.modelPrimitive.show = false; - } - continue; - } + return fragments; + } - 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 - }); + function meshArcs(topology, o, filter) { + var arcs = []; - model.readyPromise.otherwise(onModelError); + if (arguments.length > 1) { + var geomsByArc = [], + geom; - model.id = entity; - primitives.add(model); + function arc(i) { + var j = i < 0 ? ~i : i; + (geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom}); + } - modelData = { - modelPrimitive : model, - uri : uri, - animationsRunning : false, - nodeTransformationsScratch : {}, - originalNodeMatrixHash : {} - }; - modelHash[entity.id] = modelData; - } + function line(arcs) { + arcs.forEach(arc); + } - 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._silhouetteColor); - model.silhouetteSize = Property.getValueOrDefault(modelGraphics._silhouetteSize, time, defaultSilhouetteSize); - model.color = Property.getValueOrDefault(modelGraphics._color, time, defaultColor, model._color); - model.colorBlendMode = Property.getValueOrDefault(modelGraphics._colorBlendMode, time, defaultColorBlendMode); - model.colorBlendAmount = Property.getValueOrDefault(modelGraphics._colorBlendAmount, time, defaultColorBlendAmount); + function polygon(arcs) { + arcs.forEach(line); + } - 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; - } + function geometry(o) { + if (o.type === "GeometryCollection") o.geometries.forEach(geometry); + else if (o.type in geometryType) geom = o, geometryType[o.type](o.arcs); + } - // 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 geometryType = { + LineString: line, + MultiLineString: polygon, + Polygon: polygon, + MultiPolygon: function(arcs) { arcs.forEach(polygon); } + }; - var nodeTransformation = nodeTransformations[nodeName]; - if (!defined(nodeTransformation)) { - continue; - } + geometry(o); - var modelNode = model.getNode(nodeName); - if (!defined(modelNode)) { - continue; - } + 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); + } - var originalNodeMatrix = originalNodeMatrixHash[nodeName]; - if (!defined(originalNodeMatrix)) { - originalNodeMatrix = modelNode.matrix.clone(); - originalNodeMatrixHash[nodeName] = originalNodeMatrix; - } + return {type: "MultiLineString", arcs: stitchArcs(topology, arcs)}; + } - var transformationMatrix = Matrix4.fromTranslationRotationScale(nodeTransformation, nodeMatrixScratch); - modelNode.matrix = Matrix4.multiply(originalNodeMatrix, transformationMatrix, transformationMatrix); - } - } - } - } + function mergeArcs(topology, objects) { + var polygonsByArc = {}, + polygons = [], + components = []; - return true; - }; + objects.forEach(function(o) { + if (o.type === "Polygon") register(o.arcs); + else if (o.type === "MultiPolygon") o.arcs.forEach(register); + }); - /** - * 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; - }; + function register(polygon) { + polygon.forEach(function(ring) { + ring.forEach(function(arc) { + (polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon); + }); + }); + polygons.push(polygon); + } - /** - * 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); - }; + function exterior(ring) { + return cartesianRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]) > 0; // TODO allow spherical? + } - /** - * 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) { - - var modelData = this._modelHash[entity.id]; - if (!defined(modelData)) { - return BoundingSphereState.FAILED; + 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); + } + }); + }); + }); } + } + }); - var model = modelData.modelPrimitive; - if (!defined(model) || !model.show) { - return BoundingSphereState.FAILED; - } + polygons.forEach(function(polygon) { + delete polygon._; + }); - if (!model.ready) { - return BoundingSphereState.PENDING; - } + return { + type: "MultiPolygon", + arcs: components.map(function(polygons) { + var arcs = []; - 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; - }; + // 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); + } + }); + }); + }); - /** - * @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; + // Stitch the arcs into one or more rings. + arcs = stitchArcs(topology, arcs); - for (i = added.length - 1; i > -1; i--) { - entity = added[i]; - if (defined(entity._model) && defined(entity._position)) { - entities.set(entity.id, entity); + // 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; } + } } - 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); - } - } + return arcs; + }) + }; + } - for (i = removed.length - 1; i > -1; i--) { - entity = removed[i]; - removeModel(this, entity, modelHash, primitives); - entities.remove(entity.id); - } + 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 removeModel(visualizer, entity, modelHash, primitives) { - var modelData = modelHash[entity.id]; - if (defined(modelData)) { - primitives.removeAndDestroy(modelData.modelPrimitive); - delete modelHash[entity.id]; - } - } + function object(topology, o) { + var absolute = transformAbsolute(topology.transform), + arcs = topology.arcs; - function clearNodeTransformationsScratch(entity, modelHash) { - var modelData = modelHash[entity.id]; - if (defined(modelData)) { - modelData.nodeTransformationsScratch = {}; - } + 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 onModelError(error) { - console.error(error); + function point(p) { + p = p.slice(); + absolute(p, 0); + return p; } - return ModelVisualizer; -}); - -//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\ -\n\ -varying vec2 v_st;\n\ -varying float v_width;\n\ -varying vec4 czm_pickColor;\n\ -varying float v_angle;\n\ -\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\ -\n\ - vec2 widthAndShow = batchTable_getWidthAndShow(batchTableIndex);\n\ - float width = widthAndShow.x + 0.5;\n\ - float show = widthAndShow.y;\n\ -\n\ - if (width < 1.0)\n\ - {\n\ - show = 0.0;\n\ - }\n\ -\n\ - vec4 pickColor = batchTable_getPickColor(batchTableIndex);\n\ -\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\ -\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\ -\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\ -\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\ -\n\ - vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev, v_angle);\n\ - gl_Position = czm_viewportOrthographic * positionWC * show;\n\ -\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. - * @param {PolylineCollection} polylineCollection The renderable polyline collection. - * - * @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; + 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; + } - 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) - }); - } + function ring(arcs) { + var points = line(arcs); + while (points.length < 4) points.push(points[0].slice()); + return points; + } - var positions = options.positions; - if (!defined(positions)) { - positions = []; - } + function polygon(arcs) { + return arcs.map(ring); + } - this._positions = positions; - this._actualPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon); + 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; + } - if (this._loop && this._actualPositions.length > 2) { - if (this._actualPositions === this._positions) { - this._actualPositions = positions.slice(); - } - this._actualPositions.push(Cartesian3.clone(this._actualPositions[0])); - } + 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); } + }; - this._length = this._actualPositions.length; - this._id = options.id; + return geometry(o); + } - var modelMatrix; - if (defined(polylineCollection)) { - modelMatrix = Matrix4.clone(polylineCollection.modelMatrix); - } + 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; + } - this._modelMatrix = modelMatrix; - this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix); + 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; + } - this._actualLength = undefined; + function neighbors(objects) { + var indexesByArc = {}, // arc index -> array of object indexes + neighbors = objects.map(function() { return []; }); - 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 + 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]; + }); } - 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; - } + function polygon(arcs, i) { + arcs.forEach(function(arc) { line(arc, i); }); } - defineProperties(Polyline.prototype, { + 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); + } - /** - * 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 (value !== this._show) { - this._show = value; - makeDirty(this, SHOW_INDEX); - } - } - }, + var geometryType = { + LineString: line, + MultiLineString: polygon, + Polygon: polygon, + MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); } + }; - /** - * 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) { - - var positions = arrayRemoveDuplicates(value, Cartesian3.equalsEpsilon); + objects.forEach(geometry); - if (this._loop && positions.length > 2) { - if (positions === value) { - positions = value.slice(); - } - positions.push(Cartesian3.clone(positions[0])); - } + 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); + } + } + } - if (this._actualPositions.length !== positions.length || this._actualPositions.length !== this._length) { - makeDirty(this, POSITION_SIZE_INDEX); - } + return neighbors; + } - 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); + function presimplify(topology, triangleArea) { + var absolute = transformAbsolute(topology.transform), + relative = transformRelative(topology.transform), + heap = minAreaHeap(); - this.update(); - } - }, + if (!triangleArea) triangleArea = cartesianTriangleArea; - /** - * 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 (this._material !== material) { - this._material = material; - makeDirty(this, MATERIAL_INDEX); - } - } - }, + topology.arcs.forEach(function(arc) { + var triangles = [], + maxArea = 0, + triangle; - /** - * Gets or sets the width of the polyline. - * @memberof Polyline.prototype - * @type {Number} - */ - width: { - get: function() { - return this._width; - }, - set: function(value) { - - var width = this._width; - if (value !== width) { - this._width = value; - makeDirty(this, WIDTH_INDEX); - } - } - }, + // 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); + } - /** - * 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 (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(); - } - } - } + 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); + } - this._loop = value; - makeDirty(this, POSITION_SIZE_INDEX); - } - } - }, + for (var i = 0, n = triangles.length; i < n; ++i) { + triangle = triangles[i]; + triangle.previous = triangles[i - 1]; + triangle.next = triangles[i + 1]; + } - /** - * 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; - } - } - }, + while (triangle = heap.pop()) { + var previous = triangle.previous, + next = triangle.next; - /** - * 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 (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) { - this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition); - makeDirty(this, DISTANCE_DISPLAY_CONDITION); - } - } + // 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); } - }); - /** - * @private - */ - Polyline.prototype.update = function() { - var modelMatrix = Matrix4.IDENTITY; - if (defined(this._polylineCollection)) { - modelMatrix = this._polylineCollection.modelMatrix; + if (next) { + next.previous = previous; + next[0] = triangle[0]; + update(next); } + } - var segmentPositionsLength = this._segments.positions.length; - var segmentLengths = this._segments.lengths; + arc.forEach(relative); + }); - 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); - } + function update(triangle) { + heap.remove(triangle); + triangle[1][2] = triangleArea(triangle); + heap.push(triangle); + } - this._modelMatrix = Matrix4.clone(modelMatrix, this._modelMatrix); + return topology; + } + function cartesianRingArea(ring) { + var i = -1, + n = ring.length, + a, + b = ring[n - 1], + area = 0; - 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; - } - } - } + 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; }; - /** - * @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; + 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; }; - Polyline.prototype._clean = function() { - this._dirty = false; - var properties = this._propertiesChanged; - for ( var k = 0; k < NUMBER_OF_PROPERTIES - 1; ++k) { - properties[k] = 0; - } + 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; }; - Polyline.prototype._destroy = function() { - this._pickId = this._pickId && this._pickId.destroy(); - this._material = this._material && this._material.destroy(); - this._polylineCollection = undefined; + 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; }; + } - return Polyline; -}); + 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; + }; + } -/*global define*/ -define('Scene/PolylineCollection',[ - '../Core/BoundingSphere', - '../Core/Cartesian2', + 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; +}(); + +define('DataSources/GeoJsonDataSource',[ '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/Cartographic', '../Core/Color', - '../Core/ComponentDatatype', + '../Core/createGuid', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/destroyObject', '../Core/DeveloperError', - '../Core/EncodedCartesian3', - '../Core/IndexDatatype', - '../Core/Intersect', - '../Core/Math', - '../Core/Matrix4', - '../Core/Plane', + '../Core/Event', + '../Core/getFilenameFromUri', + '../Core/loadJson', + '../Core/PinBuilder', + '../Core/PolygonHierarchy', '../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' + '../Scene/HeightReference', + '../Scene/VerticalOrigin', + '../ThirdParty/topojson', + '../ThirdParty/when', + './BillboardGraphics', + './CallbackProperty', + './ColorMaterialProperty', + './ConstantPositionProperty', + './ConstantProperty', + './CorridorGraphics', + './DataSource', + './EntityCluster', + './EntityCollection', + './PolygonGraphics', + './PolylineGraphics' ], function( - BoundingSphere, - Cartesian2, Cartesian3, - Cartesian4, - Cartographic, Color, - ComponentDatatype, + createGuid, defaultValue, defined, defineProperties, - destroyObject, DeveloperError, - EncodedCartesian3, - IndexDatatype, - Intersect, - CesiumMath, - Matrix4, - Plane, + Event, + getFilenameFromUri, + loadJson, + PinBuilder, + PolygonHierarchy, RuntimeError, - Buffer, - BufferUsage, - ContextLimits, - DrawCommand, - Pass, - RenderState, - ShaderProgram, - ShaderSource, - VertexArray, - PolylineCommon, - PolylineFS, - PolylineVS, - BatchTable, - BlendingState, - Material, - Polyline, - SceneMode) { + HeightReference, + VerticalOrigin, + topojson, + when, + BillboardGraphics, + CallbackProperty, + ColorMaterialProperty, + ConstantPositionProperty, + ConstantProperty, + CorridorGraphics, + DataSource, + EntityCluster, + EntityCollection, + PolygonGraphics, + PolylineGraphics) { '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; + function defaultCrsFunction(coordinates) { + return Cartesian3.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); + } - 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 + var crsNames = { + 'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction, + 'EPSG:4326' : defaultCrsFunction, + 'urn:ogc:def:crs:EPSG::4326' : defaultCrsFunction }; - /** - * 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 = []; + 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; - this._polylinesUpdated = false; - this._polylinesRemoved = false; - this._createVertexArray = false; - this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES); - this._polylines = []; - this._polylineBuckets = {}; + var sizes = { + small : 24, + medium : 48, + large : 64 + }; - // The buffer usage is determined based on the usage of the attribute over time. - this._positionBufferUsage = { bufferUsage : BufferUsage.STATIC_DRAW, frameCount : 0 }; + var simpleStyleIdentifiers = ['title', 'description', // + 'marker-size', 'marker-symbol', 'marker-color', 'stroke', // + 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity']; - this._mode = undefined; + 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 + ''; + } + } + } + } - this._polylinesToUpdate = []; - this._vertexArrays = []; - this._positionBuffer = undefined; - this._texCoordExpandAndBatchIndexBuffer = undefined; + if (html.length > 0) { + html = '' + html + '
    '; + } - this._batchTable = undefined; - this._createBatchTable = false; + return html; } - 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; + function createDescriptionCallback(describe, properties, nameProperty) { + var description; + return function(time, result) { + if (!defined(description)) { + description = describe(properties, nameProperty); } - } - }); + return description; + }; + } - /** - * 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; - }; + function defaultDescribeProperty(properties, nameProperty) { + return new CallbackProperty(createDescriptionCallback(defaultDescribe, properties, nameProperty), true); + } - /** - * 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(); + //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++; } - polyline._destroy(); - return true; + id = finalId; } - return false; - }; + var entity = entityCollection.getOrCreateEntity(id); + var properties = geoJson.properties; + if (defined(properties)) { + entity.properties = properties; - /** - * 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; - }; + var nameProperty; - /** - * 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; - }; + //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(); - /** - * 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) { - - removePolylines(this); - return this._polylines[index]; - }; + 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]; + } + } - function createBatchTable(collection, context) { - if (defined(collection._batchTable)) { - collection._batchTable.destroy(); + var description = properties.description; + if (description !== null) { + entity.description = !defined(description) ? describe(properties, nameProperty) : new ConstantProperty(description); + } } - - 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); + return entity; } - 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; + 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; + } - updateMode(this, frameState); + var geoJsonObjectTypes = { + Feature : processFeature, + FeatureCollection : processFeatureCollection, + GeometryCollection : processGeometryCollection, + LineString : processLineString, + MultiLineString : processMultiLineString, + MultiPoint : processMultiPoint, + MultiPolygon : processMultiPolygon, + Point : processPoint, + Polygon : processPolygon, + Topology : processTopology + }; - var context = frameState.context; - var projection = frameState.mapProjection; - var polyline; - var properties = this._propertiesChanged; + var geometryTypes = { + GeometryCollection : processGeometryCollection, + LineString : processLineString, + MultiLineString : processMultiLineString, + MultiPoint : processMultiPoint, + MultiPolygon : processMultiPolygon, + Point : processPoint, + Polygon : processPolygon, + Topology : processTopology + }; - 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; + // 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 (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.y = distanceDisplayCondition.far; - } - - this._batchTable.setBatchedAttribute(polyline._index, 4, nearFarCartesian); - } - } - - polyline._clean(); - } - } - polylinesToUpdate.length = 0; - this._polylinesUpdated = false; + if (!defined(feature.geometry)) { + throw new RuntimeError('feature.geometry is required.'); } - properties = this._propertiesChanged; - for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) { - properties[k] = 0; + 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); + } - var modelMatrix = Matrix4.IDENTITY; - if (frameState.mode === SceneMode.SCENE3D) { - modelMatrix = this.modelMatrix; + 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); } + } - 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 - } - }); + 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); } + } - if (!defined(this._translucentRS) || this._translucentRS.depthTest.enabled !== useDepthTest) { - this._translucentRS = RenderState.fromCache({ - blending : BlendingState.ALPHA_BLEND, - depthMask : !useDepthTest, - depthTest : { - enabled : useDepthTest - } - }); - } + function createPoint(dataSource, geoJson, crsFunction, coordinates, options) { + var symbol = options.markerSymbol; + var color = options.markerColor; + var size = options.markerSize; - this._batchTable.update(frameState); + var properties = geoJson.properties; + if (defined(properties)) { + var cssColor = properties['marker-color']; + if (defined(cssColor)) { + color = Color.fromCssColorString(cssColor); + } - if (pass.render) { - var colorList = this._colorCommands; - createCommandLists(this, frameState, colorList, modelMatrix, true); + size = defaultValue(sizes[properties['marker-size']], size); + var markerSymbol = properties['marker-symbol']; + if (defined(markerSymbol)) { + symbol = markerSymbol; + } } - if (pass.pick) { - var pickList = this._pickCommands; - createCommandLists(this, frameState, pickList, modelMatrix, false); + 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 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 billboard = new BillboardGraphics(); + billboard.verticalOrigin = new ConstantProperty(VerticalOrigin.BOTTOM); - var vertexArrays = polylineCollection._vertexArrays; - var debugShowBoundingVolume = polylineCollection.debugShowBoundingVolume; + // Clamp to ground if there isn't a height specified + if (coordinates.length === 2 && options.clampToGround) { + billboard.heightReference = HeightReference.CLAMP_TO_GROUND; + } - var batchTable = polylineCollection._batchTable; - var uniformCallback = batchTable.getUniformMapCallback(); + var entity = createObject(geoJson, dataSource._entityCollection, options.describe); + entity.billboard = billboard; + entity.position = new ConstantPositionProperty(crsFunction(coordinates)); - var length = vertexArrays.length; - for ( var m = 0; m < length; ++m) { - var va = vertexArrays[m]; - var buckets = va.buckets; - var bucketLength = buckets.length; + var promise = when(canvasOrPromise).then(function(image) { + billboard.image = new ConstantProperty(image); + }).otherwise(function() { + billboard.image = new ConstantProperty(dataSource._pinBuilder.fromColor(color, size)); + }); - for ( var n = 0; n < bucketLength; ++n) { - var bucketLocator = buckets[n]; + dataSource._promises.push(promise); + } - var offset = bucketLocator.offset; - var sp = renderPass ? bucketLocator.bucket.shaderProgram : bucketLocator.bucket.pickShaderProgram; + function processPoint(dataSource, geoJson, geometry, crsFunction, options) { + createPoint(dataSource, geoJson, crsFunction, geometry.coordinates, options); + } - var polylines = bucketLocator.bucket.polylines; - var polylineLength = polylines.length; - var currentId; - var currentMaterial; - var count = 0; - var command; + 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); + } + } - 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(); + function createLineString(dataSource, geoJson, crsFunction, coordinates, options) { + var material = options.strokeMaterialProperty; + var widthProperty = options.strokeWidthProperty; - if (commandIndex >= commandsLength) { - command = new DrawCommand({ - owner : polylineCollection - }); - commands.push(command); - } else { - command = commands[commandIndex]; - } + var properties = geoJson.properties; + if (defined(properties)) { + var width = properties['stroke-width']; + if (defined(width)) { + widthProperty = new ConstantProperty(width); + } - ++commandIndex; + 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); + } + } - 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; + 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; + } - command.uniformMap = uniformCallback(currentMaterial._uniforms); - command.count = count; - command.offset = offset; + graphics.material = material; + graphics.width = widthProperty; + graphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction)); + } - offset += count; - count = 0; - cloneBoundingSphere = true; + function processLineString(dataSource, geoJson, geometry, crsFunction, options) { + createLineString(dataSource, geoJson, crsFunction, geometry.coordinates, options); + } - commandList.push(command); - } + 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); + } + } - currentMaterial = polyline._material; - currentMaterial.update(context); - currentId = mId; - } + function createPolygon(dataSource, geoJson, crsFunction, coordinates, options) { + if (coordinates.length === 0 || coordinates[0].length === 0) { + return; + } - 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 outlineColorProperty = options.strokeMaterialProperty.color; + var material = options.fillMaterialProperty; + var widthProperty = options.strokeWidthProperty; - 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); - } + var properties = geoJson.properties; + if (defined(properties)) { + var width = properties['stroke-width']; + if (defined(width)) { + widthProperty = new ConstantProperty(width); + } - if (cloneBoundingSphere) { - cloneBoundingSphere = false; - BoundingSphere.clone(boundingVolume, boundingSphereScratch); - } else { - BoundingSphere.union(boundingVolume, boundingSphereScratch, boundingSphereScratch); - } + 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(currentId) && count > 0) { - if (commandIndex >= commandsLength) { - command = new DrawCommand({ - owner : polylineCollection - }); - commands.push(command); - } else { - command = commands[commandIndex]; - } + if (defined(color)) { + outlineColorProperty = new ConstantProperty(color); + } - ++commandIndex; + 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); + } + } - 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; + var polygon = new PolygonGraphics(); + polygon.outline = new ConstantProperty(true); + polygon.outlineColor = outlineColorProperty; + polygon.outlineWidth = widthProperty; + polygon.material = material; - command.uniformMap = uniformCallback(currentMaterial._uniforms); - command.count = count; - command.offset = offset; + var holes = []; + for (var i = 1, len = coordinates.length; i < len; i++) { + holes.push(new PolygonHierarchy(coordinatesArrayToCartesianArray(coordinates[i], crsFunction))); + } - cloneBoundingSphere = true; + 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; + } - commandList.push(command); - } + var entity = createObject(geoJson, dataSource._entityCollection, options.describe); + entity.polygon = polygon; + } - currentId = undefined; - } + 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); } + } - commands.length = commandIndex; + 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); + } + } } /** - * 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. + * 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. * - * @returns {Boolean} true if this object was destroyed; otherwise, false. + * @alias GeoJsonDataSource + * @constructor * - * @see PolylineCollection#destroy + * @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: '?' + * })); */ - PolylineCollection.prototype.isDestroyed = function() { - return false; - }; + 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(); + } /** - * 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. - * + * Creates a Promise to a new instance loaded with the provided GeoJSON or TopoJSON data. * - * @example - * polylines = polylines && polylines.destroy(); + * @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. * - * @see PolylineCollection#isDestroyed + * @returns {Promise.} A promise that will resolve when the data is loaded. */ - PolylineCollection.prototype.destroy = function() { - destroyVertexArrays(this); - releaseShaders(this); - destroyPolylines(this); - this._batchTable = this._batchTable && this._batchTable.destroy(); - return destroyObject(this); + GeoJsonDataSource.load = function(data, options) { + return new GeoJsonDataSource().load(data, options); }; - 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; + 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; } - } else { - if (bufferUsage.bufferUsage !== BufferUsage.STATIC_DRAW) { - if (bufferUsage.frameCount === 0) { - usageChanged = true; - bufferUsage.bufferUsage = BufferUsage.STATIC_DRAW; - } else { - bufferUsage.frameCount--; - } + }, + /** + * 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; } - } - - return usageChanged; - } - - var emptyVertexBuffer = [0.0, 0.0, 0.0]; - - function createVertexArrays(collection, context, projection) { - collection._createVertexArray = false; - releaseShaders(collection); - destroyVertexArrays(collection); - sortPolylinesIntoBuckets(collection); + }, + /** + * 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; + } + }, + /** + * 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; + } + }, + /** + * 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; + } + }, + /** + * 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; + } + }, - //stores all of the individual indices arrays. - var totalIndices = [[]]; - var indices = totalIndices[0]; + /** + * 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; + } + }, - var batchTable = collection._batchTable; + /** + * 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; + } + }, - //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; + /** + * 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; } } + }); - 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); + defineProperties(GeoJsonDataSource.prototype, { + /** + * Gets or sets a human-readable name for this instance. + * @memberof GeoJsonDataSource.prototype + * @type {String} + */ + name : { + get : function() { + return this._name; + }, + set : function(value) { + if (this._name !== value) { + this._name = value; + this._changed.raiseEvent(this); } } - - 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 - }); + }, + /** + * 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; } - 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]; + }, + /** + * 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; + } + }, - 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 - }); + /** + * 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) { + this._entityCluster = value; + } + } + }); - vbo += vertexBufferOffset[k]; + /** + * 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) { + + DataSource.setLoading(this, true); - 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 promise = data; + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var sourceUri = options.sourceUri; + if (typeof data === 'string') { + if (!defined(sourceUri)) { + sourceUri = data; + } + promise = loadJson(data); + } - 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 - }]; + 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 buffer3D; - var bufferProperty3D; - var buffer2D; - var bufferProperty2D; + 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); + }); + }; - 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'; - } + function load(that, geoJson, options, sourceUri) { + var name; + if (defined(sourceUri)) { + name = getFilenameFromUri(sourceUri); + } - 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; + if (defined(name) && that._name !== name) { + that._name = name; + that._changed.raiseEvent(that); + } - var va = new VertexArray({ - context : context, - attributes : attributes, - indexBuffer : indexBuffer - }); - collection._vertexArrays.push({ - va : va, - buckets : vertexArrayBuckets[k] - }); - } - } + var typeHandler = geoJsonObjectTypes[geoJson.type]; + if (!defined(typeHandler)) { + throw new RuntimeError('Unsupported GeoJSON object type: ' + geoJson.type); } - } - var scratchUniformArray = []; - function createMaterialId(material) { - var uniforms = Material._uniformList[material.type]; - var length = uniforms.length; - scratchUniformArray.length = 2.0 * length; + //Check for a Coordinate Reference System. + var crs = geoJson.crs; + var crsFunction = crs !== null ? defaultCrsFunction : null; - 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; - } + if (defined(crs)) { + if (!defined(crs.properties)) { + throw new RuntimeError('crs.properties is undefined.'); + } - return material.type + ':' + JSON.stringify(scratchUniformArray); - } + 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]; + } - function sortPolylinesIntoBuckets(collection) { - var mode = collection._mode; - var modelMatrix = collection._modelMatrix; + if (!defined(handler)) { + throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties)); + } - 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); + 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); } - value.addPolyline(p); + } else { + throw new RuntimeError('Unknown crs type: ' + crs.type); } } - } - function updateMode(collection, frameState) { - var mode = frameState.mode; + return when(crsFunction, function(crsFunction) { + that._entityCollection.removeAll(); - if (collection._mode !== mode || (!Matrix4.equals(collection._modelMatrix, collection.modelMatrix))) { - collection._mode = mode; - collection._modelMatrix = Matrix4.clone(collection.modelMatrix); - collection._createVertexArray = true; - } + // 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; + }); + }); } - function removePolylines(collection) { - if (collection._polylinesRemoved) { - collection._polylinesRemoved = false; + /** + * 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. + */ - var polylines = []; + return GeoJsonDataSource; +}); - 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); - } - } +define('DataSources/GeometryUpdater',[ + '../Core/defineProperties', + '../Core/DeveloperError' + ], function( + defineProperties, + DeveloperError) { + 'use strict'; - collection._polylines = polylines; - } + /** + * 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(); } - 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(); - } - } + 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 } - } + }); - function destroyVertexArrays(collection) { - var length = collection._vertexArrays.length; - for ( var t = 0; t < length; ++t) { - collection._vertexArrays[t].va.destroy(); + 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 } - 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(); - } - } - } + /** + * 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; - function VertexArrayBucketLocator(count, offset, bucket) { - this.count = count; - this.offset = offset; - this.bucket = bucket; - } + /** + * 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; - 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; - } + /** + * 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; - PolylineBucket.prototype.addPolyline = function(p) { - var polylines = this.polylines; - polylines.push(p); - p._actualLength = this.getPolylinePositionsLength(p); - this.lengthOfPositions += p._actualLength; - p._bucket = this; - }; + /** + * 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; - PolylineBucket.prototype.updateShader = function(context, batchTable) { - if (defined(this.shaderProgram)) { - return; - } + /** + * 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; - 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' - }); + /** + * 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; - this.shaderProgram = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); + /** + * 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; - this.pickShaderProgram = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vs, - fragmentShaderSource : fsPick, - attributeLocations : attributeLocations - }); - }; + return GeometryUpdater; +}); - function intersectsIDL(polyline) { - return Cartesian3.dot(Cartesian3.UNIT_X, polyline._boundingVolume.center) < 0 || - polyline._boundingVolume.intersectPlane(Plane.ORIGIN_ZX_PLANE) === Intersect.INTERSECTING; +define('DataSources/KmlCamera',[], function() { + 'use strict'; + /** + * Representation of from KML + * @alias KmlCamera + * @constructor + * + * @param {Cartesian3} position camera position + * @param {HeadingPitchRoll} headingPitchRoll camera orientation + */ + function KmlCamera(position, headingPitchRoll) { + this.position = position; + this.headingPitchRoll = headingPitchRoll; } - PolylineBucket.prototype.getPolylinePositionsLength = function(polyline) { - var length; - if (this.mode === SceneMode.SCENE3D || !intersectsIDL(polyline)) { - length = polyline._actualPositions.length; - return length * 4.0 - 4.0; - } + return KmlCamera; +}); - 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; - } +(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 () { - return count; - }; +/*! + * 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. - var scratchWritePosition = new Cartesian3(); - var scratchWritePrevPosition = new Cartesian3(); - var scratchWriteNextPosition = new Cartesian3(); - var scratchWriteVector = new Cartesian3(); - var scratchPickColorCartesian = new Cartesian4(); - var scratchWidthShowCartesian = new Cartesian2(); + // 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" ); + } +}; - PolylineBucket.prototype.write = function(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection) { - var mode = this.mode; - var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI; +Autolinker.prototype = { + constructor : Autolinker, // fix constructor property - 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; + /** + * @cfg {Boolean} urls + * + * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. + */ + urls : true, - var pickColor = polyline.getPickId(context).color; + /** + * @cfg {Boolean} email + * + * `true` if email addresses should be automatically linked, `false` if they should not be. + */ + email : true, - var segmentIndex = 0; - var count = 0; - var position; + /** + * @cfg {Boolean} twitter + * + * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. + */ + twitter : true, - 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]; - } + /** + * @cfg {Boolean} phone + * + * `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be. + */ + phone: true, - Cartesian3.clone(position, scratchWritePrevPosition); - Cartesian3.clone(positions[j], scratchWritePosition); + /** + * @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, - 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]; - } + /** + * @cfg {Boolean} newWindow + * + * `true` if the links should open in a new window, `false` otherwise. + */ + newWindow : true, - Cartesian3.clone(position, scratchWriteNextPosition); + /** + * @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, - var segmentLength = lengths[segmentIndex]; - if (j === count + segmentLength) { - count += segmentLength; - ++segmentIndex; - } + /** + * @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, - var segmentStart = j - count === 0; - var segmentEnd = j === count + lengths[segmentIndex] - 1; + /** + * @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 : "", - if (mode === SceneMode.SCENE2D) { - scratchWritePrevPosition.z = 0.0; - scratchWritePosition.z = 0.0; - scratchWriteNextPosition.z = 0.0; - } + /** + * @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. + */ - 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); - } - } - } + /** + * @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, - var startK = (segmentStart) ? 2 : 0; - var endK = (segmentEnd) ? 2 : 4; + /** + * @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, - for (var k = startK; k < endK; ++k) { - EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex); - EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6); - EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12); + /** + * @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, - 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; + /** + * 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 = []; - positionIndex += 6 * 3; - texCoordExpandAndBatchIndexIndex += 4; - } - } + for( var i = 0, len = htmlNodes.length; i < len; i++ ) { + var node = htmlNodes[ i ], + nodeType = node.getType(), + nodeText = node.getText(); - 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); + 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 - var widthShowCartesian = scratchWidthShowCartesian; - widthShowCartesian.x = width; - widthShowCartesian.y = show ? 1.0 : 0.0; + } else if( nodeType === 'entity' || nodeType === 'comment' ) { + resultHtml.push( nodeText ); // append HTML entity nodes (such as ' ') or HTML comments (such as '') verbatim - 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); + } 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 ); - var nearFarCartesian = scratchNearFarCartesian2; - nearFarCartesian.x = 0.0; - nearFarCartesian.y = Number.MAX_VALUE; + } else { + // `text` is within an tag, simply append the text - we do not want to autolink anything + // already within an ... tag + resultHtml.push( nodeText ); + } + } + } - var distanceDisplayCondition = polyline.distanceDisplayCondition; - if (defined(distanceDisplayCondition)) { - nearFarCartesian.x = distanceDisplayCondition.near; - nearFarCartesian.y = distanceDisplayCondition.far; - } + return resultHtml.join( "" ); + }, - batchTable.setBatchedAttribute(polylineBatchIndex, 0, widthShowCartesian); - batchTable.setBatchedAttribute(polylineBatchIndex, 1, colorCartesian); + /** + * 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 ); + }, - 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(); + /** + * 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 + } - 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; + if( typeof replaceFnResult === 'string' ) { + return replaceFnResult; // `replaceFn` returned a string, use that - var segmentIndex = 0; - var count = 0; + } else if( replaceFnResult === false ) { + return match.getMatchedText(); // no replacement for the match - 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]; - } + } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { + return replaceFnResult.toAnchorString(); - prevPosition = Matrix4.multiplyByPoint(modelMatrix, prevPosition, morphPrevPositionScratch); + } 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 - var position = Matrix4.multiplyByPoint(modelMatrix, positions[j], morphPositionScratch); + return anchorTag.toAnchorString(); + } + }, - 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); + /** + * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. + * + * @protected + * @return {Autolinker.htmlParser.HtmlParser} + */ + getHtmlParser : function() { + var htmlParser = this.htmlParser; - var segmentLength = lengths[segmentIndex]; - if (j === count + segmentLength) { - count += segmentLength; - ++segmentIndex; - } + if( !htmlParser ) { + htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser(); + } - var segmentStart = j - count === 0; - var segmentEnd = j === count + lengths[segmentIndex] - 1; + return htmlParser; + }, - 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); + /** + * Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance. + * + * @protected + * @return {Autolinker.matchParser.MatchParser} + */ + getMatchParser : function() { + var matchParser = this.matchParser; - positionIndex += 6 * 3; - } - } - } - }; + 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 + } ); + } - var scratchSegmentLengths = new Array(1); + return matchParser; + }, - 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 = []; + /** + * 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; - 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; - } + if( !tagBuilder ) { + tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { + newWindow : this.newWindow, + truncate : this.truncate, + className : this.className + } ); + } - 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]; - } + return tagBuilder; + } - 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 - }); +/** + * 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 ); +}; - 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; - }; +// Autolinker Namespaces +Autolinker.match = {}; +Autolinker.htmlParser = {}; +Autolinker.matchParser = {}; - var scratchSegments = { - positions : undefined, - lengths : undefined - }; - var scratchLengths = new Array(1); - var pscratch = new Cartesian3(); - var scratchCartographic = new Cartographic(); +/*global Autolinker */ +/*jshint eqnull:true, boss:true */ +/** + * @class Autolinker.Util + * @singleton + * + * A few utility methods for Autolinker. + */ +Autolinker.Util = { - PolylineBucket.prototype.getSegments = function(polyline, projection) { - var positions = polyline._actualPositions; + /** + * @property {Function} abstractMethod + * + * A function object which represents an abstract method. + */ + abstractMethod : function() { throw "abstract"; }, - if (this.mode === SceneMode.SCENE3D) { - scratchLengths[0] = positions.length; - scratchSegments.positions = positions; - scratchSegments.lengths = scratchLengths; - return scratchSegments; - } - if (intersectsIDL(polyline)) { - positions = polyline._segments.positions; - } + /** + * @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, - 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))); - } + /** + * 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 ]; + } + } - 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); - } + return dest; + }, - scratchSegments.positions = newPositions; - scratchSegments.lengths = polyline._segments.lengths; - return scratchSegments; - }; - var scratchPositionsArray; + /** + * 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; - PolylineBucket.prototype.writeUpdate = function(index, polyline, positionBuffer, projection) { - var mode = this.mode; - var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI; + var F = function() {}; + F.prototype = superclassProto; - var positionsLength = polyline._actualLength; - if (positionsLength) { - index += this.getPolylineStartIndex(polyline); + var subclass; + if( protoProps.hasOwnProperty( 'constructor' ) ) { + subclass = protoProps.constructor; + } else { + subclass = function() { superclassProto.constructor.apply( this, arguments ); }; + } - var positionArray = scratchPositionsArray; - var positionsArrayLength = 6 * positionsLength * 3; + var subclassProto = subclass.prototype = new F(); // set up prototype chain + subclassProto.constructor = subclass; // fix constructor property + subclassProto.superclass = superclassProto; - if (!defined(positionArray) || positionArray.length < positionsArrayLength) { - positionArray = scratchPositionsArray = new Float32Array(positionsArrayLength); - } else if (positionArray.length > positionsArrayLength) { - positionArray = new Float32Array(positionArray.buffer, 0, positionsArrayLength); - } + 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 ); - var segments = this.getSegments(polyline, projection); - var positions = segments.positions; - var lengths = segments.lengths; + return subclass; + }, - 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]; - } + /** + * 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; + }, - 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]; - } + /** + * 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 ); - Cartesian3.clone(position, scratchWriteNextPosition); + } else { + for( var i = 0, len = arr.length; i < len; i++ ) { + if( arr[ i ] === element ) return i; + } + return -1; + } + }, - 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; - } + /** + * 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" ); - 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); - } + var result = [], + lastIdx = 0, + match; - if ((scratchWritePosition.x < 0.0 && scratchWriteNextPosition.x > 0.0) || - (scratchWritePosition.x > 0.0 && scratchWriteNextPosition.x < 0.0)) { - Cartesian3.clone(scratchWritePosition, scratchWriteNextPosition); - } - } - } + while( match = splitRegex.exec( str ) ) { + result.push( str.substring( lastIdx, match.index ) ); + result.push( match[ 0 ] ); // push the splitting char(s) - var startJ = (segmentStart) ? 2 : 0; - var endJ = (segmentEnd) ? 2 : 4; + lastIdx = match.index + match[ 0 ].length; + } + result.push( str.substring( lastIdx ) ); - 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; - } - } + return result; + }, - positionBuffer.copyFromArrayView(positionArray, 6 * 3 * Float32Array.BYTES_PER_ELEMENT * index); - } - }; - return PolylineCollection; -}); + /** + * 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 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'; +}; +/*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 + */ +Autolinker.HtmlTag = Autolinker.Util.extend( Object, { - /** - * 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); - } + /** + * @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. + */ - 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; - } - } - }); + /** + * @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. + */ - ScaledPositionProperty.prototype.getValue = function(time, result) { - return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result); - }; + /** + * @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. + */ - ScaledPositionProperty.prototype.setValue = function(value) { - if (this._value !== value) { - this._value = value; + /** + * @cfg {String} innerHTML + * + * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version + * for acronym names. + */ - if (defined(this._removeSubscription)) { - this._removeSubscription(); - this._removeSubscription = undefined; - } - if (defined(value)) { - this._removeSubscription = value.definitionChanged.addEventListener(this._raiseDefinitionChanged, this); - } - this._definitionChanged.raiseEvent(this); - } - }; + /** + * @protected + * @property {RegExp} whitespaceRegex + * + * Regular expression used to match whitespace in a string of CSS classes. + */ + whitespaceRegex : /\s+/, - ScaledPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { - - if (!defined(this._value)) { - return undefined; - } - result = this._value.getValueInReferenceFrame(time, referenceFrame, result); - return defined(result) ? Ellipsoid.WGS84.scaleToGeodeticSurface(result, result) : undefined; - }; + /** + * @constructor + * @param {Object} [cfg] The configuration properties for this class, in an Object (map) + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); - ScaledPositionProperty.prototype.equals = function(other) { - return this === other || (other instanceof ScaledPositionProperty && this._value === other._value); - }; + this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym + }, - ScaledPositionProperty.prototype._raiseDefinitionChanged = function() { - this._definitionChanged.raiseEvent(this); - }; - return ScaledPositionProperty; -}); + /** + * 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; + }, -/*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; + /** + * Retrieves the tag name. + * + * @return {String} + */ + getTagName : function() { + return this.tagName || ""; + }, - 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; - } + /** + * 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; - 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; - } + return this; + }, - 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; + /** + * 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 ]; + }, - 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; + /** + * 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 ); - if (sampling) { - sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep); - sampleStepsTaken = 0; - sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2); - sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1); - } - } + return this; + }, - 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; - } + /** + * 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 = {} ); + }, - 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; - } + /** + * 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 ); + }, - 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; - } + /** + * 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; - 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; - } + while( newClass = newClasses.shift() ) { + if( indexOf( classes, newClass ) === -1 ) { + classes.push( newClass ); + } + } - function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) { - subSampleCompositePropertyScratch.start = start; - subSampleCompositePropertyScratch.stop = stop; + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, - 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; - } + /** + * 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; - var sampleStop = stop; - if (JulianDate.lessThan(intervalStop, sampleStop)) { - sampleStop = intervalStop; - } + while( classes.length && ( removeClass = removeClasses.shift() ) ) { + var idx = indexOf( classes, removeClass ); + if( idx !== -1 ) { + classes.splice( idx, 1 ); + } + } - index = reallySubSample(interval.data, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result); - } - } - return index; - } + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, - function reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, index, result) { - //Unwrap any references until we have the actual property. - while (property instanceof ReferenceProperty) { - property = property.resolvedProperty; - } - if (property instanceof SampledPositionProperty) { - var times = property._property._times; - index = subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, index, result); - } else if (property instanceof CompositePositionProperty) { - index = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result); - } else if (property instanceof TimeIntervalCollectionPositionProperty) { - index = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result); - } else if (property instanceof ConstantPositionProperty || - (property instanceof ScaledPositionProperty && Property.isConstant(property))) { - 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; - } + /** + * 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' ] || ""; + }, - 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; - } + /** + * 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; + }, - 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); - } - }; + /** + * 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; - PolylineUpdater.prototype.updateObject = function(time, item) { - var entity = item.entity; - var pathGraphics = entity._path; - var positionProperty = entity._position; + return this; + }, - 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); + /** + * Retrieves the inner HTML for the tag. + * + * @return {String} + */ + getInnerHtml : function() { + return this.innerHtml || ""; + }, - //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()); - } + /** + * Override of superclass method used to generate the HTML string for the tag. + * + * @return {String} + */ + toAnchorString : function() { + var tagName = this.getTagName(), + attrsStr = this.buildAttrsStr(); - if (hasAvailability) { - var start = availability.start; - var stop = availability.stop; + attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes - if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) { - sampleStart = start; - } + return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); + }, - 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; - } + /** + * 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 - 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 attrs = this.getAttrs(), + attrsArr = []; - var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution); + for( var prop in attrs ) { + if( attrs.hasOwnProperty( prop ) ) { + attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); + } + } + return attrsArr.join( " " ); + } - 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; - } - }; +/*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, { - PolylineUpdater.prototype.destroy = function() { - this._scene.primitives.remove(this._polylineCollection); - return destroyObject(this); - }; + /** + * @cfg {Boolean} newWindow + * @inheritdoc Autolinker#newWindow + */ - /** - * 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) { - - entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this); + /** + * @cfg {Number} truncate + * @inheritdoc Autolinker#truncate + */ - this._scene = scene; - this._updaters = {}; - this._entityCollection = entityCollection; - this._items = new AssociativeArray(); + /** + * @cfg {String} className + * @inheritdoc Autolinker#className + */ - 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) { - - var updaters = this._updaters; - for ( var key in updaters) { - if (updaters.hasOwnProperty(key)) { - updaters[key].update(time); - } - } + /** + * @constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, - 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; + /** + * 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() ) + } ); - var frameToVisualize = ReferenceFrame.FIXED; - if (this._scene.mode === SceneMode.SCENE3D) { - frameToVisualize = positionProperty.referenceFrame; - } + return tag; + }, - var currentUpdater = this._updaters[frameToVisualize]; - if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) { - currentUpdater.updateObject(time, item); - continue; - } + /** + * 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 + }; - if (defined(lastUpdater)) { - lastUpdater.removeObject(item); - } + var cssClass = this.createCssClass( matchType ); + if( cssClass ) { + attrs[ 'class' ] = cssClass; + } + if( this.newWindow ) { + attrs[ 'target' ] = "_blank"; + } - if (!defined(currentUpdater)) { - currentUpdater = new PolylineUpdater(this._scene, frameToVisualize); - currentUpdater.update(time); - this._updaters[frameToVisualize] = currentUpdater; - } + return attrs; + }, - 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; - }; + /** + * 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; - /** - * Removes and destroys all primitives created by this instance. - */ - PathVisualizer.prototype.destroy = function() { - this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this); + 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" + }, - var updaters = this._updaters; - for ( var key in updaters) { - if (updaters.hasOwnProperty(key)) { - updaters[key].destroy(); - } - } - return destroyObject(this); - }; + /** + * 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 ); - PathVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { - var i; - var entity; - var item; - var items = this._items; + return anchorText; + }, - 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); - } - } - } + /** + * 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 ); + } - 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); - } - } - }; +} ); +/*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, { - //for testing - PathVisualizer._subSample = subSample; + /** + * @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 PathVisualizer; -}); + return new RegExp( [ + // for tag. Ex: ) + '(?:', + '<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag -/*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'; + // Zero or more attributes following the tag name + '(?:', + '\\s+', // one or more whitespace chars before an attribute - var defaultColor = Color.WHITE; - var defaultOutlineColor = Color.BLACK; - var defaultOutlineWidth = 0.0; - var defaultPixelSize = 1.0; - var defaultDisableDepthTestDistance = 0.0; + // Either: + // A. attr="value", or + // B. "value" alone (To cover example doctype tag: ) + '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', + ')*', + '>', + ')', - 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; - } + // All other HTML tags (i.e. tags that are not ) + '(?:', + '<(/)?', // Beginning of a tag or comment. Either '<' for a start tag, or '' - pointPrimitive = cluster.getPoint(entity); - pointPrimitive.id = entity; - item.pointPrimitive = pointPrimitive; - } + ')', + ')', + '>', + ')' + ].join( "" ), 'gi' ); + } )(), - 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); - pointPrimitive.disableDepthTestDistance = Property.getValueOrDefault(pointGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); - } 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.disableDepthTestDistance = Property.getValueOrDefault(pointGraphics._disableDepthTestDistance, time, defaultDisableDepthTestDistance); - billboard.heightReference = heightReference; + /** + * @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, - 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); - } + /** + * 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 - if (needsRedraw) { - item.color = Color.clone(newColor, item.color); - item.outlineColor = Color.clone(newOutlineColor, item.outlineColor); - item.pixelSize = newPixelSize; - item.outlineWidth = newOutlineWidth; + 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 ); - var centerAlpha = newColor.alpha; - var cssColor = newColor.toCssColorString(); - var cssOutlineColor = newOutlineColor.toCssColorString(); - var textureId = JSON.stringify([cssColor, newPixelSize, cssOutlineColor, newOutlineWidth]); + // Push TextNodes and EntityNodes for any text found between tags + if( inBetweenTagsText ) { + textAndEntityNodes = this.parseTextAndEntityNodes( inBetweenTagsText ); + nodes.push.apply( nodes, textAndEntityNodes ); + } - billboard.setImage(textureId, createCallback(centerAlpha, cssColor, cssOutlineColor, newOutlineWidth, newPixelSize)); - } - } - } - return true; - }; + // Push the CommentNode or ElementNode + if( commentText ) { + nodes.push( this.createCommentNode( tagText, commentText ) ); + } else { + nodes.push( this.createElementNode( tagText, tagName, isClosingTag ) ); + } - /** - * 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) { - - var item = this._items.get(entity.id); - if (!defined(item) || !(defined(item.pointPrimitive) || defined(item.billboard))) { - return BoundingSphereState.FAILED; - } + lastIndex = currentResult.index + tagText.length; + } - 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); - } + // 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 ); - result.radius = 0; - return BoundingSphereState.DONE; - }; + // Push TextNodes and EntityNodes for any text found between tags + if( text ) { + textAndEntityNodes = this.parseTextAndEntityNodes( text ); + nodes.push.apply( nodes, textAndEntityNodes ); + } + } - /** - * 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; - }; + return nodes; + }, - /** - * 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; + /** + * 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 - 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)); - } - } + // 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 ]; - 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); - } - } + if( textToken ) nodes.push( this.createTextNode( textToken ) ); + if( entityToken ) nodes.push( this.createEntityNode( entityToken ) ); + } + return nodes; + }, - 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); - } - } - } + /** + * 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 ) + } ); + }, - 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; + /** + * 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 + } ); + }, - 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(); - } - } + /** + * 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 '&nbsp;'). + * @return {Autolinker.htmlParser.EntityNode} + */ + createEntityNode : function( text ) { + return new Autolinker.htmlParser.EntityNode( { text: text } ); + }, - context2D.beginPath(); - context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true); - context2D.closePath(); - context2D.fillStyle = cssColor; - context2D.fill(); - return canvas; - }; - } + /** + * 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 &quot; and &nbsp; + */ +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 ); + }, - return PointVisualizer; -}); + + /** + * 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 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'; +} ); +/*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, { - 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(); + /** + * @cfg {String} comment (required) + * + * The text inside the comment tag. This text is stripped of any leading or + * trailing whitespace. + */ + comment : '', - 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) { - - 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); - } + /** + * Returns a string name for the type of node that this class represents. + * + * @return {String} + */ + getType : function() { + return 'comment'; + }, - 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; - } - } - }); + /** + * Returns the comment inside the comment tag. + * + * @return {String} + */ + getComment : function() { + return this.comment; + } - /** - * 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); - }; +} ); +/*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, - /** - * 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); - }; + + /** + * Returns a string name for the type of node that this class represents. + * + * @return {String} + */ + getType : function() { + return 'element'; + }, + - /** - * 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + /** + * 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: '&nbsp;', 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, { - var attributes; + /** + * @cfg {Boolean} urls + * @inheritdoc Autolinker#urls + */ + urls : true, - 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 - }; - } + /** + * @cfg {Boolean} email + * @inheritdoc Autolinker#email + */ + email : true, - return new GeometryInstance({ - id : entity, - geometry : new PolygonGeometry(this._options), - attributes : attributes - }); - }; + /** + * @cfg {Boolean} twitter + * @inheritdoc Autolinker#twitter + */ + twitter : true, - /** - * 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); - var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); - var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + /** + * @cfg {Boolean} phone + * @inheritdoc Autolinker#phone + */ + phone: true, - 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) - } - }); - }; + /** + * @cfg {Boolean/String} hashtag + * @inheritdoc Autolinker#hashtag + */ + hashtag : false, - /** - * 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; - }; + /** + * @cfg {Boolean} stripPrefix + * @inheritdoc Autolinker#stripPrefix + */ + stripPrefix : true, - /** - * 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; - } + /** + * @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 - var polygon = this._entity.polygon; + hashtagRegex = /(^|[^\w])#(\w{1,15})/, // For matching a Hashtag. Ex: #games - if (!defined(polygon)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + 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) - var fillProperty = polygon.fill; - var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + // 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+&@#\/%=~_()|'$*\[\]]/; - var perPositionHeightProperty = polygon.perPositionHeight; - var perPositionHeightEnabled = defined(perPositionHeightProperty) && (perPositionHeightProperty.isConstant ? perPositionHeightProperty.getValue(Iso8601.MINIMUM_VALUE) : true); + 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, + ')', - 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; - } + '(', // *** Capturing group $4, which is used to determine an email match + emailRegex.source, + domainNameRegex.source, + tldRegex.source, + ')', - 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; - } + '(', // *** 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, + ')', - 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); + '(?:', // 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, + ')', - if (outlineEnabled && onTerrain) { - oneTimeWarning(oneTimeWarning.geometryOutlines); - outlineEnabled = false; - } + '|', - var perPositionHeight = polygon.perPositionHeight; - var closeTop = polygon.closeTop; - var closeBottom = polygon.closeBottom; + '(?:', // 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, + ')', + ')', - this._fillEnabled = fillEnabled; - this._onTerrain = onTerrain; - this._outlineEnabled = outlineEnabled; + '(?:' + urlSuffixRegex.source + ')?', // match for path, query string, and/or hash anchor - optional + ')', - 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; + // 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, + ')', - 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); + '(', // *** 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' ); + } )(), - 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); - } - }; + /** + * @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 : /^(.)?\/\//, - /** - * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true. - * - * @param {PrimitiveCollection} primitives The primitive collection to use. - * @param {PrimitiveCollection} groundPrimitives The ground 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) { - - return new DynamicGeometryUpdater(primitives, groundPrimitives, this); - }; + /** + * @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. + */ - /** - * @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) { - - var geometryUpdater = this._geometryUpdater; - var onTerrain = geometryUpdater._onTerrain; + /** + * @constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder + * instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); - 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; + this.matchValidator = new Autolinker.MatchValidator(); + }, - 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; - } + /** + * 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 - if (isArray(hierarchy)) { - options.polygonHierarchy = new PolygonHierarchy(hierarchy); - } else { - options.polygonHierarchy = hierarchy; - } + 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 - var closeTopValue = Property.getValueOrDefault(polygon.closeTop, time, true); - var closeBottomValue = Property.getValueOrDefault(polygon.closeBottom, time, true); + // 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; - 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; + } else { + // Generate replacement text for the match from the `replaceFn` + var replaceStr = replaceFn.call( contextObj, matchDescObj.match ); + return matchDescObj.prefixStr + replaceStr + matchDescObj.suffixStr; + } + } ); + }, - 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; + /** + * 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`). - if (onTerrain) { - var currentColor = Color.WHITE; - if (defined(fillMaterialProperty.color)) { - currentColor = fillMaterialProperty.color.getValue(time); - } + var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch, + match, // Will be an Autolinker.match.Match object - 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; + 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. - this._primitive = primitives.add(new Primitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new PolygonGeometry(options) - }), - appearance : appearance, - asynchronous : false, - shadows : shadows - })); - } - } + // 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; + } - if (!onTerrain && Property.getValueOrDefault(polygon.outline, time, false)) { - options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + // 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 + } - 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; + if( emailAddressMatch ) { + match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } ); - 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 - })); - } - }; + } 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 } ); - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); - }; + } 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 } ); - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; - }; + } 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 } ); - 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); - }; + } 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 ] || ""; - return PolygonGeometryUpdater; -}); + 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 + } + } -//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\ -\n\ -varying vec4 v_color;\n\ -\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\ -\n\ - vec4 p = czm_computePosition();\n\ - vec4 prev = czm_computePrevPosition();\n\ - vec4 next = czm_computeNextPosition();\n\ -\n\ - v_color = color;\n\ -\n\ - float angle;\n\ - vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev, angle);\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'; + match = new Autolinker.match.Url( { + matchedText : matchStr, + url : matchStr, + protocolUrlMatch : !!protocolUrlMatch, + protocolRelativeMatch : !!protocolRelativeMatch, + stripPrefix : this.stripPrefix + } ); + } - var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineColorAppearanceVS; - var defaultFragmentShaderSource = PerInstanceFlatColorAppearanceFS; + return { + prefixStr : prefixStr, + suffixStr : suffixStr, + match : match + }; + }, - /** - * 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; + /** + * 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 ); - /** - * 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; + if( lastChar === ')' ) { + var openParensMatch = matchStr.match( /\(/g ), + closeParensMatch = matchStr.match( /\)/g ), + numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, + numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; - /** - * When true, the geometry is expected to appear translucent so - * {@link PolylineColorAppearance#renderState} has alpha blending enabled. - * - * @type {Boolean} - * - * @default true - */ - this.translucent = translucent; + 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; + } - this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource); - this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource); - this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState); - this._closed = closed; + return true; + }, - // Non-derived members - this._vertexFormat = vertexFormat; - } + /** + * 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(); - defineProperties(PolylineColorAppearance.prototype, { - /** - * The GLSL source code for the vertex shader. - * - * @memberof PolylineColorAppearance.prototype - * - * @type {String} - * @readonly - */ - vertexShaderSource : { - get : function() { - return this._vertexShaderSource; - } - }, + return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' ); + }, - /** - * 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; - } - }, + /** + * 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 ); + }, - /** - * 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; - } - } - }); + /** + * 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; + } + }, - /** - * 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 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 ) ); + } - /** - * 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; +} ); +/*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 ); + }, - /** - * 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; + + /** + * 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; + }, + - return PolylineColorAppearance; -}); + /** + * 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 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'; +} ); +/*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. + */ + - //We use this object to create one polyline collection per-scene. - var polylineCollections = {}; + /** + * 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; + }, + - var defaultMaterial = new ColorMaterialProperty(Color.WHITE); - var defaultShow = new ConstantProperty(true); - var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); - var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); + /** + * 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, { - function GeometryOptions(entity) { - this.id = entity; - this.vertexFormat = undefined; - this.positions = undefined; - this.width = undefined; - this.followSurface = undefined; - this.granularity = undefined; - } + /** + * @cfg {String} serviceName (required) + * + * The service to point hashtag matches to. See {@link Autolinker#hashtag} + * for available values. + */ - /** - * 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) { - - 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._depthFailMaterialProperty = undefined; - this._options = new GeometryOptions(entity); - this._onEntityPropertyChanged(entity, 'polyline', entity.polyline, undefined); - } + /** + * @cfg {String} hashtag (required) + * + * The Hashtag that was matched, without the '#'. + */ - 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 the material property used to fill the geometry when it fails the depth test. - * @memberof PolylineGeometryUpdater.prototype - * - * @type {MaterialProperty} - * @readonly - */ - depthFailMaterialProperty : { - get : function() { - return this._depthFailMaterialProperty; - } - }, - /** - * 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; - } - } - }); + /** + * Returns the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'hashtag'; + }, - /** - * 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); - }; + /** + * Returns the matched hashtag. + * + * @return {String} + */ + getHashtag : function() { + return this.hashtag; + }, - /** - * 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) { - - 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); - var attributes = { - show : show, - distanceDisplayCondition : distanceDisplayConditionAttribute - }; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + var serviceName = this.serviceName, + hashtag = this.hashtag; - var currentColor; - if (this._materialProperty instanceof ColorMaterialProperty) { - currentColor = Color.WHITE; - if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) { - currentColor = this._materialProperty.color.getValue(time); - } - attributes.color = ColorGeometryInstanceAttribute.fromColor(currentColor); - } + switch( serviceName ) { + case 'twitter' : + return 'https://twitter.com/hashtag/' + hashtag; + case 'facebook' : + return 'https://www.facebook.com/hashtag/' + hashtag; - if (defined(this._depthFailMaterialProperty) && this._depthFailMaterialProperty instanceof ColorMaterialProperty) { - currentColor = Color.WHITE; - if (defined(this._depthFailMaterialProperty.color) && (this._depthFailMaterialProperty.color.isConstant || isAvailable)) { - currentColor = this._depthFailMaterialProperty.color.getValue(time); - } - attributes.depthFailColor = ColorGeometryInstanceAttribute.fromColor(currentColor); - } + 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 ); + } + }, - 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) { - }; + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return '#' + this.hashtag; + } - /** - * 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; - }; +} ); +/*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, { - /** - * 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); - }; + /** + * @cfg {String} number (required) + * + * The phone number that was matched. + */ - PolylineGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { - if (!(propertyName === 'availability' || propertyName === 'polyline')) { - return; - } - var polyline = this._entity.polyline; + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'phone'; + }, - if (!defined(polyline)) { - if (this._fillEnabled) { - this._fillEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } - var positionsProperty = polyline.positions; + /** + * Returns the phone number that was matched. + * + * @return {String} + */ + getNumber: function() { + return this.number; + }, - 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._depthFailMaterialProperty = polyline.depthFailMaterial; - this._showProperty = defaultValue(show, defaultShow); - this._shadowsProperty = defaultValue(polyline.shadows, defaultShadows); - this._distanceDisplayConditionProperty = defaultValue(polyline.distanceDisplayCondition, defaultDistanceDisplayCondition); - this._fillEnabled = true; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'tel:' + this.number; + }, - 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); + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return this.matchedText; + } - //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; - } +} ); - var vertexFormat; - if (isColorMaterial && (!defined(this._depthFailMaterialProperty) || this._depthFailMaterialProperty instanceof ColorMaterialProperty)) { - vertexFormat = PolylineColorAppearance.VERTEX_FORMAT; - } else { - vertexFormat = PolylineMaterialAppearance.VERTEX_FORMAT; - } +/*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. + */ + - options.vertexFormat = vertexFormat; - 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); - } - }; + /** + * 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; + }, + - /** - * 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) { - - return new DynamicGeometryUpdater(primitives, this); - }; + /** + * 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 - */ - var generateCartesianArcOptions = { - positions : undefined, - granularity : undefined, - height : undefined, - ellipsoid : undefined - }; + /** + * @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, + - function DynamicGeometryUpdater(primitives, geometryUpdater) { - var sceneId = geometryUpdater._scene.id; + /** + * 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; + }, + - 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); - } + /** + * 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; - var line = polylineCollection.add(); - line.id = geometryUpdater._entity; +})); - this._line = line; - this._primitives = primitives; - this._geometryUpdater = geometryUpdater; - this._positions = []; +/** +@license + Copyright (c) 2013 Gildas Lormeau. All rights reserved. - 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; + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: - if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) { - line.show = false; - return; - } + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. - var positionsProperty = polyline.positions; - var positions = Property.getValueOrUndefined(positionsProperty, time, this._positions); - if (!defined(positions) || positions.length < 2) { - line.show = false; - return; - } + 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. - 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); - } + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. - 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); - }; + 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. +**/ - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - - var line = this._line; - if (line.show && line.positions.length > 0) { - BoundingSphere.fromPoints(line.positions, result); - return BoundingSphereState.DONE; - } - return BoundingSphereState.FAILED; - }; +define('ThirdParty/zip',[ + '../Core/buildModuleUrl', + '../Core/defineProperties' + ], function( + buildModuleUrl, + defineProperties) { + var tmp = {}; - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; - }; +(function(obj) { - 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); - }; + 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; - return PolylineGeometryUpdater; -}); + var INFLATE_JS = "inflate.js"; + var DEFLATE_JS = "deflate.js"; -/*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 TEXT_PLAIN = "text/plain"; - 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(); + var MESSAGE_EVENT = "message"; - function GeometryOptions(entity) { - this.id = entity; - this.vertexFormat = undefined; - this.polylinePositions = undefined; - this.shapePositions = undefined; - this.cornerType = undefined; - this.granularity = undefined; - } + var appendABViewSupported; + try { + appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0; + } catch (e) { + } - /** - * 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) { - - 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); - } + 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; + })(); - 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 - } - }); + 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); + } - 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; - } - } - }); + 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) + }; + } - /** - * 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); - }; + // Readers + function Reader() { + } - /** - * 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); - }; + function TextReader(text) { + var that = this, blobReader; - /** - * 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + 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); + } - var attributes; + function readUint8Array(index, length, callback, onerror) { + blobReader.readUint8Array(index, length, callback, onerror); + } - 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 - }; - } + that.size = 0; + that.init = init; + that.readUint8Array = readUint8Array; + } + TextReader.prototype = new Reader(); + TextReader.prototype.constructor = TextReader; - return new GeometryInstance({ - id : entity, - geometry : new PolylineVolumeGeometry(this._options), - attributes : attributes - }); - }; + function Data64URIReader(dataURI) { + var that = this, dataStart; - /** - * 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); - var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); - var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + 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(); + } - 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) - } - }); - }; + 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); + } - /** - * 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; - }; + that.size = 0; + that.init = init; + that.readUint8Array = readUint8Array; + } + Data64URIReader.prototype = new Reader(); + Data64URIReader.prototype.constructor = Data64URIReader; - /** - * 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); - }; + function BlobReader(blob) { + var that = this; - PolylineVolumeGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { - if (!(propertyName === 'availability' || propertyName === 'polylineVolume')) { - return; - } + function init(callback) { + this.size = blob.size; + callback(); + } - var polylineVolume = this._entity.polylineVolume; + 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)); + } - if (!defined(polylineVolume)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + that.size = 0; + that.init = init; + that.readUint8Array = readUint8Array; + } + BlobReader.prototype = new Reader(); + BlobReader.prototype.constructor = BlobReader; - var fillProperty = polylineVolume.fill; - var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + // Writers - var outlineProperty = polylineVolume.outline; - var outlineEnabled = defined(outlineProperty); - if (outlineEnabled && outlineProperty.isConstant) { - outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); - } + function Writer() { + } + Writer.prototype.getData = function(callback) { + callback(this.data); + }; - if (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + function TextWriter(encoding) { + var that = this, blob; - var positions = polylineVolume.positions; - var shape = polylineVolume.shape; + function init(callback) { + blob = new Blob([], { + type : TEXT_PLAIN + }); + callback(); + } - 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; - } + function writeUint8Array(array, callback) { + blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], { + type : TEXT_PLAIN + }); + callback(); + } - 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); + function getData(callback, onerror) { + var reader = new FileReader(); + reader.onload = function(e) { + callback(e.target.result); + }; + reader.onerror = onerror; + reader.readAsText(blob, encoding); + } - var granularity = polylineVolume.granularity; - var outlineWidth = polylineVolume.outlineWidth; - var cornerType = polylineVolume.cornerType; + that.init = init; + that.writeUint8Array = writeUint8Array; + that.getData = getData; + } + TextWriter.prototype = new Writer(); + TextWriter.prototype.constructor = TextWriter; - this._fillEnabled = fillEnabled; - this._outlineEnabled = outlineEnabled; + function Data64URIWriter(contentType) { + var that = this, data = "", pending = ""; - 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); - } - }; + function init(callback) { + data += "data:" + (contentType || "") + ";base64,"; + callback(); + } - /** - * 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) { - - return new DynamicGeometryUpdater(primitives, this); - }; + 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(); + } - /** - * @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) { - - var primitives = this._primitives; - primitives.removeAndDestroy(this._primitive); - primitives.removeAndDestroy(this._outlinePrimitive); - this._primitive = undefined; - this._outlinePrimitive = undefined; + function getData(callback) { + callback(data + window.btoa(pending)); + } - 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; - } + that.init = init; + that.writeUint8Array = writeUint8Array; + that.getData = getData; + } + Data64URIWriter.prototype = new Writer(); + Data64URIWriter.prototype.constructor = Data64URIWriter; - 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; - } + function BlobWriter(contentType) { + var blob, that = this; - options.polylinePositions = positions; - options.shapePositions = shape; - options.granularity = Property.getValueOrUndefined(polylineVolume.granularity, time); - options.cornerType = Property.getValueOrUndefined(polylineVolume.cornerType, time); + function init(callback) { + blob = new Blob([], { + type : contentType + }); + callback(); + } - var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + function writeUint8Array(array, callback) { + blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], { + type : contentType + }); + callback(); + } - if (!defined(polylineVolume.fill) || polylineVolume.fill.getValue(time)) { - var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); - this._material = material; + function getData(callback) { + callback(blob); + } - var appearance = new MaterialAppearance({ - material : material, - translucent : material.isTranslucent(), - closed : true - }); - options.vertexFormat = appearance.vertexFormat; + that.init = init; + that.writeUint8Array = writeUint8Array; + that.getData = getData; + } + BlobWriter.prototype = new Writer(); + BlobWriter.prototype.constructor = BlobWriter; - this._primitive = primitives.add(new Primitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new PolylineVolumeGeometry(options) - }), - appearance : appearance, - asynchronous : false, - shadows : shadows - })); - } + // inflate/deflate core functions - if (defined(polylineVolume.outline) && polylineVolume.outline.getValue(time)) { - options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) { + var chunkIndex = 0, index, outputSize; - 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; + function onflush() { + worker.removeEventListener(MESSAGE_EVENT, onmessage, false); + onend(outputSize); + } - 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 - })); - } - }; + function onmessage(event) { + var message = event.data, data = message.data; - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); - }; + 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); + } - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; - }; + 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 + }); + } - DynamicGeometryUpdater.prototype.destroy = function() { - var primitives = this._primitives; - primitives.removeAndDestroy(this._primitive); - primitives.removeAndDestroy(this._outlinePrimitive); - destroyObject(this); - }; + outputSize = 0; + worker.addEventListener(MESSAGE_EVENT, onmessage, false); + step(); + } - return PolylineVolumeGeometryUpdater; -}); + function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) { + var chunkIndex = 0, index, outputSize = 0; -/*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'; + 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); + } + } - 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(); + step(); + } - 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; - } + function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { + var worker, crc32 = new Crc32(); - /** - * 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) { - - 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); + function oninflateappend(sending, array) { + if (computeCrc32 && !sending) + crc32.append(array); + } - this._onEntityPropertyChanged(entity, 'rectangle', entity.rectangle, undefined); - } + function oninflateend(outputSize) { + onend(outputSize, crc32.get()); + } - 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 - } - }); + 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; + } - 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; - } - } - }); + function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) { + var worker, crc32 = new Crc32(); - /** - * 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); - }; + function ondeflateappend(sending, array) { + if (sending) + crc32.append(array); + } - /** - * 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); - }; + function ondeflateend(outputSize) { + onend(outputSize, crc32.get()); + } - /** - * 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + function onmessage() { + worker.removeEventListener(MESSAGE_EVENT, onmessage, false); + launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror); + } - var attributes; + 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; + } - 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 - }; - } + function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { + var chunkIndex = 0, crc32 = new Crc32(); - return new GeometryInstance({ - id : entity, - geometry : new RectangleGeometry(this._options), - attributes : attributes - }); - }; + 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()); + } - /** - * 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) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); - var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); - var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + step(); + } - 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) - } - }); - }; + // ZipReader - /** - * 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; - }; + 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; + } - /** - * 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); - }; + function decodeUTF8(string) { + return decodeURIComponent(escape(string)); + } - RectangleGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { - if (!(propertyName === 'availability' || propertyName === 'rectangle')) { - return; - } + function getString(bytes) { + var i, str = ""; + for (i = 0; i < bytes.length; i++) + str += String.fromCharCode(bytes[i]); + return str; + } - var rectangle = this._entity.rectangle; + 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) { + } + } - if (!defined(rectangle)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + 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); + } - var fillProperty = rectangle.fill; - var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + function createZipReader(reader, onerror) { + function Entry() { + } - var outlineProperty = rectangle.outline; - var outlineEnabled = defined(outlineProperty); - if (outlineEnabled && outlineProperty.isConstant) { - outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); - } + Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) { + var that = this, worker; - if (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); - } - return; - } + function terminate(callback, param) { + if (worker) + worker.terminate(); + worker = null; + if (callback) + callback(param); + } - var coordinates = rectangle.coordinates; + function testCrc32(crc32) { + var dataCrc32 = getDataHelper(4); + dataCrc32.view.setUint32(0, crc32); + return that.crc32 == dataCrc32.view.getUint32(0); + } - 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; - } + function getWriterData(uncompressedSize, crc32) { + if (checkCrc32 && !testCrc32(crc32)) + onreaderror(); + else + writer.getData(function(data) { + terminate(onend, data); + }); + } - 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); + function onreaderror() { + terminate(onerror, ERR_READ_DATA); + } - 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); + function onwriteerror() { + terminate(onerror, ERR_WRITE_DATA); + } - if (outlineEnabled && onTerrain) { - oneTimeWarning(oneTimeWarning.geometryOutlines); - outlineEnabled = false; - } + 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); + }; - this._fillEnabled = fillEnabled; - this._onTerrain = onTerrain; - this._outlineEnabled = outlineEnabled; + 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); + }); + } - 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); - } - }; + 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(); + } + }; + } - /** - * 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) { - - return new DynamicGeometryUpdater(primitives, groundPrimitives, this); - }; + // ZipWriter - /** - * @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) { - - var geometryUpdater = this._geometryUpdater; - var onTerrain = geometryUpdater._onTerrain; + function encodeUTF8(string) { + return unescape(encodeURIComponent(string)); + } - 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; + function getBytes(str) { + var i, array = []; + for (i = 0; i < str.length; i++) + array.push(str.charCodeAt(i)); + return array; + } - var entity = geometryUpdater._entity; - var rectangle = entity.rectangle; - if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(rectangle.show, time, true)) { - return; - } + function createZipWriter(writer, onerror, dontDeflate) { + var worker, files = {}, filenames = [], datalength = 0; - var options = this._options; - var coordinates = Property.getValueOrUndefined(rectangle.coordinates, time, options.rectangle); - if (!defined(coordinates)) { - return; - } + function terminate(callback, message) { + if (worker) + worker.terminate(); + worker = null; + if (callback) + callback(message); + } - 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); + function onwriteerror() { + terminate(onerror, ERR_WRITE); + } - var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + function onreaderror() { + terminate(onerror, ERR_READ_DATA); + } - if (Property.getValueOrDefault(rectangle.fill, time, true)) { - var fillMaterialProperty = geometryUpdater.fillMaterialProperty; - var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); - this._material = material; + return { + add : function(name, reader, onend, onprogress, options) { + var header, filename, date; - if (onTerrain) { - var currentColor = Color.WHITE; - if (defined(fillMaterialProperty.color)) { - currentColor = fillMaterialProperty.color.getValue(time); - } + 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); + } - 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) - }); + 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); + } - options.vertexFormat = appearance.vertexFormat; + 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); + } - this._primitive = primitives.add(new Primitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new RectangleGeometry(options) - }), - appearance : appearance, - asynchronous : false, - shadows : shadows - })); - } - } + 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); + } + }; + } - if (!onTerrain && Property.getValueOrDefault(rectangle.outline, time, false)) { - options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + 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 outlineColor = Property.getValueOrClonedDefault(rectangle.outlineColor, time, Color.BLACK, scratchColor); - var outlineWidth = Property.getValueOrDefault(rectangle.outlineWidth, time, 1.0); - var translucent = outlineColor.alpha !== 1.0; + var workerScriptsPath; - 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 - })); + defineProperties(obj.zip, { + 'workerScriptsPath' : { + get : function() { + if (typeof workerScriptsPath === 'undefined') { + workerScriptsPath = buildModuleUrl('ThirdParty/Workers/'); + } + return workerScriptsPath; + } } - }; + }); - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); - }; +})(tmp); - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; - }; + return tmp.zip; +}); - 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); - }; +define('DataSources/KmlLookAt',[], function() { + 'use strict'; + /** + * @alias KmlLookAt + * @constructor + * + * @param {Cartesian3} position camera position + * @param {HeadingPitchRange} headingPitchRange camera orientation + */ + function KmlLookAt(position, headingPitchRange) { + this.position = position; + this.headingPitchRange = headingPitchRange; + } - return RectangleGeometryUpdater; + return KmlLookAt; }); -/*global define*/ -define('DataSources/WallGeometryUpdater',[ - '../Core/Color', - '../Core/ColorGeometryInstanceAttribute', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/destroyObject', - '../Core/DeveloperError', - '../Core/DistanceDisplayCondition', - '../Core/DistanceDisplayConditionGeometryInstanceAttribute', +define('DataSources/KmlTour',[ '../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' + '../Core/defined' ], 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) { + defined + ) { '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 + * @alias KMLTour * @constructor * - * @param {Entity} entity The entity containing the geometry to be visualized. - * @param {Scene} scene The scene where visualization is taking place. + * @param {String} name name parsed from KML + * @param {String} id id parsed from KML + * @param {Array} playlist array with KMLTourFlyTos, KMLTourWaits and KMLTourSoundCues */ - function WallGeometryUpdater(entity, scene) { - - 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; - } - }, + function KmlTour(name, id) { /** - * Gets a value indicating if fill visibility varies with simulation time. - * @memberof WallGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * Id of kml gx:Tour entry */ - hasConstantFill : { - get : function() { - return !this._fillEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._fillProperty)); - } - }, + this.id = id; /** - * Gets the material property used to fill the geometry. - * @memberof WallGeometryUpdater.prototype - * - * @type {MaterialProperty} - * @readonly + * Tour name */ - fillMaterialProperty : { - get : function() { - return this._materialProperty; - } - }, + this.name = name; /** - * Gets a value indicating if the geometry has an outline component. - * @memberof WallGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * Index of current entry from playlist + * @type Number */ - outlineEnabled : { - get : function() { - return this._outlineEnabled; - } - }, + this.playlistIndex = 0; /** - * Gets a value indicating if the geometry has an outline component. - * @memberof WallGeometryUpdater.prototype - * - * @type {Boolean} - * @readonly + * Array of playlist entries + * @type Array */ - hasConstantOutline : { - get : function() { - return !this._outlineEnabled || - (!defined(this._entity.availability) && - Property.isConstant(this._showProperty) && - Property.isConstant(this._showOutlineProperty)); - } - }, + this.playlist = []; /** - * Gets the {@link Color} property for the geometry outline. - * @memberof WallGeometryUpdater.prototype - * - * @type {Property} - * @readonly + * Event will be called when tour starts to play, + * before any playlist entry starts to play. + * @type Event */ - outlineColorProperty : { - get : function() { - return this._outlineColorProperty; - } - }, + this.tourStart = new Event(); /** - * Gets the constant with of the geometry outline, in pixels. - * This value is only valid if isDynamic is false. - * @memberof WallGeometryUpdater.prototype + * Event will be called when all playlist entries are + * played, or tour playback being canceled. * - * @type {Number} - * @readonly + * If tour playback was terminated, event callback will + * be called with terminated=true parameter. + * @type Event */ - outlineWidth : { - get : function() { - return this._outlineWidth; - } - }, + this.tourEnd = new Event(); /** - * Gets the property specifying whether the geometry - * casts or receives shadows from each light source. - * @memberof WallGeometryUpdater.prototype + * Event will be called when entry from playlist starts to play. * - * @type {Property} - * @readonly + * Event callback will be called with curent entry as first parameter. + * @type Event */ - shadowsProperty : { - get : function() { - return this._shadowsProperty; - } - }, + this.entryStart = new Event(); /** - * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed. - * @memberof WallGeometryUpdater.prototype + * Event will be called when entry from playlist ends to play. * - * @type {Property} - * @readonly + * Event callback will be called with following parameters: + * 1. entry - entry + * 2. terminated - true if playback was terminated by calling {@link KmlTour#stop} + * @type Event */ - distanceDisplayConditionProperty : { - get : function() { - return this._distanceDisplayConditionProperty; + this.entryEnd = new Event(); + + this._activeEntries = []; + } + + /** + * Add entry to this tour playlist. + * + * @param {KmlTourFlyTo|KmlTourWait} entry an entry to add to the playlist. + */ + KmlTour.prototype.addPlaylistEntry = function(entry) { + this.playlist.push(entry); + }; + + /** + * Play this tour. + * + * @param {Viewer} viewer viewer widget. + * @param {Object} [cameraOptions] these options will be merged with {@link Camera#flyTo} + * options for FlyTo playlist entries. + */ + KmlTour.prototype.play = function(viewer, cameraOptions) { + this.tourStart.raiseEvent(); + + var tour = this; + playEntry.call(this, viewer, cameraOptions, function(terminated) { + tour.playlistIndex = 0; + // Stop nonblocking entries + if (!terminated) { + cancelAllEntries(tour._activeEntries); } - }, - /** - * 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; + tour.tourEnd.raiseEvent(terminated); + }); + }; + + /** + * Stop curently playing tour. + */ + KmlTour.prototype.stop = function() { + cancelAllEntries(this._activeEntries); + }; + + /** + * Stop all activeEntries. + * @param {Array} activeEntries + */ + function cancelAllEntries(activeEntries) { + for(var entry = activeEntries.pop(); entry !== undefined; entry = activeEntries.pop()) { + entry.stop(); + } + } + + /** + * Play playlist entry. + * This function is called recursevly with playNext + * and iterates over all entries from playlist. + * + * @param {ViewerWidget} viewer Cesium viewer. + * @param {Object} cameraOptions see {@link Camera#flyTo}. + * @param {Function} allDone a function will be called when all entries from playlist + * being played or user call {@link KmlTour#stop}. + */ + function playEntry(viewer, cameraOptions, allDone) { + var entry = this.playlist[this.playlistIndex]; + if (entry) { + var _playNext = playNext.bind(this, viewer, cameraOptions, allDone); + this._activeEntries.push(entry); + this.entryStart.raiseEvent(entry); + if (entry.blocking) { + entry.play(_playNext, viewer.scene.camera, cameraOptions); } - }, - /** - * 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; + else { + var tour = this; + entry.play(function() { + tour.entryEnd.raiseEvent(entry); + var indx = tour._activeEntries.indexOf(entry); + if (indx >= 0) { + tour._activeEntries.splice(indx, 1); + } + }); + _playNext(viewer, cameraOptions, allDone); } - }, - /** - * 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; + } + else if(defined(allDone)) { + allDone(false); + } + } + + /** + * Increment playlistIndex and call playEntry + * if terminated isn't true. + * + * @param {ViewerWidget} viewer passed for recursion. + * @param {Object} cameraOptions passed for recursion. + * @param {Function} allDone passed for recursion. + * @param {Boolean} terminated true if active entry was terminated, + * and the whole tour should be terminated. + */ + function playNext(viewer, cameraOptions, allDone, terminated) { + var entry = this.playlist[this.playlistIndex]; + this.entryEnd.raiseEvent(entry, terminated); + + if (terminated) { + allDone(terminated); + } + else { + var indx = this._activeEntries.indexOf(entry); + if (indx >= 0) { + this._activeEntries.splice(indx, 1); } + this.playlistIndex++; + playEntry.call(this, viewer, cameraOptions, allDone); } - }); + } + + return KmlTour; +}); +define('DataSources/KmlTourFlyTo',[ + '../Core/defined', + '../Core/combine', + '../Core/BoundingSphere', + '../Core/EasingFunction' + ], function( + defined, + combine, + BoundingSphere, + EasingFunction + ) { + 'use strict'; /** - * Checks if the geometry is outlined at the provided time. + * @alias KmlTourFlyTo + * @constructor * - * @param {JulianDate} time The time for which to retrieve visibility. - * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise. + * @param {Number} duration entry duration + * @param {String} flyToMode KML fly to mode: bounce, smooth, etc + * @param {KmlCamera|KmlLookAt} view KmlCamera or KmlLookAt */ - WallGeometryUpdater.prototype.isOutlineVisible = function(time) { - var entity = this._entity; - return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time); - }; + function KmlTourFlyTo(duration, flyToMode, view) { + this.type = 'KmlTourFlyTo'; + this.blocking = true; + this.activeCamera = null; + this.activeCallback = null; + + this.duration = duration; + this.view = view; + this.flyToMode = flyToMode; + } /** - * Checks if the geometry is filled at the provided time. + * Play this playlist entry * - * @param {JulianDate} time The time for which to retrieve visibility. - * @returns {Boolean} true if geometry is filled at the provided time, false otherwise. + * @param {KmlTourFlyTo~DoneCallback} done function which will be called when playback ends + * @param {Camera} camera Cesium camera + * @param {Object} [cameraOptions] which will be merged with camera flyTo options. See {@link Camera#flyTo} */ - WallGeometryUpdater.prototype.isFilled = function(time) { - var entity = this._entity; - return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time); + KmlTourFlyTo.prototype.play = function(done, camera, cameraOptions) { + this.activeCamera = camera; + if (defined(done) && done !== null) { + var self = this; + this.activeCallback = function(terminated) { + delete self.activeCallback; + delete self.activeCamera; + done(defined(terminated) ? false : terminated); + }; + } + + var options = this.getCameraOptions(cameraOptions); + if (this.view.headingPitchRoll) { + camera.flyTo(options); + } + else if (this.view.headingPitchRange) { + var target = new BoundingSphere(this.view.position); + camera.flyToBoundingSphere(target, options); + } }; /** - * 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. + * Stop execution of curent entry. Cancel camera flyTo + */ + KmlTourFlyTo.prototype.stop = function() { + if (defined(this.activeCamera)) { + this.activeCamera.cancelFlight(); + } + if (defined(this.activeCallback)) { + this.activeCallback(true); + } + }; + + /** + * Returns options for {@link Camera#flyTo} or {@link Camera#flyToBoundingSphere} + * depends on this.view type. * - * @exception {DeveloperError} This instance does not represent a filled geometry. + * @param {Object} cameraOptions options to merge with generated. See {@link Camera#flyTo} + * @returns {Object} {@link Camera#flyTo} or {@link Camera#flyToBoundingSphere} options */ - WallGeometryUpdater.prototype.createFillGeometryInstance = function(time) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); + KmlTourFlyTo.prototype.getCameraOptions = function(cameraOptions) { + var options = { + duration: this.duration + }; - var attributes; + if (defined(this.activeCallback)) { + options.complete = this.activeCallback; + } - 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 - }; + if (this.flyToMode === 'smooth' ) { + options.easingFunction = EasingFunction.LINEAR_NONE; } - return new GeometryInstance({ - id : entity, - geometry : new WallGeometry(this._options), - attributes : attributes - }); + if (this.view.headingPitchRoll) { + options.destination = this.view.position; + options.orientation = this.view.headingPitchRoll; + } + else if (this.view.headingPitchRange) { + options.offset = this.view.headingPitchRange; + } + + if (defined(cameraOptions)) { + options = combine(options, cameraOptions); + } + return options; }; /** - * Creates the geometry instance which represents the outline of the geometry. + * A function that will be executed when the flight completes. + * @callback KmlTourFlyTo~DoneCallback * - * @param {JulianDate} time The time to use when retrieving initial attribute values. - * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry. + * @param {Boolean} terminated true if {@link KmlTourFlyTo#stop} was + * called before entry done playback. + */ + + return KmlTourFlyTo; +}); + +define('DataSources/KmlTourWait',[ + '../Core/defined' + ], function( + defined + ) { + 'use strict'; + /** + * @alias KmlTourWait + * @constructor * - * @exception {DeveloperError} This instance does not represent an outlined geometry. + * @param {Number} duration entry duration */ - WallGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) { - - var entity = this._entity; - var isAvailable = entity.isAvailable(time); - var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK); - var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time); + function KmlTourWait(duration) { + this.type = 'KmlTourWait'; + this.blocking = true; + this.duration = duration; - 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) - } - }); - }; + this.timeout = null; + } /** - * Returns true if this object was destroyed; otherwise, false. + * Play this playlist entry * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * @param {KmlTourWait~DoneCallback} done function which will be called when playback ends */ - WallGeometryUpdater.prototype.isDestroyed = function() { - return false; + KmlTourWait.prototype.play = function(done) { + var self = this; + this.activeCallback = done; + this.timeout = setTimeout(function() { + delete self.activeCallback; + done(false); + }, this.duration * 1000); }; /** - * Destroys and resources used by the object. Once an object is destroyed, it should not be used. + * Stop execution of curent entry, cancel curent timeout + */ + KmlTourWait.prototype.stop = function() { + clearTimeout(this.timeout); + if (defined(this.activeCallback)) { + this.activeCallback(true); + } + }; + + /** + * A function which will be called when playback ends. * - * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * @callback KmlTourWait~DoneCallback + * @param {Boolean} terminated true if {@link KmlTourWait#stop} was + * called before entry done playback. */ - WallGeometryUpdater.prototype.destroy = function() { - this._entitySubscription(); - destroyObject(this); + + return KmlTourWait; +}); + +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/objectToQuery', + '../Core/oneTimeWarning', + '../Core/PinBuilder', + '../Core/PolygonHierarchy', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/TimeInterval', + '../Core/TimeIntervalCollection', + '../Core/HeadingPitchRoll', + '../Core/HeadingPitchRange', + '../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', + './KmlLookAt', + './KmlCamera', + './KmlTour', + './KmlTourFlyTo', + './KmlTourWait' + ], 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, + objectToQuery, + oneTimeWarning, + PinBuilder, + PolygonHierarchy, + Rectangle, + RuntimeError, + TimeInterval, + TimeIntervalCollection, + HeadingPitchRoll, + HeadingPitchRange, + 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, + KmlLookAt, + KmlCamera, + KmlTour, + KmlTourFlyTo, + KmlTourWait) { + '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]; + } }; - WallGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) { - if (!(propertyName === 'availability' || propertyName === 'wall')) { - return; + 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 wall = this._entity.wall; + var BILLBOARD_SIZE = 32; - if (!defined(wall)) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); + 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 embedDataUris(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); + } } - return; } + } - var fillProperty = wall.fill; - var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true; + function applyBasePath(div, elementType, attributeName, proxy, sourceUri, query) { + var elements = div.querySelectorAll(elementType); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + var value = element.getAttribute(attributeName); + var uri = resolveHref(value, proxy, sourceUri, query); + element.setAttribute(attributeName, uri); + } + } - var outlineProperty = wall.outline; - var outlineEnabled = defined(outlineProperty); - if (outlineEnabled && outlineProperty.isConstant) { - outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE); + function proxyUrl(url, proxy, query) { + if (defined(proxy)) { + if (new Uri(url).isAbsolute()) { + url = proxy.getURL(url); + } + } + if (defined(query)) { + url = joinUrls(url, '?' + query, false); + } + 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 (!fillEnabled && !outlineEnabled) { - if (this._fillEnabled || this._outlineEnabled) { - this._fillEnabled = false; - this._outlineEnabled = false; - this._geometryChanged.raiseEvent(this); + // 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; } - return; } - var positions = wall.positions; + entity = entityCollection.add(new Entity({id : id})); + if (!defined(entity.kml)) { + entity.addProperty('kml'); + entity.kml = new KmlFeatureData(); + } + return entity; + } - 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); + 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; } + return undefined; + } - 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); + 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; + } - var minimumHeights = wall.minimumHeights; - var maximumHeights = wall.maximumHeights; - var outlineWidth = wall.outlineWidth; - var granularity = wall.granularity; + 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; + } - this._fillEnabled = fillEnabled; - this._outlineEnabled = outlineEnabled; + 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; + } - 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); + 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, query) { + 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) { + if (defined(sourceUri)) { + href = getAbsoluteUri(href, getAbsoluteUri(sourceUri)); } + href = proxyUrl(href, proxy, query); + } + return href; + } + + var colorOptions = {}; + + function parseColorString(value, isRandom) { + if (!defined(value) || /^\s*$/gm.test(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 { - 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); + colorOptions.blue = 0; } - }; - - /** - * 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) { - - 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); + colorOptions.alpha = alpha; + return Color.fromRandom(colorOptions); } - DynamicGeometryUpdater.prototype.update = function(time) { - - 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; + function queryColorValue(node, tagName, namespace) { + var value = queryStringValue(node, tagName, namespace); + if (!defined(value)) { + return undefined; } + return parseColorString(value, queryStringValue(node, 'colorMode', namespace) === 'random'); + } - var options = this._options; - var positions = Property.getValueOrUndefined(wall.positions, time, options.positions); - if (!defined(positions)) { - return; + 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; } - 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); + //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; + } - var shadows = this._geometryUpdater.shadowsProperty.getValue(time); + function processTimeSpan(featureNode) { + var node = queryFirstNode(featureNode, 'TimeSpan', namespaces.kmlgx); + if (!defined(node)) { + return undefined; + } + var result; - if (Property.getValueOrDefault(wall.fill, time, true)) { - var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material); - this._material = material; + var beginNode = queryFirstNode(node, 'begin', namespaces.kmlgx); + var beginDate = defined(beginNode) ? JulianDate.fromIso8601(beginNode.textContent) : undefined; - var appearance = new MaterialAppearance({ - material : material, - translucent : material.isTranslucent(), - closed : defined(options.extrudedHeight) - }); - options.vertexFormat = appearance.vertexFormat; + var endNode = queryFirstNode(node, 'end', namespaces.kmlgx); + var endDate = defined(endNode) ? JulianDate.fromIso8601(endNode.textContent) : undefined; - this._primitive = primitives.add(new Primitive({ - geometryInstances : new GeometryInstance({ - id : entity, - geometry : new WallGeometry(options) - }), - appearance : appearance, - asynchronous : false, - shadows : shadows + 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 })); } - if (Property.getValueOrDefault(wall.outline, time, false)) { - options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; + return result; + } - 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; + 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; + } - 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 - })); + 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, query) { + var href = queryStringValue(iconNode, 'href', namespaces.kml); + if (!defined(href) || (href.length === 0)) { + return undefined; } - }; - DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) { - return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result); - }; + if (href.indexOf('root://icons/palette-') === 0) { + var palette = href.charAt(21); - DynamicGeometryUpdater.prototype.isDestroyed = function() { - return false; - }; + // 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; - DynamicGeometryUpdater.prototype.destroy = function() { - var primitives = this._primitives; - primitives.removeAndDestroy(this._primitive); - primitives.removeAndDestroy(this._outlinePrimitive); - destroyObject(this); - }; + href = 'https://maps.google.com/mapfiles/kml/pal' + palette + '/icon' + iconNum + '.png'; + } - return WallGeometryUpdater; -}); + href = resolveHref(href, dataSource._proxy, sourceUri, uriResolver, query); -/*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'; + if (canRefresh) { + var refreshMode = queryStringValue(iconNode, 'refreshMode', namespaces.kml); + var viewRefreshMode = queryStringValue(iconNode, 'viewRefreshMode', namespaces.kml); + if (refreshMode === 'onInterval' || refreshMode === 'onExpire') { + oneTimeWarning('kml-refreshMode-' + refreshMode, 'KML - Unsupported Icon refreshMode: ' + refreshMode); + } else if (viewRefreshMode === 'onStop' || viewRefreshMode === 'onRegion') { + oneTimeWarning('kml-refreshMode-' + viewRefreshMode, 'KML - Unsupported Icon viewRefreshMode: ' + viewRefreshMode); + } - /** - * 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) { - - GroundPrimitive.initializeTerrainHeights(); + 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 scene = options.scene; - var dataSourceCollection = options.dataSourceCollection; + var icon = joinUrls(href, queryString, false); + return processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, icon, viewBoundScale, dataSource._lastCameraView.bbox); + } - this._eventHelper = new EventHelper(); - this._eventHelper.add(dataSourceCollection.dataSourceAdded, this._onDataSourceAdded, this); - this._eventHelper.add(dataSourceCollection.dataSourceRemoved, this._onDataSourceRemoved, this); + return href; + } - this._dataSourceCollection = dataSourceCollection; - this._scene = scene; - this._visualizersCallback = defaultValue(options.visualizersCallback, DataSourceDisplay.defaultVisualizersCallback); + function processBillboardIcon(dataSource, node, targetEntity, sourceUri, uriResolver, query) { + var scale = queryNumericValue(node, 'scale', namespaces.kml); + var heading = queryNumericValue(node, 'heading', namespaces.kml); + var color = queryColorValue(node, 'color', namespaces.kml); - for (var i = 0, len = dataSourceCollection.length; i < len; i++) { - this._onDataSourceAdded(dataSourceCollection, dataSourceCollection.get(i)); + var iconNode = queryFirstNode(node, 'Icon', namespaces.kml); + var icon = getIconHref(iconNode, dataSource, sourceUri, uriResolver, false, query); + 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; } - var defaultDataSource = new CustomDataSource(); - this._onDataSourceAdded(undefined, defaultDataSource); - this._defaultDataSource = defaultDataSource; + billboard.image = icon; + billboard.scale = scale; + billboard.color = color; - this._ready = false; - } + if (defined(x) || defined(y) || defined(w) || defined(h)) { + billboard.imageSubRegion = new BoundingRectangle(x, y, w, h); + } - /** - * 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)]; - }; + //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; + } - 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; - } - }, + //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); - /** - * 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; + 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; } - }); - /** - * 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; - }; + 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; + } - /** - * 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(); + yOffset -= BILLBOARD_SIZE * 0.5 * scale; + } - var dataSourceCollection = this._dataSourceCollection; - for (var i = 0, length = dataSourceCollection.length; i < length; ++i) { - this._onDataSourceRemoved(this._dataSourceCollection, dataSourceCollection.get(i)); + if (defined(xOffset) || defined(yOffset)) { + billboard.pixelOffset = new Cartesian2(xOffset, yOffset); } - this._onDataSourceRemoved(undefined, this._defaultDataSource); + } - return destroyObject(this); - }; + function applyStyle(dataSource, styleNode, targetEntity, sourceUri, uriResolver, query) { + 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, query); + } 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))) { + oneTimeWarning('kml-gx:outerColor', 'KML - gx:outerColor is not supported in a LineStyle'); + } + if (defined(queryNumericValue(node, 'outerWidth', namespaces.gx))) { + oneTimeWarning('kml-gx:outerWidth', 'KML - gx:outerWidth is not supported in a LineStyle'); + } + if (defined(queryNumericValue(node, 'physicalWidth', namespaces.gx))) { + oneTimeWarning('kml-gx:physicalWidth', 'KML - gx:physicalWidth is not supported in a LineStyle'); + } + if (defined(queryBooleanValue(node, 'labelVisibility', namespaces.gx))) { + oneTimeWarning('kml-gx:labelVisibility', '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); - /** - * 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 (!GroundPrimitive._initialized) { - this._ready = false; - return false; + //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') { + oneTimeWarning('kml-listStyle-' + listItemType, 'KML - Unsupported ListStyle with listItemType: ' + listItemType); + } + } } - - 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; - } + //Processes and merges any inline styles for the provided node into the provided entity. + function computeFinalStyle(entity, dataSource, placeMark, styleCollection, sourceUri, uriResolver, query) { + var result = new Entity(); + var styleEntity; - visualizers = dataSource._visualizers; - vLength = visualizers.length; - for (x = 0; x < vLength; x++) { - result = visualizers[x].update(time) && result; + //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; } } - visualizers = this._defaultDataSource._visualizers; - vLength = visualizers.length; - for (x = 0; x < vLength; x++) { - result = visualizers[x].update(time) && result; + if (styleIndex !== -1) { + var inlineStyleNode = childNodes[styleIndex]; + if (inlineStyleNode.localName === 'Style') { + applyStyle(dataSource, inlineStyleNode, result, sourceUri, uriResolver, query); + } 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, query); + } + } else { + oneTimeWarning('kml-styleMap-' + key, 'KML - Unsupported StyleMap key: ' + key); + } + } + } } - this._ready = result; + //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]; + } - return result; - }; + styleEntity = styleCollection.getById(id); + if (!defined(styleEntity)) { + styleEntity = styleCollection.getById('#' + id); + } + if (defined(styleEntity)) { + result.merge(styleEntity); + } + } - var getBoundingSphereArrayScratch = []; - var getBoundingSphereBoundingSphereScratch = new BoundingSphere(); + return result; + } - /** - * 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 (!this._ready) { - return BoundingSphereState.PENDING; - } + //Asynchronously processes an external style file. + function processExternalStyles(dataSource, uri, styleCollection, query) { + return loadXML(proxyUrl(uri, dataSource._proxy, query)).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, query) { var i; - var length; - var dataSource = this._defaultDataSource; - if (!dataSource.entities.contains(entity)) { - dataSource = undefined; + var id; + var styleEntity; - 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; + 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, query); + } } } } - if (!defined(dataSource)) { - return BoundingSphereState.FAILED; - } + 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 boundingSpheres = getBoundingSphereArrayScratch; - var tmp = getBoundingSphereBoundingSphereScratch; + var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml); + if (defined(styleUrl)) { + if (styleUrl[0] !== '#') { + styleUrl = '#' + styleUrl; + } - var count = 0; - var state = BoundingSphereState.DONE; - var visualizers = dataSource._visualizers; - var visualizersLength = visualizers.length; + if (isExternal && defined(sourceUri)) { + styleUrl = sourceUri + styleUrl; + } + var base = styleCollection.getById(styleUrl); - 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 (defined(base)) { + styleEntity.merge(base); + } + } else { + node = queryFirstNode(pair, 'Style', namespaces.kml); + applyStyle(dataSource, node, styleEntity, sourceUri, uriResolver, query); + } + } + } else { + oneTimeWarning('kml-styleMap-' + key, 'KML - Unsupported StyleMap key: ' + key); + } + } } } } - if (count === 0) { - return BoundingSphereState.FAILED; + 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, query)); + } + } + } } - boundingSpheres.length = count; - BoundingSphere.fromBoundingSpheres(boundingSpheres, result); - return BoundingSphereState.DONE; - }; + return promises; + } - DataSourceDisplay.prototype._onDataSourceAdded = function(dataSourceCollection, dataSource) { - var scene = this._scene; + 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]); + } - var entityCluster = dataSource.clustering; - entityCluster._initialize(scene); + function heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode) { + if (!defined(altitudeMode) && !defined(gxAltitudeMode) || altitudeMode === 'clampToGround') { + return HeightReference.CLAMP_TO_GROUND; + } - scene.primitives.add(entityCluster); + if (altitudeMode === 'relativeToGround') { + return HeightReference.RELATIVE_TO_GROUND; + } - dataSource._visualizers = this._visualizersCallback(scene, entityCluster, dataSource); - }; + if (altitudeMode === 'absolute') { + return HeightReference.NONE; + } - DataSourceDisplay.prototype._onDataSourceRemoved = function(dataSourceCollection, dataSource) { - var scene = this._scene; - var entityCluster = dataSource.clustering; - scene.primitives.remove(entityCluster); + if (gxAltitudeMode === 'clampToSeaFloor') { + oneTimeWarning('kml-gx:altitudeMode-clampToSeaFloor', 'KML - :clampToSeaFloor is currently not supported, using :clampToGround.'); + return HeightReference.CLAMP_TO_GROUND; + } - var visualizers = dataSource._visualizers; - var length = visualizers.length; - for (var i = 0; i < length; i++) { - visualizers[i].destroy(); + if (gxAltitudeMode === 'relativeToSeaFloor') { + oneTimeWarning('kml-gx:altitudeMode-relativeToSeaFloor', 'KML - :relativeToSeaFloor is currently not supported, using :relativeToGround.'); + return HeightReference.RELATIVE_TO_GROUND; } - dataSource._visualizers = undefined; - }; + if (defined(altitudeMode)) { + oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown :' + altitudeMode + ', using :CLAMP_TO_GROUND.'); + } else { + oneTimeWarning('kml-gx:altitudeMode-unknown', 'KML - Unknown :' + gxAltitudeMode + ', using :CLAMP_TO_GROUND.'); + } - /** - * 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)]; - * } - */ + // Clamp to ground is the default + return HeightReference.CLAMP_TO_GROUND; + } - return DataSourceDisplay; -}); + function createPositionPropertyFromAltitudeMode(property, altitudeMode, gxAltitudeMode) { + if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') { + //Just return the ellipsoid referenced property until we support MSL + return property; + } -/*global define*/ -define('DataSources/DynamicGeometryUpdater',[ - '../Core/DeveloperError' - ], function( - DeveloperError) { - 'use strict'; + if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || // + (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) { + oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode)); + } - /** - * 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(); + // Clamp to ground is the default + return new ScaledPositionProperty(property); } - /** - * 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; + function createPositionPropertyArrayFromAltitudeMode(properties, altitudeMode, gxAltitudeMode) { + if (!defined(properties)) { + return undefined; + } - /** - * 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; + if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') { + //Just return the ellipsoid referenced property until we support MSL + return properties; + } - return DynamicGeometryUpdater; -}); + if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || // + (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) { + oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode)); + } -/*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'; + // 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; + } - 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 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; - 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 invertVelocity = false; - var xBasis; - var yBasis; - var zBasis; + var billboard = entity.billboard; + if (!defined(billboard)) { + billboard = defined(styleEntity.billboard) ? styleEntity.billboard.clone() : createDefaultBillboard(); + entity.billboard = billboard; + } - 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. - JulianDate.addSeconds(time, 0.001, deltaTime); - var deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1); + if (!defined(billboard.image)) { + billboard.image = dataSource._pinBuilder.fromColor(Color.YELLOW, 64); + } - // If no valid position at (time + 0.001), sample at (time - 0.001) and invert the vector - if (!defined(deltaCartesian)) { - JulianDate.addSeconds(time, -0.001, deltaTime); - deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1); - invertVelocity = true; - } + 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(deltaCartesian)) { - var toInertial = Transforms.computeFixedToIcrfMatrix(time, updateTransformMatrix3Scratch1); - var toInertialDelta = Transforms.computeFixedToIcrfMatrix(deltaTime, updateTransformMatrix3Scratch2); - var toFixed; + if (defined(heightReference) && dataSource._clampToGround) { + billboard.heightReference = heightReference; + label.heightReference = heightReference; + } + } - 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); - } + function processPathGraphics(dataSource, entity, styleEntity) { + var path = entity.path; + if (!defined(path)) { + path = new PathGraphics(); + path.leadTime = 0; + entity.path = path; + } - var inertialCartesian = Matrix3.multiplyByVector(toInertial, cartesian, updateTransformCartesian3Scratch5); - var inertialDeltaCartesian = Matrix3.multiplyByVector(toInertialDelta, deltaCartesian, updateTransformCartesian3Scratch6); + var polyline = styleEntity.polyline; + if (defined(polyline)) { + path.material = polyline.material; + path.width = polyline.width; + } + } - Cartesian3.subtract(inertialCartesian, inertialDeltaCartesian, updateTransformCartesian3Scratch4); - var inertialVelocity = Cartesian3.magnitude(updateTransformCartesian3Scratch4) * 1000.0; // meters/sec + 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); - // http://en.wikipedia.org/wiki/Standard_gravitational_parameter - // Consider adding this to Cesium.Ellipsoid? - var mu = 3.986004418e14; // m^3 / sec^2 + var position = readCoordinate(coordinatesString); - var semiMajorAxis = -mu / (inertialVelocity * inertialVelocity - (2 * mu / Cartesian3.magnitude(inertialCartesian))); + entity.position = position; + processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode)); - if (semiMajorAxis < 0 || semiMajorAxis > northUpAxisFactor * ellipsoid.maximumRadius) { - // North-up viewing from deep space. + if (extrude && isExtrudable(altitudeMode, gxAltitudeMode)) { + createDropLine(entityCollection, entity, styleEntity); + } - // X along the nadir - xBasis = updateTransformCartesian3Scratch2; - Cartesian3.normalize(cartesian, xBasis); - Cartesian3.negate(xBasis, xBasis); + return true; + } - // Z is North - zBasis = Cartesian3.clone(Cartesian3.UNIT_Z, updateTransformCartesian3Scratch3); + 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); - // 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); + if (defined(queryNumericValue(geometryNode, 'drawOrder', namespaces.gx))) { + oneTimeWarning('kml-gx:drawOrder', 'KML - gx:drawOrder is not supported in LineStrings'); + } - zBasis = Cartesian3.cross(xBasis, yBasis, updateTransformCartesian3Scratch3); - Cartesian3.normalize(zBasis, zBasis); + 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; - hasBasis = true; - } - } else if (!Cartesian3.equalsEpsilon(cartesian, deltaCartesian, CesiumMath.EPSILON7)) { - // Approximation of VVLH (Vehicle Velocity Local Horizontal) with the Z-axis flipped. + if (defined(polygon)) { + wall.fill = polygon.fill; + wall.material = polygon.material; + } - // Z along the position - zBasis = updateTransformCartesian3Scratch2; - Cartesian3.normalize(inertialCartesian, zBasis); - Cartesian3.normalize(inertialDeltaCartesian, inertialDeltaCartesian); + //Always outline walls so they show up in 2D. + wall.outline = true; + if (defined(polyline)) { + wall.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE; + wall.outlineWidth = polyline.width; + } else if (defined(polygon)) { + wall.outlineColor = defined(polygon.material) ? polygon.material.color : Color.WHITE; + } + } 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; + } + } - // Y is along the angular momentum vector (e.g. "orbit normal") - yBasis = Cartesian3.cross(zBasis, inertialDeltaCartesian, updateTransformCartesian3Scratch3); + return true; + } - if(invertVelocity) { - yBasis = Cartesian3.multiplyByScalar(yBasis, -1, yBasis); - } + 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); - 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); + var polygon = defined(styleEntity.polygon) ? styleEntity.polygon.clone() : createDefaultPolygon(); - Matrix3.multiplyByVector(toFixed, xBasis, xBasis); - Matrix3.multiplyByVector(toFixed, yBasis, yBasis); - Matrix3.multiplyByVector(toFixed, zBasis, zBasis); + var polyline = styleEntity.polyline; + if (defined(polyline)) { + polygon.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE; + polygon.outlineWidth = polyline.width; + } + entity.polygon = polygon; - Cartesian3.normalize(xBasis, xBasis); - Cartesian3.normalize(yBasis, yBasis); - Cartesian3.normalize(zBasis, zBasis); + if (canExtrude) { + polygon.perPositionHeight = true; + polygon.extrudedHeight = extrude ? 0 : undefined; + } else if (!dataSource._clampToGround) { + polygon.height = 0; + } - hasBasis = true; - } + 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; + } - if (defined(that.boundingSphere)) { - cartesian = that.boundingSphere.center; - } + return true; + } - var position; - var direction; - var up; + 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 (saveCamera) { - position = Cartesian3.clone(camera.position, updateTransformCartesian3Scratch4); - direction = Cartesian3.clone(camera.direction, updateTransformCartesian3Scratch5); - up = Cartesian3.clone(camera.up, updateTransformCartesian3Scratch6); - } + if (angleNodes.length > 0) { + oneTimeWarning('kml-gx:angles', 'KML - gx:angles are not supported in gx:Tracks'); + } - 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); - } + 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); - camera._setTransform(transform); + entity.availability = new TimeIntervalCollection(); - if (saveCamera) { - Cartesian3.clone(position, camera.position); - Cartesian3.clone(direction, camera.direction); - Cartesian3.clone(up, camera.up); - Cartesian3.cross(direction, up, camera.right); - } + if (timeNodes.length > 0) { + entity.availability.addInterval(new TimeInterval({ + start : times[0], + stop : times[times.length - 1] + })); } - if (updateLookAt) { - var offset = (mode === SceneMode.SCENE2D || Cartesian3.equals(that._offset3D, Cartesian3.ZERO)) ? undefined : that._offset3D; - camera.lookAtTransform(camera.transform, offset); + if (canExtrude && extrude) { + createDropLine(entityCollection, entity, styleEntity); } + + return true; } - /** - * 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) { + function addToMultiTrack(times, positions, composite, availability, dropShowProperty, extrude, altitudeMode, gxAltitudeMode, includeEndPoints) { + var start = times[0]; + var stop = times[times.length - 1]; - /** - * The entity to track with the camera. - * @type {Entity} - */ - this.entity = entity; + var data = new SampledPositionProperty(); + data.addSamples(times, positions); - /** - * The scene in which to track the object. - * @type {Scene} - */ - this.scene = scene; + 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 + })); + } - /** - * The ellipsoid to use for orienting the camera. - * @type {Ellipsoid} - */ - this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + 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. - /** - * The bounding sphere of the object. - * @type {BoundingSphere} - */ - this.boundingSphere = undefined; + var interpolate = queryBooleanValue(geometryNode, 'interpolate', namespaces.gx); + var trackNodes = queryChildNodes(geometryNode, 'Track', namespaces.gx); - //Shadow copies of the objects so we can detect changes. - this._lastEntity = undefined; - this._mode = undefined; + 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); - this._lastCartesian = new Cartesian3(); - this._defaultOffset3D = undefined; + var length = Math.min(coordNodes.length, timeNodes.length); - this._offset3D = new Cartesian3(); - } + 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)); + } - // 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()); + 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); } - }); - // Initialize the static property. - EntityView.defaultOffset3D = new Cartesian3(-14000, 3500, 3500); + entity.availability = availability; + entity.position = composite; + processPositionGraphics(dataSource, entity, styleEntity); + processPathGraphics(dataSource, entity, styleEntity); + if (needDropLine) { + createDropLine(entityCollection, entity, styleEntity); + entity.polyline.show = dropShowProperty; + } - var scratchHeadingPitchRange = new HeadingPitchRange(); - var scratchCartesian = new Cartesian3(); + return true; + } - /** - * Should be called each animation frame to update the camera - * to the latest settings. - * @param {JulianDate} time The current animation time. - * @param {BoundingSphere} boundingSphere bounding sphere of the object. - * - */ - EntityView.prototype.update = function(time, boundingSphere) { - var scene = this.scene; - var entity = this.entity; - var ellipsoid = this.ellipsoid; + var geometryTypes = { + Point : processPoint, + LineString : processLineStringOrLinearRing, + LinearRing : processLineStringOrLinearRing, + Polygon : processPolygon, + Track : processTrack, + MultiTrack : processMultiTrack, + MultiGeometry : processMultiGeometry, + Model : processUnsupportedGeometry + }; - - var sceneMode = scene.mode; - if (sceneMode === SceneMode.MORPHING) { - return; + 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; + } + } } - var positionProperty = entity.position; - var objectChanged = entity !== this._lastEntity; - var sceneModeChanged = sceneMode !== this._mode; + return hasGeometry; + } - var offset3D = this._offset3D; - var camera = scene.camera; + function processUnsupportedGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity) { + oneTimeWarning('kml-unsupportedGeometry', 'KML - Unsupported geometry: ' + geometryNode.localName); + return false; + } - var updateLookAt = objectChanged || sceneModeChanged; - var saveCamera = true; + function processExtendedData(node, entity) { + var extendedDataNode = queryFirstNode(node, 'ExtendedData', namespaces.kml); - if (objectChanged) { - var viewFromProperty = entity.viewFrom; - var hasViewFrom = defined(viewFromProperty); + if (!defined(extendedDataNode)) { + return undefined; + } - if (!hasViewFrom && defined(boundingSphere)) { - var controller = scene.screenSpaceCameraController; - controller.minimumZoomDistance = Math.min(controller.minimumZoomDistance, boundingSphere.radius * 0.5); + if (defined(queryFirstNode(extendedDataNode, 'SchemaData', namespaces.kml))) { + oneTimeWarning('kml-schemaData', 'KML - SchemaData is unsupported'); + } + if (defined(queryStringAttribute(extendedDataNode, 'xmlns:prefix'))) { + oneTimeWarning('kml-extendedData', 'KML - ExtendedData with xmlns:prefix is unsupported'); + } - //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; + 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) + }; } - - 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); } - }; + entity.kml.extendedData = result; + } - return EntityView; -}); + var scratchDiv = document.createElement('div'); -/** -@license -topojson - https://github.com/mbostock/topojson + function processDescription(node, entity, styleEntity, uriResolver, proxy, sourceUri) { + var i; + var key; + var keys; -Copyright (c) 2012, Michael Bostock -All rights reserved. + var kmlData = entity.kml; + var extendedData = kmlData.extendedData; + var description = queryStringValue(node, 'description', namespaces.kml); -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: + var balloonStyle = defaultValue(entity.balloonStyle, styleEntity.balloonStyle); -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + var background = Color.WHITE; + var foreground = Color.BLACK; + var text = description; -* 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. + if (defined(balloonStyle)) { + background = defaultValue(balloonStyle.bgColor, Color.WHITE); + foreground = defaultValue(balloonStyle.textColor, Color.BLACK); + text = defaultValue(balloonStyle.text, description); + } -* The name Michael Bostock may not be used to endorse or promote products - derived from this software without specific prior written permission. + 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); -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. -*/ + //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]', ''); -!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 - }; + 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$/, ''); - function stitchArcs(topology, arcs) { - var stitchedArcs = {}, - fragmentByStart = {}, - fragmentByEnd = {}, - fragments = [], - emptyIndex = -1; + 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 += ''; + } + text += '
    ' + defaultValue(value.displayName, key) + '' + defaultValue(value.value, '') + '
    '; + } + } - // 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; - } - }); + if (!defined(text)) { + //No description + return; + } - arcs.forEach(function(i) { - var e = ends(i), - start = e[0], - end = e[1], - f, g; + //Turns non-explicit links into clickable links. + text = autolinker.link(text); - 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; + //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'); } - } 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; + + //Rewrite any KMZ embedded urls + if (defined(uriResolver) && uriResolver.keys.length > 1) { + embedDataUris(scratchDiv, 'a', 'href', uriResolver); + embedDataUris(scratchDiv, 'img', 'src', uriResolver); } - } 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]; - } + //Make relative urls absolute using the sourceUri + applyBasePath(scratchDiv, 'a', 'href', proxy, sourceUri); + applyBasePath(scratchDiv, 'img', 'src', proxy, sourceUri); - 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); - } - } + var tmp = '
    '; + tmp += scratchDiv.innerHTML + '
    '; + scratchDiv.innerHTML = ''; - flush(fragmentByEnd, fragmentByStart); - flush(fragmentByStart, fragmentByEnd); - arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); }); + //Set the final HTML as the description. + entity.description = tmp; + } - return fragments; - } + function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var entity = createEntity(featureNode, entityCollection, context); + var kmlData = entity.kml; + var styleEntity = computeFinalStyle(entity, dataSource, featureNode, styleCollection, sourceUri, uriResolver, query); - function meshArcs(topology, o, filter) { - var arcs = []; + var name = queryStringValue(featureNode, 'name', namespaces.kml); + entity.name = name; + entity.parent = parent; - if (arguments.length > 1) { - var geomsByArc = [], - geom; + var availability = processTimeSpan(featureNode); + if (!defined(availability)) { + availability = processTimeStamp(featureNode); + } + entity.availability = availability; - function arc(i) { - var j = i < 0 ? ~i : i; - (geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom}); - } + mergeAvailabilityWithParent(entity); - function line(arcs) { - arcs.forEach(arc); - } + // 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); + } - function polygon(arcs) { - arcs.forEach(line); - } + var visibility = queryBooleanValue(featureNode, 'visibility', namespaces.kml); + entity.show = ancestryIsVisible(parent) && defaultValue(visibility, true); + //var open = queryBooleanValue(featureNode, 'open', namespaces.kml); - 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 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 geometryType = { - LineString: line, - MultiLineString: polygon, - Polygon: polygon, - MultiPolygon: function(arcs) { arcs.forEach(polygon); } - }; + 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'); - geometry(o); + kmlData.address = queryStringValue(featureNode, 'address', namespaces.kml); + kmlData.phoneNumber = queryStringValue(featureNode, 'phoneNumber', namespaces.kml); + kmlData.snippet = queryStringValue(featureNode, 'Snippet', namespaces.kml); - 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); - } + processExtendedData(featureNode, entity); + processDescription(featureNode, entity, styleEntity, uriResolver, dataSource._proxy, sourceUri); + processLookAt(featureNode, entity); + processCamera(featureNode, entity); - return {type: "MultiLineString", arcs: stitchArcs(topology, arcs)}; - } + if (defined(queryFirstNode(featureNode, 'Region', namespaces.kml))) { + oneTimeWarning('kml-region', 'KML - Placemark Regions are unsupported'); + } - function mergeArcs(topology, objects) { - var polygonsByArc = {}, - polygons = [], - components = []; + return { + entity : entity, + styleEntity : styleEntity + }; + } - objects.forEach(function(o) { - if (o.type === "Polygon") register(o.arcs); - else if (o.type === "MultiPolygon") o.arcs.forEach(register); - }); + // 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 : processTour + }; - 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 processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var featureTypeNames = Object.keys(featureTypes); + var featureTypeNamesLength = featureTypeNames.length; - function exterior(ring) { - return cartesianRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]) > 0; // TODO allow spherical? - } + for (var i = 0; i < featureTypeNamesLength; i++) { + var featureName = featureTypeNames[i]; + var processFeatureNode = featureTypes[featureName]; - 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); + 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, query); } - }); - }); - }); + } } - } - }); - - 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); - } - }); - }); - }); + function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query); + processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query); + } - // Stitch the arcs into one or more rings. - arcs = stitchArcs(topology, arcs); + function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query); + var entity = r.entity; + var styleEntity = r.styleEntity; - // 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; + 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; } - } } - 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); - } + if (!hasGeometry) { + entity.merge(styleEntity); + processPositionGraphics(dataSource, entity, styleEntity); + } + } - function feature(topology, o) { - var f = { - type: "Feature", - id: o.id, - properties: o.properties || {}, - geometry: object(topology, o) + var playlistNodeProcessors = { + FlyTo: processTourFlyTo, + Wait: processTourWait, + SoundCue: processTourUnsupportedNode, + AnimatedUpdate: processTourUnsupportedNode, + TourControl: processTourUnsupportedNode }; - if (o.id == null) delete f.id; - return f; - } - function object(topology, o) { - var absolute = transformAbsolute(topology.transform), - arcs = topology.arcs; + function processTour(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { + var name = queryStringValue(node, 'name', namespaces.kml); + var id = queryStringAttribute(node, 'id'); + var tour = new KmlTour(name, id); + + var playlistNode = queryFirstNode(node, 'Playlist', namespaces.gx); + if(playlistNode) { + var childNodes = playlistNode.childNodes; + for(var i = 0; i < childNodes.length; i++) { + var entryNode = childNodes[i]; + if (entryNode.localName) { + var playlistNodeProcessor = playlistNodeProcessors[entryNode.localName]; + if (playlistNodeProcessor) { + playlistNodeProcessor(tour, entryNode); + } + else { + console.log('Unknown KML Tour playlist entry type ' + entryNode.localName); + } + } + } + } - 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); - } + if (!defined(dataSource.kmlTours)) { + dataSource.kmlTours = []; + } - function point(p) { - p = p.slice(); - absolute(p, 0); - return p; + dataSource.kmlTours.push(tour); } - 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 processTourUnsupportedNode(tour, entryNode) { + oneTimeWarning('KML Tour unsupported node ' + entryNode.localName); } - function ring(arcs) { - var points = line(arcs); - while (points.length < 4) points.push(points[0].slice()); - return points; + function processTourWait(tour, entryNode) { + var duration = queryNumericValue(entryNode, 'duration', namespaces.gx); + tour.addPlaylistEntry(new KmlTourWait(duration)); } - function polygon(arcs) { - return arcs.map(ring); - } + function processTourFlyTo(tour, entryNode) { + var duration = queryNumericValue(entryNode, 'duration', namespaces.gx); + var flyToMode = queryStringValue(entryNode, 'flyToMode', namespaces.gx); - 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 t = {kml: {}}; + + processLookAt(entryNode, t); + processCamera(entryNode, t); + + var view = t.kml.lookAt || t.kml.camera; + + var flyto = new KmlTourFlyTo(duration, flyToMode, view); + tour.addPlaylistEntry(flyto); } - 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); } - }; + function processCamera(featureNode, entity) { + var camera = queryFirstNode(featureNode, 'Camera', namespaces.kml); + if(defined(camera)) { + var lon = defaultValue(queryNumericValue(camera, 'longitude', namespaces.kml), 0.0); + var lat = defaultValue(queryNumericValue(camera, 'latitude', namespaces.kml), 0.0); + var altitude = defaultValue(queryNumericValue(camera, 'altitude', namespaces.kml), 0.0); - return geometry(o); - } + var heading = defaultValue(queryNumericValue(camera, 'heading', namespaces.kml), 0.0); + var tilt = defaultValue(queryNumericValue(camera, 'tilt', namespaces.kml), 0.0); + var roll = defaultValue(queryNumericValue(camera, 'roll', namespaces.kml), 0.0); - 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; - } + var position = Cartesian3.fromDegrees(lon, lat, altitude); + var hpr = HeadingPitchRoll.fromDegrees(heading, tilt - 90.0, roll); - 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; + entity.kml.camera = new KmlCamera(position, hpr); + } } - return lo; - } - function neighbors(objects) { - var indexesByArc = {}, // arc index -> array of object indexes - neighbors = objects.map(function() { return []; }); + function processLookAt(featureNode, entity) { + var lookAt = queryFirstNode(featureNode, 'LookAt', namespaces.kml); + if(defined(lookAt)) { + var lon = defaultValue(queryNumericValue(lookAt, 'longitude', namespaces.kml), 0.0); + var lat = defaultValue(queryNumericValue(lookAt, 'latitude', namespaces.kml), 0.0); + var altitude = defaultValue(queryNumericValue(lookAt, 'altitude', namespaces.kml), 0.0); + var heading = queryNumericValue(lookAt, 'heading', namespaces.kml); + var tilt = queryNumericValue(lookAt, 'tilt', namespaces.kml); + var range = defaultValue(queryNumericValue(lookAt, 'range', namespaces.kml), 0.0); - 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]; - }); - } + tilt = CesiumMath.toRadians(defaultValue(tilt, 0.0)); + heading = CesiumMath.toRadians(defaultValue(heading, 0.0)); - function polygon(arcs, i) { - arcs.forEach(function(arc) { line(arc, i); }); + var hpr = new HeadingPitchRange(heading, tilt - 90.0, range); + var viewPoint = Cartesian3.fromDegrees(lon, lat, altitude); + + entity.kml.lookAt = new KmlLookAt(viewPoint, hpr); + } } - 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); + function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query); + 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, query); + if (defined(href)) { + if (isLatLonQuad) { + oneTimeWarning('kml-gx:LatLonQuad', '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)) { + oneTimeWarning('kml-groundOverlay-xywh', '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') { + oneTimeWarning('kml-altitudeMode-unknown', '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') { + oneTimeWarning('kml-altitudeMode-relativeToSeaFloor', 'KML - altitudeMode relativeToSeaFloor is currently not supported, treating as absolute.'); + geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml); + } else if (altitudeMode === 'clampToSeaFloor') { + oneTimeWarning('kml-altitudeMode-clampToSeaFloor', 'KML - altitudeMode clampToSeaFloor is currently not supported, treating as clampToGround.'); + } else if (defined(altitudeMode)) { + oneTimeWarning('kml-altitudeMode-unknown', '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); + oneTimeWarning('kml-unsupportedFeature-' + node.nodeName, 'KML - Unsupported feature: ' + node.nodeName); } - var geometryType = { - LineString: line, - MultiLineString: polygon, - Polygon: polygon, - MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); } + var RefreshMode = { + INTERVAL : 0, + EXPIRE : 1, + STOP : 2 }; - 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); + function cleanupString(s) { + if (!defined(s) || s.length === 0) { + return ''; } - } - } - - return neighbors; - } - function presimplify(topology, triangleArea) { - var absolute = transformAbsolute(topology.transform), - relative = transformRelative(topology.transform), - heap = minAreaHeap(); + var sFirst = s[0]; + if (sFirst === '&') { + s.splice(0, 1); + } - if (!triangleArea) triangleArea = cartesianTriangleArea; + if (sFirst !== '?') { + s = '?' + s; + } - topology.arcs.forEach(function(arc) { - var triangles = [], - maxArea = 0, - triangle; + return s; + } - // 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); - } + function makeQueryString(string1, string2) { + var result = ''; + if ((defined(string1) && string1.length > 0) || (defined(string2) && string2.length > 0)) { + result += joinUrls(cleanupString(string1), cleanupString(string2), false); + } - 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); - } + return result; + } - for (var i = 0, n = triangles.length; i < n; ++i) { - triangle = triangles[i]; - triangle.previous = triangles[i - 1]; - triangle.next = triangles[i + 1]; - } + var zeroRectangle = new Rectangle(); + var scratchCartographic = new Cartographic(); + var scratchCartesian2 = new Cartesian2(); + var scratchCartesian3 = new Cartesian3(); - while (triangle = heap.pop()) { - var previous = triangle.previous, - next = triangle.next; + 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; + } - // 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]; + function fixLongitude(value) { + if (value > CesiumMath.PI) { + return value - CesiumMath.TWO_PI; + } else if (value < -CesiumMath.PI) { + return value + CesiumMath.TWO_PI; + } - if (previous) { - previous.next = next; - previous[2] = triangle[2]; - update(previous); + return value; } - if (next) { - next.previous = previous; - next[0] = triangle[0]; - update(next); - } - } + if (defined(camera) && camera._mode !== SceneMode.MORPHING) { + var wgs84 = Ellipsoid.WGS84; + var centerCartesian; + var centerCartographic; - arc.forEach(relative); - }); + 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); + } - function update(triangle) { - heap.remove(triangle); - triangle[1][2] = triangleArea(triangle); - heap.push(triangle); - } + if (defined(centerCartesian)) { + centerCartographic = wgs84.cartesianToCartographic(centerCartesian, scratchCartographic); + } else { + centerCartographic = Rectangle.center(bbox, scratchCartographic); + centerCartesian = wgs84.cartographicToCartesian(centerCartographic); + } - return topology; - } - function cartesianRingArea(ring) { - var i = -1, - n = ring.length, - a, - b = ring[n - 1], - area = 0; + 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) + ); + } - while (++i < n) { - a = b; - b = ring[i]; - area += a[0] * b[1] - a[1] * b[0]; - } + 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()); - return area * .5; - } + 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()); - 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])); - } + 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()); - function compareArea(a, b) { - return a[1][2] - b[1][2]; - } + 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'); - function minAreaHeap() { - var heap = {}, - array = [], - size = 0; + 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]', ''); - heap.push = function(object) { - up(array[object._ = size] = object, size++); - return size; - }; + queryString = queryString.replace('[cameraLon]', ''); + queryString = queryString.replace('[cameraLat]', ''); + queryString = queryString.replace('[cameraAlt]', ''); + queryString = queryString.replace('[horizFov]', ''); + queryString = queryString.replace('[vertFov]', ''); + } - 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; - }; + if (defined(canvas)) { + queryString = queryString.replace('[horizPixels]', canvas.clientWidth); + queryString = queryString.replace('[vertPixels]', canvas.clientHeight); + } else { + queryString = queryString.replace('[horizPixels]', ''); + queryString = queryString.replace('[vertPixels]', ''); + } - 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; - }; + 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'); - 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; - } + return queryString; } - 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; - } - } + function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query); + var networkEntity = r.entity; - return heap; - } + var link = queryFirstNode(node, 'Link', namespaces.kml); - 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; - }; - } + if (!defined(link)) { + link = queryFirstNode(node, 'Url', namespaces.kml); + } + if (defined(link)) { + var href = queryStringValue(link, 'href', namespaces.kml); + var viewRefreshMode; + var viewBoundScale; + var queryString; + if (defined(href)) { + var newSourceUri = href; + href = resolveHref(href, undefined, sourceUri, uriResolver, query); + var linkUrl; - 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; - }; - } + // 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; - function noop() {} + // 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 + viewRefreshMode = queryStringValue(link, 'viewRefreshMode', namespaces.kml); + 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); + queryString = makeQueryString(viewFormat, httpQuery); - if (typeof define === "function" && define.amd) define('ThirdParty/topojson',topojson); - else if (typeof module === "object" && module.exports) module.exports = topojson; - else this.topojson = topojson; -}(); + linkUrl = processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, joinUrls(href, queryString, false), + viewBoundScale, dataSource._lastCameraView.bbox); + } -/*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'; + 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); + } - function defaultCrsFunction(coordinates) { - return Cartesian3.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); - } + entities.add(newEntity); + } + entities.resumeEvents(); - var crsNames = { - 'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction, - 'EPSG:4326' : defaultCrsFunction, - 'urn:ogc:def:crs:EPSG::4326' : defaultCrsFunction - }; + // 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 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 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 sizes = { - small : 24, - medium : 48, - large : 64 - }; + var minRefreshPeriod = 0; + if (hasNetworkLinkControl) { + networkLinkInfo.cookie = defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), ''); + minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0); + } - var simpleStyleIdentifiers = ['title', 'description', // - 'marker-size', 'marker-symbol', 'marker-color', 'stroke', // - 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity']; + 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) { + oneTimeWarning('kml-refreshMode-onInterval-onExpire', 'KML - NetworkLinkControl expires is not a valid date'); + } + } else { + oneTimeWarning('kml-refreshMode-onExpire', '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 { + oneTimeWarning('kml-refrehMode-onStop-noCamera', 'A NetworkLink with viewRefreshMode=onStop requires a camera be passed in when creating the KmlDataSource'); + } - 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 (defined(networkLinkInfo.refreshMode)) { + dataSource._networkLinks.set(networkLinkInfo.id, networkLinkInfo); + } + } else if (viewRefreshMode === 'onRegion') { + oneTimeWarning('kml-refrehMode-onRegion', 'KML - Unsupported viewRefreshMode: onRegion'); } - } - } - } + }); - if (html.length > 0) { - html = '' + html + '
    '; + promises.push(promise); + } } - - return html; } - function createDescriptionCallback(describe, properties, nameProperty) { - var description; - return function(time, result) { - if (!defined(description)) { - description = describe(properties, nameProperty); - } - return description; - }; + function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query) { + var featureProcessor = featureTypes[node.localName]; + if (defined(featureProcessor)) { + featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context, query); + } else { + processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); + } } - function defaultDescribeProperty(properties, nameProperty) { - return new CallbackProperty(createDescriptionCallback(defaultDescribe, properties, nameProperty), true); - } + function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context, query) { + entityCollection.removeAll(); - //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 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); } - var entity = entityCollection.getOrCreateEntity(id); - var properties = geoJson.properties; - if (defined(properties)) { - entity.properties = properties; + // Only set the name from the root document + if (!defined(dataSource._name)) { + dataSource._name = name; + } - var nameProperty; + var styleCollection = new EntityCollection(dataSource); + return when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver, query)).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, query); + entityCollection.resumeEvents(); - //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(); + return when.all(promises).then(function() { + return kml.documentElement; + }); + }); + } - 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; + 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); } } } - if (defined(nameProperty)) { - entity.name = properties[nameProperty]; + + // 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); + }); - var description = properties.description; - if (description !== null) { - entity.description = !defined(description) ? describe(properties, nameProperty) : new ConstantProperty(description); - } - } - return entity; + return deferred.promise; } - 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; - } + 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 query = defined(options.query) ? objectToQuery(options.query) : undefined; - // 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; + var promise = data; + if (typeof data === 'string') { + promise = loadBlob(proxyUrl(data, dataSource._proxy, query)); + sourceUri = defaultValue(sourceUri, data); } - if (!defined(feature.geometry)) { - throw new RuntimeError('feature.geometry is required.'); - } + 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. - 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); + //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, query); + }); + }); + } + return loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver, context, query); + }) + .otherwise(function(error) { + dataSource._error.raiseEvent(dataSource, error); + console.log(error); + return when.reject(error); + }); } - 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); - } + /** + * 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; + + + 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) + }; } - 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); + /** + * 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. + * @param {Object} [options.query] Key-value pairs which are appended to all URIs in the CZML. + * + * @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 or sets 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; + }, + set : function(value) { + if (this._name !== value) { + this._name = value; + this._changed.raiseEvent(this); + } + } + }, + /** + * 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) { + this._entityCluster = value; } - 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; + /** + * 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. + * @param {Object} [options.query] Key-value pairs which are appended to all URIs in the CZML. + */ + KmlDataSource.prototype.load = function(data, options) { + + options = defaultValue(options, {}); + DataSource.setLoading(this, true); - var properties = geoJson.properties; - if (defined(properties)) { - var cssColor = properties['marker-color']; - if (defined(cssColor)) { - color = Color.fromCssColorString(cssColor); + 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)); } - size = defaultValue(sizes[properties['marker-size']], size); - var markerSymbol = properties['marker-symbol']; - if (defined(markerSymbol)) { - symbol = markerSymbol; + var changed = false; + if (clock !== that._clock) { + that._clock = clock; + changed = true; } - } - 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); + if (oldName !== that._name) { + changed = true; } - } 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; - } + if (changed) { + that._changed.raiseEvent(that); + } - var entity = createObject(geoJson, dataSource._entityCollection, options.describe); - entity.billboard = billboard; - entity.position = new ConstantPositionProperty(crsFunction(coordinates)); + DataSource.setLoading(that, false); - var promise = when(canvasOrPromise).then(function(image) { - billboard.image = new ConstantProperty(image); - }).otherwise(function() { - billboard.image = new ConstantProperty(dataSource._pinBuilder.fromColor(color, size)); + return that; + }).otherwise(function(error) { + DataSource.setLoading(that, false); + that._error.raiseEvent(that, error); + console.log(error); + return when.reject(error); }); + }; - dataSource._promises.push(promise); + 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 processPoint(dataSource, geoJson, geometry, crsFunction, options) { - createPoint(dataSource, geoJson, crsFunction, geometry.coordinates, options); - } + 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); - 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); - } - } + var minRefreshPeriod = 0; + if (hasNetworkLinkControl) { + if (defined(queryFirstNode(networkLinkControl, 'Update', namespaces.kml))) { + oneTimeWarning('kml-networkLinkControl-update', '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); + } - function createLineString(dataSource, geoJson, crsFunction, coordinates, options) { - var material = options.strokeMaterialProperty; - var widthProperty = options.strokeWidthProperty; + 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) { + oneTimeWarning('kml-networkLinkControl-expires', 'KML - NetworkLinkControl expires is not a valid date'); + remove = true; + } + } else { + oneTimeWarning('kml-refreshMode-onExpire', 'KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element'); + remove = true; + } + } - var properties = geoJson.properties; - if (defined(properties)) { - var width = properties['stroke-width']; - if (defined(width)) { - widthProperty = new ConstantProperty(width); + 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 < count; ++i) { + removeChildren(children[i]); + } } - var color; - var stroke = properties.stroke; - if (defined(stroke)) { - color = Color.fromCssColorString(stroke); + // Remove old entities + entityCollection.suspendEvents(); + var entitiesCopy = entityCollection.values.slice(); + var i; + for (i = 0; i < entitiesCopy.length; ++i) { + var entityToRemove = entitiesCopy[i]; + if (entityToRemove.parent === networkLinkEntity) { + entityToRemove.parent = undefined; + removeChildren(entityToRemove); + } } - var opacity = properties['stroke-opacity']; - if (defined(opacity) && opacity !== 1.0) { - if (!defined(color)) { - color = material.color.clone(); + entityCollection.resumeEvents(); + + // Add new entities + entityCollection.suspendEvents(); + for (i = 0; i < newEntities.length; i++) { + var newEntity = newEntities[i]; + if (!defined(newEntity.parent)) { + newEntity.parent = networkLinkEntity; + mergeAvailabilityWithParent(newEntity); } - color.alpha = opacity; + entityCollection.add(newEntity); } - if (defined(color)) { - material = new ColorMaterialProperty(color); + entityCollection.resumeEvents(); + + // No refresh information remove it, otherwise update lastUpdate time + if (remove) { + networkLinks.remove(networkLink.id); + } else { + networkLink.lastUpdated = now; } - } - 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; - } + var availability = entityCollection.computeAvailability(); - graphics.material = material; - graphics.width = widthProperty; - graphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction)); - } + 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 clock = dataSource._clock; - function processLineString(dataSource, geoJson, geometry, crsFunction, options) { - createLineString(dataSource, geoJson, crsFunction, geometry.coordinates, options); - } + if (clock.startTime !== start || clock.stopTime !== stop) { + clock.startTime = start; + clock.stopTime = stop; + dataSource._changed.raiseEvent(dataSource); + } + } - 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); - } + networkLink.updating = false; + networkLink.needsUpdate = false; + dataSource._refresh.raiseEvent(dataSource, processedHref); + }; } - function createPolygon(dataSource, geoJson, crsFunction, coordinates, options) { - if (coordinates.length === 0 || coordinates[0].length === 0) { - return; + var entitiesToIgnore = new AssociativeArray(); + + /** + * Updates any NetworkLink that require updating + * @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. + */ + KmlDataSource.prototype.update = function(time) { + var networkLinks = this._networkLinks; + if (networkLinks.length === 0) { + return true; } - var outlineColorProperty = options.strokeMaterialProperty.color; - var material = options.fillMaterialProperty; - var widthProperty = options.strokeWidthProperty; + var now = JulianDate.now(); + var that = this; - var properties = geoJson.properties; - if (defined(properties)) { - var width = properties['stroke-width']; - if (defined(width)) { - widthProperty = new ConstantProperty(width); + entitiesToIgnore.removeAll(); + + function recurseIgnoreEntities(entity) { + var children = entity._children; + var count = children.length; + for (var i = 0; i < count; ++i) { + var child = children[i]; + entitiesToIgnore.set(child.id, child); + recurseIgnoreEntities(child); } + } - var color; - var stroke = properties.stroke; - if (defined(stroke)) { - color = Color.fromCssColorString(stroke); + var cameraViewUpdate = false; + var lastCameraView = this._lastCameraView; + var camera = this._camera; + if (defined(camera) && + !(camera.positionWC.equalsEpsilon(lastCameraView.position, CesiumMath.EPSILON7) && + camera.directionWC.equalsEpsilon(lastCameraView.direction, CesiumMath.EPSILON7) && + camera.upWC.equalsEpsilon(lastCameraView.up, CesiumMath.EPSILON7))) { + + // Camera has changed so update the last view + lastCameraView.position = Cartesian3.clone(camera.positionWC); + lastCameraView.direction = Cartesian3.clone(camera.directionWC); + lastCameraView.up = Cartesian3.clone(camera.upWC); + lastCameraView.bbox = camera.computeViewRectangle(); + cameraViewUpdate = true; + } + + var newNetworkLinks = new AssociativeArray(); + var changed = false; + networkLinks.values.forEach(function(networkLink) { + var entity = networkLink.entity; + if (entitiesToIgnore.contains(entity.id)) { + return; } - var opacity = properties['stroke-opacity']; - if (defined(opacity) && opacity !== 1.0) { - if (!defined(color)) { - color = options.strokeMaterialProperty.color.clone(); + + if (!networkLink.updating) { + var doUpdate = false; + if (networkLink.refreshMode === RefreshMode.INTERVAL) { + if (JulianDate.secondsDifference(now, networkLink.lastUpdated) > networkLink.time) { + doUpdate = true; + } } - color.alpha = opacity; - } + else if (networkLink.refreshMode === RefreshMode.EXPIRE) { + if (JulianDate.greaterThan(now, networkLink.time)) { + doUpdate = true; + } - if (defined(color)) { - outlineColorProperty = new ConstantProperty(color); - } + } else if (networkLink.refreshMode === RefreshMode.STOP) { + if (cameraViewUpdate) { + networkLink.needsUpdate = true; + networkLink.cameraUpdateTime = now; + } - 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(); + if (networkLink.needsUpdate && JulianDate.secondsDifference(now, networkLink.cameraUpdateTime) >= networkLink.time) { + doUpdate = true; + } } - 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; + 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); + }); - var holes = []; - for (var i = 1, len = coordinates.length; i < len; i++) { - holes.push(new PolygonHierarchy(coordinatesArrayToCartesianArray(coordinates[i], crsFunction))); + if (changed) { + this._networkLinks = newNetworkLinks; + this._changed.raiseEvent(this); } - 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; - } + return true; + }; - var entity = createObject(geoJson, dataSource._entityCollection, options.describe); - entity.polygon = polygon; - } + /** + * 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 + }; - function processPolygon(dataSource, geoJson, geometry, crsFunction, options) { - createPolygon(dataSource, geoJson, crsFunction, geometry.coordinates, options); - } + /** + * 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 + }; - 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); - } + /** + * 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; } - 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); - } - } - } + return KmlDataSource; +}); - var geoJsonObjectTypes = { - Feature : processFeature, - FeatureCollection : processFeatureCollection, - GeometryCollection : processGeometryCollection, - LineString : processLineString, - MultiLineString : processMultiLineString, - MultiPoint : processMultiPoint, - MultiPolygon : processMultiPolygon, - Point : processPoint, - Polygon : processPolygon, - Topology : processTopology - }; +define('DataSources/Visualizer',[ + '../Core/DeveloperError' + ], function( + DeveloperError) { + 'use strict'; - var geometryTypes = { - GeometryCollection : processGeometryCollection, - LineString : processLineString, - MultiLineString : processMultiLineString, - MultiPoint : processMultiPoint, - MultiPolygon : processMultiPolygon, - Point : processPoint, - Polygon : processPolygon, - Topology : processTopology - }; + /** + * 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(); + } /** - * 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. + * Updates the visualization to the provided time. + * @function * - * @alias GeoJsonDataSource - * @constructor + * @param {JulianDate} time The time. * - * @param {String} [name] The name of this data source. If undefined, a name will be taken from - * the name of the GeoJSON file. + * @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. * - * @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} + * @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 * - * @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: '?' - * })); + * @returns {Boolean} True if this object was destroyed; otherwise, false. */ - 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(); + 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; +}); + +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; + + /** + * The pass in which to run this command. + * + * @type {Pass} + * + * @default undefined + */ + this.pass = options.pass; } /** - * Creates a Promise to a new instance loaded with the provided GeoJSON or TopoJSON data. + * Clears color to (0.0, 0.0, 0.0, 0.0); depth to 1.0; and stencil to 0. * - * @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. + * @type {ClearCommand} * - * @returns {Promise.} A promise that will resolve when the data is loaded. + * @constant */ - GeoJsonDataSource.load = function(data, options) { - return new GeoJsonDataSource().load(data, options); + 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); }; - defineProperties(GeoJsonDataSource, { + return ClearCommand; +}); + +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); + /** - * Gets or sets the default size of the map pin created for each point, in pixels. - * @memberof GeoJsonDataSource - * @type {Number} - * @default 48 + * The vertex array. If none is provided, a viewport quad will be used. + * + * @type {VertexArray} + * @default undefined */ - markerSize : { - get : function() { - return defaultMarkerSize; - }, - set : function(value) { - defaultMarkerSize = value; - } - }, + this.vertexArray = options.vertexArray; + /** - * 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} + * The fragment shader source. The default vertex shader is ViewportQuadVS. + * + * @type {ShaderSource} + * @default undefined */ - markerSymbol : { - get : function() { - return defaultMarkerSymbol; - }, - set : function(value) { - defaultMarkerSymbol = value; - } - }, + this.fragmentShaderSource = options.fragmentShaderSource; + /** - * Gets or sets the default color of the map pin created for each point. - * @memberof GeoJsonDataSource - * @type {Color} - * @default Color.ROYALBLUE + * The shader program to apply. + * + * @type {ShaderProgram} + * @default undefined */ - markerColor : { - get : function() { - return defaultMarkerColor; - }, - set : function(value) { - defaultMarkerColor = value; - } - }, + this.shaderProgram = options.shaderProgram; + /** - * Gets or sets the default color of polylines and polygon outlines. - * @memberof GeoJsonDataSource - * @type {Color} - * @default Color.BLACK + * An object with functions whose names match the uniforms in the shader program + * and return values to set those uniforms. + * + * @type {Object} + * @default undefined */ - stroke : { - get : function() { - return defaultStroke; - }, - set : function(value) { - defaultStroke = value; - } - }, + this.uniformMap = options.uniformMap; + /** - * Gets or sets the default width of polylines and polygon outlines. - * @memberof GeoJsonDataSource - * @type {Number} - * @default 2.0 + * Texture to use for offscreen rendering. + * + * @type {Texture} + * @default undefined */ - strokeWidth : { - get : function() { - return defaultStrokeWidth; - }, - set : function(value) { - defaultStrokeWidth = value; - } - }, + this.outputTexture = options.outputTexture; + /** - * Gets or sets default color for polygon interiors. - * @memberof GeoJsonDataSource - * @type {Color} - * @default Color.YELLOW + * 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 */ - fill : { - get : function() { - return defaultFill; - }, - set : function(value) { - defaultFill = value; - } - }, + this.preExecute = options.preExecute; + /** - * Gets or sets default of whether to clamp to the ground. - * @memberof GeoJsonDataSource + * 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 */ - clampToGround : { - get : function() { - return defaultClampToGround; - }, - set : function(value) { - defaultClampToGround = value; - } - }, + this.persists = defaultValue(options.persists, false); /** - * 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} + * The pass when to render. Always compute pass. + * + * @type {Pass} + * @default Pass.COMPUTE; */ - crsNames : { - get : function() { - return crsNames; - } - }, + this.pass = Pass.COMPUTE; /** - * 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 + * 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 */ - crsLinkHrefs : { - get : function() { - return crsLinkHrefs; + this.owner = options.owner; + } + + /** + * Executes the compute command. + * + * @param {Context} computeEngine 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. +define('Shaders/ViewportQuadVS',[],function() { + 'use strict'; + return "attribute vec4 position;\n\ +attribute vec2 textureCoordinates;\n\ +\n\ +varying vec2 v_textureCoordinates;\n\ +\n\ +void main() \n\ +{\n\ + gl_Position = position;\n\ + v_textureCoordinates = textureCoordinates;\n\ +}\n\ +"; +}); +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) { + + // This may modify the command's resources, so do error checking afterwards + if (defined(computeCommand.preExecute)) { + computeCommand.preExecute(computeCommand); + } + + + 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); - /** - * 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; + framebuffer.destroy(); + + if (!computeCommand.persists) { + shaderProgram.destroy(); + if (defined(computeCommand.vertexArray)) { + vertexArray.destroy(); } } - }); - defineProperties(GeoJsonDataSource.prototype, { - /** - * Gets a human-readable name for this instance. - * @memberof GeoJsonDataSource.prototype - * @type {String} - */ - name : { - get : function() { - return this._name; - } - }, + if (defined(computeCommand.postExecute)) { + computeCommand.postExecute(outputTexture); + } + }; + + ComputeEngine.prototype.isDestroyed = function() { + return false; + }; + + ComputeEngine.prototype.destroy = function() { + return destroyObject(this); + }; + + return ComputeEngine; +}); + +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) { /** - * This DataSource only defines static data, therefore this property is always undefined. - * @memberof GeoJsonDataSource.prototype - * @type {DataSourceClock} + * The context used to execute commands for this pass. + * + * @type {Context} */ - clock : { - value : undefined, - writable : false - }, + this.context = context; + /** - * Gets the collection of {@link Entity} instances. - * @memberof GeoJsonDataSource.prototype - * @type {EntityCollection} + * 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 */ - entities : { - get : function() { - return this._entityCollection; - } - }, + this.framebuffer = undefined; + /** - * Gets a value indicating if the data source is currently loading data. - * @memberof GeoJsonDataSource.prototype + * 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 */ - 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; - } - }, + this.blendingEnabled = undefined; + /** - * Gets an event that will be raised if an error is encountered during processing. - * @memberof GeoJsonDataSource.prototype - * @type {Event} + * 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 */ - errorEvent : { - get : function() { - return this._error; - } - }, + this.scissorTest = undefined; + /** - * Gets an event that will be raised when the data source either starts or stops loading. - * @memberof GeoJsonDataSource.prototype - * @type {Event} + * The viewport used when one is not defined by a {@link DrawCommand}'s render state. + * @type {BoundingRectangle} + * @default undefined */ - loadingEvent : { + this.viewport = undefined; + } + + return PassState; +}); + +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); +}); + +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); + + + 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; + + + 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._loading; + return this._format; } }, - /** - * Gets whether or not this data source should be displayed. - * @memberof GeoJsonDataSource.prototype - * @type {Boolean} - */ - show : { + width: { get : function() { - return this._entityCollection.show; - }, - set : function(value) { - this._entityCollection.show = value; + return this._width; } }, - - /** - * 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 : { + height: { get : function() { - return this._entityCluster; - }, - set : function(value) { - this._entityCluster = value; + 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; +}); + +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'; + /** - * 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. + * @private */ - GeoJsonDataSource.prototype.load = function(data, options) { - - DataSource.setLoading(this, true); + 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(); - 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); + 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; } - 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) - }; + this._passState.viewport.width = width; + this._passState.viewport.height = height; - 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); - }); + return this._passState; }; - function load(that, geoJson, options, sourceUri) { - var name; - if (defined(sourceUri)) { - name = getFilenameFromUri(sourceUri); - } + var colorScratch = new Color(); - if (defined(name) && that._name !== name) { - that._name = name; - that._changed.raiseEvent(that); - } + PickFramebuffer.prototype.end = function(screenSpaceRectangle) { + var width = defaultValue(screenSpaceRectangle.width, 1.0); + var height = defaultValue(screenSpaceRectangle.height, 1.0); - var typeHandler = geoJsonObjectTypes[geoJson.type]; - if (!defined(typeHandler)) { - throw new RuntimeError('Unsupported GeoJSON object type: ' + geoJson.type); - } + var context = this._context; + var pixels = context.readPixels({ + x : screenSpaceRectangle.x, + y : screenSpaceRectangle.y, + width : width, + height : height, + framebuffer : this._fb + }); - //Check for a Coordinate Reference System. - var crs = geoJson.crs; - var crsFunction = crs !== null ? defaultCrsFunction : null; + var max = Math.max(width, height); + var length = max * max; + var halfWidth = Math.floor(width * 0.5); + var halfHeight = Math.floor(height * 0.5); - if (defined(crs)) { - if (!defined(crs.properties)) { - throw new RuntimeError('crs.properties is undefined.'); - } + var x = 0; + var y = 0; + var dx = 0; + var dy = -1; - 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]; - } + // Spiral around the center pixel, this is a workaround until + // we can access the depth buffer on all browsers. - if (!defined(handler)) { - throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties)); - } + // 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); - 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); + 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; } - } 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); + // 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; } - return when.all(that._promises, function() { - that._promises.length = 0; - DataSource.setLoading(that, false); - return that; - }); - }); - } + x += dx; + y += dy; + } - /** - * 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 undefined; + }; - return GeoJsonDataSource; + 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('DataSources/GeometryUpdater',[ +define('Renderer/ShaderCache',[ + '../Core/defined', '../Core/defineProperties', - '../Core/DeveloperError' + '../Core/destroyObject', + './ShaderProgram', + './ShaderSource' ], function( + defined, defineProperties, - DeveloperError) { + destroyObject, + ShaderProgram, + ShaderSource) { '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 + * @private */ - function GeometryUpdater(entity, scene) { - DeveloperError.throwInstantiationError(); + function ShaderCache(context) { + this._context = context; + this._shaders = {}; + this._numberOfShaders = 0; + this._shadersToRelease = {}; } - 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 + defineProperties(ShaderCache.prototype, { + numberOfShaders : { + get : function() { + return this._numberOfShaders; + } } }); /** - * Checks if the geometry is outlined at the provided time. - * @function + * 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 {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; + * @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. - /** - * Checks if the geometry is filled at the provided time. - * @function + * @returns {ShaderProgram} The cached or newly created shader program. * - * @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. + * @example + * this._shaderProgram = context.shaderCache.replaceShaderProgram({ + * shaderProgram : this._shaderProgram, + * vertexShaderSource : vs, + * fragmentShaderSource : fs, + * attributeLocations : attributeLocations + * }); * - * @exception {DeveloperError} This instance does not represent a filled geometry. + * @see ShaderCache#getShaderProgram */ - GeometryUpdater.prototype.createFillGeometryInstance = DeveloperError.throwInstantiationError; + ShaderCache.prototype.replaceShaderProgram = function(options) { + if (defined(options.shaderProgram)) { + options.shaderProgram.destroy(); + } + + return this.getShaderProgram(options); + }; /** - * Creates the geometry instance which represents the outline of the geometry. - * @function + * 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 {JulianDate} time The time to use when retrieving initial attribute values. - * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry. + * @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. * - * @exception {DeveloperError} This instance does not represent an outlined geometry. + * @returns {ShaderProgram} The cached or newly created shader program. */ - GeometryUpdater.prototype.createOutlineGeometryInstance = DeveloperError.throwInstantiationError; + 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. - /** - * 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; + var vertexShaderSource = options.vertexShaderSource; + var fragmentShaderSource = options.fragmentShaderSource; + var attributeLocations = options.attributeLocations; - /** - * 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; + if (typeof vertexShaderSource === 'string') { + vertexShaderSource = new ShaderSource({ + sources : [vertexShaderSource] + }); + } - /** - * 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; + if (typeof fragmentShaderSource === 'string') { + fragmentShaderSource = new ShaderSource({ + sources : [fragmentShaderSource] + }); + } - return GeometryUpdater; -}); + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(this._context); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(this._context); -(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 () { + var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations); + var cachedShader; -/*! - * 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. + if (defined(this._shaders[keyword])) { + cachedShader = this._shaders[keyword]; - // 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" ); - } -}; + // 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, + derivedKeywords : [], + 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.getDerivedShaderProgram = function(shaderProgram, keyword) { + var cachedShader = shaderProgram._cachedShader; + var derivedKeyword = keyword + cachedShader.keyword; + var cachedDerivedShader = this._shaders[derivedKeyword]; + if (!defined(cachedDerivedShader)) { + return undefined; + } + + return cachedDerivedShader.shaderProgram; + }; + + ShaderCache.prototype.createDerivedShaderProgram = function(shaderProgram, keyword, options) { + var cachedShader = shaderProgram._cachedShader; + var derivedKeyword = keyword + cachedShader.keyword; + + 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 context = this._context; + + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(context); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(context); + + var derivedShaderProgram = new ShaderProgram({ + gl : context._gl, + logShaderCompilation : context.logShaderCompilation, + debugShaders : context.debugShaders, + vertexShaderSource : vertexShaderSource, + vertexShaderText : vertexShaderText, + fragmentShaderSource : fragmentShaderSource, + fragmentShaderText : fragmentShaderText, + attributeLocations : attributeLocations + }); + + var derivedCachedShader = { + cache : this, + shaderProgram : derivedShaderProgram, + keyword : derivedKeyword, + derivedKeywords : [], + count : 0 + }; + + cachedShader.derivedKeywords.push(keyword); + derivedShaderProgram._cachedShader = derivedCachedShader; + this._shaders[derivedKeyword] = derivedCachedShader; + return derivedShaderProgram; + }; + + function destroyShader(cache, cachedShader) { + var derivedKeywords = cachedShader.derivedKeywords; + var length = derivedKeywords.length; + for (var i = 0; i < length; ++i) { + var keyword = derivedKeywords[i] + cachedShader.keyword; + var derivedCachedShader = cache._shaders[keyword]; + destroyShader(cache, derivedCachedShader); + } + + delete cache._shaders[cachedShader.keyword]; + cachedShader.shaderProgram.finalDestroy(); + } -Autolinker.prototype = { - constructor : Autolinker, // fix constructor property + ShaderCache.prototype.destroyReleasedShaderPrograms = function() { + var shadersToRelease = this._shadersToRelease; - /** - * @cfg {Boolean} urls - * - * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. - */ - urls : true, + for ( var keyword in shadersToRelease) { + if (shadersToRelease.hasOwnProperty(keyword)) { + var cachedShader = shadersToRelease[keyword]; + destroyShader(this, cachedShader); + --this._numberOfShaders; + } + } - /** - * @cfg {Boolean} email - * - * `true` if email addresses should be automatically linked, `false` if they should not be. - */ - email : true, + this._shadersToRelease = {}; + }; - /** - * @cfg {Boolean} twitter - * - * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. - */ - twitter : true, + ShaderCache.prototype.releaseShaderProgram = function(shaderProgram) { + if (defined(shaderProgram)) { + var cachedShader = shaderProgram._cachedShader; + if (cachedShader && (--cachedShader.count === 0)) { + this._shadersToRelease[cachedShader.keyword] = cachedShader; + } + } + }; - /** - * @cfg {Boolean} phone - * - * `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be. - */ - phone: true, + ShaderCache.prototype.isDestroyed = function() { + return false; + }; - /** - * @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, + ShaderCache.prototype.destroy = function() { + var shaders = this._shaders; + for (var keyword in shaders) { + if (shaders.hasOwnProperty(keyword)) { + shaders[keyword].shaderProgram.finalDestroy(); + } + } + return destroyObject(this); + }; - /** - * @cfg {Boolean} newWindow - * - * `true` if the links should open in a new window, `false` otherwise. - */ - newWindow : true, + return ShaderCache; +}); - /** - * @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, +define('Renderer/UniformState',[ + './Sampler', + '../Core/BoundingRectangle', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Cartographic', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/EncodedCartesian3', + '../Core/Math', + '../Core/Matrix3', + '../Core/Matrix4', + '../Core/OrthographicFrustum', + '../Core/Simon1994PlanetaryPositions', + '../Core/Transforms', + '../Scene/SceneMode' + ], function( + Sampler, + BoundingRectangle, + Cartesian2, + Cartesian3, + Cartesian4, + Cartographic, + Color, + defaultValue, + defined, + defineProperties, + EncodedCartesian3, + CesiumMath, + Matrix3, + Matrix4, + OrthographicFrustum, + Simon1994PlanetaryPositions, + Transforms, + SceneMode) { + 'use strict'; - /** - * @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, + /** + * @private + */ + function UniformState() { + /** + * @type {Texture} + */ + this.globeDepthTexture = 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 : "", + this._viewport = new BoundingRectangle(); + this._viewportCartesian4 = new Cartesian4(); + this._viewportDirty = false; + this._viewportOrthographicMatrix = Matrix4.clone(Matrix4.IDENTITY); + this._viewportTransformation = Matrix4.clone(Matrix4.IDENTITY); - /** - * @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. - */ + 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); - /** - * @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, + // Derived members + this._view3DDirty = true; + this._view3D = new Matrix4(); - /** - * @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, + this._inverseView3DDirty = true; + this._inverseView3D = new Matrix4(); - /** - * @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, + this._inverseModelDirty = true; + this._inverseModel = new Matrix4(); - /** - * 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 = []; + this._inverseTransposeModelDirty = true; + this._inverseTransposeModel = new Matrix3(); - for( var i = 0, len = htmlNodes.length; i < len; i++ ) { - var node = htmlNodes[ i ], - nodeType = node.getType(), - nodeText = node.getText(); + this._viewRotation = new Matrix3(); + this._inverseViewRotation = new Matrix3(); - 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 + this._viewRotation3D = new Matrix3(); + this._inverseViewRotation3D = new Matrix3(); - } else if( nodeType === 'entity' || nodeType === 'comment' ) { - resultHtml.push( nodeText ); // append HTML entity nodes (such as ' ') or HTML comments (such as '') verbatim + this._inverseProjectionDirty = true; + this._inverseProjection = new Matrix4(); - } 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 ); + this._modelViewDirty = true; + this._modelView = new Matrix4(); - } else { - // `text` is within an tag, simply append the text - we do not want to autolink anything - // already within an ... tag - resultHtml.push( nodeText ); - } - } - } + this._modelView3DDirty = true; + this._modelView3D = new Matrix4(); - return resultHtml.join( "" ); - }, + this._modelViewRelativeToEyeDirty = true; + this._modelViewRelativeToEye = new Matrix4(); - /** - * 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 ); - }, + this._inverseModelViewDirty = true; + this._inverseModelView = new Matrix4(); + this._inverseModelView3DDirty = true; + this._inverseModelView3D = new Matrix4(); - /** - * 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 - } + this._viewProjectionDirty = true; + this._viewProjection = new Matrix4(); - if( typeof replaceFnResult === 'string' ) { - return replaceFnResult; // `replaceFn` returned a string, use that + this._inverseViewProjectionDirty = true; + this._inverseViewProjection = new Matrix4(); - } else if( replaceFnResult === false ) { - return match.getMatchedText(); // no replacement for the match + this._modelViewProjectionDirty = true; + this._modelViewProjection = new Matrix4(); - } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { - return replaceFnResult.toAnchorString(); + this._inverseModelViewProjectionDirty = true; + this._inverseModelViewProjection = new Matrix4(); - } 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 + this._modelViewProjectionRelativeToEyeDirty = true; + this._modelViewProjectionRelativeToEye = new Matrix4(); - return anchorTag.toAnchorString(); - } - }, + this._modelViewInfiniteProjectionDirty = true; + this._modelViewInfiniteProjection = new Matrix4(); + this._normalDirty = true; + this._normal = new Matrix3(); - /** - * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. - * - * @protected - * @return {Autolinker.htmlParser.HtmlParser} - */ - getHtmlParser : function() { - var htmlParser = this.htmlParser; + this._normal3DDirty = true; + this._normal3D = new Matrix3(); - if( !htmlParser ) { - htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser(); - } + this._inverseNormalDirty = true; + this._inverseNormal = new Matrix3(); - return htmlParser; - }, + this._inverseNormal3DDirty = true; + this._inverseNormal3D = new Matrix3(); + this._encodedCameraPositionMCDirty = true; + this._encodedCameraPositionMC = new EncodedCartesian3(); + this._cameraPosition = new Cartesian3(); - /** - * Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance. - * - * @protected - * @return {Autolinker.matchParser.MatchParser} - */ - getMatchParser : function() { - var matchParser = this.matchParser; + this._sunPositionWC = new Cartesian3(); + this._sunPositionColumbusView = new Cartesian3(); + this._sunDirectionWC = new Cartesian3(); + this._sunDirectionEC = new Cartesian3(); + this._moonDirectionEC = new Cartesian3(); - 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 - } ); - } + 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._orthographicIn3D = false; + this._backgroundColor = new Color(); - return matchParser; - }, + this._brdfLut = new Sampler(); + this._environmentMap = new Sampler(); + this._fogDensity = undefined; - /** - * 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; + this._imagerySplitPosition = 0.0; + this._pixelSizePerMeter = undefined; + this._geometricToleranceOverMeter = undefined; - if( !tagBuilder ) { - tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { - newWindow : this.newWindow, - truncate : this.truncate, - className : this.className - } ); - } + this._minimumDisableDepthTestDistance = undefined; + } - return tagBuilder; - } + 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; + } + } + }, -/** - * 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 ); -}; + /** + * @memberof UniformState.prototype + * @private + */ + viewportCartesian4 : { + get : function() { + return this._viewportCartesian4; + } + }, + viewportOrthographic : { + get : function() { + cleanViewport(this); + return this._viewportOrthographicMatrix; + } + }, -// Autolinker Namespaces -Autolinker.match = {}; -Autolinker.htmlParser = {}; -Autolinker.matchParser = {}; + viewportTransformation : { + get : function() { + cleanViewport(this); + return this._viewportTransformation; + } + }, -/*global Autolinker */ -/*jshint eqnull:true, boss:true */ -/** - * @class Autolinker.Util - * @singleton - * - * A few utility methods for Autolinker. - */ -Autolinker.Util = { + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + model : { + get : function() { + return this._model; + }, + set : function(matrix) { + Matrix4.clone(matrix, this._model); - /** - * @property {Function} abstractMethod - * - * A function object which represents an abstract method. - */ - abstractMethod : function() { throw "abstract"; }, + 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; - /** - * @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, + Matrix4.inverse(this._model, this._inverseModel); + } + return this._inverseModel; + } + }, - /** - * 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 ]; - } - } + /** + * @memberof UniformState.prototype + * @private + */ + inverseTransposeModel : { + get : function() { + var m = this._inverseTransposeModel; + if (this._inverseTransposeModelDirty) { + this._inverseTransposeModelDirty = false; - return dest; - }, + Matrix4.getRotation(this.inverseModel, m); + Matrix3.transpose(m, m); + } + return m; + } + }, - /** - * 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; + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + view : { + get : function() { + return this._view; + } + }, - var F = function() {}; - F.prototype = superclassProto; + /** + * 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; + } + }, - var subclass; - if( protoProps.hasOwnProperty( 'constructor' ) ) { - subclass = protoProps.constructor; - } else { - subclass = function() { superclassProto.constructor.apply( this, arguments ); }; - } + /** + * 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; + } + }, - var subclassProto = subclass.prototype = new F(); // set up prototype chain - subclassProto.constructor = subclass; // fix constructor property - subclassProto.superclass = superclassProto; + /** + * @memberof UniformState.prototype + * @type {Matrix3} + */ + viewRotation3D : { + get : function() { + updateView3D(this); + return this._viewRotation3D; + } + }, - 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 ); + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + inverseView : { + get : function() { + return this._inverseView; + } + }, - return subclass; - }, + /** + * 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; + } + }, - /** - * 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; - }, + /** + * 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; + } + }, - /** - * 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 ); + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + inverseProjection : { + get : function() { + cleanInverseProjection(this); + return this._inverseProjection; + } + }, - } else { - for( var i = 0, len = arr.length; i < len; i++ ) { - if( arr[ i ] === element ) return i; - } - return -1; - } - }, + /** + * @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; + } + }, - /** - * 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" ); + /** + * Model-view relative to eye matrix. + * + * @memberof UniformState.prototype + * @type {Matrix4} + */ + modelViewRelativeToEye : { + get : function() { + cleanModelViewRelativeToEye(this); + return this._modelViewRelativeToEye; + } + }, - var result = [], - lastIdx = 0, - match; + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + inverseModelView : { + get : function() { + cleanInverseModelView(this); + return this._inverseModelView; + } + }, - while( match = splitRegex.exec( str ) ) { - result.push( str.substring( lastIdx, match.index ) ); - result.push( match[ 0 ] ); // push the splitting char(s) + /** + * 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; - lastIdx = match.index + match[ 0 ].length; - } - result.push( str.substring( lastIdx ) ); + } + }, - return result; - }, + /** + * @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; + } + }, - /** - * 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, '' ); - } + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + modelViewProjection : { + get : function() { + cleanModelViewProjection(this); + return this._modelViewProjection; -}; -/*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 - */ -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. - */ + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + inverseModelViewProjection : { + get : function() { + cleanInverseModelViewProjection(this); + return this._inverseModelViewProjection; + + } + }, - /** - * @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. - */ + /** + * Model-view-projection relative to eye matrix. + * + * @memberof UniformState.prototype + * @type {Matrix4} + */ + modelViewProjectionRelativeToEye : { + get : function() { + cleanModelViewProjectionRelativeToEye(this); + return this._modelViewProjectionRelativeToEye; + } + }, - /** - * @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. - */ + /** + * @memberof UniformState.prototype + * @type {Matrix4} + */ + modelViewInfiniteProjection : { + get : function() { + cleanModelViewInfiniteProjection(this); + return this._modelViewInfiniteProjection; + } + }, - /** - * @cfg {String} innerHTML - * - * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version - * for acronym names. - */ + /** + * 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; - /** - * @protected - * @property {RegExp} whitespaceRegex - * - * Regular expression used to match whitespace in a string of CSS classes. - */ - whitespaceRegex : /\s+/, + } + }, + /** + * 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; + } + }, - /** - * @constructor - * @param {Object} [cfg] The configuration properties for this class, in an Object (map) - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); + /** + * 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; + } + }, - this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym - }, + /** + * 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; + } + }, - /** - * 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; - }, + /** + * 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; + } + }, - /** - * Retrieves the tag name. - * - * @return {String} - */ - getTagName : function() { - return this.tagName || ""; - }, + /** + * 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; + } + }, - /** - * 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; + /** + * 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; + } + }, - return this; - }, + /** + * 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; + } + }, - /** - * 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 ]; - }, + /** + * 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; + } + }, - /** - * 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 ); + /** + * 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; + } + }, - return this; - }, + /** + * 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; + } + }, - /** - * 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 = {} ); - }, + /** + * A scalar that represents the geometric tolerance per meter + * @memberof UniformStat.prototype + * @type {Number} + */ + geometricToleranceOverMeter: { + get: function() { + return this._geometricToleranceOverMeter; + } + }, + /** + * @memberof UniformState.prototype + * @type {Pass} + */ + pass : { + get : function() { + return this._pass; + } + }, - /** - * 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 ); - }, + /** + * The current background color + * @memberof UniformState.prototype + * @type {Color} + */ + backgroundColor : { + get : function() { + return this._backgroundColor; + } + }, + /** + * The look up texture used to find the BRDF for a material + * @memberof UniformState.prototype + * @type {Sampler} + */ + brdfLut : { + get : function() { + return this._brdfLut; + } + }, - /** - * 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; + /** + * The environment map of the scene + * @memberof UniformState.prototype + * @type {Sampler} + */ + environmentMap : { + get : function() { + return this._environmentMap; + } + }, - while( newClass = newClasses.shift() ) { - if( indexOf( classes, newClass ) === -1 ) { - classes.push( newClass ); - } - } + /** + * @memberof UniformState.prototype + * @type {Number} + */ + imagerySplitPosition : { + get : function() { + return this._imagerySplitPosition; + } + }, - this.getAttrs()[ 'class' ] = classes.join( " " ); - return this; - }, + /** + * The distance from the camera at which to disable the depth test of billboards, labels and points + * to, for example, prevent clipping against terrain. When set to zero, the depth test should always + * be applied. When less than zero, the depth test should never be applied. + * + * @memberof UniformState.prototype + * @type {Number} + */ + minimumDisableDepthTestDistance : { + get : function() { + return this._minimumDisableDepthTestDistance; + } + } + }); + function setView(uniformState, matrix) { + Matrix4.clone(matrix, uniformState._view); + Matrix4.getRotation(matrix, uniformState._viewRotation); - /** - * 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; + 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; + } - while( classes.length && ( removeClass = removeClasses.shift() ) ) { - var idx = indexOf( classes, removeClass ); - if( idx !== -1 ) { - classes.splice( idx, 1 ); - } - } + function setInverseView(uniformState, matrix) { + Matrix4.clone(matrix, uniformState._inverseView); + Matrix4.getRotation(matrix, uniformState._inverseViewRotation); + } - this.getAttrs()[ 'class' ] = classes.join( " " ); - return this; - }, + function setProjection(uniformState, matrix) { + Matrix4.clone(matrix, uniformState._projection); + uniformState._inverseProjectionDirty = true; + uniformState._viewProjectionDirty = true; + uniformState._inverseViewProjectionDirty = true; + uniformState._modelViewProjectionDirty = true; + uniformState._modelViewProjectionRelativeToEyeDirty = true; + } - /** - * 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' ] || ""; - }, + function setInfiniteProjection(uniformState, matrix) { + Matrix4.clone(matrix, uniformState._infiniteProjection); + uniformState._modelViewInfiniteProjectionDirty = true; + } - /** - * 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; - }, + 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); + } - /** - * 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; + var position = Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(frameState.time, uniformState._sunPositionWC); + Matrix3.multiplyByVector(transformMatrix, position, position); - return this; - }, + Cartesian3.normalize(position, uniformState._sunDirectionWC); + position = Matrix3.multiplyByVector(uniformState.viewRotation3D, position, uniformState._sunDirectionEC); + Cartesian3.normalize(position, position); - /** - * Retrieves the inner HTML for the tag. - * - * @return {String} - */ - getInnerHtml : function() { - return this.innerHtml || ""; - }, + 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); + } - /** - * Override of superclass method used to generate the HTML string for the tag. - * - * @return {String} - */ - toAnchorString : function() { - var tagName = this.getTagName(), - attrsStr = this.buildAttrsStr(); + /** + * 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); - attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes + this._entireFrustum.x = camera.frustum.near; + this._entireFrustum.y = camera.frustum.far; + this.updateFrustum(camera.frustum); - return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); - }, + this._orthographicIn3D = this._mode !== SceneMode.SCENE2D && camera.frustum instanceof OrthographicFrustum; + }; + /** + * 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; - /** - * 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 + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } - var attrs = this.getAttrs(), - attrsArr = []; + this._frustumPlanes.x = frustum.top; + this._frustumPlanes.y = frustum.bottom; + this._frustumPlanes.z = frustum.left; + this._frustumPlanes.w = frustum.right; + }; - for( var prop in attrs ) { - if( attrs.hasOwnProperty( prop ) ) { - attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); - } - } - return attrsArr.join( " " ); - } + 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; -/*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, { + var canvas = frameState.context._canvas; + this._resolutionScale = canvas.width / canvas.clientWidth; - /** - * @cfg {Boolean} newWindow - * @inheritdoc Autolinker#newWindow - */ + var camera = frameState.camera; + this.updateCamera(camera); - /** - * @cfg {Number} truncate - * @inheritdoc Autolinker#truncate - */ + 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; + } - /** - * @cfg {String} className - * @inheritdoc Autolinker#className - */ + setSunAndMoonDirections(this, frameState); + var brdfLutGenerator = frameState.brdfLutGenerator; + var brdfLut = defined(brdfLutGenerator) ? brdfLutGenerator.colorTexture : undefined; + this._brdfLut = brdfLut; - /** - * @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._environmentMap = defaultValue(frameState.environmentMap, frameState.context.defaultCubeMap); + this._fogDensity = frameState.fog.density; - /** - * 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() ) - } ); + this._frameState = frameState; + this._temeToPseudoFixed = Transforms.computeTemeToPseudoFixedMatrix(frameState.time, this._temeToPseudoFixed); - return tag; - }, + // Convert the relative imagerySplitPosition to absolute pixel coordinates + this._imagerySplitPosition = frameState.imagerySplitPosition * frameState.context.drawingBufferWidth; + var fov = camera.frustum.fov; + var viewport = this._viewport; + var pixelSizePerMeter; + if (viewport.height > viewport.width) { + pixelSizePerMeter = Math.tan(0.5 * fov) * 2.0 / viewport.height; + } else { + pixelSizePerMeter = Math.tan(0.5 * fov) * 2.0 / viewport.width; + } + this._geometricToleranceOverMeter = pixelSizePerMeter * frameState.maximumScreenSpaceError; + Color.clone(frameState.backgroundColor, this._backgroundColor); - /** - * 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 - }; + this._minimumDisableDepthTestDistance = frameState.minimumDisableDepthTestDistance; + this._minimumDisableDepthTestDistance *= this._minimumDisableDepthTestDistance; + if (this._minimumDisableDepthTestDistance === Number.POSITIVE_INFINITY) { + this._minimumDisableDepthTestDistance = -1.0; + } + }; - var cssClass = this.createCssClass( matchType ); - if( cssClass ) { - attrs[ 'class' ] = cssClass; - } - if( this.newWindow ) { - attrs[ 'target' ] = "_blank"; - } + 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; + } + } - return attrs; - }, + function cleanInverseProjection(uniformState) { + if (uniformState._inverseProjectionDirty) { + uniformState._inverseProjectionDirty = false; + if (uniformState._mode !== SceneMode.SCENE2D && uniformState._mode !== SceneMode.MORPHING && !uniformState._orthographicIn3D) { + Matrix4.inverse(uniformState._projection, uniformState._inverseProjection); + } else { + Matrix4.clone(Matrix4.ZERO, uniformState._inverseProjection); + } + } + } - /** - * 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; + // Derived + function cleanModelView(uniformState) { + if (uniformState._modelViewDirty) { + uniformState._modelViewDirty = false; - 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" - }, + Matrix4.multiplyTransformation(uniformState._view, uniformState._model, uniformState._modelView); + } + } + function cleanModelView3D(uniformState) { + if (uniformState._modelView3DDirty) { + uniformState._modelView3DDirty = false; - /** - * 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 ); + Matrix4.multiplyTransformation(uniformState.view3D, uniformState._model, uniformState._modelView3D); + } + } - return anchorText; - }, + function cleanInverseModelView(uniformState) { + if (uniformState._inverseModelViewDirty) { + uniformState._inverseModelViewDirty = false; + Matrix4.inverse(uniformState.modelView, uniformState._inverseModelView); + } + } - /** - * 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 ); - } + function cleanInverseModelView3D(uniformState) { + if (uniformState._inverseModelView3DDirty) { + uniformState._inverseModelView3DDirty = false; -} ); -/*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, { + Matrix4.inverse(uniformState.modelView3D, uniformState._inverseModelView3D); + } + } - /** - * @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]' + function cleanViewProjection(uniformState) { + if (uniformState._viewProjectionDirty) { + uniformState._viewProjectionDirty = false; - return new RegExp( [ - // for tag. Ex: ) - '(?:', - '<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag + Matrix4.multiply(uniformState._projection, uniformState._view, uniformState._viewProjection); + } + } - // Zero or more attributes following the tag name - '(?:', - '\\s+', // one or more whitespace chars before an attribute + function cleanInverseViewProjection(uniformState) { + if (uniformState._inverseViewProjectionDirty) { + uniformState._inverseViewProjectionDirty = false; - // Either: - // A. attr="value", or - // B. "value" alone (To cover example doctype tag: ) - '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', - ')*', - '>', - ')', + Matrix4.inverse(uniformState.viewProjection, uniformState._inverseViewProjection); + } + } - '|', + function cleanModelViewProjection(uniformState) { + if (uniformState._modelViewProjectionDirty) { + uniformState._modelViewProjectionDirty = false; - // All other HTML tags (i.e. tags that are not ) - '(?:', - '<(/)?', // Beginning of a tag or comment. Either '<' for a start tag, or '' + Matrix4.multiply(uniformState._projection, uniformState.modelViewRelativeToEye, uniformState._modelViewProjectionRelativeToEye); + } + } - ')', - ')', - '>', - ')' - ].join( "" ), 'gi' ); - } )(), + function cleanModelViewInfiniteProjection(uniformState) { + if (uniformState._modelViewInfiniteProjectionDirty) { + uniformState._modelViewInfiniteProjectionDirty = false; - /** - * @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, + Matrix4.multiply(uniformState._infiniteProjection, uniformState.modelView, uniformState._modelViewInfiniteProjection); + } + } + function cleanNormal(uniformState) { + if (uniformState._normalDirty) { + uniformState._normalDirty = false; - /** - * 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 + var m = uniformState._normal; + Matrix4.getRotation(uniformState.inverseModelView, m); + Matrix3.transpose(m, m); + } + } - 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 ); + function cleanNormal3D(uniformState) { + if (uniformState._normal3DDirty) { + uniformState._normal3DDirty = false; - // Push TextNodes and EntityNodes for any text found between tags - if( inBetweenTagsText ) { - textAndEntityNodes = this.parseTextAndEntityNodes( inBetweenTagsText ); - nodes.push.apply( nodes, textAndEntityNodes ); - } + var m = uniformState._normal3D; + Matrix4.getRotation(uniformState.inverseModelView3D, m); + Matrix3.transpose(m, m); + } + } - // Push the CommentNode or ElementNode - if( commentText ) { - nodes.push( this.createCommentNode( tagText, commentText ) ); - } else { - nodes.push( this.createElementNode( tagText, tagName, isClosingTag ) ); - } + function cleanInverseNormal(uniformState) { + if (uniformState._inverseNormalDirty) { + uniformState._inverseNormalDirty = false; - lastIndex = currentResult.index + tagText.length; - } + Matrix4.getRotation(uniformState.inverseModelView, uniformState._inverseNormal); + } + } - // 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 ); + function cleanInverseNormal3D(uniformState) { + if (uniformState._inverseNormal3DDirty) { + uniformState._inverseNormal3DDirty = false; - // Push TextNodes and EntityNodes for any text found between tags - if( text ) { - textAndEntityNodes = this.parseTextAndEntityNodes( text ); - nodes.push.apply( nodes, textAndEntityNodes ); - } - } + Matrix4.getRotation(uniformState.inverseModelView3D, uniformState._inverseNormal3D); + } + } - return nodes; - }, + var cameraPositionMC = new Cartesian3(); + function cleanEncodedCameraPositionMC(uniformState) { + if (uniformState._encodedCameraPositionMCDirty) { + uniformState._encodedCameraPositionMCDirty = false; - /** - * 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 + Matrix4.multiplyByPoint(uniformState.inverseModel, uniformState._cameraPosition, cameraPositionMC); + EncodedCartesian3.fromCartesian(cameraPositionMC, uniformState._encodedCameraPositionMC); + } + } - // 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 ]; + 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(); - if( textToken ) nodes.push( this.createTextNode( textToken ) ); - if( entityToken ) nodes.push( this.createEntityNode( entityToken ) ); - } - return nodes; - }, + 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; - /** - * 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 ) - } ); - }, + 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; - /** - * 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 - } ); - }, + // 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); - /** - * 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 '&nbsp;'). - * @return {Autolinker.htmlParser.EntityNode} - */ - createEntityNode : function( text ) { - return new Autolinker.htmlParser.EntityNode( { text: text } ); - }, + // 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); - /** - * 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 } ); - } + // Compute the view matrix based on the new fixed-frame camera position and directions. + if (!defined(result)) { + result = new Matrix4(); + } -} ); -/*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 &quot; and &nbsp; - */ -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 ); - }, + 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; - - /** - * 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; - } + return result; + } -} ); -/*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, { + 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; + } + } - /** - * @cfg {String} comment (required) - * - * The text inside the comment tag. This text is stripped of any leading or - * trailing whitespace. - */ - comment : '', + function updateInverseView3D(that){ + if (that._inverseView3DDirty) { + Matrix4.inverseTransformation(that.view3D, that._inverseView3D); + Matrix4.getRotation(that._inverseView3D, that._inverseViewRotation3D); + that._inverseView3DDirty = false; + } + } + return UniformState; +}); - /** - * Returns a string name for the type of node that this class represents. - * - * @return {String} - */ - getType : function() { - return 'comment'; - }, +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 + ')'; + } - /** - * Returns the comment inside the comment tag. - * - * @return {String} - */ - getComment : function() { - return this.comment; - } + return message; + } -} ); -/*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, + function createErrorMessage(gl, glFunc, glFuncArguments, error) { + var message = errorToString(gl, error) + ': ' + glFunc.name + '('; - - /** - * Returns a string name for the type of node that this class represents. - * - * @return {String} - */ - getType : function() { - return 'element'; - }, - + for ( var i = 0; i < glFuncArguments.length; ++i) { + if (i !== 0) { + message += ', '; + } + message += glFuncArguments[i]; + } + message += ');'; - /** - * 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: '&nbsp;', 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, { + return message; + } - /** - * @cfg {Boolean} urls - * @inheritdoc Autolinker#urls - */ - urls : true, + function throwOnError(gl, glFunc, glFuncArguments) { + var error = gl.getError(); + if (error !== gl.NO_ERROR) { + throw new RuntimeError(createErrorMessage(gl, glFunc, glFuncArguments, error)); + } + } - /** - * @cfg {Boolean} email - * @inheritdoc Autolinker#email - */ - email : true, + function makeGetterSetter(gl, propertyName, logFunction) { + return { + get : function() { + var value = gl[propertyName]; + logFunction(gl, 'get: ' + propertyName, value); + return gl[propertyName]; + }, + set : function(value) { + gl[propertyName] = value; + logFunction(gl, 'set: ' + propertyName, value); + } + }; + } - /** - * @cfg {Boolean} twitter - * @inheritdoc Autolinker#twitter - */ - twitter : true, + function wrapGL(gl, logFunction) { + if (!defined(logFunction)) { + return gl; + } - /** - * @cfg {Boolean} phone - * @inheritdoc Autolinker#phone - */ - phone: true, + function wrapFunction(property) { + return function() { + var result = property.apply(gl, arguments); + logFunction(gl, property, arguments); + return result; + }; + } - /** - * @cfg {Boolean/String} hashtag - * @inheritdoc Autolinker#hashtag - */ - hashtag : false, + var glWrapper = {}; - /** - * @cfg {Boolean} stripPrefix - * @inheritdoc Autolinker#stripPrefix - */ - stripPrefix : true, + // JavaScript linters normally demand 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. + /*eslint-disable guard-for-in*/ + for (var propertyName in gl) { + var property = gl[propertyName]; + // wrap any functions we encounter, otherwise just copy the property to the wrapper. + if (property instanceof Function) { + glWrapper[propertyName] = wrapFunction(property); + } else { + Object.defineProperty(glWrapper, propertyName, makeGetterSetter(gl, propertyName, logFunction)); + } + } + /*eslint-enable guard-for-in*/ - /** - * @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 + return glWrapper; + } - hashtagRegex = /(^|[^\w])#(\w{1,15})/, // For matching a Hashtag. Ex: #games + 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; + } + } - 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) + return undefined; + } - // 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+&@#\/%=~_()|'$*\[\]]/; + /** + * @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.'); + } - 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, - ')', + + this._canvas = canvas; - '|', + options = clone(options, true); + options = defaultValue(options, {}); + options.allowTextureFilterAnisotropic = defaultValue(options.allowTextureFilterAnisotropic, true); + var webglOptions = defaultValue(options.webgl, {}); - '(', // *** Capturing group $4, which is used to determine an email match - emailRegex.source, - domainNameRegex.source, - tldRegex.source, - ')', + // 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 requestWebgl2 = defaultToWebgl2 && (typeof WebGL2RenderingContext !== 'undefined'); + var webgl2 = false; - '(', // *** 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, - ')', + var glContext; + var getWebGLStub = options.getWebGLStub; - '|', + if (!defined(getWebGLStub)) { + if (requestWebgl2) { + 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.'); + } + } else { + // Use WebGL stub when requested for unit tests + glContext = getWebGLStub(canvas, webglOptions); + } - '(?:', // 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, - ')', + this._originalGLContext = glContext; + this._gl = glContext; + this._webgl2 = webgl2; + this._id = createGuid(); - '|', + // Validation and logging disabled by default for speed. + this.validateFramebuffer = false; + this.validateShaderProgram = false; + this.logShaderCompilation = false; - '(?:', // 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, - ')', - ')', + this._throwOnWebGLError = false; - '(?:' + urlSuffixRegex.source + ')?', // match for path, query string, and/or hash anchor - optional - ')', + this._shaderCache = new ShaderCache(this); - '|', + var gl = glContext; - // 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, - ')', + 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 - '(', // *** 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' ); - } )(), + var aliasedLineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); // must include 1 + ContextLimits._minimumAliasedLineWidth = aliasedLineWidthRange[0]; + ContextLimits._maximumAliasedLineWidth = aliasedLineWidthRange[1]; - /** - * @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 : /^(.)?\/\//, + var aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE); // must include 1 + ContextLimits._minimumAliasedPointSize = aliasedPointSizeRange[0]; + ContextLimits._maximumAliasedPointSize = aliasedPointSizeRange[1]; - /** - * @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. - */ + 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; - /** - * @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._antialias = gl.getContextAttributes().antialias; - this.matchValidator = new Autolinker.MatchValidator(); - }, + // Query and initialize extensions + this._standardDerivatives = !!getExtension(gl, ['OES_standard_derivatives']); + this._blendMinmax = !!getExtension(gl, ['EXT_blend_minmax']); + 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']); + this._colorBufferFloat = this._webgl2 && !!getExtension(gl, ['EXT_color_buffer_float']); - /** - * 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 + this._s3tc = !!getExtension(gl, ['WEBGL_compressed_texture_s3tc', 'MOZ_WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc']); + this._pvrtc = !!getExtension(gl, ['WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc']); + this._etc1 = !!getExtension(gl, ['WEBGL_compressed_texture_etc1']); - 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 + 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; - // 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; + var glCreateVertexArray; + var glBindVertexArray; + var glDeleteVertexArray; - } else { - // Generate replacement text for the match from the `replaceFn` - var replaceStr = replaceFn.call( contextObj, matchDescObj.match ); - return matchDescObj.prefixStr + replaceStr + matchDescObj.suffixStr; - } - } ); - }, + var glDrawElementsInstanced; + var glDrawArraysInstanced; + var glVertexAttribDivisor; + var glDrawBuffers; - /** - * 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 vertexArrayObject; + var instancedArrays; + var drawBuffers; - var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch, - match, // Will be an Autolinker.match.Match object + if (webgl2) { + var that = this; - 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. + glCreateVertexArray = function () { return that._gl.createVertexArray(); }; + glBindVertexArray = function(vao) { that._gl.bindVertexArray(vao); }; + glDeleteVertexArray = function(vao) { that._gl.deleteVertexArray(vao); }; - // 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; - } + 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); }; - // 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 - } + 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); }; + } - if( emailAddressMatch ) { - match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } ); + 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); }; + } - } 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 } ); + drawBuffers = getExtension(gl, ['WEBGL_draw_buffers']); + if (defined(drawBuffers)) { + glDrawBuffers = function(buffers) { drawBuffers.drawBuffersWEBGL(buffers); }; + } + } - } 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 } ); + this.glCreateVertexArray = glCreateVertexArray; + this.glBindVertexArray = glBindVertexArray; + this.glDeleteVertexArray = glDeleteVertexArray; - } 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 } ); + this.glDrawElementsInstanced = glDrawElementsInstanced; + this.glDrawArraysInstanced = glDrawArraysInstanced; + this.glVertexAttribDivisor = glVertexAttribDivisor; - } 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 ] || ""; + this.glDrawBuffers = glDrawBuffers; - 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 - } - } + this._vertexArrayObject = !!vertexArrayObject; + this._instancedArrays = !!instancedArrays; + this._drawBuffers = !!drawBuffers; - match = new Autolinker.match.Url( { - matchedText : matchStr, - url : matchStr, - protocolUrlMatch : !!protocolUrlMatch, - protocolRelativeMatch : !!protocolRelativeMatch, - stripPrefix : this.stripPrefix - } ); - } + ContextLimits._maximumDrawBuffers = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_DRAW_BUFFERS) : 1; + ContextLimits._maximumColorAttachments = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_COLOR_ATTACHMENTS) : 1; - return { - prefixStr : prefixStr, - suffixStr : suffixStr, - match : match - }; - }, + this._clearColor = new Color(0.0, 0.0, 0.0, 0.0); + this._clearDepth = 1.0; + this._clearStencil = 0; + + 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; - /** - * 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 ); + this._us = us; + this._currentRenderState = rs; + this._currentPassState = ps; + this._currentFramebuffer = undefined; + this._maxFrameTextureUnitIndex = 0; - if( lastChar === ')' ) { - var openParensMatch = matchStr.match( /\(/g ), - closeParensMatch = matchStr.match( /\)/g ), - numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, - numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 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); + } - if( numOpenParens < numCloseParens ) { - return true; - } - } + this._pickObjects = {}; + this._nextPickColor = new Uint32Array(1); - return false; - } + /** + * @example + * { + * webgl : { + * alpha : false, + * depth : true, + * stencil : false, + * antialias : true, + * premultipliedAlpha : true, + * preserveDrawingBuffer : false, + * failIfMajorPerformanceCaveat : true + * }, + * allowTextureFilterAnisotropic : true + * } + */ + this.options = options; -} ); -/*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, { + /** + * 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 = {}; - /** - * @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]\/\//, + RenderState.apply(gl, rs, ps); + } - /** - * 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]+:\/\//, + var defaultFramebufferMarker = {}; - /** - * 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]+:/, + 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; + } + }, - /** - * 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]/, + /** + * 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; + } + }, - /** - * 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; - } + /** + * 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; + } + }, - return true; - }, + /** + * 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 || this._webgl2; + } + }, + /** + * true if the EXT_blend_minmax extension is supported. This + * extension extends blending capabilities by adding two new blend equations: + * the minimum or maximum color components of the source and destination colors. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_blend_minmax/} + */ + blendMinmax : { + get : function() { + return this._blendMinmax || this._webgl2; + } + }, - /** - * 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(); + /** + * 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; + } + }, - return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' ); - }, + /** + * 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 || this._webgl2; + } + }, + /** + * 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 || this._colorBufferFloat; + } + }, - /** - * 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 ); - }, + textureFilterAnisotropic : { + get : function() { + return !!this._textureFilterAnisotropic; + } + }, + /** + * true if WEBGL_texture_compression_s3tc is supported. This extension provides + * access to DXT compressed textures. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/} + */ + s3tc : { + get : function() { + return this._s3tc; + } + }, - /** - * 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; - } - }, + /** + * true if WEBGL_texture_compression_pvrtc is supported. This extension provides + * access to PVR compressed textures. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/} + */ + pvrtc : { + get : function() { + return this._pvrtc; + } + }, + /** + * true if WEBGL_texture_compression_etc1 is supported. This extension provides + * access to ETC1 compressed textures. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/} + */ + etc1 : { + get : function() { + return this._etc1; + } + }, - /** - * 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 ) ); - } + /** + * 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; + } + }, -} ); -/*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 ); - }, + /** + * 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 || this._webgl2; + } + }, - - /** - * 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; - }, - + /** + * 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; + } + }, - /** - * 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 + /** + * true if the EXT_color_buffer_float extension is supported. This + * extension makes the formats gl.R16F, gl.RG16F, gl.RGBA16F, gl.R32F, gl.RG32F, + * gl.RGBA32F, gl.R11F_G11F_B10F color renderable. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_float/} + */ + colorBufferFloat : { + get : function() { + return this._colorBufferFloat; + } + }, -} ); -/*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. - */ - + /** + * 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; + } + }, - /** - * 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; - }, - + debugShaders : { + get : function() { + return this._debugShaders; + } + }, - /** - * 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, { + throwOnWebGLError : { + get : function() { + return this._throwOnWebGLError; + }, + set : function(value) { + this._throwOnWebGLError = value; + this._gl = wrapGL(this._originalGLContext, value ? throwOnError : undefined); + } + }, - /** - * @cfg {String} serviceName (required) - * - * The service to point hashtag matches to. See {@link Autolinker#hashtag} - * for available values. - */ + /** + * 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, + //acevedo eliminate white flasihing but image flickers. bug 1640 in Cesium. + arrayBufferView : new Uint8Array([0, 0, 0, 0]) + //arrayBufferView : new Uint8Array([255, 255, 255, 255]) + } + }); + } - /** - * @cfg {String} hashtag (required) - * - * The Hashtag that was matched, without the '#'. - */ + 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]) + }; - /** - * Returns the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'hashtag'; - }, + this._defaultCubeMap = new CubeMap({ + context : this, + source : { + positiveX : face, + negativeX : face, + positiveY : face, + negativeY : face, + positiveZ : face, + negativeZ : face + } + }); + } + return this._defaultCubeMap; - /** - * Returns the matched hashtag. - * - * @return {String} - */ - getHashtag : function() { - return this.hashtag; - }, + } + }, + /** + * 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; + } + }, - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - var serviceName = this.serviceName, - hashtag = this.hashtag; + /** + * 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; + } + }, - switch( serviceName ) { - case 'twitter' : - return 'https://twitter.com/hashtag/' + hashtag; - case 'facebook' : - return 'https://www.facebook.com/hashtag/' + hashtag; + /** + * 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; + } + } + }); - 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 ); - } - }, + /** + * Validates a framebuffer. + * Available in debug builds only. + * @private + */ + function validateFramebuffer(context) { + } + 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); + } - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return '#' + this.hashtag; - } + var scratchBackBufferArray; + // this check must use typeof, not defined, because defined doesn't work with undeclared variables. + if (typeof WebGLRenderingContext !== 'undefined') { + scratchBackBufferArray = [WebGLConstants.BACK]; + } -} ); -/*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, { + function bindFramebuffer(context, framebuffer) { + if (framebuffer !== context._currentFramebuffer) { + context._currentFramebuffer = framebuffer; + var buffers = scratchBackBufferArray; - /** - * @cfg {String} number (required) - * - * The phone number that was matched. - */ + 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); + } - /** - * Returns a string name for the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'phone'; - }, + if (context.drawBuffers) { + context.glDrawBuffers(buffers); + } + } + } + var defaultClearCommand = new ClearCommand(); - /** - * Returns the phone number that was matched. - * - * @return {String} - */ - getNumber: function() { - return this.number; - }, + Context.prototype.clear = function(clearCommand, passState) { + clearCommand = defaultValue(clearCommand, defaultClearCommand); + passState = defaultValue(passState, this._defaultPassState); + var gl = this._gl; + var bitmask = 0; - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - return 'tel:' + this.number; - }, + 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; + } - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return this.matchedText; - } + 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; + } -/*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. - */ - + var rs = defaultValue(clearCommand.renderState, this._defaultRenderState); + applyRenderState(this, rs, passState, true); - /** - * 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; - }, - + // 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); - /** - * 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 - */ - + gl.clear(bitmask); + }; - /** - * @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, - + function beginDraw(context, framebuffer, drawCommand, passState) { + var rs = defaultValue(drawCommand._renderState, context._defaultRenderState); + + + bindFramebuffer(context, framebuffer); - /** - * 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; - }, - + applyRenderState(context, rs, passState, false); - /** - * 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; + 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; -/** -@license - Copyright (c) 2013 Gildas Lormeau. All rights reserved. + + context._us.model = defaultValue(drawCommand._modelMatrix, Matrix4.IDENTITY); + drawCommand._shaderProgram._setUniforms(drawCommand._uniformMap, context._us, context.validateShaderProgram); - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: + va._bind(); + var indexBuffer = va.indexBuffer; - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. + 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); + } + } - 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. + va._unBind(); + } - 3. The names of the authors may not be used to endorse or promote products - derived from this software without specific prior written permission. + Context.prototype.draw = function(drawCommand, passState) { + + 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); - 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. -**/ + beginDraw(this, framebuffer, drawCommand, passState); + continueDraw(this, drawCommand); + }; -define('ThirdParty/zip',[ - '../Core/buildModuleUrl', - '../Core/defineProperties' - ], function( - buildModuleUrl, - defineProperties) { - var tmp = {}; + Context.prototype.endFrame = function() { + var gl = this._gl; + gl.useProgram(null); -(function(obj) { + this._currentFramebuffer = undefined; + gl.bindFramebuffer(gl.FRAMEBUFFER, null); - 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 buffers = scratchBackBufferArray; + if (this.drawBuffers) { + this.glDrawBuffers(buffers); + } - var INFLATE_JS = "inflate.js"; - var DEFLATE_JS = "deflate.js"; + var length = this._maxFrameTextureUnitIndex; + this._maxFrameTextureUnitIndex = 0; - var TEXT_PLAIN = "text/plain"; + 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); + } + }; - var MESSAGE_EVENT = "message"; + Context.prototype.readPixels = function(readState) { + var gl = this._gl; - var appendABViewSupported; - try { - appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0; - } catch (e) { - } + 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; - 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; - })(); + + var pixels = new Uint8Array(4 * width * height); - 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); - } + bindFramebuffer(this, framebuffer); - 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) - }; - } + gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); - // Readers - function Reader() { - } + return pixels; + }; - function TextReader(text) { - var that = this, blobReader; + var viewportQuadAttributeLocations = { + position : 0, + textureCoordinates : 1 + }; - 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); - } + Context.prototype.getViewportQuadVertexArray = function() { + // Per-context cache for viewport quads + var vertexArray = this.cache.viewportQuad_vertexArray; - function readUint8Array(index, length, callback, onerror) { - blobReader.readUint8Array(index, length, callback, onerror); - } + 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 + ] + }), - that.size = 0; - that.init = init; - that.readUint8Array = readUint8Array; - } - TextReader.prototype = new Reader(); - TextReader.prototype.constructor = TextReader; + 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 + }); - function Data64URIReader(dataURI) { - var that = this, dataStart; + vertexArray = VertexArray.fromGeometry({ + context : this, + geometry : geometry, + attributeLocations : viewportQuadAttributeLocations, + bufferUsage : BufferUsage.STATIC_DRAW, + interleave : true + }); - 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(); - } + this.cache.viewportQuad_vertexArray = vertexArray; + } - 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); - } + return vertexArray; + }; - that.size = 0; - that.init = init; - that.readUint8Array = readUint8Array; - } - Data64URIReader.prototype = new Reader(); - Data64URIReader.prototype.constructor = Data64URIReader; + Context.prototype.createViewportQuadCommand = function(fragmentShaderSource, overrides) { + overrides = defaultValue(overrides, defaultValue.EMPTY_OBJECT); - function BlobReader(blob) { - var that = this; + 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, + pass : overrides.pass + }); + }; - function init(callback) { - this.size = blob.size; - callback(); - } + Context.prototype.createPickFramebuffer = function() { + return new PickFramebuffer(this); + }; - 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)); - } + /** + * 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) { + + return this._pickObjects[pickColor.toRgba()]; + }; - that.size = 0; - that.init = init; - that.readUint8Array = readUint8Array; - } - BlobReader.prototype = new Reader(); - BlobReader.prototype.constructor = BlobReader; + function PickId(pickObjects, key, color) { + this._pickObjects = pickObjects; + this.key = key; + this.color = color; + } - // Writers + defineProperties(PickId.prototype, { + object : { + get : function() { + return this._pickObjects[this.key]; + }, + set : function(value) { + this._pickObjects[this.key] = value; + } + } + }); - function Writer() { - } - Writer.prototype.getData = function(callback) { - callback(this.data); - }; + PickId.prototype.destroy = function() { + delete this._pickObjects[this.key]; + return undefined; + }; - function TextWriter(encoding) { - var that = this, blob; + /** + * 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) { + + // 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.'); + } - function init(callback) { - blob = new Blob([], { - type : TEXT_PLAIN - }); - callback(); - } + this._pickObjects[key] = object; + return new PickId(this._pickObjects, key, Color.fromRgba(key)); + }; - function writeUint8Array(array, callback) { - blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], { - type : TEXT_PLAIN - }); - callback(); - } + Context.prototype.isDestroyed = function() { + return false; + }; - function getData(callback, onerror) { - var reader = new FileReader(); - reader.onload = function(e) { - callback(e.target.result); - }; - reader.onerror = onerror; - reader.readAsText(blob, encoding); - } + 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(); + } + } + } - that.init = init; - that.writeUint8Array = writeUint8Array; - that.getData = getData; - } - TextWriter.prototype = new Writer(); - TextWriter.prototype.constructor = TextWriter; + this._shaderCache = this._shaderCache.destroy(); + this._defaultTexture = this._defaultTexture && this._defaultTexture.destroy(); + this._defaultCubeMap = this._defaultCubeMap && this._defaultCubeMap.destroy(); - function Data64URIWriter(contentType) { - var that = this, data = "", pending = ""; + return destroyObject(this); + }; - function init(callback) { - data += "data:" + (contentType || "") + ";base64,"; - callback(); - } + return Context; +}); - 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(); - } +define('Renderer/loadCubeMap',[ + '../Core/defined', + '../Core/DeveloperError', + '../Core/loadImage', + '../ThirdParty/when', + './CubeMap' + ], function( + defined, + DeveloperError, + loadImage, + when, + CubeMap) { + 'use strict'; - function getData(callback) { - callback(data + window.btoa(pending)); - } + /** + * 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 URL of each image. 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) { + + // 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. - that.init = init; - that.writeUint8Array = writeUint8Array; - that.getData = getData; - } - Data64URIWriter.prototype = new Writer(); - Data64URIWriter.prototype.constructor = Data64URIWriter; + 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) + ]; - function BlobWriter(contentType) { - var blob, that = this; + 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] + } + }); + }); + } - function init(callback) { - blob = new Blob([], { - type : contentType - }); - callback(); - } + return loadCubeMap; +}); - function writeUint8Array(array, callback) { - blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], { - type : contentType - }); - callback(); - } +define('Scene/DiscardMissingTileImagePolicy',[ + '../Core/defaultValue', + '../Core/defined', + '../Core/DeveloperError', + '../Core/getImagePixels', + '../Core/loadImageViaBlob', + '../ThirdParty/when' + ], function( + defaultValue, + defined, + DeveloperError, + getImagePixels, + loadImageViaBlob, + when) { + 'use strict'; - function getData(callback) { - callback(blob); - } + /** + * 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); - that.init = init; - that.writeUint8Array = writeUint8Array; - that.getData = getData; - } - BlobWriter.prototype = new Writer(); - BlobWriter.prototype.constructor = BlobWriter; + + this._pixelsToCheck = options.pixelsToCheck; + this._missingImagePixels = undefined; + this._missingImageByteLength = undefined; + this._isReady = false; - // inflate/deflate core functions + var that = this; - function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) { - var chunkIndex = 0, index, outputSize; + function success(image) { + if (defined(image.blob)) { + that._missingImageByteLength = image.blob.size; + } - function onflush() { - worker.removeEventListener(MESSAGE_EVENT, onmessage, false); - onend(outputSize); - } + var pixels = getImagePixels(image); - function onmessage(event) { - var message = event.data, data = message.data; + if (options.disableCheckIfAllPixelsAreTransparent) { + var allAreTransparent = true; + var width = image.width; - 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); - } + 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]; - 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 - }); - } + if (alpha > 0) { + allAreTransparent = false; + } + } - outputSize = 0; - worker.addEventListener(MESSAGE_EVENT, onmessage, false); - step(); - } + if (allAreTransparent) { + pixels = undefined; + } + } - function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) { - var chunkIndex = 0, index, outputSize = 0; + that._missingImagePixels = pixels; + that._isReady = true; + } - 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); - } - } + 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; + } - step(); - } + when(loadImageViaBlob(options.missingImageUrl), success, failure); + } - function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { - var worker, crc32 = new Crc32(); + /** + * 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; + }; - function oninflateappend(sending, array) { - if (computeCrc32 && !sending) - crc32.append(array); - } + /** + * 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) { + + var pixelsToCheck = this._pixelsToCheck; + var missingImagePixels = this._missingImagePixels; - function oninflateend(outputSize) { - onend(outputSize, crc32.get()); - } + // If missingImagePixels is undefined, it indicates that the discard check has been disabled. + if (!defined(missingImagePixels)) { + return false; + } - 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; - } + if (defined(image.blob) && image.blob.size !== this._missingImageByteLength) { + return false; + } - function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) { - var worker, crc32 = new Crc32(); + var pixels = getImagePixels(image); + var width = image.width; - function ondeflateappend(sending, array) { - if (sending) - crc32.append(array); - } + 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; + }; - function ondeflateend(outputSize) { - onend(outputSize, crc32.get()); - } + return DiscardMissingTileImagePolicy; +}); - function onmessage() { - worker.removeEventListener(MESSAGE_EVENT, onmessage, false); - launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror); - } +define('Scene/ImageryLayerFeatureInfo',[ + '../Core/defined' + ], function( + defined) { + 'use strict'; - 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; - } + /** + * 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; - function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { - var chunkIndex = 0, crc32 = new Crc32(); + /** + * 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; - 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()); - } + /** + * Gets or sets the position of the feature, or undefined if the position is not known. + * + * @type {Cartographic} + */ + this.position = undefined; - step(); - } + /** + * 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; - // ZipReader + /** + * Gets or sets the image layer of the feature. + * @type {Object} + */ + this.imageryLayer = undefined; + } - 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; - } + /** + * 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 += ''; + } else { + html += ''; + } + } + } + } + html += '
    ' + key + '' + describe(value) + '
    ' + key + '' + value + '
    '; - function decodeUTF8(string) { - return decodeURIComponent(escape(string)); - } + return html; + } - function getString(bytes) { - var i, str = ""; - for (i = 0; i < bytes.length; i++) - str += String.fromCharCode(bytes[i]); - return str; - } + this.description = describe(properties); + }; - 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) { - } - } + return ImageryLayerFeatureInfo; +}); - 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); - } +define('Scene/ImageryProvider',[ + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/loadCRN', + '../Core/loadImage', + '../Core/loadImageViaBlob', + '../Core/loadKTX' + ], function( + defined, + defineProperties, + DeveloperError, + loadCRN, + loadImage, + loadImageViaBlob, + loadKTX) { + 'use strict'; - function createZipReader(reader, onerror) { - function Entry() { - } + /** + * 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 BingMapsImageryProvider + * @see createOpenStreetMapImageryProvider + * @see createTileMapServiceImageryProvider + * @see GoogleEarthEnterpriseImageryProvider + * @see GoogleEarthEnterpriseMapsProvider + * @see GridImageryProvider + * @see MapboxImageryProvider + * @see SingleTileImageryProvider + * @see TileCoordinatesImageryProvider + * @see UrlTemplateImageryProvider + * @see WebMapServiceImageryProvider + * @see WebMapTileServiceImageryProvider + * + * @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; - Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) { - var that = this, worker; + /** + * 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; - function terminate(callback, param) { - if (worker) - worker.terminate(); - worker = null; - if (callback) - callback(param); - } + /** + * 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; - function testCrc32(crc32) { - var dataCrc32 = getDataHelper(4); - dataCrc32.view.setUint32(0, crc32); - return that.crc32 == dataCrc32.view.getUint32(0); - } + /** + * The default hue of this provider in radians. 0.0 uses the unmodified imagery color. + * + * @type {Number} + * @default undefined + */ + this.defaultHue = undefined; - function getWriterData(uncompressedSize, crc32) { - if (checkCrc32 && !testCrc32(crc32)) - onreaderror(); - else - writer.getData(function(data) { - terminate(onend, data); - }); - } + /** + * 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; - function onreaderror() { - terminate(onerror, ERR_READ_DATA); - } + /** + * The default gamma correction to apply to this provider. 1.0 uses the unmodified imagery color. + * + * @type {Number} + * @default undefined + */ + this.defaultGamma = undefined; - function onwriteerror() { - terminate(onerror, ERR_WRITE_DATA); - } + DeveloperError.throwInstantiationError(); + } - 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); - }; + 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 + }, - 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); - }); - } + /** + * Gets a promise that resolves to true when the provider is ready for use. + * @memberof ImageryProvider.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : DeveloperError.throwInstantiationError + }, - 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(); - } - }; - } + /** + * 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 + }, - // ZipWriter + /** + * 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 + }, - function encodeUTF8(string) { - return unescape(encodeURIComponent(string)); - } + /** + * 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 + }, - function getBytes(str) { - var i, array = []; - for (i = 0; i < str.length; i++) - array.push(str.charCodeAt(i)); - return array; - } + /** + * 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 + }, - function createZipWriter(writer, onerror, dontDeflate) { - var worker, files = {}, filenames = [], datalength = 0; + /** + * 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 + }, - function terminate(callback, message) { - if (worker) - worker.terminate(); - worker = null; - if (callback) - callback(message); - } + /** + * 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 + }, - function onwriteerror() { - terminate(onerror, ERR_WRITE); - } + /** + * 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 + }, - function onreaderror() { - terminate(onerror, ERR_READ_DATA); - } + /** + * 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 + }, - return { - add : function(name, reader, onend, onprogress, options) { - var header, filename, date; + /** + * 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 + }, - 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); - } + /** + * Gets the proxy used by this provider. + * @memberof ImageryProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : DeveloperError.throwInstantiationError + }, - 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); - } + /** + * 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 + } + }); - 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); - } + /** + * 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; - 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); - } - }; - } + /** + * 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. + * @param {Request} [request] The request object. Intended for internal use only. + * @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; - 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 - }; + /** + * 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; - var workerScriptsPath; + var ktxRegex = /\.ktx$/i; + var crnRegex = /\.crn$/i; - defineProperties(obj.zip, { - 'workerScriptsPath' : { - get : function() { - if (typeof workerScriptsPath === 'undefined') { - workerScriptsPath = buildModuleUrl('ThirdParty/Workers/'); - } - return workerScriptsPath; - } + /** + * 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 {ImageryProvider} imageryProvider The imagery provider for the URL. + * @param {String} url The URL of the image. + * @param {Request} [request] The request object. Intended for internal use only. + * @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, request) { + if (ktxRegex.test(url)) { + return loadKTX(url, undefined, request); + } else if (crnRegex.test(url)) { + return loadCRN(url, undefined, request); + } else if (defined(imageryProvider.tileDiscardPolicy)) { + return loadImageViaBlob(url, request); } - }); -})(tmp); + return loadImage(url, undefined, request); + }; - return tmp.zip; + return ImageryProvider; }); -/*global define*/ -define('DataSources/KmlDataSource',[ - '../Core/AssociativeArray', - '../Core/BoundingRectangle', +define('Scene/ArcGisMapServerImageryProvider',[ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', - '../Core/ClockRange', - '../Core/ClockStep', - '../Core/Color', - '../Core/createGuid', + '../Core/Credit', '../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/GeographicTilingScheme', + '../Core/loadJson', + '../Core/loadJsonp', '../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', + '../Core/TileProviderError', + '../Core/WebMercatorProjection', + '../Core/WebMercatorTilingScheme', '../ThirdParty/when', - '../ThirdParty/zip', - './BillboardGraphics', - './CompositePositionProperty', - './CorridorGraphics', - './DataSource', - './DataSourceClock', - './Entity', - './EntityCluster', - './EntityCollection', - './LabelGraphics', - './PathGraphics', - './PolygonGraphics', - './PolylineGraphics', - './PositionPropertyArray', - './RectangleGraphics', - './ReferenceProperty', - './SampledPositionProperty', - './ScaledPositionProperty', - './TimeIntervalCollectionProperty', - './WallGraphics' + './DiscardMissingTileImagePolicy', + './ImageryLayerFeatureInfo', + './ImageryProvider' ], function( - AssociativeArray, - BoundingRectangle, Cartesian2, Cartesian3, Cartographic, - ClockRange, - ClockStep, - Color, - createGuid, + Credit, defaultValue, defined, defineProperties, DeveloperError, - Ellipsoid, Event, - getAbsoluteUri, - getExtensionFromUri, - getFilenameFromUri, - Iso8601, - joinUrls, - JulianDate, - loadBlob, - loadXML, + GeographicTilingScheme, + loadJson, + loadJsonp, CesiumMath, - NearFarScalar, - PinBuilder, - PolygonHierarchy, Rectangle, RuntimeError, - TimeInterval, - TimeIntervalCollection, - HeightReference, - HorizontalOrigin, - LabelStyle, - SceneMode, - Autolinker, - Uri, + TileProviderError, + WebMercatorProjection, + WebMercatorTilingScheme, when, - zip, - BillboardGraphics, - CompositePositionProperty, - CorridorGraphics, - DataSource, - DataSourceClock, - Entity, - EntityCluster, - EntityCollection, - LabelGraphics, - PathGraphics, - PolygonGraphics, - PolylineGraphics, - PositionPropertyArray, - RectangleGraphics, - ReferenceProperty, - SampledPositionProperty, - ScaledPositionProperty, - TimeIntervalCollectionProperty, - WallGraphics) { + DiscardMissingTileImagePolicy, + ImageryLayerFeatureInfo, + ImageryProvider) { '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]; - } - }; + /** + * 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 GoogleEarthEnterpriseMapsProvider + * @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, {}); - 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; - } - } - }); + + this._url = options.url; + this._token = options.token; + this._tileDiscardPolicy = options.tileDiscardPolicy; + this._proxy = options.proxy; - var BILLBOARD_SIZE = 32; + 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; - var BILLBOARD_NEAR_DISTANCE = 2414016; - var BILLBOARD_NEAR_RATIO = 1.0; - var BILLBOARD_FAR_DISTANCE = 1.6093e+7; - var BILLBOARD_FAR_RATIO = 0.1; + /** + * 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); - 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; - } + this._errorEvent = new Event(); - 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; - } + this._ready = false; + this._readyPromise = when.defer(); - function loadXmlFromZip(reader, entry, uriResolver, deferred) { - entry.getData(new zip.TextWriter(), function(text) { - uriResolver.kml = parser.parseFromString(text, 'application/xml'); - deferred.resolve(); - }); - } + // Grab the details of this MapServer. + var that = this; + var metadataError; - 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 metadataSuccess(data) { + var tileInfo = data.tileInfo; + if (!defined(tileInfo)) { + that._useTiles = false; + } else { + that._tileWidth = tileInfo.rows; + that._tileHeight = tileInfo.cols; - function embedDataUris(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); + 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; } - } - } - } - - function applyBasePath(div, elementType, attributeName, proxy, sourceUri) { - var elements = div.querySelectorAll(elementType); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - var value = element.getAttribute(attributeName); - var uri = resolveHref(value, proxy, sourceUri); - element.setAttribute(attributeName, uri); - } - } - - 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); - } + that._maximumLevel = data.tileInfo.lods.length - 1; - var digits = value.match(/[^\s,\n]+/g); - if (!defined(digits)) { - return Cartesian3.fromDegrees(0, 0, 0); - } + 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 longitude = parseFloat(digits[0]); - var latitude = parseFloat(digits[1]); - var height = parseFloat(digits[2]); + 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; + } - longitude = isNaN(longitude) ? 0.0 : longitude; - latitude = isNaN(latitude) ? 0.0 : latitude; - height = isNaN(height) ? 0.0 : height; + // 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 + }); + } - return Cartesian3.fromDegrees(longitude, latitude, height); - } + that._useTiles = true; + } - function readCoordinates(element) { - if (!defined(element)) { - return undefined; - } + if (defined(data.copyrightText) && data.copyrightText.length > 0) { + that._credit = new Credit(data.copyrightText); + } - var tuples = element.textContent.match(/[^\s\n]+/g); - if (!defined(tuples)) { - return undefined; + that._ready = true; + that._readyPromise.resolve(true); + TileProviderError.handleSuccess(metadataError); } - var length = tuples.length; - var result = new Array(length); - var resultIndex = 0; - for (var i = 0; i < length; i++) { - result[resultIndex++] = readCoordinate(tuples[i]); + 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)); } - 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 requestMetadata() { + var parameters = { + f: 'json' + }; - function queryNumericAttribute(node, attributeName) { - if (!defined(node)) { - return undefined; - } + if (defined(that._token)) { + parameters.token = that._token; + } - var value = node.getAttribute(attributeName); - if (value !== null) { - var result = parseFloat(value); - return !isNaN(result) ? result : undefined; + var metadata = loadJsonp(that._url, { + parameters : parameters, + proxy : that._proxy + }); + when(metadata, metadataSuccess, metadataFailure); } - return undefined; - } - function queryStringAttribute(node, attributeName) { - if (!defined(node)) { - return undefined; + if (this._useTiles) { + requestMetadata(); + } else { + this._ready = true; + this._readyPromise.resolve(true); } - 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; + 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'; } - } - return undefined; - } + url += '&size=' + imageryProvider._tileWidth + '%2C' + imageryProvider._tileHeight; + url += '&format=png&transparent=true&f=image'; - 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); + if (imageryProvider.layers) { + url += '&layers=show:' + imageryProvider.layers; } } - 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); + var token = imageryProvider._token; + if (defined(token)) { + if (url.indexOf('?') === -1) { + url += '?'; } + if (url[url.length - 1] !== '?'){ + url += '&'; + } + url += 'token=' + token; } - 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; + var proxy = imageryProvider._proxy; + if (defined(proxy)) { + url = proxy.getURL(url); } - return undefined; - } - function queryStringValue(node, tagName, namespace) { - var result = queryFirstNode(node, tagName, namespace); - if (defined(result)) { - return result.textContent.trim(); - } - return undefined; + return url; } - 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; - } + defineProperties(ArcGisMapServerImageryProvider.prototype, { + /** + * Gets the URL of the ArcGIS MapServer. + * @memberof ArcGisMapServerImageryProvider.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._url; + } + }, - 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; - } + /** + * Gets the ArcGIS token used to authenticate with the ArcGis MapServer service. + * @memberof ArcGisMapServerImageryProvider.prototype + * @type {String} + * @readonly + */ + token : { + get : function() { + return this._token; } - } - if (!hrefResolved && defined(sourceUri)) { - href = getAbsoluteUri(href, getAbsoluteUri(sourceUri)); - href = proxyUrl(href, proxy); - } - return href; - } + }, - var colorOptions = {}; + /** + * Gets the proxy used by this provider. + * @memberof ArcGisMapServerImageryProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._proxy; + } + }, - function parseColorString(value, isRandom) { - if (!defined(value) || /^\s*$/gm.test(value)) { - return undefined; - } + /** + * 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() { + + return this._tileWidth; + } + }, - if (value[0] === '#') { - value = value.substring(1); - } + /** + * 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() { + + return this._tileHeight; + } + }, - 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; + /** + * 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() { + + return this._maximumLevel; + } + }, - if (!isRandom) { - return new Color(red, green, blue, alpha); - } + /** + * 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() { + + return 0; + } + }, - 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); - } + /** + * 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() { + + return this._tilingScheme; + } + }, - function queryColorValue(node, tagName, namespace) { - var value = queryStringValue(node, tagName, namespace); - if (!defined(value)) { - return undefined; - } - return parseColorString(value, queryStringValue(node, 'colorMode', namespace) === 'random'); - } + /** + * 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() { + + return this._rectangle; + } + }, - function processTimeStamp(featureNode) { - var node = queryFirstNode(featureNode, 'TimeStamp', namespaces.kmlgx); - var whenString = queryStringValue(node, 'when', namespaces.kmlgx); + /** + * 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() { + + return this._tileDiscardPolicy; + } + }, - if (!defined(node) || !defined(whenString) || whenString.length === 0) { - 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 ArcGisMapServerImageryProvider.prototype + * @type {Event} + * @readonly + */ + errorEvent : { + get : function() { + return this._errorEvent; + } + }, - //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; - } + /** + * 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; + } + }, - function processTimeSpan(featureNode) { - var node = queryFirstNode(featureNode, 'TimeSpan', namespaces.kmlgx); - if (!defined(node)) { - return undefined; - } - var result; + /** + * 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; + } + }, - var beginNode = queryFirstNode(node, 'begin', namespaces.kmlgx); - var beginDate = defined(beginNode) ? JulianDate.fromIso8601(beginNode.textContent) : undefined; + /** + * 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; + } + }, - var endNode = queryFirstNode(node, 'end', namespaces.kmlgx); - var endDate = defined(endNode) ? JulianDate.fromIso8601(endNode.textContent) : undefined; + /** + * 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; + } + }, - if (defined(beginDate) && defined(endDate)) { - if (JulianDate.lessThan(endDate, beginDate)) { - var tmp = beginDate; - beginDate = endDate; - endDate = tmp; + /** + * 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; } - 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; - } + /** + * Gets the comma-separated list of layer IDs to show. + * @memberof ArcGisMapServerImageryProvider.prototype + * + * @type {String} + */ + layers : { + get : function() { + return this._layers; + } + } + }); - 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; - } + /** + * 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; + }; - 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; - } + /** + * 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. + * @param {Request} [request] The request object. Intended for internal use only. + * @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, request) { + + var url = buildImageUrl(this, x, y, level); + return ImageryProvider.loadImage(this, url, request); + }; - function getIconHref(iconNode, dataSource, sourceUri, uriResolver, canRefresh) { - var href = queryStringValue(iconNode, 'href', namespaces.kml); - if (!defined(href) || (href.length === 0)) { + /** + /** + * 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.enablePickFeatures) { 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; + var rectangle = this._tilingScheme.tileXYToNativeRectangle(x, y, level); - href = 'https://maps.google.com/mapfiles/kml/pal' + palette + '/icon' + iconNum + '.png'; + 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'; } - 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 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; - 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); + url += '&layers=visible'; + if (defined(this._layers)) { + url += ':' + this._layers; + } - var icon = joinUrls(href, queryString, false); - return processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, icon, viewBoundScale, dataSource._lastCameraView.bbox); + if (defined(this._token)) { + url += '&token=' + this._token; } - return href; - } + if (defined(this._proxy)) { + url = this._proxy.getURL(url); + } - 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); + return loadJson(url).then(function(json) { + var result = []; - 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 features = json.results; + if (!defined(features)) { + return result; + } - 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'); + for (var i = 0; i < features.length; ++i) { + var feature = features[i]; - var billboard = targetEntity.billboard; - if (!defined(billboard)) { - billboard = createDefaultBillboard(); - targetEntity.billboard = billboard; - } + var featureInfo = new ImageryLayerFeatureInfo(); + featureInfo.data = feature; + featureInfo.name = feature.value; + featureInfo.properties = feature.attributes; + featureInfo.configureDescriptionFromProperties(feature.attributes); - billboard.image = icon; - billboard.scale = scale; - billboard.color = color; + // 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)); + } + } - if (defined(x) || defined(y) || defined(w) || defined(h)) { - billboard.imageSubRegion = new BoundingRectangle(x, y, w, h); - } + result.push(featureInfo); + } - //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; - } + return result; + }); + }; - //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); + return ArcGisMapServerImageryProvider; +}); - 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; - } +define('Scene/Cesium3DTileColorBlendMode',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; - 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; - } + /** + * Defines how per-feature colors set from the Cesium API or declarative styling blend with the source colors from + * the original feature, e.g. glTF material or per-point color in the tile. + *

    + * When REPLACE or MIX are used and the source color is a glTF material, the technique must assign the + * _3DTILESDIFFUSE semantic to the diffuse color parameter. Otherwise only HIGHLIGHT is supported. + *

    + *
    
    +     * "techniques": {
    +     *   "technique0": {
    +     *     "parameters": {
    +     *       "diffuse": {
    +     *         "semantic": "_3DTILESDIFFUSE",
    +     *         "type": 35666
    +     *       }
    +     *     }
    +     *   }
    +     * }
    +     * 
    + * + * @exports Cesium3DTileColorBlendMode + */ + var Cesium3DTileColorBlendMode = { + /** + * Multiplies the source color by the feature color. + * + * @type {Number} + * @constant + */ + HIGHLIGHT : 0, - yOffset -= BILLBOARD_SIZE * 0.5 * scale; - } + /** + * Replaces the source color with the feature color. + * + * @type {Number} + * @constant + */ + REPLACE : 1, - if (defined(xOffset) || defined(yOffset)) { - billboard.pixelOffset = new Cartesian2(xOffset, yOffset); - } - } + /** + * Blends the source color and feature color together. + * + * @type {Number} + * @constant + */ + MIX : 2 + }; - 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); + return freezeObject(Cesium3DTileColorBlendMode); +}); - //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); - } - } - } - } +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'; - //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; + var ComponentsPerAttribute = { + SCALAR : 1, + VEC2 : 2, + VEC3 : 3, + VEC4 : 4, + MAT2 : 4, + MAT3 : 9, + MAT4 : 16 + }; - //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; - } - } + var ClassPerType = { + SCALAR : undefined, + VEC2 : Cartesian2, + VEC3 : Cartesian3, + VEC4 : Cartesian4, + MAT2 : Matrix2, + MAT3 : Matrix3, + MAT4 : Matrix4 + }; - 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); - } - } - } + /** + * @private + */ + function getBinaryAccessor(accessor) { + var componentType = accessor.componentType; + var componentDatatype; + if (typeof componentType === 'string') { + componentDatatype = ComponentDatatype.fromName(componentType); + } else { + componentDatatype = componentType; } - //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); + 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 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); - } - } - } - } + return getBinaryAccessor; +}); - 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); +define('Scene/Cesium3DTileBatchTable',[ + '../Core/arrayFill', + '../Core/Cartesian2', + '../Core/Cartesian4', + '../Core/Check', + '../Core/clone', + '../Core/Color', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/Math', + '../Core/PixelFormat', + '../Core/RuntimeError', + '../Renderer/ContextLimits', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/PixelDatatype', + '../Renderer/RenderState', + '../Renderer/Sampler', + '../Renderer/ShaderSource', + '../Renderer/Texture', + '../Renderer/TextureMagnificationFilter', + '../Renderer/TextureMinificationFilter', + './AttributeType', + './BlendingState', + './Cesium3DTileColorBlendMode', + './CullFace', + './getBinaryAccessor', + './StencilFunction', + './StencilOperation' + ], function( + arrayFill, + Cartesian2, + Cartesian4, + Check, + clone, + Color, + combine, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + CesiumMath, + PixelFormat, + RuntimeError, + ContextLimits, + DrawCommand, + Pass, + PixelDatatype, + RenderState, + Sampler, + ShaderSource, + Texture, + TextureMagnificationFilter, + TextureMinificationFilter, + AttributeType, + BlendingState, + Cesium3DTileColorBlendMode, + CullFace, + getBinaryAccessor, + StencilFunction, + StencilOperation) { + 'use strict'; - var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml); - if (defined(styleUrl)) { - if (styleUrl[0] !== '#') { - styleUrl = '#' + styleUrl; - } + var DEFAULT_COLOR_VALUE = Color.WHITE; + var DEFAULT_SHOW_VALUE = true; - if (isExternal && defined(sourceUri)) { - styleUrl = sourceUri + styleUrl; - } - var base = styleCollection.getById(styleUrl); + /** + * @private + */ + function Cesium3DTileBatchTable(content, featuresLength, batchTableJson, batchTableBinary) { + /** + * @readonly + */ + this.featuresLength = featuresLength; - 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); - } - } - } - } - } + this._translucentFeaturesLength = 0; // Number of features in the tile that are translucent - 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)); - } - } + /** + * @private + */ + this.batchTableJson = batchTableJson; + + /** + * @private + */ + this.batchTableBinary = batchTableBinary; + + var batchTableHierarchy; + var batchTableBinaryProperties; + if (defined(batchTableJson)) { + // Extract the hierarchy and remove it from the batch table json + batchTableHierarchy = batchTableJson.HIERARCHY; + if (defined(batchTableHierarchy)) { + delete batchTableJson.HIERARCHY; + batchTableHierarchy = initializeHierarchy(batchTableHierarchy, batchTableBinary); } + // Get the binary properties + batchTableBinaryProperties = Cesium3DTileBatchTable.getBinaryProperties(featuresLength, batchTableJson, batchTableBinary); } - return promises; - } + this._batchTableHierarchy = batchTableHierarchy; + this._batchTableBinaryProperties = batchTableBinaryProperties; - 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]); - } + // PERFORMANCE_IDEA: These parallel arrays probably generate cache misses in get/set color/show + // and use A LOT of memory. How can we use less memory? + this._showAlphaProperties = undefined; // [Show (0 or 255), Alpha (0 to 255)] property for each feature + this._batchValues = undefined; // Per-feature RGBA (A is based on the color's alpha and feature's show property) - function heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode) { - if (!defined(altitudeMode) && !defined(gxAltitudeMode) || altitudeMode === 'clampToGround') { - return HeightReference.CLAMP_TO_GROUND; - } + this._batchValuesDirty = false; + this._batchTexture = undefined; + this._defaultTexture = undefined; - if (altitudeMode === 'relativeToGround') { - return HeightReference.RELATIVE_TO_GROUND; - } + this._pickTexture = undefined; + this._pickIds = []; - if (altitudeMode === 'absolute') { - return HeightReference.NONE; - } + this._content = content; - if (gxAltitudeMode === 'clampToSeaFloor') { - console.log('KML - :clampToSeaFloor is currently not supported, using :clampToGround.'); - return HeightReference.CLAMP_TO_GROUND; - } + // Dimensions for batch and pick textures + var textureDimensions; + var textureStep; - if (gxAltitudeMode === 'relativeToSeaFloor') { - console.log('KML - :relativeToSeaFloor is currently not supported, using :relativeToGround.'); - return HeightReference.RELATIVE_TO_GROUND; - } + if (featuresLength > 0) { + // PERFORMANCE_IDEA: this can waste memory in the last row in the uncommon case + // when more than one row is needed (e.g., > 16K features in one tile) + var width = Math.min(featuresLength, ContextLimits.maximumTextureSize); + var height = Math.ceil(featuresLength / ContextLimits.maximumTextureSize); + var stepX = 1.0 / width; + var centerX = stepX * 0.5; + var stepY = 1.0 / height; + var centerY = stepY * 0.5; - if (defined(altitudeMode)) { - console.log('KML - Unknown :' + altitudeMode + ', using :CLAMP_TO_GROUND.'); - } else { - console.log('KML - Unknown :' + gxAltitudeMode + ', using :CLAMP_TO_GROUND.'); + textureDimensions = new Cartesian2(width, height); + textureStep = new Cartesian4(stepX, centerX, stepY, centerY); } - // Clamp to ground is the default - return HeightReference.CLAMP_TO_GROUND; + this._textureDimensions = textureDimensions; + this._textureStep = textureStep; } - function createPositionPropertyFromAltitudeMode(property, altitudeMode, gxAltitudeMode) { - if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') { - //Just return the ellipsoid referenced property until we support MSL - return property; + defineProperties(Cesium3DTileBatchTable.prototype, { + memorySizeInBytes : { + get : function() { + var memory = 0; + if (defined(this._pickTexture)) { + memory += this._pickTexture.sizeInBytes; + } + if (defined(this._batchTexture)) { + memory += this._batchTexture.sizeInBytes; + } + return memory; + } } + }); - if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || // - (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) { - console.log('KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode)); - } + function initializeHierarchy(json, binary) { + var i; + var classId; + var binaryAccessor; + + var instancesLength = json.instancesLength; + var classes = json.classes; + var classIds = json.classIds; + var parentCounts = json.parentCounts; + var parentIds = json.parentIds; + var parentIdsLength = instancesLength; + + if (defined(classIds.byteOffset)) { + classIds.componentType = defaultValue(classIds.componentType, ComponentDatatype.UNSIGNED_SHORT); + classIds.type = AttributeType.SCALAR; + binaryAccessor = getBinaryAccessor(classIds); + classIds = binaryAccessor.createArrayBufferView(binary.buffer, binary.byteOffset + classIds.byteOffset, instancesLength); + } + + var parentIndexes; + if (defined(parentCounts)) { + if (defined(parentCounts.byteOffset)) { + parentCounts.componentType = defaultValue(parentCounts.componentType, ComponentDatatype.UNSIGNED_SHORT); + parentCounts.type = AttributeType.SCALAR; + binaryAccessor = getBinaryAccessor(parentCounts); + parentCounts = binaryAccessor.createArrayBufferView(binary.buffer, binary.byteOffset + parentCounts.byteOffset, instancesLength); + } + parentIndexes = new Uint16Array(instancesLength); + parentIdsLength = 0; + for (i = 0; i < instancesLength; ++i) { + parentIndexes[i] = parentIdsLength; + parentIdsLength += parentCounts[i]; + } + } + + if (defined(parentIds) && defined(parentIds.byteOffset)) { + parentIds.componentType = defaultValue(parentIds.componentType, ComponentDatatype.UNSIGNED_SHORT); + parentIds.type = AttributeType.SCALAR; + binaryAccessor = getBinaryAccessor(parentIds); + parentIds = binaryAccessor.createArrayBufferView(binary.buffer, binary.byteOffset + parentIds.byteOffset, parentIdsLength); + } + + var classesLength = classes.length; + for (i = 0; i < classesLength; ++i) { + var classInstancesLength = classes[i].length; + var properties = classes[i].instances; + var binaryProperties = Cesium3DTileBatchTable.getBinaryProperties(classInstancesLength, properties, binary); + classes[i].instances = combine(binaryProperties, properties); + } + + var classCounts = arrayFill(new Array(classesLength), 0); + var classIndexes = new Uint16Array(instancesLength); + for (i = 0; i < instancesLength; ++i) { + classId = classIds[i]; + classIndexes[i] = classCounts[classId]; + ++classCounts[classId]; + } + + var hierarchy = { + classes : classes, + classIds : classIds, + classIndexes : classIndexes, + parentCounts : parentCounts, + parentIndexes : parentIndexes, + parentIds : parentIds + }; - // Clamp to ground is the default - return new ScaledPositionProperty(property); + + return hierarchy; } - function createPositionPropertyArrayFromAltitudeMode(properties, altitudeMode, gxAltitudeMode) { - if (!defined(properties)) { - return undefined; + + Cesium3DTileBatchTable.getBinaryProperties = function(featuresLength, json, binary) { + var binaryProperties; + for (var name in json) { + if (json.hasOwnProperty(name)) { + var property = json[name]; + var byteOffset = property.byteOffset; + if (defined(byteOffset)) { + // This is a binary property + var componentType = property.componentType; + var type = property.type; + if (!defined(componentType)) { + throw new RuntimeError('componentType is required.'); + } + if (!defined(type)) { + throw new RuntimeError('type is required.'); + } + if (!defined(binary)) { + throw new RuntimeError('Property ' + name + ' requires a batch table binary.'); + } + + var binaryAccessor = getBinaryAccessor(property); + var componentCount = binaryAccessor.componentsPerAttribute; + var classType = binaryAccessor.classType; + var typedArray = binaryAccessor.createArrayBufferView(binary.buffer, binary.byteOffset + byteOffset, featuresLength); + + if (!defined(binaryProperties)) { + binaryProperties = {}; + } + + // Store any information needed to access the binary data, including the typed array, + // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4). + binaryProperties[name] = { + typedArray : typedArray, + componentCount : componentCount, + type : classType + }; + } + } } + return binaryProperties; + }; - if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') { - //Just return the ellipsoid referenced property until we support MSL - return properties; - } + function getByteLength(batchTable) { + var dimensions = batchTable._textureDimensions; + return (dimensions.x * dimensions.y) * 4; + } - if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || // - (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) { - console.log('KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode)); + function getBatchValues(batchTable) { + if (!defined(batchTable._batchValues)) { + // Default batch texture to RGBA = 255: white highlight (RGB) and show/alpha = true/255 (A). + var byteLength = getByteLength(batchTable); + var bytes = new Uint8Array(byteLength); + arrayFill(bytes, 255); + batchTable._batchValues = bytes; } - // 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; + return batchTable._batchValues; } - function processPositionGraphics(dataSource, entity, styleEntity, heightReference) { - var label = entity.label; - if (!defined(label)) { - label = defined(styleEntity.label) ? styleEntity.label.clone() : createDefaultLabel(); - entity.label = label; + function getShowAlphaProperties(batchTable) { + if (!defined(batchTable._showAlphaProperties)) { + var byteLength = 2 * batchTable.featuresLength; + var bytes = new Uint8Array(byteLength); + // [Show = true, Alpha = 255] + arrayFill(bytes, 255); + batchTable._showAlphaProperties = bytes; } - label.text = entity.name; + return batchTable._showAlphaProperties; + } - var billboard = entity.billboard; - if (!defined(billboard)) { - billboard = defined(styleEntity.billboard) ? styleEntity.billboard.clone() : createDefaultBillboard(); - entity.billboard = billboard; + function checkBatchId(batchId, featuresLength) { + if (!defined(batchId) || (batchId < 0) || (batchId > featuresLength)) { + throw new DeveloperError('batchId is required and between zero and featuresLength - 1 (' + featuresLength - + ').'); } + } - if (!defined(billboard.image)) { - billboard.image = dataSource._pinBuilder.fromColor(Color.YELLOW, 64); + Cesium3DTileBatchTable.prototype.setShow = function(batchId, show) { + + if (show && !defined(this._showAlphaProperties)) { + // Avoid allocating since the default is show = true + return; } - 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; - } - } + var showAlphaProperties = getShowAlphaProperties(this); + var propertyOffset = batchId * 2; - if (defined(heightReference) && dataSource._clampToGround) { - billboard.heightReference = heightReference; - label.heightReference = heightReference; - } - } + var newShow = show ? 255 : 0; + if (showAlphaProperties[propertyOffset] !== newShow) { + showAlphaProperties[propertyOffset] = newShow; - function processPathGraphics(dataSource, entity, styleEntity) { - var path = entity.path; - if (!defined(path)) { - path = new PathGraphics(); - path.leadTime = 0; - entity.path = path; + var batchValues = getBatchValues(this); + + // Compute alpha used in the shader based on show and color.alpha properties + var offset = (batchId * 4) + 3; + batchValues[offset] = show ? showAlphaProperties[propertyOffset + 1] : 0; + + this._batchValuesDirty = true; } + }; - var polyline = styleEntity.polyline; - if (defined(polyline)) { - path.material = polyline.material; - path.width = polyline.width; + Cesium3DTileBatchTable.prototype.setAllShow = function(show) { + + var featuresLength = this.featuresLength; + for (var i = 0; i < featuresLength; ++i) { + this.setShow(i, show); } - } + }; - 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); + Cesium3DTileBatchTable.prototype.getShow = function(batchId) { + + if (!defined(this._showAlphaProperties)) { + // Avoid allocating since the default is show = true + return true; + } - var position = readCoordinate(coordinatesString); + var offset = batchId * 2; + return (this._showAlphaProperties[offset] === 255); + }; - entity.position = position; - processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode)); + var scratchColorBytes = new Array(4); - if (extrude && isExtrudable(altitudeMode, gxAltitudeMode)) { - createDropLine(entityCollection, entity, styleEntity); + Cesium3DTileBatchTable.prototype.setColor = function(batchId, color) { + + if (Color.equals(color, DEFAULT_COLOR_VALUE) && !defined(this._batchValues)) { + // Avoid allocating since the default is white + return; } - 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); + var newColor = color.toBytes(scratchColorBytes); + var newAlpha = newColor[3]; - if (defined(queryNumericValue(geometryNode, 'drawOrder', namespaces.gx))) { - console.log('KML - gx:drawOrder is not supported in LineStrings'); - } + var batchValues = getBatchValues(this); + var offset = batchId * 4; - 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; + var showAlphaProperties = getShowAlphaProperties(this); + var propertyOffset = batchId * 2; - if (defined(polygon)) { - wall.fill = polygon.fill; - wall.material = polygon.material; - } + if ((batchValues[offset] !== newColor[0]) || + (batchValues[offset + 1] !== newColor[1]) || + (batchValues[offset + 2] !== newColor[2]) || + (showAlphaProperties[propertyOffset + 1] !== newAlpha)) { - //Always outline walls so they show up in 2D. - wall.outline = true; - if (defined(polyline)) { - wall.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE; - wall.outlineWidth = polyline.width; - } else if (defined(polygon)) { - wall.outlineColor = defined(polygon.material) ? polygon.material.color : Color.WHITE; - } - } 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; - } - } - } + batchValues[offset] = newColor[0]; + batchValues[offset + 1] = newColor[1]; + batchValues[offset + 2] = newColor[2]; - return true; - } + var wasTranslucent = (showAlphaProperties[propertyOffset + 1] !== 255); - 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); + // Compute alpha used in the shader based on show and color.alpha properties + var show = showAlphaProperties[propertyOffset] !== 0; + batchValues[offset + 3] = show ? newAlpha : 0; + showAlphaProperties[propertyOffset + 1] = newAlpha; - var polygon = defined(styleEntity.polygon) ? styleEntity.polygon.clone() : createDefaultPolygon(); + // Track number of translucent features so we know if this tile needs + // opaque commands, translucent commands, or both for rendering. + var isTranslucent = (newAlpha !== 255); + if (isTranslucent && !wasTranslucent) { + ++this._translucentFeaturesLength; + } else if (!isTranslucent && wasTranslucent) { + --this._translucentFeaturesLength; + } - var polyline = styleEntity.polyline; - if (defined(polyline)) { - polygon.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE; - polygon.outlineWidth = polyline.width; + this._batchValuesDirty = true; } - entity.polygon = polygon; + }; - if (canExtrude) { - polygon.perPositionHeight = true; - polygon.extrudedHeight = extrude ? 0 : undefined; - } else if (!dataSource._clampToGround) { - polygon.height = 0; + Cesium3DTileBatchTable.prototype.setAllColor = function(color) { + + var featuresLength = this.featuresLength; + for (var i = 0; i < featuresLength; ++i) { + this.setColor(i, color); } + }; - 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; + Cesium3DTileBatchTable.prototype.getColor = function(batchId, result) { + + if (!defined(this._batchValues)) { + return Color.clone(DEFAULT_COLOR_VALUE, result); } - 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); + var batchValues = this._batchValues; + var offset = batchId * 4; - if (angleNodes.length > 0) { - console.log('KML - gx:angles are not supported in gx:Tracks'); - } + var showAlphaProperties = this._showAlphaProperties; + var propertyOffset = batchId * 2; - 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); + return Color.fromBytes(batchValues[offset], + batchValues[offset + 1], + batchValues[offset + 2], + showAlphaProperties[propertyOffset + 1], + result); + }; - entity.availability = new TimeIntervalCollection(); + var scratchColor = new Color(); - if (timeNodes.length > 0) { - entity.availability.addInterval(new TimeInterval({ - start : times[0], - stop : times[times.length - 1] - })); + Cesium3DTileBatchTable.prototype.applyStyle = function(frameState, style) { + if (!defined(style)) { + this.setAllColor(DEFAULT_COLOR_VALUE); + this.setAllShow(true); + return; } - if (canExtrude && extrude) { - createDropLine(entityCollection, entity, styleEntity); + var content = this._content; + var length = this.featuresLength; + for (var i = 0; i < length; ++i) { + var feature = content.getFeature(i); + var color = defined(style.color) ? style.color.evaluateColor(frameState, feature, scratchColor) : DEFAULT_COLOR_VALUE; + var show = defined(style.show) ? style.show.evaluate(frameState, feature) : DEFAULT_SHOW_VALUE; + this.setColor(i, color); + this.setShow(i, show); } + }; - return true; + function getBinaryProperty(binaryProperty, index) { + var typedArray = binaryProperty.typedArray; + var componentCount = binaryProperty.componentCount; + if (componentCount === 1) { + return typedArray[index]; + } + return binaryProperty.type.unpack(typedArray, index * componentCount); } - 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 setBinaryProperty(binaryProperty, index, value) { + var typedArray = binaryProperty.typedArray; + var componentCount = binaryProperty.componentCount; + if (componentCount === 1) { + typedArray[index] = value; + } else { + binaryProperty.type.pack(value, typedArray, index * componentCount); + } } - 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); + // The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large. + var scratchVisited = []; + var scratchStack = []; + var marker = 0; + function traverseHierarchyMultipleParents(hierarchy, instanceIndex, endConditionCallback) { + var classIds = hierarchy.classIds; + var parentCounts = hierarchy.parentCounts; + var parentIds = hierarchy.parentIds; + var parentIndexes = hierarchy.parentIndexes; + var instancesLength = classIds.length; - 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); + // Ignore instances that have already been visited. This occurs in diamond inheritance situations. + // Use a marker value to indicate that an instance has been visited, which increments with each run. + // This is more efficient than clearing the visited array every time. + var visited = scratchVisited; + visited.length = Math.max(visited.length, instancesLength); + var visitedMarker = ++marker; - var length = Math.min(coordNodes.length, timeNodes.length); + var stack = scratchStack; + stack.length = 0; + stack.push(instanceIndex); - 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)); + while (stack.length > 0) { + instanceIndex = stack.pop(); + if (visited[instanceIndex] === visitedMarker) { + // This instance has already been visited, stop traversal + continue; } - - 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); + visited[instanceIndex] = visitedMarker; + var result = endConditionCallback(hierarchy, instanceIndex); + if (defined(result)) { + // The end condition was met, stop the traversal and return the result + return result; + } + var parentCount = parentCounts[instanceIndex]; + var parentIndex = parentIndexes[instanceIndex]; + for (var i = 0; i < parentCount; ++i) { + var parentId = parentIds[parentIndex + i]; + // Stop the traversal when the instance has no parent (its parentId equals itself) + // else add the parent to the stack to continue the traversal. + if (parentId !== instanceIndex) { + stack.push(parentId); } - 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; - } + function traverseHierarchySingleParent(hierarchy, instanceIndex, endConditionCallback) { + var hasParent = true; + while (hasParent) { + var result = endConditionCallback(hierarchy, instanceIndex); + if (defined(result)) { + // The end condition was met, stop the traversal and return the result + return result; } + var parentId = hierarchy.parentIds[instanceIndex]; + hasParent = parentId !== instanceIndex; + instanceIndex = parentId; } - - 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; + function traverseHierarchy(hierarchy, instanceIndex, endConditionCallback) { + // Traverse over the hierarchy and process each instance with the endConditionCallback. + // When the endConditionCallback returns a value, the traversal stops and that value is returned. + var parentCounts = hierarchy.parentCounts; + var parentIds = hierarchy.parentIds; + if (!defined(parentIds)) { + return endConditionCallback(hierarchy, instanceIndex); + } else if (defined(parentCounts)) { + return traverseHierarchyMultipleParents(hierarchy, instanceIndex, endConditionCallback); } + return traverseHierarchySingleParent(hierarchy, instanceIndex, endConditionCallback); + } - 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'); - } + function hasPropertyInHierarchy(batchTable, batchId, name) { + var hierarchy = batchTable._batchTableHierarchy; + var result = traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { + var classId = hierarchy.classIds[instanceIndex]; + var instances = hierarchy.classes[classId].instances; + if (defined(instances[name])) { + return true; + } + }); + return defined(result); + } - 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) - }; + function getPropertyNamesInHierarchy(batchTable, batchId, results) { + var hierarchy = batchTable._batchTableHierarchy; + traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { + var classId = hierarchy.classIds[instanceIndex]; + var instances = hierarchy.classes[classId].instances; + for (var name in instances) { + if (instances.hasOwnProperty(name)) { + if (results.indexOf(name) === -1) { + results.push(name); + } } } - } - entity.kml.extendedData = result; + }); } - var scratchDiv = document.createElement('div'); - - function processDescription(node, entity, styleEntity, uriResolver, proxy, sourceUri) { - 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, '')); - } - } + function getHierarchyProperty(batchTable, batchId, name) { + var hierarchy = batchTable._batchTableHierarchy; + return traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { + var classId = hierarchy.classIds[instanceIndex]; + var instanceClass = hierarchy.classes[classId]; + var indexInClass = hierarchy.classIndexes[instanceIndex]; + var propertyValues = instanceClass.instances[name]; + if (defined(propertyValues)) { + if (defined(propertyValues.typedArray)) { + return getBinaryProperty(propertyValues, indexInClass); } + return clone(propertyValues[indexInClass], true); } - } 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 += ''; + }); + } + + function setHierarchyProperty(batchTable, batchId, name, value) { + var hierarchy = batchTable._batchTableHierarchy; + var result = traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { + var classId = hierarchy.classIds[instanceIndex]; + var instanceClass = hierarchy.classes[classId]; + var indexInClass = hierarchy.classIndexes[instanceIndex]; + var propertyValues = instanceClass.instances[name]; + if (defined(propertyValues)) { + if (defined(propertyValues.typedArray)) { + setBinaryProperty(propertyValues, indexInClass, value); + } else { + propertyValues[indexInClass] = clone(value, true); } - text += '
    ' + defaultValue(value.displayName, key) + '' + defaultValue(value.value, '') + '
    '; + return true; } - } + }); + return defined(result); + } - if (!defined(text)) { - //No description - return; + Cesium3DTileBatchTable.prototype.isClass = function(batchId, className) { + + // PERFORMANCE_IDEA : cache results in the ancestor classes to speed up this check if this area becomes a hotspot + var hierarchy = this._batchTableHierarchy; + if (!defined(hierarchy)) { + return false; } - //Turns non-explicit links into clickable links. - text = autolinker.link(text); + // PERFORMANCE_IDEA : treat class names as integers for faster comparisons + var result = traverseHierarchy(hierarchy, batchId, function(hierarchy, instanceIndex) { + var classId = hierarchy.classIds[instanceIndex]; + var instanceClass = hierarchy.classes[classId]; + if (instanceClass.name === className) { + return true; + } + }); + return defined(result); + }; - //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'); - } + Cesium3DTileBatchTable.prototype.isExactClass = function(batchId, className) { + + return (this.getExactClassName(batchId) === className); + }; - //Rewrite any KMZ embedded urls - if (defined(uriResolver) && uriResolver.keys.length > 1) { - embedDataUris(scratchDiv, 'a', 'href', uriResolver); - embedDataUris(scratchDiv, 'img', 'src', uriResolver); + Cesium3DTileBatchTable.prototype.getExactClassName = function(batchId) { + + var hierarchy = this._batchTableHierarchy; + if (!defined(hierarchy)) { + return undefined; } + var classId = hierarchy.classIds[batchId]; + var instanceClass = hierarchy.classes[classId]; + return instanceClass.name; + }; - //Make relative urls absolute using the sourceUri - applyBasePath(scratchDiv, 'a', 'href', proxy, sourceUri); - applyBasePath(scratchDiv, 'img', 'src', proxy, sourceUri); + Cesium3DTileBatchTable.prototype.hasProperty = function(batchId, name) { + + var json = this.batchTableJson; + return (defined(json) && defined(json[name])) || (defined(this._batchTableHierarchy) && hasPropertyInHierarchy(this, batchId, name)); + }; - var tmp = '
    '; - tmp += scratchDiv.innerHTML + '
    '; - scratchDiv.innerHTML = ''; + Cesium3DTileBatchTable.prototype.getPropertyNames = function(batchId, results) { + + results = defined(results) ? results : []; + results.length = 0; - //Set the final HTML as the description. - entity.description = tmp; - } + var json = this.batchTableJson; + for (var name in json) { + if (json.hasOwnProperty(name)) { + results.push(name); + } + } - 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); + if (defined(this._batchTableHierarchy)) { + getPropertyNamesInHierarchy(this, batchId, results); + } - var name = queryStringValue(featureNode, 'name', namespaces.kml); - entity.name = name; - entity.parent = parent; + return results; + }; - var availability = processTimeSpan(featureNode); - if (!defined(availability)) { - availability = processTimeStamp(featureNode); + Cesium3DTileBatchTable.prototype.getProperty = function(batchId, name) { + + if (!defined(this.batchTableJson)) { + return undefined; } - 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; + if (defined(this._batchTableBinaryProperties)) { + var binaryProperty = this._batchTableBinaryProperties[name]; + if (defined(binaryProperty)) { + return getBinaryProperty(binaryProperty, batchId); } - 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, dataSource._proxy, sourceUri); - - 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'); + var propertyValues = this.batchTableJson[name]; + if (defined(propertyValues)) { + return clone(propertyValues[batchId], true); } - return { - entity : entity, - styleEntity : styleEntity - }; - } + if (defined(this._batchTableHierarchy)) { + var hierarchyProperty = getHierarchyProperty(this, batchId, name); + if (defined(hierarchyProperty)) { + return hierarchyProperty; + } + } - var geometryTypes = { - Point : processPoint, - LineString : processLineStringOrLinearRing, - LinearRing : processLineStringOrLinearRing, - Polygon : processPolygon, - Track : processTrack, - MultiTrack : processMultiTrack, - MultiGeometry : processMultiGeometry, - Model : processUnsupportedGeometry + return undefined; }; - 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); - } + Cesium3DTileBatchTable.prototype.setProperty = function(batchId, name, value) { + var featuresLength = this.featuresLength; + + if (defined(this._batchTableBinaryProperties)) { + var binaryProperty = this._batchTableBinaryProperties[name]; + if (defined(binaryProperty)) { + setBinaryProperty(binaryProperty, batchId, value); + return; } } - } - - 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 (defined(this._batchTableHierarchy)) { + if (setHierarchyProperty(this, batchId, name, value)) { + return; } } - if (!hasGeometry) { - entity.merge(styleEntity); - processPositionGraphics(dataSource, entity, styleEntity); + if (!defined(this.batchTableJson)) { + // Tile payload did not have a batch table. Create one for new user-defined properties. + this.batchTableJson = {}; } - } - - 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 propertyValues = this.batchTableJson[name]; - var rotation = queryNumericValue(latLonBox, 'rotation', namespaces.kml); - if (defined(rotation)) { - geometry.rotation = CesiumMath.toRadians(rotation); - } - } + if (!defined(propertyValues)) { + // Property does not exist. Create it. + this.batchTableJson[name] = new Array(featuresLength); + propertyValues = this.batchTableJson[name]; } - 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'); - } + propertyValues[batchId] = clone(value, true); + }; - geometry.material = href; - geometry.material.color = queryColorValue(groundOverlay, 'color', namespaces.kml); - geometry.material.transparent = true; - } else { - geometry.material = queryColorValue(groundOverlay, 'color', namespaces.kml); + function getGlslComputeSt(batchTable) { + // GLSL batchId is zero-based: [0, featuresLength - 1] + if (batchTable._textureDimensions.y === 1) { + return 'uniform vec4 tile_textureStep; \n' + + 'vec2 computeSt(float batchId) \n' + + '{ \n' + + ' float stepX = tile_textureStep.x; \n' + + ' float centerX = tile_textureStep.y; \n' + + ' return vec2(centerX + (batchId * stepX), 0.5); \n' + + '} \n'; } - var altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.kml); + return 'uniform vec4 tile_textureStep; \n' + + 'uniform vec2 tile_textureDimensions; \n' + + 'vec2 computeSt(float batchId) \n' + + '{ \n' + + ' float stepX = tile_textureStep.x; \n' + + ' float centerX = tile_textureStep.y; \n' + + ' float stepY = tile_textureStep.z; \n' + + ' float centerY = tile_textureStep.w; \n' + + ' float xId = mod(batchId, tile_textureDimensions.x); \n' + + ' float yId = floor(batchId / tile_textureDimensions.x); \n' + + ' return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' + + '} \n'; + } - 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); - } + Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function(handleTranslucent, batchIdAttributeName) { + if (this.featuresLength === 0) { + return; } - } - 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 that = this; + return function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'tile_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, perform per-feature show/hide in the vertex shader + newMain = + 'uniform sampler2D tile_batchTexture; \n' + + 'uniform bool tile_translucentCommand; \n' + + 'varying vec4 tile_featureColor; \n' + + 'void main() \n' + + '{ \n' + + ' tile_main(); \n' + + ' vec2 st = computeSt(' + batchIdAttributeName + '); \n' + + ' vec4 featureProperties = texture2D(tile_batchTexture, st); \n' + + ' float show = ceil(featureProperties.a); \n' + // 0 - false, non-zeo - true + ' gl_Position *= show; \n'; // Per-feature show/hide + if (handleTranslucent) { + newMain += + ' bool isStyleTranslucent = (featureProperties.a != 1.0); \n' + + ' if (czm_pass == czm_passTranslucent) \n' + + ' { \n' + + ' if (!isStyleTranslucent && !tile_translucentCommand) \n' + // Do not render opaque features in the translucent pass + ' { \n' + + ' gl_Position *= 0.0; \n' + + ' } \n' + + ' } \n' + + ' else \n' + + ' { \n' + + ' if (isStyleTranslucent) \n' + // Do not render translucent features in the opaque pass + ' { \n' + + ' gl_Position *= 0.0; \n' + + ' } \n' + + ' } \n'; + } + newMain += + ' tile_featureColor = featureProperties; \n' + + '}'; + } else { + newMain = + 'varying vec2 tile_featureSt; \n' + + 'void main() \n' + + '{ \n' + + ' tile_main(); \n' + + ' tile_featureSt = computeSt(' + batchIdAttributeName + '); \n' + + '}'; + } - var RefreshMode = { - INTERVAL : 0, - EXPIRE : 1, - STOP : 2 + return renamedSource + '\n' + getGlslComputeSt(that) + newMain; + }; }; + function getHighlightOnlyShader(source) { + source = ShaderSource.replaceMain(source, 'tile_main'); + return source + + 'void tile_color(vec4 tile_featureColor) \n' + + '{ \n' + + ' tile_main(); \n' + + ' gl_FragColor *= tile_featureColor; \n' + + '} \n'; + } - function cleanupString(s) { - if (!defined(s) || s.length === 0) { - return ''; + function modifyDiffuse(source, diffuseUniformName) { + // If the glTF does not specify the _3DTILESDIFFUSE semantic, return a basic highlight shader. + // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime. + if (!defined(diffuseUniformName)) { + return getHighlightOnlyShader(source); } - var sFirst = s[0]; - if (sFirst === '&') { - s.splice(0, 1); - } + // Find the diffuse uniform. Examples matches: + // uniform vec3 u_diffuseColor; + // uniform sampler2D diffuseTexture; + var regex = new RegExp('uniform\\s+(vec[34]|sampler2D)\\s+' + diffuseUniformName + ';'); + var uniformMatch = source.match(regex); - if(sFirst !== '?') { - s = '?' + s; + if (!defined(uniformMatch)) { + // Could not find uniform declaration of type vec3, vec4, or sampler2D + return getHighlightOnlyShader(source); } - return s; - } + var declaration = uniformMatch[0]; + var type = uniformMatch[1]; - function makeQueryString(string1, string2) { - var result = ''; - if ((defined(string1) && string1.length > 0) || (defined(string2) && string2.length > 0)) { - result += joinUrls(cleanupString(string1), cleanupString(string2), false); - } + source = ShaderSource.replaceMain(source, 'tile_main'); + source = source.replace(declaration, ''); // Remove uniform declaration for now so the replace below doesn't affect it - return result; - } + // If the tile color is white, use the source color. This implies the feature has not been styled. + // Highlight: tile_colorBlend is 0.0 and the source color is used + // Replace: tile_colorBlend is 1.0 and the tile color is used + // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix + var finalDiffuseFunction = + 'vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n' + + '{ \n' + + ' vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n' + + ' vec4 diffuse = (tileDiffuse.rgb == vec3(1.0)) ? sourceDiffuse : blendDiffuse; \n' + + ' return vec4(diffuse.rgb, sourceDiffuse.a); \n' + + '} \n'; - 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; - } + // The color blend mode is intended for the RGB channels so alpha is always just multiplied. + // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight) + var applyHighlight = + ' gl_FragColor.a *= tile_featureColor.a; \n' + + ' float highlight = ceil(tile_colorBlend); \n' + + ' gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n'; + + var setColor; + if (type === 'vec3' || type === 'vec4') { + var sourceDiffuse = (type === 'vec3') ? ('vec4(' + diffuseUniformName + ', 1.0)') : diffuseUniformName; + var replaceDiffuse = (type === 'vec3') ? 'tile_diffuse.xyz' : 'tile_diffuse'; + regex = new RegExp(diffuseUniformName, 'g'); + source = source.replace(regex, replaceDiffuse); + setColor = + ' vec4 source = ' + sourceDiffuse + '; \n' + + ' tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n' + + ' tile_main(); \n'; + } else if (type === 'sampler2D') { + regex = new RegExp('texture2D\\(' + diffuseUniformName + '.*?\\)', 'g'); + source = source.replace(regex, 'tile_diffuse_final($&, tile_diffuse)'); + setColor = + ' tile_diffuse = tile_featureColor; \n' + + ' tile_main(); \n'; + } + + source = + 'uniform float tile_colorBlend; \n' + + 'vec4 tile_diffuse = vec4(1.0); \n' + + finalDiffuseFunction + + declaration + '\n' + + source + '\n' + + 'void tile_color(vec4 tile_featureColor) \n' + + '{ \n' + + setColor + + applyHighlight + + '} \n'; - function fixLongitude(value) { - if (value > CesiumMath.PI) { - return value - CesiumMath.TWO_PI; - } else if (value < -CesiumMath.PI) { - return value + CesiumMath.TWO_PI; - } + return source; + } - return value; + Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function(handleTranslucent, diffuseUniformName) { + if (this.featuresLength === 0) { + return; } - - 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); + return function(source) { + source = modifyDiffuse(source, diffuseUniformName); + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, per-feature show/hide already happened in the fragment shader + source += + 'varying vec4 tile_featureColor; \n' + + 'void main() \n' + + '{ \n' + + ' tile_color(tile_featureColor); \n' + + '}'; } else { - centerCartographic = Rectangle.center(bbox, scratchCartographic); - centerCartesian = wgs84.cartographicToCartesian(centerCartographic); + source += + 'uniform sampler2D tile_batchTexture; \n' + + 'uniform bool tile_translucentCommand; \n' + + 'varying vec2 tile_featureSt; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n' + + ' if (featureProperties.a == 0.0) { \n' + // show: alpha == 0 - false, non-zeo - true + ' discard; \n' + + ' } \n'; + + if (handleTranslucent) { + source += + ' bool isStyleTranslucent = (featureProperties.a != 1.0); \n' + + ' if (czm_pass == czm_passTranslucent) \n' + + ' { \n' + + ' if (!isStyleTranslucent && !tile_translucentCommand) \n' + // Do not render opaque features in the translucent pass + ' { \n' + + ' discard; \n' + + ' } \n' + + ' } \n' + + ' else \n' + + ' { \n' + + ' if (isStyleTranslucent) \n' + // Do not render translucent features in the opaque pass + ' { \n' + + ' discard; \n' + + ' } \n' + + ' } \n'; + } + + source += + ' tile_color(featureProperties); \n' + + '} \n'; } + return source; + }; + }; - - 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) - ); + function getColorBlend(batchTable) { + var tileset = batchTable._content._tileset; + var colorBlendMode = tileset.colorBlendMode; + var colorBlendAmount = tileset.colorBlendAmount; + if (colorBlendMode === Cesium3DTileColorBlendMode.HIGHLIGHT) { + return 0.0; + } + if (colorBlendMode === Cesium3DTileColorBlendMode.REPLACE) { + return 1.0; + } + if (colorBlendMode === Cesium3DTileColorBlendMode.MIX) { + // The value 0.0 is reserved for highlight, so clamp to just above 0.0. + return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0); + } } - 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()); + Cesium3DTileBatchTable.prototype.getUniformMapCallback = function() { + if (this.featuresLength === 0) { + return; + } - 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; + var that = this; + return function(uniformMap) { + var batchUniformMap = { + tile_batchTexture : function() { + // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. + return defaultValue(that._batchTexture, that._defaultTexture); + }, + tile_textureDimensions : function() { + return that._textureDimensions; + }, + tile_textureStep : function() { + return that._textureStep; + }, + tile_colorBlend : function() { + return getColorBlend(that); } - } - 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]', ''); - } + return combine(uniformMap, batchUniformMap); + }; + }; - if (defined(canvas)) { - queryString = queryString.replace('[horizPixels]', canvas.clientWidth); - queryString = queryString.replace('[vertPixels]', canvas.clientHeight); - } else { - queryString = queryString.replace('[horizPixels]', ''); - queryString = queryString.replace('[vertPixels]', ''); + Cesium3DTileBatchTable.prototype.getPickVertexShaderCallback = function(batchIdAttributeName) { + if (this.featuresLength === 0) { + return; } - 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 that = this; + return function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'tile_main'); + var newMain; + + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, perform per-feature show/hide in the vertex shader + newMain = + 'uniform sampler2D tile_batchTexture; \n' + + 'varying vec2 tile_featureSt; \n' + + 'void main() \n' + + '{ \n' + + ' tile_main(); \n' + + ' vec2 st = computeSt(' + batchIdAttributeName + '); \n' + + ' vec4 featureProperties = texture2D(tile_batchTexture, st); \n' + + ' float show = ceil(featureProperties.a); \n' + // 0 - false, non-zero - true + ' gl_Position *= show; \n' + // Per-feature show/hide + ' tile_featureSt = st; \n' + + '}'; + } else { + newMain = + 'varying vec2 tile_featureSt; \n' + + 'void main() \n' + + '{ \n' + + ' tile_main(); \n' + + ' tile_featureSt = computeSt(' + batchIdAttributeName + '); \n' + + '}'; + } - var link = queryFirstNode(node, 'Link', namespaces.kml); + return renamedSource + '\n' + getGlslComputeSt(that) + newMain; + }; + }; - if(!defined(link)){ - link = queryFirstNode(node, 'Url', namespaces.kml); + Cesium3DTileBatchTable.prototype.getPickFragmentShaderCallback = function() { + if (this.featuresLength === 0) { + return; } - 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; + return function(source) { + var renamedSource = ShaderSource.replaceMain(source, 'tile_main'); + var newMain; + + // Pick shaders do not need to take into account per-feature color/alpha. + // (except when alpha is zero, which is treated as if show is false, so + // it does not write depth in the color or pick pass). + if (ContextLimits.maximumVertexTextureImageUnits > 0) { + // When VTF is supported, per-feature show/hide already happened in the fragment shader + newMain = + 'uniform sampler2D tile_pickTexture; \n' + + 'varying vec2 tile_featureSt; \n' + + 'void main() \n' + + '{ \n' + + ' tile_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + // per-feature show: alpha == 0 - false, non-zeo - true + ' discard; \n' + + ' } \n' + + ' gl_FragColor = texture2D(tile_pickTexture, tile_featureSt); \n' + + '}'; + } else { + newMain = + 'uniform sampler2D tile_pickTexture; \n' + + 'uniform sampler2D tile_batchTexture; \n' + + 'varying vec2 tile_featureSt; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n' + + ' if (featureProperties.a == 0.0) { \n' + // per-feature show: alpha == 0 - false, non-zeo - true + ' discard; \n' + + ' } \n' + + ' tile_main(); \n' + + ' if (gl_FragColor.a == 0.0) { \n' + + ' discard; \n' + + ' } \n' + + ' gl_FragColor = texture2D(tile_pickTexture, tile_featureSt); \n' + + '}'; + } + + return renamedSource + '\n' + newMain; + }; + }; - // 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); + Cesium3DTileBatchTable.prototype.getPickUniformMapCallback = function() { + if (this.featuresLength === 0) { + return; + } - linkUrl = processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, joinUrls(href, queryString, false), - viewBoundScale, dataSource._lastCameraView.bbox); + var that = this; + return function(uniformMap) { + var batchUniformMap = { + tile_batchTexture : function() { + return defaultValue(that._batchTexture, that._defaultTexture); + }, + tile_textureDimensions : function() { + return that._textureDimensions; + }, + tile_textureStep : function() { + return that._textureStep; + }, + tile_pickTexture : function() { + return that._pickTexture; } + }; - 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); + return combine(batchUniformMap, uniformMap); + }; + }; - 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); - } + var StyleCommandsNeeded = { + ALL_OPAQUE : 0, + ALL_TRANSLUCENT : 1, + OPAQUE_AND_TRANSLUCENT : 2 + }; - 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'); - } - } + Cesium3DTileBatchTable.prototype.addDerivedCommands = function(frameState, commandStart) { + var commandList = frameState.commandList; + var commandEnd = commandList.length; + var tile = this._content._tile; + var tileset = tile._tileset; + var bivariateVisibilityTest = tileset.skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer; + var styleCommandsNeeded = getStyleCommandsNeeded(this); - if (defined(networkLinkInfo.refreshMode)) { - dataSource._networkLinks.set(networkLinkInfo.id, networkLinkInfo); - } - } else if (viewRefreshMode === 'onRegion'){ - console.log('KML - Unsupported viewRefreshMode: onRegion'); + for (var i = commandStart; i < commandEnd; ++i) { + var command = commandList[i]; + var derivedCommands = command.derivedCommands.tileset; + if (!defined(derivedCommands)) { + derivedCommands = {}; + command.derivedCommands.tileset = derivedCommands; + derivedCommands.originalCommand = deriveCommand(command); + } + + updateDerivedCommand(derivedCommands.originalCommand, command); + + if (styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE) { + if (!defined(derivedCommands.translucent)) { + derivedCommands.translucent = deriveTranslucentCommand(derivedCommands.originalCommand); + } + updateDerivedCommand(derivedCommands.translucent, command); + } + + if (bivariateVisibilityTest) { + if (command.pass !== Pass.TRANSLUCENT) { + if (!defined(derivedCommands.zback)) { + derivedCommands.zback = deriveZBackfaceCommand(derivedCommands.originalCommand); } - }); + tileset._backfaceCommands.push(derivedCommands.zback); + } + if (!defined(derivedCommands.stencil) || tile._selectionDepth !== tile._lastSelectionDepth) { + derivedCommands.stencil = deriveStencilCommand(derivedCommands.originalCommand, tile._selectionDepth); + tile._lastSelectionDepth = tile._selectionDepth; + } + updateDerivedCommand(derivedCommands.stencil, command); + } - promises.push(promise); + var opaqueCommand = bivariateVisibilityTest ? derivedCommands.stencil : derivedCommands.originalCommand; + var translucentCommand = derivedCommands.translucent; + + // If the command was originally opaque: + // * If the styling applied to the tile is all opaque, use the original command + // (with one additional uniform needed for the shader). + // * If the styling is all translucent, use new (cached) derived commands (front + // and back faces) with a translucent render state. + // * If the styling causes both opaque and translucent features in this tile, + // then use both sets of commands. + if (command.pass !== Pass.TRANSLUCENT) { + if (styleCommandsNeeded === StyleCommandsNeeded.ALL_OPAQUE) { + commandList[i] = opaqueCommand; + } + if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) { + commandList[i] = translucentCommand; + } + if (styleCommandsNeeded === StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT) { + // PERFORMANCE_IDEA: if the tile has multiple commands, we do not know what features are in what + // commands so this case may be overkill. + commandList[i] = opaqueCommand; + commandList.push(translucentCommand); + } + } else { + // Command was originally translucent so no need to derive new commands; + // as of now, a style can't change an originally translucent feature to + // opaque since the style's alpha is modulated, not a replacement. When + // this changes, we need to derive new opaque commands here. + commandList[i] = opaqueCommand; } } + }; + + function updateDerivedCommand(derivedCommand, command) { + derivedCommand.castShadows = command.castShadows; + derivedCommand.receiveShadows = command.receiveShadows; + derivedCommand.primitiveType = command.primitiveType; } - // 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 getStyleCommandsNeeded(batchTable) { + var translucentFeaturesLength = batchTable._translucentFeaturesLength; - 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); + if (translucentFeaturesLength === 0) { + return StyleCommandsNeeded.ALL_OPAQUE; + } else if (translucentFeaturesLength === batchTable.featuresLength) { + return StyleCommandsNeeded.ALL_TRANSLUCENT; } + + return StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT; } - function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context) { - entityCollection.removeAll(); + function deriveCommand(command) { + var derivedCommand = DrawCommand.shallowClone(command); - 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); - } + // Add a uniform to indicate if the original command was translucent so + // the shader knows not to cull vertices that were originally transparent + // even though their style is opaque. + var translucentCommand = (derivedCommand.pass === Pass.TRANSLUCENT); - // Only set the name from the root document - if (!defined(dataSource._name)) { - dataSource._name = name; - } + derivedCommand.uniformMap = defined(derivedCommand.uniformMap) ? derivedCommand.uniformMap : {}; + derivedCommand.uniformMap.tile_translucentCommand = function() { + return translucentCommand; + }; - 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 derivedCommand; + } - return when.all(promises).then(function() { - return kml.documentElement; - }); - }); + function deriveTranslucentCommand(command) { + var derivedCommand = DrawCommand.shallowClone(command); + derivedCommand.pass = Pass.TRANSLUCENT; + derivedCommand.renderState = getTranslucentRenderState(command.renderState); + return derivedCommand; + } + + function deriveZBackfaceCommand(command) { + // Write just backface depth of unresolved tiles so resolved stenciled tiles do not appear in front + var derivedCommand = DrawCommand.shallowClone(command); + var rs = clone(derivedCommand.renderState, true); + rs.cull.enabled = true; + rs.cull.face = CullFace.FRONT; + derivedCommand.renderState = RenderState.fromCache(rs); + derivedCommand.castShadows = false; + derivedCommand.receiveShadows = false; + return derivedCommand; + } + + function deriveStencilCommand(command, reference) { + var derivedCommand = command; + if (command.renderState.depthMask) { // ignore if tile does not write depth (ex. translucent) + // Tiles only draw if their selection depth is >= the tile drawn already. They write their + // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top + derivedCommand = DrawCommand.shallowClone(command); + var rs = clone(derivedCommand.renderState, true); + rs.stencilTest.enabled = true; + rs.stencilTest.reference = reference; + rs.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL; + rs.stencilTest.frontOperation.zPass = StencilOperation.REPLACE; + derivedCommand.renderState = RenderState.fromCache(rs); + } + return derivedCommand; } - 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); - } - } - } + function getTranslucentRenderState(renderState) { + var rs = clone(renderState, true); + rs.cull.enabled = false; + rs.depthTest.enabled = true; + rs.depthMask = false; + rs.blending = BlendingState.ALPHA_BLEND; - // 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 RenderState.fromCache(rs); + } + + /////////////////////////////////////////////////////////////////////////// + + function createTexture(batchTable, context, bytes) { + var dimensions = batchTable._textureDimensions; + return new Texture({ + context : context, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.UNSIGNED_BYTE, + source : { + width : dimensions.x, + height : dimensions.y, + arrayBufferView : bytes + }, + sampler : new Sampler({ + minificationFilter : TextureMinificationFilter.NEAREST, + magnificationFilter : TextureMagnificationFilter.NEAREST + }) }); + } - return deferred.promise; + function createPickTexture(batchTable, context) { + var featuresLength = batchTable.featuresLength; + if (!defined(batchTable._pickTexture) && (featuresLength > 0)) { + var pickIds = batchTable._pickIds; + var byteLength = getByteLength(batchTable); + var bytes = new Uint8Array(byteLength); + var content = batchTable._content; + + // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating + // a continuous range of pickIds and then converting the base pickId + batchId + // to RGBA in the shader. The only consider is precision issues, which might + // not be an issue in WebGL 2. + for (var i = 0; i < featuresLength; ++i) { + var pickId = context.createPickId(content.getFeature(i)); + pickIds.push(pickId); + + var pickColor = pickId.color; + var offset = i * 4; + bytes[offset] = Color.floatToByte(pickColor.red); + bytes[offset + 1] = Color.floatToByte(pickColor.green); + bytes[offset + 2] = Color.floatToByte(pickColor.blue); + bytes[offset + 3] = Color.floatToByte(pickColor.alpha); + } + + batchTable._pickTexture = createTexture(batchTable, context, bytes); + content._tileset._statistics.batchTableByteLength += batchTable._pickTexture.sizeInBytes; + } } - function load(dataSource, entityCollection, data, options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var sourceUri = options.sourceUri; - var uriResolver = options.uriResolver; - var context = options.context; + function updateBatchTexture(batchTable) { + var dimensions = batchTable._textureDimensions; + // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained + // texture updates when less than, for example, 10%, of the values changed. Or + // even just optimize the common case when one feature show/color changed. + batchTable._batchTexture.copyFrom({ + width : dimensions.x, + height : dimensions.y, + arrayBufferView : batchTable._batchValues + }); + } - var promise = data; - if (typeof data === 'string') { - promise = loadBlob(proxyUrl(data, dataSource._proxy)); - sourceUri = defaultValue(sourceUri, data); + Cesium3DTileBatchTable.prototype.update = function(tileset, frameState) { + var context = frameState.context; + this._defaultTexture = context.defaultTexture; + + if (frameState.passes.pick) { + // Create pick texture on-demand + createPickTexture(this, context); } - 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. + if (this._batchValuesDirty) { + this._batchValuesDirty = false; - //IE raises an exception - var kml; - var error; - try { - kml = parser.parseFromString(text, 'application/xml'); - } catch (e) { - error = e.toString(); - } + // Create batch texture on-demand + if (!defined(this._batchTexture)) { + this._batchTexture = createTexture(this, context, this._batchValues); + tileset._statistics.batchTableByteLength += this._batchTexture.sizeInBytes; + } - //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; + updateBatchTexture(this); // Apply per-feature show/color updates + } + }; - //Chrome has it in the body text. - if (!msg) { - msg = kml.body.innerText; - } + Cesium3DTileBatchTable.prototype.isDestroyed = function() { + return false; + }; - //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); - }); - } + Cesium3DTileBatchTable.prototype.destroy = function() { + this._batchTexture = this._batchTexture && this._batchTexture.destroy(); + this._pickTexture = this._pickTexture && this._pickTexture.destroy(); + + var pickIds = this._pickIds; + var length = pickIds.length; + for (var i = 0; i < length; ++i) { + pickIds[i].destroy(); + } + + return destroyObject(this); + }; + + return Cesium3DTileBatchTable; +}); + +define('Scene/Cesium3DTileFeature',[ + '../Core/Color', + '../Core/defined', + '../Core/defineProperties' + ], function( + Color, + defined, + defineProperties) { + 'use strict'; /** - * A {@link DataSource} which processes Keyhole Markup Language 2.2 (KML). + * A feature of a {@link Cesium3DTileset}. *

    - * 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. + * Provides access to a feature's properties stored in the tile's batch table, as well + * as the ability to show/hide a feature and change its highlight color via + * {@link Cesium3DTileFeature#show} and {@link Cesium3DTileFeature#color}, respectively. *

    *

    - * 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. + * Modifications to a Cesium3DTileFeature object have the lifetime of the tile's + * content. If the tile's content is unloaded, e.g., due to it going out of view and needing + * to free space in the cache for visible tiles, listen to the {@link Cesium3DTileset#tileUnload} event to save any + * modifications. Also listen to the {@link Cesium3DTileset#tileVisible} event to reapply any modifications. + *

    + *

    + * Do not construct this directly. Access it through {@link Cesium3DTileContent#getFeature} + * or picking using {@link Scene#pick} and {@link Scene#pickPosition}. *

    * - * @alias KmlDataSource + * @alias Cesium3DTileFeature * @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 - * }) - * ); + * // On mouse over, display all the properties for a feature in the console log. + * handler.setInputAction(function(movement) { + * var feature = scene.pick(movement.endPosition); + * if (feature instanceof Cesium.Cesium3DTileFeature) { + * var propertyNames = feature.getPropertyNames(); + * var length = propertyNames.length; + * for (var i = 0; i < length; ++i) { + * var propertyName = propertyNames[i]; + * console.log(propertyName + ': ' + feature.getProperty(propertyName)); + * } + * } + * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); */ - function KmlDataSource(options) { - options = defaultValue(options, {}); - var camera = options.camera; - var canvas = options.canvas; - - - 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) - }; + function Cesium3DTileFeature(tileset, content, batchId) { + this._content = content; + this._batchId = batchId; + this._color = undefined; // for calling getColor } - /** - * 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; - } - }, + defineProperties(Cesium3DTileFeature.prototype, { /** - * Gets a value indicating if the data source is currently loading data. - * @memberof KmlDataSource.prototype + * Gets or sets if the feature will be shown. This is set for all features + * when a style's show is evaluated. + * + * @memberof Cesium3DTileFeature.prototype + * * @type {Boolean} + * + * @default true */ - 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 : { + show : { get : function() { - return this._loading; + return this._content.batchTable.getShow(this._batchId); + }, + set : function(value) { + this._content.batchTable.setShow(this._batchId, value); } }, + /** - * Gets an event that will be raised when the data source refreshes a network link. - * @memberof KmlDataSource.prototype - * @type {Event} + * Gets or sets the highlight color multiplied with the feature's color. When + * this is white, the feature's color is not changed. This is set for all features + * when a style's color is evaluated. + * + * @memberof Cesium3DTileFeature.prototype + * + * @type {Color} + * + * @default {@link Color.WHITE} */ - refreshEvent : { + color : { get : function() { - return this._refresh; + if (!defined(this._color)) { + this._color = new Color(); + } + return this._content.batchTable.getColor(this._batchId, this._color); + }, + set : function(value) { + this._content.batchTable.setColor(this._batchId, value); } }, + /** - * Gets an event that will be raised when the data source finds an unsupported node type. - * @memberof KmlDataSource.prototype - * @type {Event} + * Gets the content of the tile containing the feature. + * + * @memberof Cesium3DTileFeature.prototype + * + * @type {Cesium3DTileContent} + * + * @readonly + * @private */ - unsupportedNodeEvent : { + content : { get : function() { - return this._unsupportedNode; + return this._content; } }, + /** - * Gets whether or not this data source should be displayed. - * @memberof KmlDataSource.prototype - * @type {Boolean} + * Gets the tileset containing the feature. + * + * @memberof Cesium3DTileFeature.prototype + * + * @type {Cesium3DTileset} + * + * @readonly */ - show : { + tileset : { get : function() { - return this._entityCollection.show; - }, - set : function(value) { - this._entityCollection.show = value; + return this._content.tileset; } }, /** - * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources. + * All objects returned by {@link Scene#pick} have a primitive property. This returns + * the tileset containing the feature. * - * @memberof KmlDataSource.prototype - * @type {EntityCluster} + * @memberof Cesium3DTileFeature.prototype + * + * @type {Cesium3DTileset} + * + * @readonly */ - clustering : { + primitive : { get : function() { - return this._entityCluster; - }, - set : function(value) { - this._entityCluster = value; + return this._content.tileset; } } }); /** - * Asynchronously loads the provided KML data, replacing any existing data. + * Returns whether the feature contains this property. This includes properties from this feature's + * class and inherited classes when using a batch table hierarchy. * - * @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. + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/TileFormats/BatchTable#batch-table-hierarchy} + * + * @param {String} name The case-sensitive name of the property. + * @returns {Boolean} Whether the feature contains this property. */ - KmlDataSource.prototype.load = function(data, options) { - - 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; + Cesium3DTileFeature.prototype.hasProperty = function(name) { + return this._content.batchTable.hasProperty(this._batchId, name); + }; - var availability = that._entityCollection.computeAvailability(); + /** + * Returns an array of property names for the feature. This includes properties from this feature's + * class and inherited classes when using a batch table hierarchy. + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/TileFormats/BatchTable#batch-table-hierarchy} + * + * @param {String[]} results An array into which to store the results. + * @returns {String[]} The names of the feature's properties. + */ + Cesium3DTileFeature.prototype.getPropertyNames = function(results) { + return this._content.batchTable.getPropertyNames(this._batchId, results); + }; - 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; + /** + * Returns a copy of the value of the feature's property with the given name. This includes properties from this feature's + * class and inherited classes when using a batch table hierarchy. + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/TileFormats/BatchTable#batch-table-hierarchy} + * + * @param {String} name The case-sensitive name of the property. + * @returns {*} The value of the property or undefined if the property does not exist. + * + * @example + * // Display all the properties for a feature in the console log. + * var propertyNames = feature.getPropertyNames(); + * var length = propertyNames.length; + * for (var i = 0; i < length; ++i) { + * var propertyName = propertyNames[i]; + * console.log(propertyName + ': ' + feature.getProperty(propertyName)); + * } + */ + Cesium3DTileFeature.prototype.getProperty = function(name) { + return this._content.batchTable.getProperty(this._batchId, name); + }; - //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); - } + /** + * Sets the value of the feature's property with the given name. + *

    + * If a property with the given name doesn't exist, it is created. + *

    + * + * @param {String} name The case-sensitive name of the property. + * @param {*} value The value of the property that will be copied. + * + * @exception {DeveloperError} Inherited batch table hierarchy property is read only. + * + * @example + * var height = feature.getProperty('Height'); // e.g., the height of a building + * + * @example + * var name = 'clicked'; + * if (feature.getProperty(name)) { + * console.log('already clicked'); + * } else { + * feature.setProperty(name, true); + * console.log('first click'); + * } + */ + Cesium3DTileFeature.prototype.setProperty = function(name, value) { + this._content.batchTable.setProperty(this._batchId, name, value); - //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); - } + // PERFORMANCE_IDEA: Probably overkill, but maybe only mark the tile dirty if the + // property is in one of the style's expressions or - if it can be done quickly - + // if the new property value changed the result of an expression. + this._content.featurePropertiesDirty = true; + }; - 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)); - } + /** + * Returns whether the feature's class name equals className. Unlike {@link Cesium3DTileFeature#isClass} + * this function only checks the feature's exact class and not inherited classes. + *

    + * This function returns false if no batch table hierarchy is present. + *

    + * + * @param {String} className The name to check against. + * @returns {Boolean} Whether the feature's class name equals className + * + * @private + */ + Cesium3DTileFeature.prototype.isExactClass = function(className) { + return this._content.batchTable.isExactClass(this._batchId, className); + }; - var changed = false; - if (clock !== that._clock) { - that._clock = clock; - changed = true; - } + /** + * Returns whether the feature's class or any inherited classes are named className. + *

    + * This function returns false if no batch table hierarchy is present. + *

    + * + * @param {String} className The name to check against. + * @returns {Boolean} Whether the feature's class or inherited classes are named className + * + * @private + */ + Cesium3DTileFeature.prototype.isClass = function(className) { + return this._content.batchTable.isClass(this._batchId, className); + }; - if (oldName !== that._name) { - changed = true; - } + /** + * Returns the feature's class name. + *

    + * This function returns undefined if no batch table hierarchy is present. + *

    + * + * @returns {String} The feature's class name. + * + * @private + */ + Cesium3DTileFeature.prototype.getExactClassName = function() { + return this._content.batchTable.getExactClassName(this._batchId); + }; - if (changed) { - that._changed.raiseEvent(that); - } + return Cesium3DTileFeature; +}); - DataSource.setLoading(that, false); +define('Scene/Cesium3DTileFeatureTable',[ + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined' + ], function( + ComponentDatatype, + defaultValue, + defined) { + 'use strict'; - return that; - }).otherwise(function(error) { - DataSource.setLoading(that, false); - that._error.raiseEvent(that, error); - console.log(error); - return when.reject(error); - }); - }; + /** + * @private + */ + function Cesium3DTileFeatureTable(featureTableJson, featureTableBinary) { + this.json = featureTableJson; + this.buffer = featureTableBinary; + this._cachedTypedArrays = {}; + this.featuresLength = 0; + } - 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 getTypedArrayFromBinary(featureTable, semantic, componentType, componentLength, count, byteOffset) { + var cachedTypedArrays = featureTable._cachedTypedArrays; + var typedArray = cachedTypedArrays[semantic]; + if (!defined(typedArray)) { + typedArray = ComponentDatatype.createArrayBufferView(componentType, featureTable.buffer.buffer, featureTable.buffer.byteOffset + byteOffset, count * componentLength); + cachedTypedArrays[semantic] = typedArray; } + return typedArray; } - 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; - } + return getTypedArrayFromArray(this, semantic, componentType, jsonValue); + }; - } else if (networkLink.refreshMode === RefreshMode.STOP) { - if (cameraViewUpdate) { - networkLink.needsUpdate = true; - networkLink.cameraUpdateTime = now; - } + Cesium3DTileFeatureTable.prototype.getProperty = function(semantic, componentType, componentLength, featureId, result) { + var jsonValue = this.json[semantic]; + if (!defined(jsonValue)) { + return undefined; + } - if (networkLink.needsUpdate && JulianDate.secondsDifference(now, networkLink.cameraUpdateTime) >= networkLink.time) { - doUpdate = true; - } - } + var typedArray = this.getPropertyArray(semantic, componentType, componentLength); - 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 (componentLength === 1) { + return typedArray[featureId]; + } - if (changed) { - this._networkLinks = newNetworkLinks; - this._changed.raiseEvent(this); + for (var i = 0; i < componentLength; ++i) { + result[i] = typedArray[componentLength * featureId + i]; } - return true; + return result; }; - /** - * 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; + return Cesium3DTileFeatureTable; }); -/*global define*/ -define('DataSources/VelocityOrientationProperty',[ - '../Core/Cartesian3', +define('Scene/Batched3DModel3DTileContent',[ + '../Core/Check', + '../Core/Color', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/Ellipsoid', - '../Core/Event', - '../Core/Matrix3', - '../Core/Quaternion', - '../Core/Transforms', - './Property', - './VelocityVectorProperty' + '../Core/deprecationWarning', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/FeatureDetection', + '../Core/getAbsoluteUri', + '../Core/getBaseUri', + '../Core/getStringFromTypedArray', + '../Core/RequestType', + '../Core/RuntimeError', + '../Renderer/Pass', + './Cesium3DTileBatchTable', + './Cesium3DTileFeature', + './Cesium3DTileFeatureTable', + './getAttributeOrUniformBySemantic', + './Model' ], function( - Cartesian3, + Check, + Color, defaultValue, defined, defineProperties, - Ellipsoid, - Event, - Matrix3, - Quaternion, - Transforms, - Property, - VelocityVectorProperty) { + deprecationWarning, + destroyObject, + DeveloperError, + FeatureDetection, + getAbsoluteUri, + getBaseUri, + getStringFromTypedArray, + RequestType, + RuntimeError, + Pass, + Cesium3DTileBatchTable, + Cesium3DTileFeature, + Cesium3DTileFeatureTable, + getAttributeOrUniformBySemantic, + Model) { '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 {}; + } + /** - * A {@link Property} which evaluates to a {@link Quaternion} rotation - * based on the velocity of the provided {@link PositionProperty}. + * Represents the contents of a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Batched3DModel/README.md|Batched 3D Model} + * tile in a {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset. + *

    + * Implements the {@link Cesium3DTileContent} interface. + *

    * - * @alias VelocityOrientationProperty + * @alias Batched3DModel3DTileContent * @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) - * })); + * @private */ - function VelocityOrientationProperty(position, ellipsoid) { - this._velocityVectorProperty = new VelocityVectorProperty(position, true); - this._subscription = undefined; - this._ellipsoid = undefined; - this._definitionChanged = new Event(); + function Batched3DModel3DTileContent(tileset, tile, url, arrayBuffer, byteOffset) { + this._tileset = tileset; + this._tile = tile; + this._url = url; + this._model = undefined; + this._batchTable = undefined; + this._features = undefined; + + /** + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty + */ + this.featurePropertiesDirty = false; + + initialize(this, arrayBuffer, byteOffset); + } + + // This can be overridden for testing purposes + Batched3DModel3DTileContent._deprecationWarning = deprecationWarning; + + defineProperties(Batched3DModel3DTileContent.prototype, { + /** + * @inheritdoc Cesium3DTileContent#featuresLength + */ + featuresLength : { + get : function() { + return this._batchTable.featuresLength; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#pointsLength + */ + pointsLength : { + get : function() { + return 0; + } + }, - this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + /** + * @inheritdoc Cesium3DTileContent#trianglesLength + */ + trianglesLength : { + get : function() { + return this._model.trianglesLength; + } + }, - var that = this; - this._velocityVectorProperty.definitionChanged.addEventListener(function() { - that._definitionChanged.raiseEvent(that); - }); - } + /** + * @inheritdoc Cesium3DTileContent#geometryByteLength + */ + geometryByteLength : { + get : function() { + return this._model.geometryByteLength; + } + }, - defineProperties(VelocityOrientationProperty.prototype, { /** - * Gets a value indicating if this property is constant. - * @memberof VelocityOrientationProperty.prototype - * - * @type {Boolean} - * @readonly + * @inheritdoc Cesium3DTileContent#texturesByteLength */ - isConstant : { + texturesByteLength : { get : function() { - return Property.isConstant(this._velocityVectorProperty); + return this._model.texturesByteLength; } }, + /** - * Gets the event that is raised whenever the definition of this property changes. - * @memberof VelocityOrientationProperty.prototype - * - * @type {Event} - * @readonly + * @inheritdoc Cesium3DTileContent#batchTableByteLength */ - definitionChanged : { + batchTableByteLength : { get : function() { - return this._definitionChanged; + return this._batchTable.memorySizeInBytes; } }, + /** - * Gets or sets the position property used to compute orientation. - * @memberof VelocityOrientationProperty.prototype - * - * @type {Property} + * @inheritdoc Cesium3DTileContent#innerContents */ - position : { + innerContents : { get : function() { - return this._velocityVectorProperty.position; - }, - set : function(value) { - this._velocityVectorProperty.position = value; + return undefined; } }, + /** - * Gets or sets the ellipsoid used to determine which way is up. - * @memberof VelocityOrientationProperty.prototype - * - * @type {Property} + * @inheritdoc Cesium3DTileContent#readyPromise */ - ellipsoid : { + readyPromise : { get : function() { - return this._ellipsoid; - }, - set : function(value) { - var oldValue = this._ellipsoid; - if (oldValue !== value) { - this._ellipsoid = value; - this._definitionChanged.raiseEvent(this); - } + return this._model.readyPromise; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#tileset + */ + tileset : { + get : function() { + return this._tileset; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#tile + */ + tile : { + get : function() { + return this._tile; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#url + */ + url: { + get: function() { + return this._url; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#batchTable + */ + batchTable : { + get : function() { + return this._batchTable; } } }); - var positionScratch = new Cartesian3(); - var velocityScratch = new Cartesian3(); - var rotationScratch = new Matrix3(); + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - /** - * 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); + function getBatchIdAttributeName(gltf) { + var batchIdAttributeName = getAttributeOrUniformBySemantic(gltf, '_BATCHID'); + if (!defined(batchIdAttributeName)) { + batchIdAttributeName = getAttributeOrUniformBySemantic(gltf, 'BATCHID'); + if (defined(batchIdAttributeName)) { + Batched3DModel3DTileContent._deprecationWarning('b3dm-legacy-batchid', 'The glTF in this b3dm uses the semantic `BATCHID`. Application-specific semantics should be prefixed with an underscore: `_BATCHID`.'); + } + } + return batchIdAttributeName; + } - if (!defined(velocity)) { - return undefined; + function getVertexShaderCallback(content) { + return function(vs) { + var batchTable = content._batchTable; + var gltf = content._model.gltf; + var batchIdAttributeName = getBatchIdAttributeName(gltf); + var callback = batchTable.getVertexShaderCallback(true, batchIdAttributeName); + return defined(callback) ? callback(vs) : vs; + }; + } + + function getPickVertexShaderCallback(content) { + return function(vs) { + var batchTable = content._batchTable; + var gltf = content._model.gltf; + var batchIdAttributeName = getBatchIdAttributeName(gltf); + var callback = batchTable.getPickVertexShaderCallback(batchIdAttributeName); + return defined(callback) ? callback(vs) : vs; + }; + } + + function getFragmentShaderCallback(content) { + return function(fs) { + var batchTable = content._batchTable; + var gltf = content._model.gltf; + var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE'); + var callback = batchTable.getFragmentShaderCallback(true, diffuseUniformName); + return defined(callback) ? callback(fs) : fs; + }; + } + + function initialize(content, arrayBuffer, byteOffset) { + var tileset = content._tileset; + var tile = content._tile; + var basePath = getAbsoluteUri(getBaseUri(content._url, true)); + + var byteStart = defaultValue(byteOffset, 0); + byteOffset = byteStart; + + var uint8Array = new Uint8Array(arrayBuffer); + var view = new DataView(arrayBuffer); + byteOffset += sizeOfUint32; // Skip magic + + var version = view.getUint32(byteOffset, true); + if (version !== 1) { + throw new RuntimeError('Only Batched 3D Model version 1 is supported. Version ' + version + ' is not.'); } + byteOffset += sizeOfUint32; - Transforms.rotationMatrixFromPositionVelocity(positionScratch, velocity, this._ellipsoid, rotationScratch); - return Quaternion.fromRotationMatrix(rotationScratch, result); - }; + var byteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var featureTableJsonByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var featureTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchTableJsonByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchLength; + + // Legacy header #1: [batchLength] [batchTableByteLength] + // Legacy header #2: [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength] + // Current header: [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] + // If the header is in the first legacy format 'batchTableJsonByteLength' will be the start of the JSON string (a quotation mark) or the glTF magic. + // Accordingly its first byte will be either 0x22 or 0x67, and so the minimum uint32 expected is 0x22000000 = 570425344 = 570MB. It is unlikely that the feature table JSON will exceed this length. + // The check for the second legacy format is similar, except it checks 'batchTableBinaryByteLength' instead + if (batchTableJsonByteLength >= 570425344) { + // First legacy check + byteOffset -= sizeOfUint32 * 2; + batchLength = featureTableJsonByteLength; + batchTableJsonByteLength = featureTableBinaryByteLength; + batchTableBinaryByteLength = 0; + featureTableJsonByteLength = 0; + featureTableBinaryByteLength = 0; + Batched3DModel3DTileContent._deprecationWarning('b3dm-legacy-header', 'This b3dm header is using the legacy format [batchLength] [batchTableByteLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Batched3DModel/README.md.'); + } else if (batchTableBinaryByteLength >= 570425344) { + // Second legacy check + byteOffset -= sizeOfUint32; + batchLength = batchTableJsonByteLength; + batchTableJsonByteLength = featureTableJsonByteLength; + batchTableBinaryByteLength = featureTableBinaryByteLength; + featureTableJsonByteLength = 0; + featureTableBinaryByteLength = 0; + Batched3DModel3DTileContent._deprecationWarning('b3dm-legacy-header', 'This b3dm header is using the legacy format [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Batched3DModel/README.md.'); + } + + var featureTableJson; + if (featureTableJsonByteLength === 0) { + featureTableJson = { + BATCH_LENGTH : defaultValue(batchLength, 0) + }; + } else { + var featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength); + featureTableJson = JSON.parse(featureTableString); + byteOffset += featureTableJsonByteLength; + } + + var featureTableBinary = new Uint8Array(arrayBuffer, byteOffset, featureTableBinaryByteLength); + byteOffset += featureTableBinaryByteLength; + + var featureTable = new Cesium3DTileFeatureTable(featureTableJson, featureTableBinary); + + batchLength = featureTable.getGlobalProperty('BATCH_LENGTH'); + featureTable.featuresLength = batchLength; + + var batchTableJson; + var batchTableBinary; + if (batchTableJsonByteLength > 0) { + // PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the + // arraybuffer/string compressed in memory and then decompress it when it is first accessed. + // + // We could also make another request for it, but that would make the property set/get + // API async, and would double the number of numbers in some cases. + var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength); + batchTableJson = JSON.parse(batchTableString); + byteOffset += batchTableJsonByteLength; + + if (batchTableBinaryByteLength > 0) { + // Has a batch table binary + batchTableBinary = new Uint8Array(arrayBuffer, byteOffset, batchTableBinaryByteLength); + // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed + batchTableBinary = new Uint8Array(batchTableBinary); + byteOffset += batchTableBinaryByteLength; + } + } + + var batchTable = new Cesium3DTileBatchTable(content, batchLength, batchTableJson, batchTableBinary); + content._batchTable = batchTable; + + var gltfByteLength = byteStart + byteLength - byteOffset; + if (gltfByteLength === 0) { + throw new RuntimeError('glTF byte length must be greater than 0.'); + } + + var gltfView; + if (byteOffset % 4 === 0) { + gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength); + } else { + // Create a copy of the glb so that it is 4-byte aligned + Batched3DModel3DTileContent._deprecationWarning('b3dm-glb-unaligned', 'The embedded glb is not aligned to a 4-byte boundary.'); + gltfView = new Uint8Array(uint8Array.subarray(byteOffset, byteOffset + gltfByteLength)); + } + + var pickObject = { + content : content, + primitive : tileset + }; + + // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes. + // The pick shader still needs to be patched. + content._model = new Model({ + gltf : gltfView, + cull : false, // The model is already culled by 3D Tiles + releaseGltfJson : true, // Models are unique and will not benefit from caching so save memory + opaquePass : Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass + basePath : basePath, + requestType : RequestType.TILES3D, + modelMatrix : tile.computedTransform, + upAxis : tileset._gltfUpAxis, + shadows: tileset.shadows, + debugWireframe: tileset.debugWireframe, + incrementallyLoadTextures : false, + vertexShaderLoaded : getVertexShaderCallback(content), + fragmentShaderLoaded : getFragmentShaderCallback(content), + uniformMapLoaded : batchTable.getUniformMapCallback(), + pickVertexShaderLoaded : getPickVertexShaderCallback(content), + pickFragmentShaderLoaded : batchTable.getPickFragmentShaderCallback(), + pickUniformMapLoaded : batchTable.getPickUniformMapCallback(), + addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute + pickObject : pickObject + }); + } + + function createFeatures(content) { + var tileset = content._tileset; + var featuresLength = content.featuresLength; + if (!defined(content._features) && (featuresLength > 0)) { + var features = new Array(featuresLength); + for (var i = 0; i < featuresLength; ++i) { + features[i] = new Cesium3DTileFeature(tileset, content, i); + } + content._features = features; + } + } /** - * 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. + * @inheritdoc Cesium3DTileContent#hasProperty */ - 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))); + Batched3DModel3DTileContent.prototype.hasProperty = function(batchId, name) { + return this._batchTable.hasProperty(batchId, name); }; - return VelocityOrientationProperty; -}); - -/*global define*/ -define('DataSources/Visualizer',[ - '../Core/DeveloperError' - ], function( - DeveloperError) { - 'use strict'; + /** + * @inheritdoc Cesium3DTileContent#getFeature + */ + Batched3DModel3DTileContent.prototype.getFeature = function(batchId) { + + createFeatures(this); + return this._features[batchId]; + }; /** - * 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 + * @inheritdoc Cesium3DTileContent#applyDebugSettings */ - function Visualizer() { - DeveloperError.throwInstantiationError(); - } + Batched3DModel3DTileContent.prototype.applyDebugSettings = function(enabled, color) { + color = enabled ? color : Color.WHITE; + if (this.featuresLength === 0) { + this._model.color = color; + } else { + this._batchTable.setAllColor(color); + } + }; /** - * 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. + * @inheritdoc Cesium3DTileContent#applyStyle */ - Visualizer.prototype.update = DeveloperError.throwInstantiationError; + Batched3DModel3DTileContent.prototype.applyStyle = function(frameState, style) { + this._batchTable.applyStyle(frameState, style); + }; /** - * 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 + * @inheritdoc Cesium3DTileContent#update */ - Visualizer.prototype.getBoundingSphere = DeveloperError.throwInstantiationError; + Batched3DModel3DTileContent.prototype.update = function(tileset, frameState) { + var commandStart = frameState.commandList.length; + + // In the PROCESSING state we may be calling update() to move forward + // the content's resource loading. In the READY state, it will + // actually generate commands. + this._batchTable.update(tileset, frameState); + this._model.modelMatrix = this._tile.computedTransform; + this._model.shadows = this._tileset.shadows; + this._model.debugWireframe = this._tileset.debugWireframe; + this._model.update(frameState); + + // If any commands were pushed, add derived commands + var commandEnd = frameState.commandList.length; + if ((commandStart < commandEnd) && frameState.passes.render) { + this._batchTable.addDerivedCommands(frameState, commandStart); + } + }; /** - * Returns true if this object was destroyed; otherwise, false. - * @function - * - * @returns {Boolean} True if this object was destroyed; otherwise, false. + * @inheritdoc Cesium3DTileContent#isDestroyed */ - Visualizer.prototype.isDestroyed = DeveloperError.throwInstantiationError; + Batched3DModel3DTileContent.prototype.isDestroyed = function() { + return false; + }; /** - * Removes all visualization and cleans up any resources associated with this instance. - * @function + * @inheritdoc Cesium3DTileContent#destroy */ - Visualizer.prototype.destroy = DeveloperError.throwInstantiationError; + Batched3DModel3DTileContent.prototype.destroy = function() { + this._model = this._model && this._model.destroy(); + this._batchTable = this._batchTable && this._batchTable.destroy(); + return destroyObject(this); + }; - return Visualizer; + return Batched3DModel3DTileContent; }); -/*global define*/ -define('Renderer/ClearCommand',[ - '../Core/Color', - '../Core/defaultValue', +define('Scene/BingMapsStyle',[ '../Core/freezeObject' ], function( - Color, - defaultValue, freezeObject) { 'use strict'; /** - * Represents a command to the renderer for clearing a framebuffer. + * The types of imagery provided by Bing Maps. * - * @private + * @exports BingMapsStyle + * + * @see BingMapsImageryProvider */ - function ClearCommand(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - + var BingMapsStyle = { /** - * The value to clear the color buffer to. When undefined, the color buffer is not cleared. - * - * @type {Color} + * Aerial imagery. * - * @default undefined + * @type {String} + * @constant */ - this.color = options.color; + AERIAL : 'Aerial', /** - * The value to clear the depth buffer to. When undefined, the depth buffer is not cleared. - * - * @type {Number} + * Aerial imagery with a road overlay. * - * @default undefined + * @type {String} + * @constant */ - this.depth = options.depth; + AERIAL_WITH_LABELS : 'AerialWithLabels', /** - * The value to clear the stencil buffer to. When undefined, the stencil buffer is not cleared. - * - * @type {Number} + * Roads without additional imagery. * - * @default undefined + * @type {String} + * @constant */ - this.stencil = options.stencil; + ROAD : 'Road', /** - * 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} + * A dark version of the road maps. * - * @default undefined + * @type {String} + * @constant */ - this.renderState = options.renderState; + CANVAS_DARK : 'CanvasDark', /** - * The framebuffer to clear. - * - * @type {Framebuffer} + * A lighter version of the road maps. * - * @default undefined + * @type {String} + * @constant */ - this.framebuffer = options.framebuffer; + CANVAS_LIGHT : 'CanvasLight', /** - * 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 + * A grayscale version of the road maps. * - * @see Scene#debugCommandFilter + * @type {String} + * @constant */ - this.owner = options.owner; + CANVAS_GRAY : 'CanvasGray', /** - * The pass in which to run this command. - * - * @type {Pass} + * Ordnance Survey imagery. This imagery is visible only for the London, UK area. * - * @default undefined + * @type {String} + * @constant */ - this.pass = options.pass; - } - - /** - * 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 - })); + ORDNANCE_SURVEY : 'OrdnanceSurvey', - ClearCommand.prototype.execute = function(context, passState) { - context.clear(this, passState); + /** + * Collins Bart imagery. + * + * @type {String} + * @constant + */ + COLLINS_BART : 'CollinsBart' }; - return ClearCommand; + return freezeObject(BingMapsStyle); }); -/*global define*/ -define('Renderer/ComputeCommand',[ +define('Scene/BingMapsImageryProvider',[ + '../Core/BingMapsApi', + '../Core/Cartesian2', + '../Core/Credit', '../Core/defaultValue', - './Pass' + '../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, - Pass) { + defined, + defineProperties, + DeveloperError, + Event, + loadJsonp, + CesiumMath, + Rectangle, + RuntimeError, + TileProviderError, + WebMercatorTilingScheme, + when, + BingMapsStyle, + DiscardMissingTileImagePolicy, + ImageryProvider) { 'use strict'; /** - * Represents a command to the renderer for GPU Compute (using old-school GPGPU). + * Provides tiled imagery using the Bing Maps Imagery REST API. * - * @private + * @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 GoogleEarthEnterpriseMapsProvider + * @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 ComputeCommand(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + function BingMapsImageryProvider(options) { + options = defaultValue(options, {}); + + + 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 vertex array. If none is provided, a viewport quad will be used. + * 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 {VertexArray} - * @default undefined + * @type {Number} + * @default 1.0 */ - this.vertexArray = options.vertexArray; + 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, { /** - * The fragment shader source. The default vertex shader is ViewportQuadVS. - * - * @type {ShaderSource} - * @default undefined + * Gets the name of the BingMaps server url hosting the imagery. + * @memberof BingMapsImageryProvider.prototype + * @type {String} + * @readonly */ - this.fragmentShaderSource = options.fragmentShaderSource; + url : { + get : function() { + return this._url; + } + }, /** - * The shader program to apply. - * - * @type {ShaderProgram} - * @default undefined + * Gets the proxy used by this provider. + * @memberof BingMapsImageryProvider.prototype + * @type {Proxy} + * @readonly */ - this.shaderProgram = options.shaderProgram; + proxy : { + get : function() { + return this._proxy; + } + }, + /** - * An object with functions whose names match the uniforms in the shader program - * and return values to set those uniforms. - * - * @type {Object} - * @default undefined + * Gets the Bing Maps key. + * @memberof BingMapsImageryProvider.prototype + * @type {String} + * @readonly */ - this.uniformMap = options.uniformMap; + key : { + get : function() { + return this._key; + } + }, /** - * Texture to use for offscreen rendering. - * - * @type {Texture} - * @default undefined + * Gets the type of Bing Maps imagery to load. + * @memberof BingMapsImageryProvider.prototype + * @type {BingMapsStyle} + * @readonly */ - this.outputTexture = options.outputTexture; + mapStyle : { + get : function() { + return this._mapStyle; + } + }, /** - * 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 + * 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 */ - this.preExecute = options.preExecute; + culture : { + get : function() { + return this._culture; + } + }, /** - * Function that is called after the ComputeCommand is executed. Takes the output - * texture as its single argument. - * - * @type {Function} - * @default undefined + * 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 */ - this.postExecute = options.postExecute; + tileWidth : { + get : function() { + + return this._tileWidth; + } + }, /** - * Whether the renderer resources will persist beyond this call. If not, they - * will be destroyed after completion. - * + * 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() { + + 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() { + + 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() { + + 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() { + + 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() { + + 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() { + + 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} - * @default false + * @readonly */ - this.persists = defaultValue(options.persists, false); + ready : { + get : function() { + return this._ready; + } + }, /** - * The pass when to render. Always compute pass. - * - * @type {Pass} - * @default Pass.COMPUTE; + * Gets a promise that resolves to true when the provider is ready for use. + * @memberof BingMapsImageryProvider.prototype + * @type {Promise.} + * @readonly */ - this.pass = Pass.COMPUTE; + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + }, /** - * 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 + * 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 */ - this.owner = options.owner; - } + 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(); /** - * Executes the compute command. + * Gets the credits to be displayed when a given tile is displayed. * - * @param {Context} computeEngine The context that processes the compute command. + * @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. */ - ComputeCommand.prototype.execute = function(computeEngine) { - computeEngine.execute(this); - }; + BingMapsImageryProvider.prototype.getTileCredits = function(x, y, level) { + + var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch); + var result = getRectangleAttribution(this._attributionList, level, rectangle); - return ComputeCommand; -}); + if (defined(this._keyErrorCredit)) { + result.push(this._keyErrorCredit); + } -//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\ -\n\ -varying vec2 v_textureCoordinates;\n\ -\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'; + return result; + }; /** - * @private + * 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. + * @param {Request} [request] The request object. Intended for internal use only. + * @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. */ - function ComputeEngine(context) { - this._context = context; - } + BingMapsImageryProvider.prototype.requestImage = function(x, y, level, request) { + + var url = buildImageUrl(this, x, y, level); + return ImageryProvider.loadImage(this, url, request); + }; - var renderStateScratch; - var drawCommandScratch = new DrawCommand({ - primitiveType : PrimitiveType.TRIANGLES - }); - var clearCommandScratch = new ClearCommand({ - color : new Color(0.0, 0.0, 0.0, 0.0) - }); + /** + * 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(x, y, level, longitude, latitude) { + return undefined; + }; - function createFramebuffer(context, outputTexture) { - return new Framebuffer({ - context : context, - colorTextures : [outputTexture], - destroyAttachments : false - }); - } + 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=='; - function createViewportQuadShader(context, fragmentShaderSource) { - return ShaderProgram.fromCache({ - context : context, - vertexShaderSource : ViewportQuadVS, - fragmentShaderSource : fragmentShaderSource, - attributeLocations : { - position : 0, - textureCoordinates : 1 + /** + * 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; } - }); - } - function createRenderState(width, height) { - if ((!defined(renderStateScratch)) || - (renderStateScratch.viewport.width !== width) || - (renderStateScratch.viewport.height !== height)) { + if ((y & bitmask) !== 0) { + digit |= 2; + } - renderStateScratch = RenderState.fromCache({ - viewport : new BoundingRectangle(0, 0, width, height) - }); + quadkey += digit; } - return renderStateScratch; - } + return quadkey; + }; - ComputeEngine.prototype.execute = function(computeCommand) { - - // This may modify the command's resources, so do error checking afterwards - if (defined(computeCommand.preExecute)) { - computeCommand.preExecute(computeCommand); + /** + * 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); } - - var outputTexture = computeCommand.outputTexture; - var width = outputTexture.width; - var height = outputTexture.height; + return imageUrl; + } + + var intersectionScratch = new Rectangle(); - 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; + function getRectangleAttribution(attributionList, level, rectangle) { + // Bing levels start at 1, while ours start at 0. + ++level; - var clearCommand = clearCommandScratch; - clearCommand.framebuffer = framebuffer; - clearCommand.renderState = renderState; - clearCommand.execute(context); + var result = []; - var drawCommand = drawCommandScratch; - drawCommand.vertexArray = vertexArray; - drawCommand.renderState = renderState; - drawCommand.shaderProgram = shaderProgram; - drawCommand.uniformMap = uniformMap; - drawCommand.framebuffer = framebuffer; - drawCommand.execute(context); + for (var attributionIndex = 0, attributionLength = attributionList.length; attributionIndex < attributionLength; ++attributionIndex) { + var attribution = attributionList[attributionIndex]; + var coverageAreas = attribution.coverageAreas; - framebuffer.destroy(); + var included = false; - if (!computeCommand.persists) { - shaderProgram.destroy(); - if (defined(computeCommand.vertexArray)) { - vertexArray.destroy(); + 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 (defined(computeCommand.postExecute)) { - computeCommand.postExecute(outputTexture); + if (included) { + result.push(attribution.credit); + } } - }; - - ComputeEngine.prototype.isDestroyed = function() { - return false; - }; - ComputeEngine.prototype.destroy = function() { - return destroyObject(this); - }; + return result; + } - return ComputeEngine; + return BingMapsImageryProvider; }); -/*global define*/ -define('Renderer/PassState',[], function() { +define('Scene/BoxEmitter',[ + '../Core/Cartesian3', + '../Core/Check', + '../Core/defaultValue', + '../Core/defineProperties', + '../Core/Math' + ], function( + Cartesian3, + Check, + defaultValue, + defineProperties, + CesiumMath) { 'use strict'; + var defaultDimensions = new Cartesian3(1.0, 1.0, 1.0); + /** - * The state for a particular rendering pass. This is used to supplement the state - * in a command being executed. + * A ParticleEmitter that emits particles within a box. + * Particles will be positioned randomly within the box and have initial velocities emanating from the center of the box. * - * @private + * @alias BoxEmitter + * @constructor + * + * @param {Cartesian3} dimensions The width, height and depth dimensions of the box. */ - 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; + function BoxEmitter(dimensions) { + dimensions = defaultValue(dimensions, defaultDimensions); - /** - * 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; + + this._dimensions = Cartesian3.clone(dimensions); + } + defineProperties(BoxEmitter.prototype, { /** - * The viewport used when one is not defined by a {@link DrawCommand}'s render state. - * @type {BoundingRectangle} - * @default undefined + * The width, height and depth dimensions of the box in meters. + * @memberof BoxEmitter.prototype + * @type {Cartesian3} + * @default new Cartesian3(1.0, 1.0, 1.0) */ - this.viewport = undefined; - } + dimensions : { + get : function() { + return this._dimensions; + }, + set : function(value) { + Cartesian3.clone(value, this._dimensions); + } + } - return PassState; -}); + }); -/*global define*/ -define('Renderer/RenderbufferFormat',[ - '../Core/freezeObject', - '../Core/WebGLConstants' - ], function( - freezeObject, - WebGLConstants) { - 'use strict'; + var scratchHalfDim = new Cartesian3(); /** + * Initializes the given {Particle} by setting it's position and velocity. + * * @private + * @param {Particle} particle The particle to initialize. */ - 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, + BoxEmitter.prototype.emit = function(particle) { + var dim = this._dimensions; + var halfDim = Cartesian3.multiplyByScalar(dim, 0.5, scratchHalfDim); - 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)); - } + var x = CesiumMath.randomBetween(-halfDim.x, halfDim.x); + var y = CesiumMath.randomBetween(-halfDim.y, halfDim.y); + var z = CesiumMath.randomBetween(-halfDim.z, halfDim.z); + + particle.position = Cartesian3.fromElements(x, y, z, particle.position); + particle.velocity = Cartesian3.normalize(particle.position, particle.velocity); }; - return freezeObject(RenderbufferFormat); + return BoxEmitter; }); -/*global define*/ -define('Renderer/Renderbuffer',[ - '../Core/defaultValue', +//This file is automatically rebuilt by the Cesium build process. +define('Shaders/BrdfLutGeneratorFS',[],function() { + 'use strict'; + return "varying vec2 v_textureCoordinates;\n\ +const float M_PI = 3.141592653589793;\n\ +\n\ +float vdcRadicalInverse(int i)\n\ +{\n\ + float r;\n\ + float base = 2.0;\n\ + float value = 0.0;\n\ + float invBase = 1.0 / base;\n\ + float invBi = invBase;\n\ + for (int x = 0; x < 100; x++)\n\ + {\n\ + if (i <= 0)\n\ + {\n\ + break;\n\ + }\n\ + r = mod(float(i), base);\n\ + value += r * invBi;\n\ + invBi *= invBase;\n\ + i = int(float(i) * invBase);\n\ + }\n\ + return value;\n\ +}\n\ +\n\ +vec2 hammersley2D(int i, int N)\n\ +{\n\ + return vec2(float(i) / float(N), vdcRadicalInverse(i));\n\ +}\n\ +\n\ +vec3 importanceSampleGGX(vec2 xi, float roughness, vec3 N)\n\ +{\n\ + float a = roughness * roughness;\n\ + float phi = 2.0 * M_PI * xi.x;\n\ + float cosTheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));\n\ + float sinTheta = sqrt(1.0 - cosTheta * cosTheta);\n\ + vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);\n\ + vec3 upVector = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);\n\ + vec3 tangentX = normalize(cross(upVector, N));\n\ + vec3 tangentY = cross(N, tangentX);\n\ + return tangentX * H.x + tangentY * H.y + N * H.z;\n\ +}\n\ +\n\ +float G1_Smith(float NdotV, float k)\n\ +{\n\ + return NdotV / (NdotV * (1.0 - k) + k);\n\ +}\n\ +\n\ +float G_Smith(float roughness, float NdotV, float NdotL)\n\ +{\n\ + float k = roughness * roughness / 2.0;\n\ + return G1_Smith(NdotV, k) * G1_Smith(NdotL, k);\n\ +}\n\ +\n\ +vec2 integrateBrdf(float roughness, float NdotV)\n\ +{\n\ + vec3 V = vec3(sqrt(1.0 - NdotV * NdotV), 0.0, NdotV);\n\ + float A = 0.0;\n\ + float B = 0.0;\n\ + const int NumSamples = 1024;\n\ + for (int i = 0; i < NumSamples; i++)\n\ + {\n\ + vec2 xi = hammersley2D(i, NumSamples);\n\ + vec3 H = importanceSampleGGX(xi, roughness, vec3(0.0, 0.0, 1.0));\n\ + vec3 L = 2.0 * dot(V, H) * H - V;\n\ + float NdotL = clamp(L.z, 0.0, 1.0);\n\ + float NdotH = clamp(H.z, 0.0, 1.0);\n\ + float VdotH = clamp(dot(V, H), 0.0, 1.0);\n\ + if (NdotL > 0.0)\n\ + {\n\ + float G = G_Smith(roughness, NdotV, NdotL);\n\ + float G_Vis = G * VdotH / (NdotH * NdotV);\n\ + float Fc = pow(1.0 - VdotH, 5.0);\n\ + A += (1.0 - Fc) * G_Vis;\n\ + B += Fc * G_Vis;\n\ + }\n\ + }\n\ + return vec2(A, B) / float(NumSamples);\n\ +}\n\ +\n\ +void main()\n\ +{\n\ + gl_FragColor = vec4(integrateBrdf(1.0 - v_textureCoordinates.y, v_textureCoordinates.x), 0.0, 1.0);\n\ +}\n\ +"; +}); +define('Scene/BrdfLutGenerator',[ + '../Core/BoundingRectangle', + '../Core/Color', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', - '../Core/DeveloperError', - './ContextLimits', - './RenderbufferFormat' + '../Core/PixelFormat', + '../Renderer/Framebuffer', + '../Renderer/Pass', + '../Renderer/PixelDatatype', + '../Renderer/RenderState', + '../Renderer/Sampler', + '../Renderer/ShaderSource', + '../Renderer/Texture', + '../Renderer/TextureMagnificationFilter', + '../Renderer/TextureMinificationFilter', + '../Renderer/TextureWrap', + '../Shaders/BrdfLutGeneratorFS' ], function( - defaultValue, + BoundingRectangle, + Color, defined, defineProperties, destroyObject, - DeveloperError, - ContextLimits, - RenderbufferFormat) { + PixelFormat, + Framebuffer, + Pass, + PixelDatatype, + RenderState, + Sampler, + ShaderSource, + Texture, + TextureMagnificationFilter, + TextureMinificationFilter, + TextureWrap, + BrdfLutGeneratorFS) { 'use strict'; /** * @private */ - function Renderbuffer(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - - 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; - - - 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); + function BrdfLutGenerator() { + this._framebuffer = undefined; + this._colorTexture = undefined; + this._drawCommand = undefined; } - defineProperties(Renderbuffer.prototype, { - format: { - get : function() { - return this._format; - } - }, - width: { - get : function() { - return this._width; - } - }, - height: { + defineProperties(BrdfLutGenerator.prototype, { + colorTexture : { get : function() { - return this._height; + return this._colorTexture; } } }); - Renderbuffer.prototype._getRenderbuffer = function() { - return this._renderbuffer; + function createCommand(generator, context) { + var framebuffer = generator._framebuffer; + + var drawCommand = context.createViewportQuadCommand(BrdfLutGeneratorFS, { + framebuffer : framebuffer, + renderState : RenderState.fromCache({ + viewport : new BoundingRectangle(0.0, 0.0, 256.0, 256.0) + }) + }); + + generator._drawCommand = drawCommand; + } + + function createFramebuffer(generator, context) { + var colorTexture = new Texture({ + context : context, + width : 256, + height: 256, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.UNSIGNED_BYTE, + sampler : new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.NEAREST, + magnificationFilter : TextureMagnificationFilter.NEAREST + }) + }); + + generator._colorTexture = colorTexture; + + var framebuffer = new Framebuffer({ + context : context, + colorTextures : [colorTexture], + destroyAttachments : false + }); + + generator._framebuffer = framebuffer; + } + + BrdfLutGenerator.prototype.update = function(frameState) { + if (!defined(this._colorTexture)) { + var context = frameState.context; + + createFramebuffer(this, context); + createCommand(this, context); + this._drawCommand.execute(context); + this._framebuffer = this._framebuffer && this._framebuffer.destroy(); + this._drawCommand.shaderProgram = this._drawCommand.shaderProgram && this._drawCommand.shaderProgram.destroy(); + } }; - Renderbuffer.prototype.isDestroyed = function() { + BrdfLutGenerator.prototype.isDestroyed = function() { return false; }; - Renderbuffer.prototype.destroy = function() { - this._gl.deleteRenderbuffer(this._renderbuffer); + BrdfLutGenerator.prototype.destroy = function() { + this._colorTexture = this._colorTexture && this._colorTexture.destroy(); return destroyObject(this); }; - return Renderbuffer; + return BrdfLutGenerator; }); -/*global define*/ -define('Renderer/PickFramebuffer',[ - '../Core/BoundingRectangle', - '../Core/Color', +define('Scene/CameraFlightPath',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', '../Core/defaultValue', '../Core/defined', - '../Core/destroyObject', - './Framebuffer', - './PassState', - './Renderbuffer', - './RenderbufferFormat', - './Texture' + '../Core/DeveloperError', + '../Core/EasingFunction', + '../Core/Math', + '../Core/PerspectiveFrustum', + '../Core/PerspectiveOffCenterFrustum', + './SceneMode' ], function( - BoundingRectangle, - Color, + Cartesian2, + Cartesian3, + Cartographic, defaultValue, defined, - destroyObject, - Framebuffer, - PassState, - Renderbuffer, - RenderbufferFormat, - Texture) { + DeveloperError, + EasingFunction, + CesiumMath, + PerspectiveFrustum, + PerspectiveOffCenterFrustum, + SceneMode) { 'use strict'; /** + * Creates tweens for camera flights. + *

    + * Mouse interaction is disabled during flights. + * * @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(); + var CameraFlightPath = { + }; - this._context = context; - this._fb = undefined; - this._passState = passState; - this._width = 0; - this._height = 0; + 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); } - PickFramebuffer.prototype.begin = function(screenSpaceRectangle) { - var context = this._context; - var width = context.drawingBufferWidth; - var height = context.drawingBufferHeight; - BoundingRectangle.clone(screenSpaceRectangle, this._passState.scissorTest.rectangle); + var scratchCart = new Cartesian3(); + var scratchCart2 = new Cartesian3(); - // 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; + function createPitchFunction(startPitch, endPitch, heightFunction, pitchAdjustHeight) { + if (defined(pitchAdjustHeight) && heightFunction(0.5) > pitchAdjustHeight) { + var startHeight = heightFunction(0.0); + var endHeight = heightFunction(1.0); + var middleHeight = heightFunction(0.5); - 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; + var d1 = middleHeight - startHeight; + var d2 = middleHeight - endHeight; + + return function(time) { + var altitude = heightFunction(time); + if (time <= 0.5) { + var t1 = (altitude - startHeight) / d1; + return CesiumMath.lerp(startPitch, -CesiumMath.PI_OVER_TWO, t1); + } + + var t2 = (altitude - endHeight) / d2; + return CesiumMath.lerp(-CesiumMath.PI_OVER_TWO, endPitch, 1 - t2); + }; } + return function(time) { + return CesiumMath.lerp(startPitch, endPitch, time); + }; + } - this._passState.viewport.width = width; - this._passState.viewport.height = height; + function createHeightFunction(camera, destination, startHeight, endHeight, optionAltitude) { + var altitude = optionAltitude; + var maxHeight = Math.max(startHeight, endHeight); - return this._passState; - }; + if (!defined(altitude)) { + var start = camera.position; + var end = destination; + var up = camera.up; + var right = camera.right; + var frustum = camera.frustum; - var colorScratch = new Color(); + 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)); - PickFramebuffer.prototype.end = function(screenSpaceRectangle) { - var width = defaultValue(screenSpaceRectangle.width, 1.0); - var height = defaultValue(screenSpaceRectangle.height, 1.0); + altitude = Math.min(getAltitude(frustum, verticalDistance, horizontalDistance) * 0.20, 1000000000.0); + } - var context = this._context; - var pixels = context.readPixels({ - x : screenSpaceRectangle.x, - y : screenSpaceRectangle.y, - width : width, - height : height, - framebuffer : this._fb - }); + if (maxHeight < altitude) { + var power = 8.0; + var factor = 1000000.0; - 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 s = -Math.pow((altitude - startHeight) * factor, 1.0 / power); + var e = Math.pow((altitude - endHeight) * factor, 1.0 / power); - var x = 0; - var y = 0; - var dx = 0; - var dy = -1; + return function(t) { + var x = t * (e - s) + s; + return -Math.pow(x, power) / factor + altitude; + }; + } - // Spiral around the center pixel, this is a workaround until - // we can access the depth buffer on all browsers. + return function(t) { + return CesiumMath.lerp(startHeight, endHeight, t); + }; + } - // 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); + function adjustAngleForLERP(startAngle, endAngle) { + if (CesiumMath.equalsEpsilon(startAngle, CesiumMath.TWO_PI, CesiumMath.EPSILON11)) { + startAngle = 0.0; + } - 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]); + if (endAngle > startAngle + Math.PI) { + startAngle += CesiumMath.TWO_PI; + } else if (endAngle < startAngle - Math.PI) { + startAngle -= CesiumMath.TWO_PI; + } - var object = context.getObjectByPickColor(colorScratch); - if (defined(object)) { - return object; + 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) } - } + }); - // 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; - } + Cartesian2.lerp(start, destination, time, camera.position); + camera.position.z = heightFunction(time); + } + return update; + } - x += dx; - y += dy; + function useLongestFlight(startCart, destCart) { + if (startCart.longitude < destCart.longitude) { + startCart.longitude += CesiumMath.TWO_PI; + } else { + destCart.longitude += CesiumMath.TWO_PI; } + } - return undefined; - }; + function useShortestFlight(startCart, destCart) { + 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; + } + } - PickFramebuffer.prototype.isDestroyed = function() { - return false; - }; + var scratchStartCart = new Cartographic(); + var scratchEndCart = new Cartographic(); - PickFramebuffer.prototype.destroy = function() { - this._fb = this._fb && this._fb.destroy(); - return destroyObject(this); - }; + function createUpdate3D(scene, duration, destination, heading, pitch, roll, optionAltitude, optionFlyOverLongitude, optionFlyOverLongitudeWeight, optionPitchAdjustHeight) { + var camera = scene.camera; + var projection = scene.mapProjection; + var ellipsoid = projection.ellipsoid; - return PickFramebuffer; -}); + var startCart = Cartographic.clone(camera.positionCartographic, scratchStartCart); + var startPitch = camera.pitch; + var startHeading = adjustAngleForLERP(camera.heading, heading); + var startRoll = adjustAngleForLERP(camera.roll, roll); -/*global define*/ -define('Renderer/ShaderCache',[ - '../Core/defined', - '../Core/defineProperties', - '../Core/destroyObject', - './ShaderProgram', - './ShaderSource' - ], function( - defined, - defineProperties, - destroyObject, - ShaderProgram, - ShaderSource) { - 'use strict'; + var destCart = ellipsoid.cartesianToCartographic(destination, scratchEndCart); + startCart.longitude = CesiumMath.zeroToTwoPi(startCart.longitude); + destCart.longitude = CesiumMath.zeroToTwoPi(destCart.longitude); - /** - * @private - */ - function ShaderCache(context) { - this._context = context; - this._shaders = {}; - this._numberOfShaders = 0; - this._shadersToRelease = {}; - } + var useLongFlight = false; - defineProperties(ShaderCache.prototype, { - numberOfShaders : { - get : function() { - return this._numberOfShaders; + if (defined(optionFlyOverLongitude)) { + var hitLon = CesiumMath.zeroToTwoPi(optionFlyOverLongitude); + + var lonMin = Math.min(startCart.longitude, destCart.longitude); + var lonMax = Math.max(startCart.longitude, destCart.longitude); + + var hitInside = (hitLon >= lonMin && hitLon <= lonMax); + + if (defined(optionFlyOverLongitudeWeight)) { + // Distance inside (0...2Pi) + var din = Math.abs(startCart.longitude - destCart.longitude); + // Distance outside (0...2Pi) + var dot = CesiumMath.TWO_PI - din; + + var hitDistance = hitInside ? din : dot; + var offDistance = hitInside ? dot : din; + + if (hitDistance < offDistance * optionFlyOverLongitudeWeight && !hitInside) { + useLongFlight = true; + } + } else if (!hitInside) { + useLongFlight = true; } } - }); - - /** - * 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(); + if (useLongFlight) { + useLongestFlight(startCart, destCart); + } else { + useShortestFlight(startCart, destCart); } - return this.getShaderProgram(options); - }; + var heightFunction = createHeightFunction(camera, destination, startCart.height, destCart.height, optionAltitude); + var pitchFunction = createPitchFunction(startPitch, pitch, heightFunction, optionPitchAdjustHeight); - /** - * 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. + // Isolate scope for update function. + // to have local copies of vars used in lerp + // Othervise, if you call nex + // createUpdate3D (createAnimationTween) + // before you played animation, variables will be overwriten. + function isolateUpdateFunction() { + var startLongitude = startCart.longitude; + var destLongitude = destCart.longitude; + var startLatitude = startCart.latitude; + var destLatitude = destCart.latitude; - var vertexShaderSource = options.vertexShaderSource; - var fragmentShaderSource = options.fragmentShaderSource; - var attributeLocations = options.attributeLocations; + return function update(value) { + var time = value.time / duration; - if (typeof vertexShaderSource === 'string') { - vertexShaderSource = new ShaderSource({ - sources : [vertexShaderSource] - }); - } + var position = Cartesian3.fromRadians( + CesiumMath.lerp(startLongitude, destLongitude, time), + CesiumMath.lerp(startLatitude, destLatitude, time), + heightFunction(time) + ); - if (typeof fragmentShaderSource === 'string') { - fragmentShaderSource = new ShaderSource({ - sources : [fragmentShaderSource] - }); + camera.setView({ + destination : position, + orientation: { + heading : CesiumMath.lerp(startHeading, heading, time), + pitch : pitchFunction(time), + roll : CesiumMath.lerp(startRoll, roll, time) + } + }); + }; } + return isolateUpdateFunction(); + } - var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); - var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); + function createUpdate2D(scene, duration, destination, heading, pitch, roll, optionAltitude) { + var camera = scene.camera; - var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations); - var cachedShader; + var start = Cartesian3.clone(camera.position, scratchStart); + var startHeading = adjustAngleForLERP(camera.heading, heading); - if (defined(this._shaders[keyword])) { - cachedShader = this._shaders[keyword]; + var startHeight = camera.frustum.right - camera.frustum.left; + var heightFunction = createHeightFunction(camera, destination, startHeight, destination.z, optionAltitude); - // 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 + function update(value) { + var time = value.time / duration; + + camera.setView({ + orientation: { + heading : CesiumMath.lerp(startHeading, heading, time) + } }); - cachedShader = { - cache : this, - shaderProgram : shaderProgram, - keyword : keyword, - derivedKeywords : [], - count : 0 - }; + Cartesian2.lerp(start, destination, time, camera.position); - // A shader can't be in more than one cache. - shaderProgram._cachedShader = cachedShader; - this._shaders[keyword] = cachedShader; - ++this._numberOfShaders; + 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; + } - ++cachedShader.count; - return cachedShader.shaderProgram; - }; + var scratchCartographic = new Cartographic(); + var scratchDestination = new Cartesian3(); - ShaderCache.prototype.getDerivedShaderProgram = function(shaderProgram, keyword) { - var cachedShader = shaderProgram._cachedShader; - var derivedKeyword = keyword + cachedShader.keyword; - var cachedDerivedShader = this._shaders[derivedKeyword]; - if (!defined(cachedDerivedShader)) { - return undefined; + 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; + } - return cachedDerivedShader.shaderProgram; - }; + CameraFlightPath.createTween = function(scene, options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var destination = options.destination; - ShaderCache.prototype.createDerivedShaderProgram = function(shaderProgram, keyword, options) { - var cachedShader = shaderProgram._cachedShader; - var derivedKeyword = keyword + cachedShader.keyword; + var mode = scene.mode; - var vertexShaderSource = options.vertexShaderSource; - var fragmentShaderSource = options.fragmentShaderSource; - var attributeLocations = options.attributeLocations; + if (mode === SceneMode.MORPHING) { + return emptyFlight(); + } - if (typeof vertexShaderSource === 'string') { - vertexShaderSource = new ShaderSource({ - sources : [vertexShaderSource] - }); + var convert = defaultValue(options.convert, true); + var projection = scene.mapProjection; + var ellipsoid = projection.ellipsoid; + var maximumHeight = options.maximumHeight; + var flyOverLongitude = options.flyOverLongitude; + var flyOverLongitudeWeight = options.flyOverLongitudeWeight; + var pitchAdjustHeight = options.pitchAdjustHeight; + var easingFunction = options.easingFunction; + + if (convert && mode !== SceneMode.SCENE3D) { + ellipsoid.cartesianToCartographic(destination, scratchCartographic); + destination = projection.project(scratchCartographic, scratchDestination); } - if (typeof fragmentShaderSource === 'string') { - fragmentShaderSource = new ShaderSource({ - sources : [fragmentShaderSource] - }); + var camera = scene.camera; + var transform = options.endTransform; + if (defined(transform)) { + camera._setTransform(transform); } - var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); - var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); + 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 context = this._context; - var derivedShaderProgram = new ShaderProgram({ - gl : context._gl, - logShaderCompilation : context.logShaderCompilation, - debugShaders : context.debugShaders, - vertexShaderSource : vertexShaderSource, - vertexShaderText : vertexShaderText, - fragmentShaderSource : fragmentShaderSource, - fragmentShaderText : fragmentShaderText, - attributeLocations : attributeLocations - }); + var heading = defaultValue(options.heading, 0.0); + var pitch = defaultValue(options.pitch, -CesiumMath.PI_OVER_TWO); + var roll = defaultValue(options.roll, 0.0); - var derivedCachedShader = { - cache : this, - shaderProgram : derivedShaderProgram, - keyword : derivedKeyword, - derivedKeywords : [], - count : 0 - }; + var controller = scene.screenSpaceCameraController; + controller.enableInputs = false; - cachedShader.derivedKeywords.push(keyword); - derivedShaderProgram._cachedShader = derivedCachedShader; - this._shaders[derivedKeyword] = derivedCachedShader; - return derivedShaderProgram; - }; + var complete = wrapCallback(controller, options.complete); + var cancel = wrapCallback(controller, options.cancel); - function destroyShader(cache, cachedShader) { - var derivedKeywords = cachedShader.derivedKeywords; - var length = derivedKeywords.length; - for (var i = 0; i < length; ++i) { - var keyword = derivedKeywords[i] + cachedShader.keyword; - var derivedCachedShader = cache._shaders[keyword]; - destroyShader(cache, derivedCachedShader); + 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); } - delete cache._shaders[cachedShader.keyword]; - cachedShader.shaderProgram.finalDestroy(); - } + var updateFunctions = new Array(4); + updateFunctions[SceneMode.SCENE2D] = createUpdate2D; + updateFunctions[SceneMode.SCENE3D] = createUpdate3D; + updateFunctions[SceneMode.COLUMBUS_VIEW] = createUpdateCV; - ShaderCache.prototype.destroyReleasedShaderPrograms = function() { - var shadersToRelease = this._shadersToRelease; + if (duration <= 0.0) { + var newOnComplete = function() { + var update = updateFunctions[mode](scene, 1.0, destination, heading, pitch, roll, maximumHeight, flyOverLongitude, flyOverLongitudeWeight, pitchAdjustHeight); + update({ time: 1.0 }); - for ( var keyword in shadersToRelease) { - if (shadersToRelease.hasOwnProperty(keyword)) { - var cachedShader = shadersToRelease[keyword]; - destroyShader(this, cachedShader); - --this._numberOfShaders; - } + if (typeof complete === 'function') { + complete(); + } + }; + return emptyFlight(newOnComplete, cancel); } - this._shadersToRelease = {}; - }; + var update = updateFunctions[mode](scene, duration, destination, heading, pitch, roll, maximumHeight, flyOverLongitude, flyOverLongitudeWeight, pitchAdjustHeight); - ShaderCache.prototype.releaseShaderProgram = function(shaderProgram) { - if (defined(shaderProgram)) { - var cachedShader = shaderProgram._cachedShader; - if (cachedShader && (--cachedShader.count === 0)) { - this._shadersToRelease[cachedShader.keyword] = cachedShader; + 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; } } - }; - ShaderCache.prototype.isDestroyed = function() { - return false; + return { + duration : duration, + easingFunction : easingFunction, + startObject : { + time : 0.0 + }, + stopObject : { + time : duration + }, + update : update, + complete : complete, + cancel: cancel + }; }; - 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 CameraFlightPath; +}); + +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 ShaderCache; + return freezeObject(MapMode2D); }); -/*global define*/ -define('Renderer/UniformState',[ - '../Core/BoundingRectangle', +define('Scene/Camera',[ + '../Core/BoundingSphere', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', - '../Core/Color', + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/EncodedCartesian3', + '../Core/DeveloperError', + '../Core/EasingFunction', + '../Core/Ellipsoid', + '../Core/EllipsoidGeodesic', + '../Core/Event', + '../Core/HeadingPitchRange', + '../Core/HeadingPitchRoll', + '../Core/Intersect', + '../Core/IntersectionTests', '../Core/Math', '../Core/Matrix3', '../Core/Matrix4', - '../Core/Simon1994PlanetaryPositions', + '../Core/OrthographicFrustum', + '../Core/OrthographicOffCenterFrustum', + '../Core/PerspectiveFrustum', + '../Core/Quaternion', + '../Core/Ray', + '../Core/Rectangle', '../Core/Transforms', - '../Scene/OrthographicFrustum', - '../Scene/SceneMode' + './CameraFlightPath', + './MapMode2D', + './SceneMode' ], function( - BoundingRectangle, + BoundingSphere, Cartesian2, Cartesian3, Cartesian4, Cartographic, - Color, + defaultValue, defined, defineProperties, - EncodedCartesian3, + DeveloperError, + EasingFunction, + Ellipsoid, + EllipsoidGeodesic, + Event, + HeadingPitchRange, + HeadingPitchRoll, + Intersect, + IntersectionTests, CesiumMath, Matrix3, Matrix4, - Simon1994PlanetaryPositions, - Transforms, OrthographicFrustum, + OrthographicOffCenterFrustum, + PerspectiveFrustum, + Quaternion, + Ray, + Rectangle, + Transforms, + CameraFlightPath, + MapMode2D, SceneMode) { 'use strict'; /** - * @private + * 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 0) { - allAreTransparent = false; - } - } + movement.distance = { + startPosition : new Cartesian2(), + endPosition : new Cartesian2() + }; + movement.angleAndHeight = { + startPosition : new Cartesian2(), + endPosition : new Cartesian2() + }; + movement.prevAngle = 0.0; - if (allAreTransparent) { - pixels = undefined; + aggregator._eventHandler.setInputAction(function(event) { + aggregator._buttonsDown++; + isDown[key] = true; + pressTime[key] = new Date(); + // Compute center position and store as start point. + Cartesian2.lerp(event.position1, event.position2, 0.5, eventStartPosition[key]); + }, ScreenSpaceEventType.PINCH_START, modifier); + + aggregator._eventHandler.setInputAction(function() { + aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0); + isDown[key] = false; + releaseTime[key] = new Date(); + }, ScreenSpaceEventType.PINCH_END, modifier); + + aggregator._eventHandler.setInputAction(function(mouseMovement) { + if (isDown[key]) { + // Aggregate several input events into a single animation frame. + if (!update[key]) { + Cartesian2.clone(mouseMovement.distance.endPosition, movement.distance.endPosition); + Cartesian2.clone(mouseMovement.angleAndHeight.endPosition, movement.angleAndHeight.endPosition); + } else { + clonePinchMovement(mouseMovement, movement); + update[key] = false; + movement.prevAngle = movement.angleAndHeight.startPosition.x; + } + // Make sure our aggregation of angles does not "flip" over 360 degrees. + var angle = movement.angleAndHeight.endPosition.x; + var prevAngle = movement.prevAngle; + var TwoPI = Math.PI * 2; + while (angle >= (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); + } - that._missingImagePixels = pixels; - that._isReady = true; - } + function listenToWheel(aggregator, modifier) { + var key = getKey(CameraEventType.WHEEL, modifier); - 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; + var update = aggregator._update; + update[key] = true; + + var movement = aggregator._movement[key]; + if (!defined(movement)) { + movement = aggregator._movement[key] = {}; } - when(loadImageViaBlob(options.missingImageUrl), success, failure); + 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); } - /** - * 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; - }; + function listenMouseButtonDownUp(aggregator, modifier, type) { + var key = getKey(type, modifier); - /** - * 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) { - - var pixelsToCheck = this._pixelsToCheck; - var missingImagePixels = this._missingImagePixels; + var isDown = aggregator._isDown; + var eventStartPosition = aggregator._eventStartPosition; + var pressTime = aggregator._pressTime; + var releaseTime = aggregator._releaseTime; - // If missingImagePixels is undefined, it indicates that the discard check has been disabled. - if (!defined(missingImagePixels)) { - return false; + 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 + }; } - if (defined(image.blob) && image.blob.size !== this._missingImageByteLength) { - return 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; } - var pixels = getImagePixels(image); - var width = image.width; + 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); - 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; + 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() + }; + } } } } - return true; - }; - return DiscardMissingTileImagePolicy; -}); + 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; + } + } + } + } + } -/*global define*/ -define('Scene/ImageryLayerFeatureInfo',[ - '../Core/defined' - ], function( - defined) { - 'use strict'; + Cartesian2.clone(mouseMovement.endPosition, aggregator._currentMousePosition); + }, ScreenSpaceEventType.MOUSE_MOVE, modifier); + } /** - * Describes a rasterized feature, such as a point, polygon, polyline, etc., in an imagery layer. + * 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 ImageryLayerFeatureInfo + * @alias CameraEventAggregator * @constructor + * + * @param {Canvas} [canvas=document] The element to handle events for. + * + * @see ScreenSpaceEventHandler */ - function ImageryLayerFeatureInfo() { - /** - * Gets or sets the name of the feature. - * @type {String} - */ - this.name = undefined; + function CameraEventAggregator(canvas) { + + this._eventHandler = new ScreenSpaceEventHandler(canvas, true); - /** - * 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; + this._update = {}; + this._movement = {}; + this._lastMovement = {}; + this._isDown = {}; + this._eventStartPosition = {}; + this._pressTime = {}; + this._releaseTime = {}; - /** - * Gets or sets the position of the feature, or undefined if the position is not known. - * - * @type {Cartographic} - */ - this.position = undefined; + 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 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} + * Gets the current mouse position. + * @memberof CameraEventAggregator.prototype + * @type {Cartesian2} */ - this.data = undefined; + currentMousePosition : { + get : function() { + return this._currentMousePosition; + } + }, /** - * Gets or sets the image layer of the feature. - * @type {Object} + * Gets whether any mouse button is down, a touch has started, or the wheel has been moved. + * @memberof CameraEventAggregator.prototype + * @type {Boolean} */ - this.imageryLayer = undefined; - } + 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; + } + } + }); /** - * 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. + * Gets if a mouse button down or touch has started and has been moved. * - * @param {Object} properties An object literal containing the properties of the feature. + * @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 */ - ImageryLayerFeatureInfo.prototype.configureNameFromProperties = function(properties) { - var namePropertyPrecedence = 10; - var nameProperty; + CameraEventAggregator.prototype.isMoving = function(type, modifier) { + + var key = getKey(type, modifier); + return !this._update[key]; + }; - for (var key in properties) { - if (properties.hasOwnProperty(key) && properties[key]) { - var lowerKey = key.toLowerCase(); + /** + * 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) { + + var key = getKey(type, modifier); + var movement = this._movement[key]; + return movement; + }; - 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; - } - } + /** + * 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) { + + var key = getKey(type, modifier); + var lastMovement = this._lastMovement[key]; + if (lastMovement.valid) { + return lastMovement; } - if (defined(nameProperty)) { - this.name = properties[nameProperty]; + 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) { + + 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 (type === CameraEventType.WHEEL) { + return this._currentMousePosition; } + + var key = getKey(type, modifier); + return this._eventStartPosition[key]; }; /** - * Configures the description of this feature by creating an HTML table of properties and their values. + * Gets the time the button was pressed or the touch was started. * - * @param {Object} properties An object literal containing the properties of the feature. + * @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. */ - 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 += ''; - } else { - html += ''; - } - } - } - } - html += '
    ' + key + '' + describe(value) + '
    ' + key + '' + value + '
    '; + CameraEventAggregator.prototype.getButtonPressTime = function(type, modifier) { + + var key = getKey(type, modifier); + return this._pressTime[key]; + }; - return html; + /** + * 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) { + + 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; + } } + }; - this.description = describe(properties); + /** + * 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; }; - return ImageryLayerFeatureInfo; + /** + * 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/ImageryProvider',[ +define('Scene/Cesium3DTileChildrenVisibility',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * @private + */ + var Cesium3DTileChildrenVisibility = { + NONE : 0, // No children visible + VISIBLE : 1, // At least one child visible + IN_REQUEST_VOLUME : 2, // At least one child in viewer request volume + VISIBLE_IN_REQUEST_VOLUME : 4, // At least one child both visible and in viewer request volume + VISIBLE_NOT_IN_REQUEST_VOLUME : 8 // At least one child visible but not in viewer request volume + }; + + return freezeObject(Cesium3DTileChildrenVisibility); +}); + +define('Scene/Composite3DTileContent',[ + '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/loadCRN', - '../Core/loadImage', - '../Core/loadImageViaBlob', - '../Core/loadKTX', - '../Core/throttleRequestByServer' + '../Core/destroyObject', + '../Core/FeatureDetection', + '../Core/getMagic', + '../Core/RuntimeError', + '../ThirdParty/when' ], function( + defaultValue, defined, defineProperties, - DeveloperError, - loadCRN, - loadImage, - loadImageViaBlob, - loadKTX, - throttleRequestByServer) { + destroyObject, + FeatureDetection, + getMagic, + RuntimeError, + when) { '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 {}; + } + /** - * Provides imagery to be displayed on the surface of an ellipsoid. This type describes an - * interface and is not intended to be instantiated directly. + * Represents the contents of a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Composite/README.md|Composite} + * tile in a {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset. + *

    + * Implements the {@link Cesium3DTileContent} interface. + *

    * - * @alias ImageryProvider + * @alias Composite3DTileContent * @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} + * @private */ - 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; + function Composite3DTileContent(tileset, tile, url, arrayBuffer, byteOffset, factory) { + this._tileset = tileset; + this._tile = tile; + this._url = url; + this._contents = []; + this._readyPromise = when.defer(); - DeveloperError.throwInstantiationError(); + initialize(this, arrayBuffer, byteOffset, factory); } - defineProperties(ImageryProvider.prototype, { + defineProperties(Composite3DTileContent.prototype, { /** - * Gets a value indicating whether or not the provider is ready for use. - * @memberof ImageryProvider.prototype - * @type {Boolean} - * @readonly + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty */ - ready : { - get : DeveloperError.throwInstantiationError + featurePropertiesDirty : { + get : function() { + var contents = this._contents; + var length = contents.length; + for (var i = 0; i < length; ++i) { + if (contents[i].featurePropertiesDirty) { + return true; + } + } + + return false; + }, + set : function(value) { + var contents = this._contents; + var length = contents.length; + for (var i = 0; i < length; ++i) { + contents[i].featurePropertiesDirty = value; + } + } }, /** - * Gets a promise that resolves to true when the provider is ready for use. - * @memberof ImageryProvider.prototype - * @type {Promise.} - * @readonly + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns 0. Instead call featuresLength for a tile in the composite. */ - readyPromise : { - get : DeveloperError.throwInstantiationError + featuresLength : { + get : function() { + return 0; + } }, /** - * 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 + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns 0. Instead call pointsLength for a tile in the composite. */ - rectangle: { - get : DeveloperError.throwInstantiationError + pointsLength : { + get : function() { + return 0; + } }, /** - * 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 + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns 0. Instead call trianglesLength for a tile in the composite. */ - tileWidth : { - get : DeveloperError.throwInstantiationError + trianglesLength : { + get : function() { + return 0; + } }, /** - * 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 + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns 0. Instead call geometryByteLength for a tile in the composite. */ - tileHeight : { - get : DeveloperError.throwInstantiationError + geometryByteLength : { + get : function() { + return 0; + } }, /** - * 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 + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns 0. Instead call texturesByteLength for a tile in the composite. */ - maximumLevel : { - get : DeveloperError.throwInstantiationError + texturesByteLength : { + get : function() { + return 0; + } }, /** - * 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 + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns 0. Instead call batchTableByteLength for a tile in the composite. */ - minimumLevel : { - get : DeveloperError.throwInstantiationError + batchTableByteLength : { + get : function() { + return 0; + } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#innerContents */ - tilingScheme : { - get : DeveloperError.throwInstantiationError + innerContents : { + get : function() { + return this._contents; + } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#readyPromise */ - tileDiscardPolicy : { - get : DeveloperError.throwInstantiationError + readyPromise : { + get : function() { + return this._readyPromise.promise; + } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tileset */ - errorEvent : { - get : DeveloperError.throwInstantiationError + tileset : { + get : function() { + return this._tileset; + } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tile */ - credit : { - get : DeveloperError.throwInstantiationError + tile : { + get : function() { + return this._tile; + } }, /** - * Gets the proxy used by this provider. - * @memberof ImageryProvider.prototype - * @type {Proxy} - * @readonly + * @inheritdoc Cesium3DTileContent#url */ - proxy : { - get : DeveloperError.throwInstantiationError + url : { + get : function() { + return this._url; + } }, /** - * 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 + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns undefined. Instead call batchTable for a tile in the composite. */ - hasAlphaChannel : { - get : DeveloperError.throwInstantiationError + batchTable : { + get : function() { + return undefined; + } } }); + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + + function initialize(content, arrayBuffer, byteOffset, factory) { + byteOffset = defaultValue(byteOffset, 0); + + var uint8Array = new Uint8Array(arrayBuffer); + var view = new DataView(arrayBuffer); + byteOffset += sizeOfUint32; // Skip magic + + var version = view.getUint32(byteOffset, true); + if (version !== 1) { + throw new RuntimeError('Only Composite Tile version 1 is supported. Version ' + version + ' is not.'); + } + byteOffset += sizeOfUint32; + + // Skip byteLength + byteOffset += sizeOfUint32; + + var tilesLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var contentPromises = []; + + for (var i = 0; i < tilesLength; ++i) { + var tileType = getMagic(uint8Array, byteOffset); + + // Tile byte length is stored after magic and version + var tileByteLength = view.getUint32(byteOffset + sizeOfUint32 * 2, true); + + var contentFactory = factory[tileType]; + + if (defined(contentFactory)) { + var innerContent = contentFactory(content._tileset, content._tile, content._url, arrayBuffer, byteOffset); + content._contents.push(innerContent); + contentPromises.push(innerContent.readyPromise); + } else { + throw new RuntimeError('Unknown tile content type, ' + tileType + ', inside Composite tile'); + } + + byteOffset += tileByteLength; + } + + when.all(contentPromises).then(function() { + content._readyPromise.resolve(content); + }).otherwise(function(error) { + content._readyPromise.reject(error); + }); + } + /** - * 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. + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns false. Instead call hasProperty for a tile in the composite. */ - ImageryProvider.prototype.getTileCredits = DeveloperError.throwInstantiationError; + Composite3DTileContent.prototype.hasProperty = function(batchId, name) { + return false; + }; /** - * 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. + * Part of the {@link Cesium3DTileContent} interface. Composite3DTileContent + * always returns undefined. Instead call getFeature for a tile in the composite. */ - ImageryProvider.prototype.requestImage = DeveloperError.throwInstantiationError; + Composite3DTileContent.prototype.getFeature = function(batchId) { + return undefined; + }; /** - * 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. + * @inheritdoc Cesium3DTileContent#applyDebugSettings */ - ImageryProvider.prototype.pickFeatures = DeveloperError.throwInstantiationError; + Composite3DTileContent.prototype.applyDebugSettings = function(enabled, color) { + var contents = this._contents; + var length = contents.length; + for (var i = 0; i < length; ++i) { + contents[i].applyDebugSettings(enabled, color); + } + }; - var ktxRegex = /\.ktx$/i; - var crnRegex = /\.crn$/i; + /** + * @inheritdoc Cesium3DTileContent#applyStyle + */ + Composite3DTileContent.prototype.applyStyle = function(frameState, style) { + var contents = this._contents; + var length = contents.length; + for (var i = 0; i < length; ++i) { + contents[i].applyStyle(frameState, style); + } + }; /** - * 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 {ImageryProvider} imageryProvider The imagery provider for the URL. - * @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. + * @inheritdoc Cesium3DTileContent#update */ - ImageryProvider.loadImage = function(imageryProvider, url) { - if (ktxRegex.test(url)) { - return throttleRequestByServer(url, loadKTX); - } else if (crnRegex.test(url)) { - return throttleRequestByServer(url, loadCRN); - } else if (defined(imageryProvider.tileDiscardPolicy)) { - return throttleRequestByServer(url, loadImageViaBlob); + Composite3DTileContent.prototype.update = function(tileset, frameState) { + var contents = this._contents; + var length = contents.length; + for (var i = 0; i < length; ++i) { + contents[i].update(tileset, frameState); } - return throttleRequestByServer(url, loadImage); }; - return ImageryProvider; + /** + * @inheritdoc Cesium3DTileContent#isDestroyed + */ + Composite3DTileContent.prototype.isDestroyed = function() { + return false; + }; + + /** + * @inheritdoc Cesium3DTileContent#destroy + */ + Composite3DTileContent.prototype.destroy = function() { + var contents = this._contents; + var length = contents.length; + for (var i = 0; i < length; ++i) { + contents[i].destroy(); + } + return destroyObject(this); + }; + + return Composite3DTileContent; }); -/*global define*/ -define('Scene/ArcGisMapServerImageryProvider',[ - '../Core/Cartesian2', +define('Scene/ModelInstance',[ + '../Core/defineProperties', + '../Core/Matrix4' + ], function( + defineProperties, + Matrix4) { + 'use strict'; + + /** + * @private + */ + function ModelInstance(collection, modelMatrix, instanceId) { + this.primitive = collection; + this._modelMatrix = Matrix4.clone(modelMatrix); + this._instanceId = instanceId; + } + + defineProperties(ModelInstance.prototype, { + instanceId : { + get : function() { + return this._instanceId; + } + }, + model : { + get : function() { + return this.primitive._model; + } + }, + modelMatrix : { + get : function() { + return Matrix4.clone(this._modelMatrix); + }, + set : function(value) { + Matrix4.clone(value, this._modelMatrix); + this.primitive.expandBoundingSphere(this._modelMatrix); + this.primitive._dirty = true; + } + } + }); + + return ModelInstance; +}); + +define('Scene/ModelInstanceCollection',[ + '../Core/BoundingSphere', '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/Credit', + '../Core/clone', + '../Core/Color', + '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/destroyObject', '../Core/DeveloperError', - '../Core/Event', - '../Core/GeographicTilingScheme', - '../Core/loadJson', - '../Core/loadJsonp', - '../Core/Math', - '../Core/Rectangle', + '../Core/Matrix4', + '../Core/PrimitiveType', '../Core/RuntimeError', - '../Core/TileProviderError', - '../Core/WebMercatorProjection', - '../Core/WebMercatorTilingScheme', + '../Core/Transforms', + '../Renderer/Buffer', + '../Renderer/BufferUsage', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/ShaderSource', '../ThirdParty/when', - './DiscardMissingTileImagePolicy', - './ImageryLayerFeatureInfo', - './ImageryProvider' + './getAttributeOrUniformBySemantic', + './Model', + './ModelInstance', + './SceneMode', + './ShadowMode' ], function( - Cartesian2, + BoundingSphere, Cartesian3, - Cartographic, - Credit, + clone, + Color, + ComponentDatatype, defaultValue, defined, defineProperties, + destroyObject, DeveloperError, - Event, - GeographicTilingScheme, - loadJson, - loadJsonp, - CesiumMath, - Rectangle, + Matrix4, + PrimitiveType, RuntimeError, - TileProviderError, - WebMercatorProjection, - WebMercatorTilingScheme, + Transforms, + Buffer, + BufferUsage, + DrawCommand, + Pass, + ShaderSource, when, - DiscardMissingTileImagePolicy, - ImageryLayerFeatureInfo, - ImageryProvider) { + getAttributeOrUniformBySemantic, + Model, + ModelInstance, + SceneMode, + ShadowMode) { 'use strict'; + var LoadState = { + NEEDS_LOAD : 0, + LOADING : 1, + LOADED : 2, + FAILED : 3 + }; + /** - * Provides tiled imagery hosted by an ArcGIS MapServer. By default, the server's pre-cached tiles are - * used, if available. + * A 3D model instance collection. All instances reference the same underlying model, but have unique + * per-instance properties like model matrix, pick id, etc. * - * @alias ArcGisMapServerImageryProvider + * Instances are rendered relative-to-center and for best results instances should be positioned close to one another. + * Otherwise there may be precision issues if, for example, instances are placed on opposite sides of the globe. + * + * @alias ModelInstanceCollection * @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 - * + * @param {Object[]} [options.instances] An array of instances, where each instance contains a modelMatrix and optional batchId when options.batchTable is defined. + * @param {Cesium3DTileBatchTable} [options.batchTable] The batch table of the instanced 3D Tile. + * @param {String} [options.url] The url to the .gltf file. + * @param {Object} [options.headers] HTTP headers to send with the request. + * @param {Object} [options.requestType] The request type, used for request prioritization + * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] The object for the glTF JSON or an arraybuffer of Binary glTF defined by the CESIUM_binary_glTF extension. + * @param {String} [options.basePath=''] The base path that paths in the glTF JSON are relative to. + * @param {Boolean} [options.dynamic=false] Hint if instance model matrices will be updated frequently. + * @param {Boolean} [options.show=true] Determines if the collection will be shown. + * @param {Boolean} [options.allowPicking=true] When true, each instance is pickable with {@link Scene#pick}. + * @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 {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded. + * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the collection casts or receives shadows from each light source. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection. + * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe. * - * @example - * var esri = new Cesium.ArcGisMapServerImageryProvider({ - * url : 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' - * }); + * @exception {DeveloperError} Must specify either or , but not both. + * @exception {DeveloperError} Shader program cannot be optimized for instancing. Parameters cannot have any of the following semantics: MODEL, MODELINVERSE, MODELVIEWINVERSE, MODELVIEWPROJECTIONINVERSE, MODELINVERSETRANSPOSE. * - * @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} + * @private */ - function ArcGisMapServerImageryProvider(options) { - options = defaultValue(options, {}); + function ModelInstanceCollection(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this.show = defaultValue(options.show, true); + + this._instancingSupported = false; + this._dynamic = defaultValue(options.dynamic, false); + this._allowPicking = defaultValue(options.allowPicking, true); + this._ready = false; + this._readyPromise = when.defer(); + this._state = LoadState.NEEDS_LOAD; + this._dirty = false; + + // Undocumented options + this._cull = defaultValue(options.cull, true); + this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); + + this._instances = createInstances(this, options.instances); + + // When the model instance collection is backed by an i3dm tile, + // use its batch table resources to modify the shaders, attributes, and uniform maps. + this._batchTable = options.batchTable; + + this._model = undefined; + this._vertexBufferTypedArray = undefined; // Hold onto the vertex buffer contents when dynamic is true + this._vertexBuffer = undefined; + this._batchIdBuffer = undefined; + this._instancedUniformsByProgram = undefined; + + this._drawCommands = []; + this._pickCommands = []; + this._modelCommands = undefined; + + this._boundingSphere = createBoundingSphere(this); + this._center = Cartesian3.clone(this._boundingSphere.center); + this._rtcTransform = new Matrix4(); + this._rtcModelView = new Matrix4(); // Holds onto uniform + + this._mode = undefined; + + this.modelMatrix = Matrix4.clone(Matrix4.IDENTITY); + this._modelMatrix = Matrix4.clone(this.modelMatrix); + + // Passed on to Model this._url = options.url; - this._token = options.token; - this._tileDiscardPolicy = options.tileDiscardPolicy; - this._proxy = options.proxy; + this._headers = options.headers; + this._requestType = options.requestType; + this._gltf = options.gltf; + this._basePath = options.basePath; + this._asynchronous = options.asynchronous; + this._incrementallyLoadTextures = options.incrementallyLoadTextures; + this._upAxis = options.upAxis; // Undocumented option - 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; + this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); + this._shadows = this.shadows; - /** - * 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.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + this._debugShowBoundingVolume = false; - this._errorEvent = new Event(); + this.debugWireframe = defaultValue(options.debugWireframe, false); + this._debugWireframe = false; + } - this._ready = false; - this._readyPromise = when.defer(); + defineProperties(ModelInstanceCollection.prototype, { + allowPicking : { + get : function() { + return this._allowPicking; + } + }, + length : { + get : function() { + return this._instances.length; + } + }, + activeAnimations : { + get : function() { + return this._model.activeAnimations; + } + }, + ready : { + get : function() { + return this._ready; + } + }, + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + } + }); - // Grab the details of this MapServer. - var that = this; - var metadataError; + function createInstances(collection, instancesOptions) { + instancesOptions = defaultValue(instancesOptions, []); + var length = instancesOptions.length; + var instances = new Array(length); + for (var i = 0; i < length; ++i) { + var instanceOptions = instancesOptions[i]; + var modelMatrix = instanceOptions.modelMatrix; + var instanceId = defaultValue(instanceOptions.batchId, i); + instances[i] = new ModelInstance(collection, modelMatrix, instanceId); + } + return instances; + } - function metadataSuccess(data) { - var tileInfo = data.tileInfo; - if (!defined(tileInfo)) { - that._useTiles = false; - } else { - that._tileWidth = tileInfo.rows; - that._tileHeight = tileInfo.cols; + function createBoundingSphere(collection) { + var instancesLength = collection.length; + var points = new Array(instancesLength); + for (var i = 0; i < instancesLength; ++i) { + points[i] = Matrix4.getTranslation(collection._instances[i]._modelMatrix, new Cartesian3()); + } - 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; + return BoundingSphere.fromPoints(points); + } - 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 scratchCartesian = new Cartesian3(); + var scratchMatrix = new Matrix4(); - 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; + ModelInstanceCollection.prototype.expandBoundingSphere = function(instanceModelMatrix) { + var translation = Matrix4.getTranslation(instanceModelMatrix, scratchCartesian); + BoundingSphere.expand(this._boundingSphere, translation, this._boundingSphere); + }; + + function getInstancedUniforms(collection, programName) { + if (defined(collection._instancedUniformsByProgram)) { + return collection._instancedUniformsByProgram[programName]; + } + + var instancedUniformsByProgram = {}; + collection._instancedUniformsByProgram = instancedUniformsByProgram; + + // When using CESIUM_RTC_MODELVIEW the CESIUM_RTC center is ignored. Instances are always rendered relative-to-center. + var modelSemantics = ['MODEL', 'MODELVIEW', 'CESIUM_RTC_MODELVIEW', 'MODELVIEWPROJECTION', 'MODELINVERSE', 'MODELVIEWINVERSE', 'MODELVIEWPROJECTIONINVERSE', 'MODELINVERSETRANSPOSE', 'MODELVIEWINVERSETRANSPOSE']; + var supportedSemantics = ['MODELVIEW', 'CESIUM_RTC_MODELVIEW', 'MODELVIEWPROJECTION', 'MODELVIEWINVERSETRANSPOSE']; + + var gltf = collection._model.gltf; + var techniques = gltf.techniques; + for (var techniqueName in techniques) { + if (techniques.hasOwnProperty(techniqueName)) { + var technique = techniques[techniqueName]; + var parameters = technique.parameters; + var uniforms = technique.uniforms; + var program = technique.program; + + // Different techniques may share the same program, skip if already processed. + // This assumes techniques that share a program do not declare different semantics for the same uniforms. + if (!defined(instancedUniformsByProgram[program])) { + var uniformMap = {}; + instancedUniformsByProgram[program] = uniformMap; + for (var uniformName in uniforms) { + if (uniforms.hasOwnProperty(uniformName)) { + var parameterName = uniforms[uniformName]; + var parameter = parameters[parameterName]; + var semantic = parameter.semantic; + if (defined(semantic) && (modelSemantics.indexOf(semantic) > -1)) { + if (supportedSemantics.indexOf(semantic) > -1) { + uniformMap[uniformName] = semantic; + } else { + throw new RuntimeError('Shader program cannot be optimized for instancing. ' + + 'Parameter "' + parameter + '" in program "' + programName + + '" uses unsupported semantic "' + semantic + '"' + ); + } + } } } - } 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 - }); + return instancedUniformsByProgram[programName]; + } + + var vertexShaderCached; + + function getVertexShaderCallback(collection) { + return function(vs, programName) { + var instancedUniforms = getInstancedUniforms(collection, programName); + var usesBatchTable = defined(collection._batchTable); + + var renamedSource = ShaderSource.replaceMain(vs, 'czm_instancing_main'); + + var globalVarsHeader = ''; + var globalVarsMain = ''; + for (var uniform in instancedUniforms) { + if (instancedUniforms.hasOwnProperty(uniform)) { + var semantic = instancedUniforms[uniform]; + var varName; + if (semantic === 'MODELVIEW' || semantic === 'CESIUM_RTC_MODELVIEW') { + varName = 'czm_instanced_modelView'; + } else if (semantic === 'MODELVIEWPROJECTION') { + varName = 'czm_instanced_modelViewProjection'; + globalVarsHeader += 'mat4 czm_instanced_modelViewProjection;\n'; + globalVarsMain += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n'; + } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') { + varName = 'czm_instanced_modelViewInverseTranspose'; + globalVarsHeader += 'mat3 czm_instanced_modelViewInverseTranspose;\n'; + globalVarsMain += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n'; + } + + // Remove the uniform declaration + var regex = new RegExp('uniform.*' + uniform + '.*'); + renamedSource = renamedSource.replace(regex, ''); + + // Replace all occurrences of the uniform with the global variable + regex = new RegExp(uniform + '\\b', 'g'); + renamedSource = renamedSource.replace(regex, varName); } + } - that._useTiles = true; + // czm_instanced_model is the model matrix of the instance relative to center + // czm_instanced_modifiedModelView is the transform from the center to view + // czm_instanced_nodeTransform is the local offset of the node within the model + var uniforms = + 'uniform mat4 czm_instanced_modifiedModelView;\n' + + 'uniform mat4 czm_instanced_nodeTransform;\n'; + + var batchIdAttribute = usesBatchTable ? 'attribute float a_batchId;\n' : ''; + + var instancedSource = + uniforms + + globalVarsHeader + + 'mat4 czm_instanced_modelView;\n' + + 'attribute vec4 czm_modelMatrixRow0;\n' + + 'attribute vec4 czm_modelMatrixRow1;\n' + + 'attribute vec4 czm_modelMatrixRow2;\n' + + batchIdAttribute + + renamedSource + + 'void main()\n' + + '{\n' + + ' mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n' + + ' czm_instanced_modelView = czm_instanced_modifiedModelView * czm_instanced_model * czm_instanced_nodeTransform;\n' + + globalVarsMain + + ' czm_instancing_main();\n' + + '}'; + + vertexShaderCached = instancedSource; + + if (usesBatchTable) { + instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId')(instancedSource); } - if (defined(data.copyrightText) && data.copyrightText.length > 0) { - that._credit = new Credit(data.copyrightText); + return instancedSource; + }; + } + + function getFragmentShaderCallback(collection) { + return function(fs) { + var batchTable = collection._batchTable; + if (defined(batchTable)) { + var gltf = collection._model.gltf; + var diffuseUniformName = getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE'); + fs = batchTable.getFragmentShaderCallback(true, diffuseUniformName)(fs); } + return fs; + }; + } - that._ready = true; - that._readyPromise.resolve(true); - TileProviderError.handleSuccess(metadataError); + function getPickVertexShaderCallback(collection) { + return function (vs) { + // Use the vertex shader that was generated earlier + vs = vertexShaderCached; + var usesBatchTable = defined(collection._batchTable); + var allowPicking = collection._allowPicking; + if (usesBatchTable) { + vs = collection._batchTable.getPickVertexShaderCallback('a_batchId')(vs); + } else if (allowPicking) { + vs = ShaderSource.createPickVertexShaderSource(vs); + } + return vs; + }; + } + + function getPickFragmentShaderCallback(collection) { + return function(fs) { + var usesBatchTable = defined(collection._batchTable); + var allowPicking = collection._allowPicking; + if (usesBatchTable) { + fs = collection._batchTable.getPickFragmentShaderCallback()(fs); + } else if (allowPicking) { + fs = ShaderSource.createPickFragmentShaderSource(fs, 'varying'); + } + return fs; + }; + } + + function createModifiedModelView(collection, context) { + return function() { + return Matrix4.multiply(context.uniformState.view, collection._rtcTransform, collection._rtcModelView); + }; + } + + function createNodeTransformFunction(node) { + return function() { + return node.computedMatrix; + }; + } + + function getUniformMapCallback(collection, context) { + return function(uniformMap, programName, node) { + uniformMap = clone(uniformMap); + uniformMap.czm_instanced_modifiedModelView = createModifiedModelView(collection, context); + uniformMap.czm_instanced_nodeTransform = createNodeTransformFunction(node); + + // Remove instanced uniforms from the uniform map + var instancedUniforms = getInstancedUniforms(collection, programName); + for (var uniform in instancedUniforms) { + if (instancedUniforms.hasOwnProperty(uniform)) { + delete uniformMap[uniform]; + } + } + + if (defined(collection._batchTable)) { + uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap); + } + + return uniformMap; + }; + } + + function getPickUniformMapCallback(collection) { + return function(uniformMap) { + // Uses the uniform map generated from getUniformMapCallback + if (defined(collection._batchTable)) { + uniformMap = collection._batchTable.getPickUniformMapCallback()(uniformMap); + } + return uniformMap; + }; + } + + function getVertexShaderNonInstancedCallback(collection) { + return function(vs) { + if (defined(collection._batchTable)) { + vs = collection._batchTable.getVertexShaderCallback(true, 'a_batchId')(vs); + // Treat a_batchId as a uniform rather than a vertex attribute + vs = 'uniform float a_batchId\n;' + vs; + } + return vs; + }; + } + + function getPickVertexShaderNonInstancedCallback(collection) { + return function(vs) { + if (defined(collection._batchTable)) { + vs = collection._batchTable.getPickVertexShaderCallback('a_batchId')(vs); + // Treat a_batchId as a uniform rather than a vertex attribute + vs = 'uniform float a_batchId\n;' + vs; + } + return vs; + }; + } + + function getPickFragmentShaderNonInstancedCallback(collection) { + return function(fs) { + var usesBatchTable = defined(collection._batchTable); + var allowPicking = collection._allowPicking; + if (usesBatchTable) { + fs = collection._batchTable.getPickFragmentShaderCallback()(fs); + } else if (allowPicking) { + fs = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); + } + return fs; + }; + } + + function getUniformMapNonInstancedCallback(collection) { + return function(uniformMap) { + if (defined(collection._batchTable)) { + uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap); + } + + return uniformMap; + }; + } + + function getVertexBufferTypedArray(collection) { + var instances = collection._instances; + var instancesLength = collection.length; + var collectionCenter = collection._center; + var vertexSizeInFloats = 12; + + var bufferData = collection._vertexBufferTypedArray; + if (!defined(bufferData)) { + bufferData = new Float32Array(instancesLength * vertexSizeInFloats); + } + if (collection._dynamic) { + // Hold onto the buffer data so we don't have to allocate new memory every frame. + collection._vertexBufferTypedArray = bufferData; } - 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)); + for (var i = 0; i < instancesLength; ++i) { + var modelMatrix = instances[i]._modelMatrix; + + // Instance matrix is relative to center + var instanceMatrix = Matrix4.clone(modelMatrix, scratchMatrix); + instanceMatrix[12] -= collectionCenter.x; + instanceMatrix[13] -= collectionCenter.y; + instanceMatrix[14] -= collectionCenter.z; + + var offset = i * vertexSizeInFloats; + + // First three rows of the model matrix + bufferData[offset + 0] = instanceMatrix[0]; + bufferData[offset + 1] = instanceMatrix[4]; + bufferData[offset + 2] = instanceMatrix[8]; + bufferData[offset + 3] = instanceMatrix[12]; + bufferData[offset + 4] = instanceMatrix[1]; + bufferData[offset + 5] = instanceMatrix[5]; + bufferData[offset + 6] = instanceMatrix[9]; + bufferData[offset + 7] = instanceMatrix[13]; + bufferData[offset + 8] = instanceMatrix[2]; + bufferData[offset + 9] = instanceMatrix[6]; + bufferData[offset + 10] = instanceMatrix[10]; + bufferData[offset + 11] = instanceMatrix[14]; } - function requestMetadata() { - var parameters = { - f: 'json' + return bufferData; + } + + function createVertexBuffer(collection, context) { + var i; + var instances = collection._instances; + var instancesLength = collection.length; + var dynamic = collection._dynamic; + var usesBatchTable = defined(collection._batchTable); + var allowPicking = collection._allowPicking; + + if (usesBatchTable) { + var batchIdBufferData = new Uint16Array(instancesLength); + for (i = 0; i < instancesLength; ++i) { + batchIdBufferData[i] = instances[i]._instanceId; + } + collection._batchIdBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : batchIdBufferData, + usage : BufferUsage.STATIC_DRAW + }); + } + + if (allowPicking && !usesBatchTable) { + var pickIdBuffer = new Uint8Array(instancesLength * 4); + for (i = 0; i < instancesLength; ++i) { + var pickId = collection._pickIds[i]; + var pickColor = pickId.color; + var offset = i * 4; + pickIdBuffer[offset] = Color.floatToByte(pickColor.red); + pickIdBuffer[offset + 1] = Color.floatToByte(pickColor.green); + pickIdBuffer[offset + 2] = Color.floatToByte(pickColor.blue); + pickIdBuffer[offset + 3] = Color.floatToByte(pickColor.alpha); + } + collection._pickIdBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : pickIdBuffer, + usage : BufferUsage.STATIC_DRAW + }); + } + + var vertexBufferTypedArray = getVertexBufferTypedArray(collection); + collection._vertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : vertexBufferTypedArray, + usage : dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW + }); + } + + function updateVertexBuffer(collection) { + var vertexBufferTypedArray = getVertexBufferTypedArray(collection); + collection._vertexBuffer.copyFromArrayView(vertexBufferTypedArray); + } + + function createPickIds(collection, context) { + // PERFORMANCE_IDEA: we could skip the pick buffer completely by allocating + // a continuous range of pickIds and then converting the base pickId + batchId + // to RGBA in the shader. The only consider is precision issues, which might + // not be an issue in WebGL 2. + var instances = collection._instances; + var instancesLength = instances.length; + var pickIds = new Array(instancesLength); + for (var i = 0; i < instancesLength; ++i) { + pickIds[i] = context.createPickId(instances[i]); + } + return pickIds; + } + + function createModel(collection, context) { + var instancingSupported = collection._instancingSupported; + var usesBatchTable = defined(collection._batchTable); + var allowPicking = collection._allowPicking; + + var modelOptions = { + url : collection._url, + headers : collection._headers, + requestType : collection._requestType, + gltf : collection._gltf, + basePath : collection._basePath, + shadows : collection._shadows, + cacheKey : undefined, + asynchronous : collection._asynchronous, + allowPicking : allowPicking, + incrementallyLoadTextures : collection._incrementallyLoadTextures, + upAxis : collection._upAxis, + precreatedAttributes : undefined, + vertexShaderLoaded : undefined, + fragmentShaderLoaded : undefined, + uniformMapLoaded : undefined, + pickVertexShaderLoaded : undefined, + pickFragmentShaderLoaded : undefined, + pickUniformMapLoaded : undefined, + ignoreCommands : true, + opaquePass : collection._opaquePass + }; + + if (allowPicking && !usesBatchTable) { + collection._pickIds = createPickIds(collection, context); + } + + if (instancingSupported) { + createVertexBuffer(collection, context); + + var vertexSizeInFloats = 12; + var componentSizeInBytes = ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT); + + var instancedAttributes = { + czm_modelMatrixRow0 : { + index : 0, // updated in Model + vertexBuffer : collection._vertexBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : 0, + strideInBytes : componentSizeInBytes * vertexSizeInFloats, + instanceDivisor : 1 + }, + czm_modelMatrixRow1 : { + index : 0, // updated in Model + vertexBuffer : collection._vertexBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : componentSizeInBytes * 4, + strideInBytes : componentSizeInBytes * vertexSizeInFloats, + instanceDivisor : 1 + }, + czm_modelMatrixRow2 : { + index : 0, // updated in Model + vertexBuffer : collection._vertexBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : componentSizeInBytes * 8, + strideInBytes : componentSizeInBytes * vertexSizeInFloats, + instanceDivisor : 1 + } }; - if (defined(that._token)) { - parameters.token = that._token; + // When using a batch table, add a batch id attribute + if (usesBatchTable) { + instancedAttributes.a_batchId = { + index : 0, // updated in Model + vertexBuffer : collection._batchIdBuffer, + componentsPerAttribute : 1, + componentDatatype : ComponentDatatype.UNSIGNED_SHORT, + normalize : false, + offsetInBytes : 0, + strideInBytes : 0, + instanceDivisor : 1 + }; } - var metadata = loadJsonp(that._url, { - parameters : parameters, - proxy : that._proxy - }); - when(metadata, metadataSuccess, metadataFailure); + if (allowPicking && !usesBatchTable) { + instancedAttributes.pickColor = { + index : 0, // updated in Model + vertexBuffer : collection._pickIdBuffer, + componentsPerAttribute : 4, + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + normalize : true, + offsetInBytes : 0, + strideInBytes : 0, + instanceDivisor : 1 + }; + } + + modelOptions.precreatedAttributes = instancedAttributes; + modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection); + modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection); + modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context); + modelOptions.pickVertexShaderLoaded = getPickVertexShaderCallback(collection); + modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderCallback(collection); + modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(collection); + + if (defined(collection._url)) { + modelOptions.cacheKey = collection._url + '#instanced'; + } + } else { + modelOptions.vertexShaderLoaded = getVertexShaderNonInstancedCallback(collection); + modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection); + modelOptions.uniformMapLoaded = getUniformMapNonInstancedCallback(collection, context); + modelOptions.pickVertexShaderLoaded = getPickVertexShaderNonInstancedCallback(collection); + modelOptions.pickFragmentShaderLoaded = getPickFragmentShaderNonInstancedCallback(collection); + modelOptions.pickUniformMapLoaded = getPickUniformMapCallback(collection); } - if (this._useTiles) { - requestMetadata(); + if (defined(collection._url)) { + collection._model = Model.fromGltf(modelOptions); } else { - this._ready = true; - this._readyPromise.resolve(true); + collection._model = new Model(modelOptions); } } - 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; + function updateWireframe(collection) { + if (collection._debugWireframe !== collection.debugWireframe) { + collection._debugWireframe = collection.debugWireframe; - url = imageryProvider._url + '/export?'; - url += 'bbox=' + bbox; - if (imageryProvider._tilingScheme instanceof GeographicTilingScheme) { - url += '&bboxSR=4326&imageSR=4326'; - } else { - url += '&bboxSR=3857&imageSR=3857'; + // This assumes the original primitive was TRIANGLES and that the triangles + // are connected for the wireframe to look perfect. + var primitiveType = collection.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES; + var commands = collection._drawCommands; + var length = commands.length; + for (var i = 0; i < length; ++i) { + commands[i].primitiveType = primitiveType; } - url += '&size=' + imageryProvider._tileWidth + '%2C' + imageryProvider._tileHeight; - url += '&format=png&transparent=true&f=image'; + } + } + function updateShowBoundingVolume(collection) { + if (collection.debugShowBoundingVolume !== collection._debugShowBoundingVolume) { + collection._debugShowBoundingVolume = collection.debugShowBoundingVolume; - if (imageryProvider.layers) { - url += '&layers=show:' + imageryProvider.layers; + var commands = collection._drawCommands; + var length = commands.length; + for (var i = 0; i < length; ++i) { + commands[i].debugShowBoundingVolume = collection.debugShowBoundingVolume; } } + } - var token = imageryProvider._token; - if (defined(token)) { - if (url.indexOf('?') === -1) { - url += '?'; + function createCommands(collection, drawCommands, pickCommands) { + var commandsLength = drawCommands.length; + var instancesLength = collection.length; + var allowPicking = collection.allowPicking; + var boundingSphere = collection._boundingSphere; + var cull = collection._cull; + + for (var i = 0; i < commandsLength; ++i) { + var drawCommand = DrawCommand.shallowClone(drawCommands[i]); + drawCommand.instanceCount = instancesLength; + drawCommand.boundingVolume = boundingSphere; + drawCommand.cull = cull; + collection._drawCommands.push(drawCommand); + + if (allowPicking) { + var pickCommand = DrawCommand.shallowClone(pickCommands[i]); + pickCommand.instanceCount = instancesLength; + pickCommand.boundingVolume = boundingSphere; + pickCommand.cull = cull; + collection._pickCommands.push(pickCommand); } - if (url[url.length - 1] !== '?'){ - url += '&'; + } + } + + function createBatchIdFunction(batchId) { + return function() { + return batchId; + }; + } + + function createPickColorFunction(color) { + return function() { + return color; + }; + } + + function createCommandsNonInstanced(collection, drawCommands, pickCommands) { + // When instancing is disabled, create commands for every instance. + var instances = collection._instances; + var commandsLength = drawCommands.length; + var instancesLength = collection.length; + var allowPicking = collection.allowPicking; + var usesBatchTable = defined(collection._batchTable); + var cull = collection._cull; + + for (var i = 0; i < commandsLength; ++i) { + for (var j = 0; j < instancesLength; ++j) { + var drawCommand = DrawCommand.shallowClone(drawCommands[i]); + drawCommand.modelMatrix = new Matrix4(); // Updated in updateCommandsNonInstanced + drawCommand.boundingVolume = new BoundingSphere(); // Updated in updateCommandsNonInstanced + drawCommand.cull = cull; + drawCommand.uniformMap = clone(drawCommand.uniformMap); + if (usesBatchTable) { + drawCommand.uniformMap.a_batchId = createBatchIdFunction(instances[j]._instanceId); + } + collection._drawCommands.push(drawCommand); + + if (allowPicking) { + var pickCommand = DrawCommand.shallowClone(pickCommands[i]); + pickCommand.modelMatrix = new Matrix4(); // Updated in updateCommandsNonInstanced + pickCommand.boundingVolume = new BoundingSphere(); // Updated in updateCommandsNonInstanced + pickCommand.cull = cull; + pickCommand.uniformMap = clone(pickCommand.uniformMap); + if (usesBatchTable) { + pickCommand.uniformMap.a_batchId = createBatchIdFunction(instances[j]._instanceId); + } else if (allowPicking) { + var pickId = collection._pickIds[j]; + pickCommand.uniformMap.czm_pickColor = createPickColorFunction(pickId.color); + } + collection._pickCommands.push(pickCommand); + } + } + } + } + + function updateCommandsNonInstanced(collection) { + var modelCommands = collection._modelCommands; + var commandsLength = modelCommands.length; + var instancesLength = collection.length; + var allowPicking = collection.allowPicking; + var collectionTransform = collection._rtcTransform; + var collectionCenter = collection._center; + + for (var i = 0; i < commandsLength; ++i) { + var modelCommand = modelCommands[i]; + for (var j = 0; j < instancesLength; ++j) { + var commandIndex = i * instancesLength + j; + var drawCommand = collection._drawCommands[commandIndex]; + var instanceMatrix = Matrix4.clone(collection._instances[j]._modelMatrix, scratchMatrix); + instanceMatrix[12] -= collectionCenter.x; + instanceMatrix[13] -= collectionCenter.y; + instanceMatrix[14] -= collectionCenter.z; + instanceMatrix = Matrix4.multiply(collectionTransform, instanceMatrix, scratchMatrix); + var nodeMatrix = modelCommand.modelMatrix; + var modelMatrix = drawCommand.modelMatrix; + Matrix4.multiply(instanceMatrix, nodeMatrix, modelMatrix); + + var nodeBoundingSphere = modelCommand.boundingVolume; + var boundingSphere = drawCommand.boundingVolume; + BoundingSphere.transform(nodeBoundingSphere, instanceMatrix, boundingSphere); + + if (allowPicking) { + var pickCommand = collection._pickCommands[commandIndex]; + Matrix4.clone(modelMatrix, pickCommand.modelMatrix); + BoundingSphere.clone(boundingSphere, pickCommand.boundingVolume); + } } - url += 'token=' + token; } + } - var proxy = imageryProvider._proxy; - if (defined(proxy)) { - url = proxy.getURL(url); + function getModelCommands(model) { + var nodeCommands = model._nodeCommands; + var length = nodeCommands.length; + + var drawCommands = []; + var pickCommands = []; + + for (var i = 0; i < length; ++i) { + var nc = nodeCommands[i]; + if (nc.show) { + drawCommands.push(nc.command); + pickCommands.push(nc.pickCommand); + } } - return url; + return { + draw: drawCommands, + pick: pickCommands + }; + } + + function updateShadows(collection) { + if (collection.shadows !== collection._shadows) { + collection._shadows = collection.shadows; + + var castShadows = ShadowMode.castShadows(collection.shadows); + var receiveShadows = ShadowMode.receiveShadows(collection.shadows); + + var drawCommands = collection._drawCommands; + var length = drawCommands.length; + for (var i = 0; i < length; ++i) { + var drawCommand = drawCommands[i]; + drawCommand.castShadows = castShadows; + drawCommand.receiveShadows = receiveShadows; + } + } + } + + ModelInstanceCollection.prototype.update = function(frameState) { + if (frameState.mode === SceneMode.MORPHING) { + return; + } + + if (!this.show) { + return; + } + + if (this.length === 0) { + return; + } + + var context = frameState.context; + + if (this._state === LoadState.NEEDS_LOAD) { + this._state = LoadState.LOADING; + this._instancingSupported = context.instancedArrays; + createModel(this, context); + var that = this; + this._model.readyPromise.otherwise(function(error) { + that._state = LoadState.FAILED; + that._readyPromise.reject(error); + }); + } + + var instancingSupported = this._instancingSupported; + var model = this._model; + model.update(frameState); + + if (model.ready && (this._state === LoadState.LOADING)) { + this._state = LoadState.LOADED; + this._ready = true; + + // Expand bounding volume to fit the radius of the loaded model including the model's offset from the center + var modelRadius = model.boundingSphere.radius + Cartesian3.magnitude(model.boundingSphere.center); + this._boundingSphere.radius += modelRadius; + + var modelCommands = getModelCommands(model); + this._modelCommands = modelCommands.draw; + + if (instancingSupported) { + createCommands(this, modelCommands.draw, modelCommands.pick); + } else { + createCommandsNonInstanced(this, modelCommands.draw, modelCommands.pick); + updateCommandsNonInstanced(this); + } + + this._readyPromise.resolve(this); + return; + } + + if (this._state !== LoadState.LOADED) { + return; + } + + var modeChanged = (frameState.mode !== this._mode); + var modelMatrix = this.modelMatrix; + var modelMatrixChanged = !Matrix4.equals(this._modelMatrix, modelMatrix); + + if (modeChanged || modelMatrixChanged) { + this._mode = frameState.mode; + Matrix4.clone(modelMatrix, this._modelMatrix); + var rtcTransform = Matrix4.multiplyByTranslation(this._modelMatrix, this._center, this._rtcTransform); + if (this._mode !== SceneMode.SCENE3D) { + rtcTransform = Transforms.basisTo2D(frameState.mapProjection, rtcTransform, rtcTransform); + } + Matrix4.getTranslation(rtcTransform, this._boundingSphere.center); + } + + if (instancingSupported && this._dirty) { + // If at least one instance has moved assume the collection is now dynamic + this._dynamic = true; + this._dirty = false; + + // PERFORMANCE_IDEA: only update dirty sub-sections instead of the whole collection + updateVertexBuffer(this); + } + + // If any node changes due to an animation, update the commands. This could be inefficient if the model is + // composed of many nodes and only one changes, however it is probably fine in the general use case. + // Only applies when instancing is disabled. The instanced shader automatically handles node transformations. + if (!instancingSupported && (model.dirty || this._dirty || modeChanged || modelMatrixChanged)) { + updateCommandsNonInstanced(this); + } + + updateShadows(this); + updateWireframe(this); + updateShowBoundingVolume(this); + + var passes = frameState.passes; + var commandList = frameState.commandList; + var commands = passes.render ? this._drawCommands : this._pickCommands; + var commandsLength = commands.length; + + for (var i = 0; i < commandsLength; ++i) { + commandList.push(commands[i]); + } + }; + + ModelInstanceCollection.prototype.isDestroyed = function() { + return false; + }; + + ModelInstanceCollection.prototype.destroy = function() { + this._model = this._model && this._model.destroy(); + + var pickIds = this._pickIds; + if (defined(pickIds)) { + var length = pickIds.length; + for (var i = 0; i < length; ++i) { + pickIds[i].destroy(); + } + } + + return destroyObject(this); + }; + + return ModelInstanceCollection; +}); + +define('Scene/Instanced3DModel3DTileContent',[ + '../Core/AttributeCompression', + '../Core/Cartesian3', + '../Core/Color', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/deprecationWarning', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/Ellipsoid', + '../Core/FeatureDetection', + '../Core/getAbsoluteUri', + '../Core/getBaseUri', + '../Core/getStringFromTypedArray', + '../Core/joinUrls', + '../Core/Matrix3', + '../Core/Matrix4', + '../Core/Quaternion', + '../Core/RequestType', + '../Core/RuntimeError', + '../Core/Transforms', + '../Core/TranslationRotationScale', + '../Renderer/Pass', + './Cesium3DTileBatchTable', + './Cesium3DTileFeature', + './Cesium3DTileFeatureTable', + './ModelInstanceCollection' + ], function( + AttributeCompression, + Cartesian3, + Color, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + deprecationWarning, + destroyObject, + DeveloperError, + Ellipsoid, + FeatureDetection, + getAbsoluteUri, + getBaseUri, + getStringFromTypedArray, + joinUrls, + Matrix3, + Matrix4, + Quaternion, + RequestType, + RuntimeError, + Transforms, + TranslationRotationScale, + Pass, + Cesium3DTileBatchTable, + Cesium3DTileFeature, + Cesium3DTileFeatureTable, + ModelInstanceCollection) { + '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 {}; + } + + /** + * Represents the contents of a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Instanced3DModel/README.md|Instanced 3D Model} + * tile in a {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset. + *

    + * Implements the {@link Cesium3DTileContent} interface. + *

    + * + * @alias Instanced3DModel3DTileContent + * @constructor + * + * @private + */ + function Instanced3DModel3DTileContent(tileset, tile, url, arrayBuffer, byteOffset) { + this._tileset = tileset; + this._tile = tile; + this._url = url; + this._modelInstanceCollection = undefined; + this._batchTable = undefined; + this._features = undefined; + + /** + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty + */ + this.featurePropertiesDirty = false; + + initialize(this, arrayBuffer, byteOffset); } - defineProperties(ArcGisMapServerImageryProvider.prototype, { + // This can be overridden for testing purposes + Instanced3DModel3DTileContent._deprecationWarning = deprecationWarning; + + defineProperties(Instanced3DModel3DTileContent.prototype, { + /** + * @inheritdoc Cesium3DTileContent#featuresLength + */ + featuresLength : { + get : function() { + return this._batchTable.featuresLength; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#pointsLength + */ + pointsLength : { + get : function() { + return 0; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#trianglesLength + */ + trianglesLength : { + get : function() { + var model = this._modelInstanceCollection._model; + if (defined(model)) { + return model.trianglesLength; + } + return 0; + } + }, + /** - * Gets the URL of the ArcGIS MapServer. - * @memberof ArcGisMapServerImageryProvider.prototype - * @type {String} - * @readonly + * @inheritdoc Cesium3DTileContent#geometryByteLength */ - url : { + geometryByteLength : { get : function() { - return this._url; + var model = this._modelInstanceCollection._model; + if (defined(model)) { + return model.geometryByteLength; + } + return 0; } }, /** - * Gets the ArcGIS token used to authenticate with the ArcGis MapServer service. - * @memberof ArcGisMapServerImageryProvider.prototype - * @type {String} - * @readonly + * @inheritdoc Cesium3DTileContent#texturesByteLength */ - token : { + texturesByteLength : { get : function() { - return this._token; + var model = this._modelInstanceCollection._model; + if (defined(model)) { + return model.texturesByteLength; + } + return 0; } }, /** - * Gets the proxy used by this provider. - * @memberof ArcGisMapServerImageryProvider.prototype - * @type {Proxy} - * @readonly + * @inheritdoc Cesium3DTileContent#batchTableByteLength */ - proxy : { + batchTableByteLength : { get : function() { - return this._proxy; + return this._batchTable.memorySizeInBytes; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#innerContents */ - tileWidth : { + innerContents : { get : function() { - - return this._tileWidth; + return undefined; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#readyPromise */ - tileHeight: { + readyPromise : { get : function() { - - return this._tileHeight; + return this._modelInstanceCollection.readyPromise; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tileset */ - maximumLevel : { + tileset : { get : function() { - - return this._maximumLevel; + return this._tileset; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tile */ - minimumLevel : { + tile : { get : function() { - + return this._tile; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#url + */ + url: { + get: function() { + return this._url; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#batchTable + */ + batchTable : { + get : function() { + return this._batchTable; + } + } + }); + + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; + var propertyScratch1 = new Array(4); + var propertyScratch2 = new Array(4); + + function initialize(content, arrayBuffer, byteOffset) { + var byteStart = defaultValue(byteOffset, 0); + byteOffset = byteStart; + + var uint8Array = new Uint8Array(arrayBuffer); + var view = new DataView(arrayBuffer); + byteOffset += sizeOfUint32; // Skip magic + + var version = view.getUint32(byteOffset, true); + if (version !== 1) { + throw new RuntimeError('Only Instanced 3D Model version 1 is supported. Version ' + version + ' is not.'); + } + byteOffset += sizeOfUint32; + + var byteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var featureTableJsonByteLength = view.getUint32(byteOffset, true); + if (featureTableJsonByteLength === 0) { + throw new RuntimeError('featureTableJsonByteLength is zero, the feature table must be defined.'); + } + byteOffset += sizeOfUint32; + + var featureTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchTableJsonByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var batchTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var gltfFormat = view.getUint32(byteOffset, true); + if (gltfFormat !== 1 && gltfFormat !== 0) { + throw new RuntimeError('Only glTF format 0 (uri) or 1 (embedded) are supported. Format ' + gltfFormat + ' is not.'); + } + byteOffset += sizeOfUint32; + + var featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength); + var featureTableJson = JSON.parse(featureTableString); + byteOffset += featureTableJsonByteLength; + + var featureTableBinary = new Uint8Array(arrayBuffer, byteOffset, featureTableBinaryByteLength); + byteOffset += featureTableBinaryByteLength; + + var featureTable = new Cesium3DTileFeatureTable(featureTableJson, featureTableBinary); + var instancesLength = featureTable.getGlobalProperty('INSTANCES_LENGTH'); + featureTable.featuresLength = instancesLength; + + if (!defined(instancesLength)) { + throw new RuntimeError('Feature table global property: INSTANCES_LENGTH must be defined'); + } + + var batchTableJson; + var batchTableBinary; + if (batchTableJsonByteLength > 0) { + var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength); + batchTableJson = JSON.parse(batchTableString); + byteOffset += batchTableJsonByteLength; + + if (batchTableBinaryByteLength > 0) { + // Has a batch table binary + batchTableBinary = new Uint8Array(arrayBuffer, byteOffset, batchTableBinaryByteLength); + // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed + batchTableBinary = new Uint8Array(batchTableBinary); + byteOffset += batchTableBinaryByteLength; + } + } + + content._batchTable = new Cesium3DTileBatchTable(content, instancesLength, batchTableJson, batchTableBinary); + + var gltfByteLength = byteStart + byteLength - byteOffset; + if (gltfByteLength === 0) { + throw new RuntimeError('glTF byte length is zero, i3dm must have a glTF to instance.'); + } + + var gltfView; + if (byteOffset % 4 === 0) { + gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength); + } else { + // Create a copy of the glb so that it is 4-byte aligned + Instanced3DModel3DTileContent._deprecationWarning('i3dm-glb-unaligned', 'The embedded glb is not aligned to a 4-byte boundary.'); + gltfView = new Uint8Array(uint8Array.subarray(byteOffset, byteOffset + gltfByteLength)); + } + + // Create model instance collection + var collectionOptions = { + instances : new Array(instancesLength), + batchTable : content._batchTable, + cull : false, // Already culled by 3D Tiles + url : undefined, + requestType : RequestType.TILES3D, + gltf : undefined, + basePath : undefined, + incrementallyLoadTextures : false, + upAxis : content._tileset._gltfUpAxis, + opaquePass : Pass.CESIUM_3D_TILE // Draw opaque portions during the 3D Tiles pass + }; + + if (gltfFormat === 0) { + var gltfUrl = getStringFromTypedArray(gltfView); + + // We need to remove padding from the end of the model URL in case this tile was part of a composite tile. + // This removes all white space and null characters from the end of the string. + gltfUrl = gltfUrl.replace(/[\s\0]+$/, ''); + collectionOptions.url = getAbsoluteUri(joinUrls(getBaseUri(content._url, true), gltfUrl)); + } else { + collectionOptions.gltf = gltfView; + collectionOptions.basePath = getAbsoluteUri(getBaseUri(content._url, true)); + } + + var eastNorthUp = featureTable.getGlobalProperty('EAST_NORTH_UP'); + + var rtcCenter; + var rtcCenterArray = featureTable.getGlobalProperty('RTC_CENTER', ComponentDatatype.FLOAT, 3); + if (defined(rtcCenterArray)) { + rtcCenter = Cartesian3.unpack(rtcCenterArray); + } + + var instances = collectionOptions.instances; + var instancePosition = new Cartesian3(); + var instancePositionArray = new Array(3); + var instanceNormalRight = new Cartesian3(); + var instanceNormalUp = new Cartesian3(); + var instanceNormalForward = new Cartesian3(); + var instanceRotation = new Matrix3(); + var instanceQuaternion = new Quaternion(); + var instanceScale = new Cartesian3(); + var instanceTranslationRotationScale = new TranslationRotationScale(); + var instanceTransform = new Matrix4(); + for (var i = 0; i < instancesLength; i++) { + // Get the instance position + var position = featureTable.getProperty('POSITION', ComponentDatatype.FLOAT, 3, i, propertyScratch1); + if (!defined(position)) { + position = instancePositionArray; + var positionQuantized = featureTable.getProperty('POSITION_QUANTIZED', ComponentDatatype.UNSIGNED_SHORT, 3, i, propertyScratch1); + if (!defined(positionQuantized)) { + throw new RuntimeError('Either POSITION or POSITION_QUANTIZED must be defined for each instance.'); + } + var quantizedVolumeOffset = featureTable.getGlobalProperty('QUANTIZED_VOLUME_OFFSET', ComponentDatatype.FLOAT, 3); + if (!defined(quantizedVolumeOffset)) { + throw new RuntimeError('Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions.'); + } + var quantizedVolumeScale = featureTable.getGlobalProperty('QUANTIZED_VOLUME_SCALE', ComponentDatatype.FLOAT, 3); + if (!defined(quantizedVolumeScale)) { + throw new RuntimeError('Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions.'); + } + for (var j = 0; j < 3; j++) { + position[j] = (positionQuantized[j] / 65535.0 * quantizedVolumeScale[j]) + quantizedVolumeOffset[j]; + } + } + Cartesian3.unpack(position, 0, instancePosition); + if (defined(rtcCenter)) { + Cartesian3.add(instancePosition, rtcCenter, instancePosition); + } + instanceTranslationRotationScale.translation = instancePosition; + + // Get the instance rotation + var normalUp = featureTable.getProperty('NORMAL_UP', ComponentDatatype.FLOAT, 3, i, propertyScratch1); + var normalRight = featureTable.getProperty('NORMAL_RIGHT', ComponentDatatype.FLOAT, 3, i, propertyScratch2); + var hasCustomOrientation = false; + if (defined(normalUp)) { + if (!defined(normalRight)) { + throw new RuntimeError('To define a custom orientation, both NORMAL_UP and NORMAL_RIGHT must be defined.'); + } + Cartesian3.unpack(normalUp, 0, instanceNormalUp); + Cartesian3.unpack(normalRight, 0, instanceNormalRight); + hasCustomOrientation = true; + } else { + var octNormalUp = featureTable.getProperty('NORMAL_UP_OCT32P', ComponentDatatype.UNSIGNED_SHORT, 2, i, propertyScratch1); + var octNormalRight = featureTable.getProperty('NORMAL_RIGHT_OCT32P', ComponentDatatype.UNSIGNED_SHORT, 2, i, propertyScratch2); + if (defined(octNormalUp)) { + if (!defined(octNormalRight)) { + throw new RuntimeError('To define a custom orientation with oct-encoded vectors, both NORMAL_UP_OCT32P and NORMAL_RIGHT_OCT32P must be defined.'); + } + AttributeCompression.octDecodeInRange(octNormalUp[0], octNormalUp[1], 65535, instanceNormalUp); + AttributeCompression.octDecodeInRange(octNormalRight[0], octNormalRight[1], 65535, instanceNormalRight); + hasCustomOrientation = true; + } else if (eastNorthUp) { + Transforms.eastNorthUpToFixedFrame(instancePosition, Ellipsoid.WGS84, instanceTransform); + Matrix4.getRotation(instanceTransform, instanceRotation); + } else { + Matrix3.clone(Matrix3.IDENTITY, instanceRotation); + } + } + if (hasCustomOrientation) { + Cartesian3.cross(instanceNormalRight, instanceNormalUp, instanceNormalForward); + Cartesian3.normalize(instanceNormalForward, instanceNormalForward); + Matrix3.setColumn(instanceRotation, 0, instanceNormalRight, instanceRotation); + Matrix3.setColumn(instanceRotation, 1, instanceNormalUp, instanceRotation); + Matrix3.setColumn(instanceRotation, 2, instanceNormalForward, instanceRotation); + } + Quaternion.fromRotationMatrix(instanceRotation, instanceQuaternion); + instanceTranslationRotationScale.rotation = instanceQuaternion; + + // Get the instance scale + instanceScale = Cartesian3.fromElements(1.0, 1.0, 1.0, instanceScale); + var scale = featureTable.getProperty('SCALE', ComponentDatatype.FLOAT, 1, i); + if (defined(scale)) { + Cartesian3.multiplyByScalar(instanceScale, scale, instanceScale); + } + var nonUniformScale = featureTable.getProperty('SCALE_NON_UNIFORM', ComponentDatatype.FLOAT, 3, i, propertyScratch1); + if (defined(nonUniformScale)) { + instanceScale.x *= nonUniformScale[0]; + instanceScale.y *= nonUniformScale[1]; + instanceScale.z *= nonUniformScale[2]; + } + instanceTranslationRotationScale.scale = instanceScale; + + // Get the batchId + var batchId = featureTable.getProperty('BATCH_ID', ComponentDatatype.UNSIGNED_SHORT, 1, i); + if (!defined(batchId)) { + // If BATCH_ID semantic is undefined, batchId is just the instance number + batchId = i; + } + + // Create the model matrix and the instance + Matrix4.fromTranslationRotationScale(instanceTranslationRotationScale, instanceTransform); + var modelMatrix = instanceTransform.clone(); + instances[i] = { + modelMatrix : modelMatrix, + batchId : batchId + }; + } + + content._modelInstanceCollection = new ModelInstanceCollection(collectionOptions); + } + + function createFeatures(content) { + var tileset = content._tileset; + var featuresLength = content.featuresLength; + if (!defined(content._features) && (featuresLength > 0)) { + var features = new Array(featuresLength); + for (var i = 0; i < featuresLength; ++i) { + features[i] = new Cesium3DTileFeature(tileset, content, i); + } + content._features = features; + } + } + + /** + * @inheritdoc Cesium3DTileContent#hasProperty + */ + Instanced3DModel3DTileContent.prototype.hasProperty = function(batchId, name) { + return this._batchTable.hasProperty(batchId, name); + }; + + /** + * @inheritdoc Cesium3DTileContent#getFeature + */ + Instanced3DModel3DTileContent.prototype.getFeature = function(batchId) { + var featuresLength = this.featuresLength; + + createFeatures(this); + return this._features[batchId]; + }; + + /** + * @inheritdoc Cesium3DTileContent#applyDebugSettings + */ + Instanced3DModel3DTileContent.prototype.applyDebugSettings = function(enabled, color) { + color = enabled ? color : Color.WHITE; + this._batchTable.setAllColor(color); + }; + + /** + * @inheritdoc Cesium3DTileContent#applyStyle + */ + Instanced3DModel3DTileContent.prototype.applyStyle = function(frameState, style) { + this._batchTable.applyStyle(frameState, style); + }; + + /** + * @inheritdoc Cesium3DTileContent#update + */ + Instanced3DModel3DTileContent.prototype.update = function(tileset, frameState) { + var commandStart = frameState.commandList.length; + + // In the PROCESSING state we may be calling update() to move forward + // the content's resource loading. In the READY state, it will + // actually generate commands. + this._batchTable.update(tileset, frameState); + this._modelInstanceCollection.modelMatrix = this._tile.computedTransform; + this._modelInstanceCollection.shadows = this._tileset.shadows; + this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe; + this._modelInstanceCollection.update(frameState); + + // If any commands were pushed, add derived commands + var commandEnd = frameState.commandList.length; + if ((commandStart < commandEnd) && frameState.passes.render) { + this._batchTable.addDerivedCommands(frameState, commandStart); + } + }; + + /** + * @inheritdoc Cesium3DTileContent#isDestroyed + */ + Instanced3DModel3DTileContent.prototype.isDestroyed = function() { + return false; + }; + + /** + * @inheritdoc Cesium3DTileContent#destroy + */ + Instanced3DModel3DTileContent.prototype.destroy = function() { + this._modelInstanceCollection = this._modelInstanceCollection && this._modelInstanceCollection.destroy(); + this._batchTable = this._batchTable && this._batchTable.destroy(); + + return destroyObject(this); + }; + return Instanced3DModel3DTileContent; +}); + +define('Scene/PointCloud3DTileContent',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Color', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/FeatureDetection', + '../Core/getStringFromTypedArray', + '../Core/Matrix4', + '../Core/oneTimeWarning', + '../Core/PrimitiveType', + '../Core/RuntimeError', + '../Core/Transforms', + '../Renderer/Buffer', + '../Renderer/BufferUsage', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', + '../Renderer/VertexArray', + '../ThirdParty/when', + './BlendingState', + './Cesium3DTileBatchTable', + './Cesium3DTileFeature', + './Cesium3DTileFeatureTable', + './SceneMode', + './ShadowMode' + ], function( + Cartesian2, + Cartesian3, + Cartesian4, + Color, + combine, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + FeatureDetection, + getStringFromTypedArray, + Matrix4, + oneTimeWarning, + PrimitiveType, + RuntimeError, + Transforms, + Buffer, + BufferUsage, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + VertexArray, + when, + BlendingState, + Cesium3DTileBatchTable, + Cesium3DTileFeature, + Cesium3DTileFeatureTable, + 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 {}; + } + + /** + * Represents the contents of a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md|Points} + * tile in a {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset. + *

    + * Implements the {@link Cesium3DTileContent} interface. + *

    + * + * @alias PointCloud3DTileContent + * @constructor + * + * @private + */ + function PointCloud3DTileContent(tileset, tile, url, arrayBuffer, byteOffset) { + this._tileset = tileset; + this._tile = tile; + this._url = url; + + // Hold onto the payload until the render resources are created + this._parsedContent = undefined; + + this._drawCommand = undefined; + this._pickCommand = undefined; + this._pickId = undefined; // Only defined when batchTable is undefined + this._isTranslucent = false; + this._styleTranslucent = false; + this._constantColor = Color.clone(Color.WHITE); + this._rtcCenter = undefined; + this._batchTable = undefined; // Used when feature table contains BATCH_ID semantic + + // These values are used to regenerate the shader when the style changes + this._styleableShaderAttributes = undefined; + this._isQuantized = false; + this._isOctEncoded16P = false; + this._isRGB565 = false; + this._hasColors = false; + this._hasNormals = false; + this._hasBatchIds = false; + + // Use per-point normals to hide back-facing points. + this.backFaceCulling = false; + this._backFaceCulling = false; + + this._opaqueRenderState = undefined; + this._translucentRenderState = undefined; + + this._highlightColor = Color.clone(Color.WHITE); + this._pointSize = 1.0; + this._quantizedVolumeScale = undefined; + this._quantizedVolumeOffset = undefined; + + this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY); + this._mode = undefined; + + this._readyPromise = when.defer(); + this._pointsLength = 0; + this._geometryByteLength = 0; + + this._features = undefined; + + /** + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty + */ + this.featurePropertiesDirty = false; + + initialize(this, arrayBuffer, byteOffset); + } + + defineProperties(PointCloud3DTileContent.prototype, { + /** + * @inheritdoc Cesium3DTileContent#featuresLength + */ + featuresLength : { + get : function() { + if (defined(this._batchTable)) { + return this._batchTable.featuresLength; + } 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 + * @inheritdoc Cesium3DTileContent#pointsLength */ - tilingScheme : { + pointsLength : { get : function() { - - return this._tilingScheme; + return this._pointsLength; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#trianglesLength */ - rectangle : { + trianglesLength : { get : function() { - - return this._rectangle; + return 0; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#geometryByteLength */ - tileDiscardPolicy : { + geometryByteLength : { get : function() { - - return this._tileDiscardPolicy; + return this._geometryByteLength; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#texturesByteLength */ - errorEvent : { + texturesByteLength : { get : function() { - return this._errorEvent; + return 0; } }, /** - * Gets a value indicating whether or not the provider is ready for use. - * @memberof ArcGisMapServerImageryProvider.prototype - * @type {Boolean} - * @readonly + * @inheritdoc Cesium3DTileContent#batchTableByteLength */ - ready : { + batchTableByteLength : { get : function() { - return this._ready; + if (defined(this._batchTable)) { + return this._batchTable.memorySizeInBytes; + } + return 0; } }, /** - * Gets a promise that resolves to true when the provider is ready for use. - * @memberof ArcGisMapServerImageryProvider.prototype - * @type {Promise.} - * @readonly + * @inheritdoc Cesium3DTileContent#innerContents + */ + innerContents : { + get : function() { + return undefined; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#readyPromise */ readyPromise : { get : function() { @@ -151775,5576 +168661,9791 @@ define('Scene/ArcGisMapServerImageryProvider',[ }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tileset */ - credit : { + tileset : { get : function() { - return this._credit; + return this._tileset; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tile */ - usingPrecachedTiles : { + tile : { get : function() { - return this._useTiles; + return this._tile; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#url */ - hasAlphaChannel : { + url : { get : function() { - return true; + return this._url; } }, /** - * Gets the comma-separated list of layer IDs to show. - * @memberof ArcGisMapServerImageryProvider.prototype - * - * @type {String} + * @inheritdoc Cesium3DTileContent#batchTable */ - layers : { + batchTable : { get : function() { - return this._layers; + return this._batchTable; } } }); + var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - /** - * 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; - }; + function initialize(content, arrayBuffer, byteOffset) { + byteOffset = defaultValue(byteOffset, 0); - /** - * 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) { - - var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); - }; + var uint8Array = new Uint8Array(arrayBuffer); + var view = new DataView(arrayBuffer); + byteOffset += sizeOfUint32; // Skip magic - /** - /** - * 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.enablePickFeatures) { - return undefined; + var version = view.getUint32(byteOffset, true); + if (version !== 1) { + throw new RuntimeError('Only Point Cloud tile version 1 is supported. Version ' + version + ' is not.'); } + byteOffset += sizeOfUint32; - var rectangle = this._tilingScheme.tileXYToNativeRectangle(x, y, level); + // Skip byteLength + byteOffset += sizeOfUint32; - 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 featureTableJsonByteLength = view.getUint32(byteOffset, true); + if (featureTableJsonByteLength === 0) { + throw new RuntimeError('Feature table must have a byte length greater than zero'); } + byteOffset += sizeOfUint32; - 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; + var featureTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; - url += '&layers=visible'; - if (defined(this._layers)) { - url += ':' + this._layers; + var batchTableJsonByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + var batchTableBinaryByteLength = view.getUint32(byteOffset, true); + byteOffset += sizeOfUint32; + + var featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength); + var featureTableJson = JSON.parse(featureTableString); + byteOffset += featureTableJsonByteLength; + + var featureTableBinary = new Uint8Array(arrayBuffer, byteOffset, featureTableBinaryByteLength); + byteOffset += featureTableBinaryByteLength; + + // Get the batch table JSON and binary + var batchTableJson; + var batchTableBinary; + if (batchTableJsonByteLength > 0) { + // Has a batch table JSON + var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength); + batchTableJson = JSON.parse(batchTableString); + byteOffset += batchTableJsonByteLength; + + if (batchTableBinaryByteLength > 0) { + // Has a batch table binary + batchTableBinary = new Uint8Array(arrayBuffer, byteOffset, batchTableBinaryByteLength); + byteOffset += batchTableBinaryByteLength; + } } - if (defined(this._token)) { - url += '&token=' + this._token; + var featureTable = new Cesium3DTileFeatureTable(featureTableJson, featureTableBinary); + + var pointsLength = featureTable.getGlobalProperty('POINTS_LENGTH'); + featureTable.featuresLength = pointsLength; + + if (!defined(pointsLength)) { + throw new RuntimeError('Feature table global property: POINTS_LENGTH must be defined'); } - if (defined(this._proxy)) { - url = this._proxy.getURL(url); + // Get the positions + var positions; + var isQuantized = false; + + if (defined(featureTableJson.POSITION)) { + positions = featureTable.getPropertyArray('POSITION', ComponentDatatype.FLOAT, 3); + var rtcCenter = featureTable.getGlobalProperty('RTC_CENTER', ComponentDatatype.FLOAT, 3); + if (defined(rtcCenter)) { + content._rtcCenter = Cartesian3.unpack(rtcCenter); + } + } else if (defined(featureTableJson.POSITION_QUANTIZED)) { + positions = featureTable.getPropertyArray('POSITION_QUANTIZED', ComponentDatatype.UNSIGNED_SHORT, 3); + isQuantized = true; + + var quantizedVolumeScale = featureTable.getGlobalProperty('QUANTIZED_VOLUME_SCALE', ComponentDatatype.FLOAT, 3); + if (!defined(quantizedVolumeScale)) { + throw new RuntimeError('Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions.'); + } + content._quantizedVolumeScale = Cartesian3.unpack(quantizedVolumeScale); + + var quantizedVolumeOffset = featureTable.getGlobalProperty('QUANTIZED_VOLUME_OFFSET', ComponentDatatype.FLOAT, 3); + if (!defined(quantizedVolumeOffset)) { + throw new RuntimeError('Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions.'); + } + content._quantizedVolumeOffset = Cartesian3.unpack(quantizedVolumeOffset); } - return loadJson(url).then(function(json) { - var result = []; + if (!defined(positions)) { + throw new RuntimeError('Either POSITION or POSITION_QUANTIZED must be defined.'); + } + + // Get the colors + var colors; + var isTranslucent = false; + var isRGB565 = false; + + if (defined(featureTableJson.RGBA)) { + colors = featureTable.getPropertyArray('RGBA', ComponentDatatype.UNSIGNED_BYTE, 4); + isTranslucent = true; + } else if (defined(featureTableJson.RGB)) { + colors = featureTable.getPropertyArray('RGB', ComponentDatatype.UNSIGNED_BYTE, 3); + } else if (defined(featureTableJson.RGB565)) { + colors = featureTable.getPropertyArray('RGB565', ComponentDatatype.UNSIGNED_SHORT, 1); + isRGB565 = true; + } else if (defined(featureTableJson.CONSTANT_RGBA)) { + var constantRGBA = featureTable.getGlobalProperty('CONSTANT_RGBA', ComponentDatatype.UNSIGNED_BYTE, 4); + content._constantColor = Color.fromBytes(constantRGBA[0], constantRGBA[1], constantRGBA[2], constantRGBA[3], content._constantColor); + } else { + // Use a default constant color + content._constantColor = Color.clone(Color.DARKGRAY, content._constantColor); + } - var features = json.results; - if (!defined(features)) { - return result; + content._isTranslucent = isTranslucent; + + // Get the normals + var normals; + var isOctEncoded16P = false; + + if (defined(featureTableJson.NORMAL)) { + normals = featureTable.getPropertyArray('NORMAL', ComponentDatatype.FLOAT, 3); + } else if (defined(featureTableJson.NORMAL_OCT16P)) { + normals = featureTable.getPropertyArray('NORMAL_OCT16P', ComponentDatatype.UNSIGNED_BYTE, 2); + isOctEncoded16P = true; + } + + // Get the batchIds and batch table. BATCH_ID does not need to be defined when the point cloud has per-point properties. + var batchIds; + if (defined(featureTableJson.BATCH_ID)) { + batchIds = featureTable.getPropertyArray('BATCH_ID', ComponentDatatype.UNSIGNED_SHORT, 1); + + var batchLength = featureTable.getGlobalProperty('BATCH_LENGTH'); + if (!defined(batchLength)) { + throw new RuntimeError('Global property: BATCH_LENGTH must be defined when BATCH_ID is defined.'); } - for (var i = 0; i < features.length; ++i) { - var feature = features[i]; + if (defined(batchTableBinary)) { + // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed + batchTableBinary = new Uint8Array(batchTableBinary); + } + content._batchTable = new Cesium3DTileBatchTable(content, batchLength, batchTableJson, batchTableBinary); + } - var featureInfo = new ImageryLayerFeatureInfo(); - featureInfo.data = feature; - featureInfo.name = feature.value; - featureInfo.properties = feature.attributes; - featureInfo.configureDescriptionFromProperties(feature.attributes); + // If points are not batched and there are per-point properties, use these properties for styling purposes + var styleableProperties; + if (!defined(batchIds) && defined(batchTableBinary)) { + styleableProperties = Cesium3DTileBatchTable.getBinaryProperties(pointsLength, batchTableJson, batchTableBinary); - // 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)); + // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. + for (var name in styleableProperties) { + if (styleableProperties.hasOwnProperty(name)) { + var property = styleableProperties[name]; + var typedArray = property.typedArray; + var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); + if (componentDatatype === ComponentDatatype.INT || componentDatatype === ComponentDatatype.UNSIGNED_INT || componentDatatype === ComponentDatatype.DOUBLE) { + oneTimeWarning('Cast pnts property to floats', 'Point cloud property "' + name + '" will be casted to a float array because INT, UNSIGNED_INT, and DOUBLE are not valid WebGL vertex attribute types. Some precision may be lost.'); + property.typedArray = new Float32Array(typedArray); } } + } + } - result.push(featureInfo); + content._parsedContent = { + positions : positions, + colors : colors, + normals : normals, + batchIds : batchIds, + styleableProperties : styleableProperties + }; + content._pointsLength = pointsLength; + content._isQuantized = isQuantized; + content._isOctEncoded16P = isOctEncoded16P; + content._isRGB565 = isRGB565; + content._hasColors = defined(colors); + content._hasNormals = defined(normals); + content._hasBatchIds = defined(batchIds); + } + + var scratchPointSizeAndTilesetTime = new Cartesian2(); + + var positionLocation = 0; + var colorLocation = 1; + var normalLocation = 2; + var batchIdLocation = 3; + var numberOfAttributes = 4; + + function createResources(content, frameState) { + var context = frameState.context; + var parsedContent = content._parsedContent; + var pointsLength = content._pointsLength; + var positions = parsedContent.positions; + var colors = parsedContent.colors; + var normals = parsedContent.normals; + var batchIds = parsedContent.batchIds; + var styleableProperties = parsedContent.styleableProperties; + var hasStyleableProperties = defined(styleableProperties); + var isQuantized = content._isQuantized; + var isOctEncoded16P = content._isOctEncoded16P; + var isRGB565 = content._isRGB565; + var isTranslucent = content._isTranslucent; + var hasColors = content._hasColors; + var hasNormals = content._hasNormals; + var hasBatchIds = content._hasBatchIds; + + var batchTable = content._batchTable; + var hasBatchTable = defined(batchTable); + + var styleableVertexAttributes = []; + var styleableShaderAttributes = {}; + content._styleableShaderAttributes = styleableShaderAttributes; + + if (hasStyleableProperties) { + var attributeLocation = numberOfAttributes; + + for (var name in styleableProperties) { + if (styleableProperties.hasOwnProperty(name)) { + var property = styleableProperties[name]; + var typedArray = property.typedArray; + var componentCount = property.componentCount; + var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); + + var vertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : property.typedArray, + usage : BufferUsage.STATIC_DRAW + }); + + content._geometryByteLength += vertexBuffer.sizeInBytes; + + var vertexAttribute = { + index : attributeLocation, + vertexBuffer : vertexBuffer, + componentsPerAttribute : componentCount, + componentDatatype : componentDatatype, + normalize : false, + offsetInBytes : 0, + strideInBytes : 0 + }; + + styleableVertexAttributes.push(vertexAttribute); + styleableShaderAttributes[name] = { + location : attributeLocation, + componentCount : componentCount + }; + ++attributeLocation; + } + } + } + + var uniformMap = { + u_pointSizeAndTilesetTime : function() { + scratchPointSizeAndTilesetTime.x = content._pointSize; + scratchPointSizeAndTilesetTime.y = content._tileset.timeSinceLoad; + return scratchPointSizeAndTilesetTime; + }, + u_highlightColor : function() { + return content._highlightColor; + }, + u_constantColor : function() { + return content._constantColor; } + }; - return result; + if (isQuantized) { + uniformMap = combine(uniformMap, { + u_quantizedVolumeScale : function() { + return content._quantizedVolumeScale; + } + }); + } + + var positionsVertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : positions, + usage : BufferUsage.STATIC_DRAW }); - }; + content._geometryByteLength += positionsVertexBuffer.sizeInBytes; - return ArcGisMapServerImageryProvider; -}); + var colorsVertexBuffer; + if (hasColors) { + colorsVertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : colors, + usage : BufferUsage.STATIC_DRAW + }); + content._geometryByteLength += colorsVertexBuffer.sizeInBytes; + } -/*global define*/ -define('Scene/BingMapsStyle',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + var normalsVertexBuffer; + if (hasNormals) { + normalsVertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : normals, + usage : BufferUsage.STATIC_DRAW + }); + content._geometryByteLength += normalsVertexBuffer.sizeInBytes; + } - /** - * The types of imagery provided by Bing Maps. - * - * @exports BingMapsStyle - * - * @see BingMapsImageryProvider - */ - var BingMapsStyle = { - /** - * Aerial imagery. - * - * @type {String} - * @constant - */ - AERIAL : 'Aerial', + var batchIdsVertexBuffer; + if (hasBatchIds) { + batchIdsVertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : batchIds, + usage : BufferUsage.STATIC_DRAW + }); + content._geometryByteLength += batchIdsVertexBuffer.sizeInBytes; + } - /** - * Aerial imagery with a road overlay. - * - * @type {String} - * @constant - */ - AERIAL_WITH_LABELS : 'AerialWithLabels', + var attributes = []; + if (isQuantized) { + attributes.push({ + index : positionLocation, + vertexBuffer : positionsVertexBuffer, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.UNSIGNED_SHORT, + normalize : true, // Convert position to 0 to 1 before entering the shader + offsetInBytes : 0, + strideInBytes : 0 + }); + } else { + attributes.push({ + index : positionLocation, + vertexBuffer : positionsVertexBuffer, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : 0, + strideInBytes : 0 + }); + } - /** - * Roads without additional imagery. - * - * @type {String} - * @constant - */ - ROAD : 'Road', + if (hasColors) { + if (isRGB565) { + attributes.push({ + index : colorLocation, + vertexBuffer : colorsVertexBuffer, + componentsPerAttribute : 1, + componentDatatype : ComponentDatatype.UNSIGNED_SHORT, + normalize : false, + offsetInBytes : 0, + strideInBytes : 0 + }); + } else { + var colorComponentsPerAttribute = isTranslucent ? 4 : 3; + attributes.push({ + index : colorLocation, + vertexBuffer : colorsVertexBuffer, + componentsPerAttribute : colorComponentsPerAttribute, + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + normalize : true, + offsetInBytes : 0, + strideInBytes : 0 + }); + } + } - /** - * Ordnance Survey imagery - * - * @type {String} - * @constant - */ - ORDNANCE_SURVEY : 'OrdnanceSurvey', + if (hasNormals) { + if (isOctEncoded16P) { + attributes.push({ + index : normalLocation, + vertexBuffer : normalsVertexBuffer, + componentsPerAttribute : 2, + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + normalize : false, + offsetInBytes : 0, + strideInBytes : 0 + }); + } else { + attributes.push({ + index : normalLocation, + vertexBuffer : normalsVertexBuffer, + componentsPerAttribute : 3, + componentDatatype : ComponentDatatype.FLOAT, + normalize : false, + offsetInBytes : 0, + strideInBytes : 0 + }); + } + } - /** - * Collins Bart imagery. - * - * @type {String} - * @constant - */ - COLLINS_BART : 'CollinsBart' - }; + if (hasBatchIds) { + attributes.push({ + index : batchIdLocation, + vertexBuffer : batchIdsVertexBuffer, + componentsPerAttribute : 1, + componentDatatype : ComponentDatatype.fromTypedArray(batchIds), + normalize : false, + offsetInBytes : 0, + strideInBytes : 0 + }); + } - return freezeObject(BingMapsStyle); -}); + if (hasStyleableProperties) { + attributes = attributes.concat(styleableVertexAttributes); + } -/*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'; + var vertexArray = new VertexArray({ + context : context, + attributes : attributes + }); + + var drawUniformMap = uniformMap; + + if (hasBatchTable) { + drawUniformMap = batchTable.getUniformMapCallback()(uniformMap); + } + + var pickUniformMap; + + if (hasBatchTable) { + pickUniformMap = batchTable.getPickUniformMapCallback()(uniformMap); + } else { + content._pickId = context.createPickId({ + primitive : content._tileset, + content : content + }); + + pickUniformMap = combine(uniformMap, { + czm_pickColor : function() { + return content._pickId.color; + } + }); + } + + content._opaqueRenderState = RenderState.fromCache({ + depthTest : { + enabled : true + } + }); + + content._translucentRenderState = RenderState.fromCache({ + depthTest : { + enabled : true + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }); + + content._drawCommand = new DrawCommand({ + boundingVolume : undefined, // Updated in update + cull : false, // Already culled by 3D Tiles + modelMatrix : new Matrix4(), + primitiveType : PrimitiveType.POINTS, + vertexArray : vertexArray, + count : pointsLength, + shaderProgram : undefined, // Updated in createShaders + uniformMap : drawUniformMap, + renderState : isTranslucent ? content._translucentRenderState : content._opaqueRenderState, + pass : isTranslucent ? Pass.TRANSLUCENT : Pass.CESIUM_3D_TILE, + owner : content, + castShadows : false, + receiveShadows : false + }); + + content._pickCommand = new DrawCommand({ + boundingVolume : undefined, // Updated in update + cull : false, // Already culled by 3D Tiles + modelMatrix : new Matrix4(), + primitiveType : PrimitiveType.POINTS, + vertexArray : vertexArray, + count : pointsLength, + shaderProgram : undefined, // Updated in createShaders + uniformMap : pickUniformMap, + renderState : isTranslucent ? content._translucentRenderState : content._opaqueRenderState, + pass : isTranslucent ? Pass.TRANSLUCENT : Pass.CESIUM_3D_TILE, + owner : content + }); + } + + var defaultProperties = ['POSITION', 'COLOR', 'NORMAL', 'POSITION_ABSOLUTE']; + + function getStyleableProperties(source, properties) { + // Get all the properties used by this style + var regex = /czm_tiles3d_style_(\w+)/g; + var matches = regex.exec(source); + while (matches !== null) { + var name = matches[1]; + if (properties.indexOf(name) === -1) { + properties.push(name); + } + matches = regex.exec(source); + } + } + + function getVertexAttribute(vertexArray, index) { + var numberOfAttributes = vertexArray.numberOfAttributes; + for (var i = 0; i < numberOfAttributes; ++i) { + var attribute = vertexArray.getAttribute(i); + if (attribute.index === index) { + return attribute; + } + } + } + + function modifyStyleFunction(source) { + // Replace occurrences of czm_tiles3d_style_DEFAULTPROPERTY + var length = defaultProperties.length; + for (var i = 0; i < length; ++i) { + var property = defaultProperties[i]; + var styleName = 'czm_tiles3d_style_' + property; + var replaceName = property.toLowerCase(); + source = source.replace(new RegExp(styleName + '(\\W)', 'g'), replaceName + '$1'); + } + + // Edit the function header to accept the point position, color, and normal + return source.replace('()', '(vec3 position, vec3 position_absolute, vec4 color, vec3 normal)'); + } + + function createShaders(content, frameState, style) { + var i; + var name; + var attribute; + + var context = frameState.context; + var batchTable = content._batchTable; + var hasBatchTable = defined(batchTable); + var hasStyle = defined(style); + var isQuantized = content._isQuantized; + var isOctEncoded16P = content._isOctEncoded16P; + var isRGB565 = content._isRGB565; + var isTranslucent = content._isTranslucent; + var hasColors = content._hasColors; + var hasNormals = content._hasNormals; + var hasBatchIds = content._hasBatchIds; + var backFaceCulling = content._backFaceCulling; + var vertexArray = content._drawCommand.vertexArray; + + var colorStyleFunction; + var showStyleFunction; + var pointSizeStyleFunction; + var styleTranslucent = isTranslucent; + + if (hasBatchTable) { + // Styling is handled in the batch table + hasStyle = false; + } + + if (hasStyle) { + var shaderState = { + translucent : false + }; + colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); + showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); + pointSizeStyleFunction = style.getPointSizeShaderFunction('getPointSizeFromStyle', 'czm_tiles3d_style_', shaderState); + if (defined(colorStyleFunction) && shaderState.translucent) { + styleTranslucent = true; + } + } + + content._styleTranslucent = styleTranslucent; + + var hasColorStyle = defined(colorStyleFunction); + var hasShowStyle = defined(showStyleFunction); + var hasPointSizeStyle = defined(pointSizeStyleFunction); + + // Get the properties in use by the style + var styleableProperties = []; + + if (hasColorStyle) { + getStyleableProperties(colorStyleFunction, styleableProperties); + colorStyleFunction = modifyStyleFunction(colorStyleFunction); + } + if (hasShowStyle) { + getStyleableProperties(showStyleFunction, styleableProperties); + showStyleFunction = modifyStyleFunction(showStyleFunction); + } + if (hasPointSizeStyle) { + getStyleableProperties(pointSizeStyleFunction, styleableProperties); + pointSizeStyleFunction = modifyStyleFunction(pointSizeStyleFunction); + } + + var usesColorSemantic = styleableProperties.indexOf('COLOR') >= 0; + var usesNormalSemantic = styleableProperties.indexOf('NORMAL') >= 0; + + // Split default properties from user properties + var userProperties = styleableProperties.filter(function(property) { return defaultProperties.indexOf(property) === -1; }); + + if (usesNormalSemantic && !hasNormals) { + throw new RuntimeError('Style references the NORMAL semantic but the point cloud does not have normals'); + } + + // Disable vertex attributes that aren't used in the style, enable attributes that are + var styleableShaderAttributes = content._styleableShaderAttributes; + for (name in styleableShaderAttributes) { + if (styleableShaderAttributes.hasOwnProperty(name)) { + attribute = styleableShaderAttributes[name]; + var enabled = (userProperties.indexOf(name) >= 0); + var vertexAttribute = getVertexAttribute(vertexArray, attribute.location); + vertexAttribute.enabled = enabled; + } + } + + var usesColors = hasColors && (!hasColorStyle || usesColorSemantic); + if (hasColors) { + // Disable the color vertex attribute if the color style does not reference the color semantic + var colorVertexAttribute = getVertexAttribute(vertexArray, colorLocation); + colorVertexAttribute.enabled = usesColors; + } + + var attributeLocations = { + a_position : positionLocation + }; + if (usesColors) { + attributeLocations.a_color = colorLocation; + } + if (hasNormals) { + attributeLocations.a_normal = normalLocation; + } + if (hasBatchIds) { + attributeLocations.a_batchId = batchIdLocation; + } + + var attributeDeclarations = ''; + + var length = userProperties.length; + for (i = 0; i < length; ++i) { + name = userProperties[i]; + attribute = styleableShaderAttributes[name]; + if (!defined(attribute)) { + throw new RuntimeError('Style references a property "' + name + '" that does not exist or is not styleable.'); + } + + var componentCount = attribute.componentCount; + var attributeName = 'czm_tiles3d_style_' + name; + var attributeType; + if (componentCount === 1) { + attributeType = 'float'; + } else { + attributeType = 'vec' + componentCount; + } + + attributeDeclarations += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + attributeLocations[attributeName] = attribute.location; + } + + var vs = 'attribute vec3 a_position; \n' + + 'varying vec4 v_color; \n' + + 'uniform vec2 u_pointSizeAndTilesetTime; \n' + + 'uniform vec4 u_constantColor; \n' + + 'uniform vec4 u_highlightColor; \n' + + 'float u_pointSize; \n' + + 'float u_tilesetTime; \n'; + + vs += attributeDeclarations; + + if (usesColors) { + if (isTranslucent) { + vs += 'attribute vec4 a_color; \n'; + } else if (isRGB565) { + vs += 'attribute float a_color; \n' + + 'const float SHIFT_RIGHT_11 = 1.0 / 2048.0; \n' + + 'const float SHIFT_RIGHT_5 = 1.0 / 32.0; \n' + + 'const float SHIFT_LEFT_11 = 2048.0; \n' + + 'const float SHIFT_LEFT_5 = 32.0; \n' + + 'const float NORMALIZE_6 = 1.0 / 64.0; \n' + + 'const float NORMALIZE_5 = 1.0 / 32.0; \n'; + } else { + vs += 'attribute vec3 a_color; \n'; + } + } + if (hasNormals) { + if (isOctEncoded16P) { + vs += 'attribute vec2 a_normal; \n'; + } else { + vs += 'attribute vec3 a_normal; \n'; + } + } + + if (hasBatchIds) { + vs += 'attribute float a_batchId; \n'; + } + + if (isQuantized) { + vs += 'uniform vec3 u_quantizedVolumeScale; \n'; + } + + if (hasColorStyle) { + vs += colorStyleFunction; + } + + if (hasShowStyle) { + vs += showStyleFunction; + } + + if (hasPointSizeStyle) { + vs += pointSizeStyleFunction; + } + + vs += 'void main() \n' + + '{ \n' + + ' u_pointSize = u_pointSizeAndTilesetTime.x; \n' + + ' u_tilesetTime = u_pointSizeAndTilesetTime.y; \n'; + + if (usesColors) { + if (isTranslucent) { + vs += ' vec4 color = a_color; \n'; + } else if (isRGB565) { + vs += ' float compressed = a_color; \n' + + ' float r = floor(compressed * SHIFT_RIGHT_11); \n' + + ' compressed -= r * SHIFT_LEFT_11; \n' + + ' float g = floor(compressed * SHIFT_RIGHT_5); \n' + + ' compressed -= g * SHIFT_LEFT_5; \n' + + ' float b = compressed; \n' + + ' vec3 rgb = vec3(r * NORMALIZE_5, g * NORMALIZE_6, b * NORMALIZE_5); \n' + + ' vec4 color = vec4(rgb, 1.0); \n'; + } else { + vs += ' vec4 color = vec4(a_color, 1.0); \n'; + } + } else { + vs += ' vec4 color = u_constantColor; \n'; + } + + if (isQuantized) { + vs += ' vec3 position = a_position * u_quantizedVolumeScale; \n'; + } else { + vs += ' vec3 position = a_position; \n'; + } + vs += ' vec3 position_absolute = vec3(czm_model * vec4(position, 1.0)); \n'; + + if (hasNormals) { + if (isOctEncoded16P) { + vs += ' vec3 normal = czm_octDecode(a_normal); \n'; + } else { + vs += ' vec3 normal = a_normal; \n'; + } + } else { + vs += ' vec3 normal = vec3(1.0); \n'; + } + + if (hasColorStyle) { + vs += ' color = getColorFromStyle(position, position_absolute, color, normal); \n'; + } + + if (hasShowStyle) { + vs += ' float show = float(getShowFromStyle(position, position_absolute, color, normal)); \n'; + } + + if (hasPointSizeStyle) { + vs += ' gl_PointSize = getPointSizeFromStyle(position, position_absolute, color, normal); \n'; + } else { + vs += ' gl_PointSize = u_pointSize; \n'; + } + + vs += ' color = color * u_highlightColor; \n'; + + if (hasNormals) { + vs += ' normal = czm_normal * normal; \n' + + ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + + ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting + ' color *= diffuseStrength; \n'; + } + + vs += ' v_color = color; \n' + + ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n'; + + if (hasNormals && backFaceCulling) { + vs += ' float visible = step(-normal.z, 0.0); \n' + + ' gl_Position *= visible; \n' + + ' gl_PointSize *= visible; \n'; + } + + if (hasShowStyle) { + vs += ' gl_Position *= show; \n' + + ' gl_PointSize *= show; \n'; + } + + vs += '} \n'; + + var fs = 'varying vec4 v_color; \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragColor = v_color; \n' + + '} \n'; + + var drawVS = vs; + var drawFS = fs; + + if (hasBatchTable) { + // Batched points always use the HIGHLIGHT color blend mode + drawVS = batchTable.getVertexShaderCallback(false, 'a_batchId')(drawVS); + drawFS = batchTable.getFragmentShaderCallback(false, undefined)(drawFS); + } + + var pickVS = vs; + var pickFS = fs; + + if (hasBatchTable) { + pickVS = batchTable.getPickVertexShaderCallback('a_batchId')(pickVS); + pickFS = batchTable.getPickFragmentShaderCallback()(pickFS); + } else { + pickFS = ShaderSource.createPickFragmentShaderSource(pickFS, 'uniform'); + } + + var drawCommand = content._drawCommand; + if (defined(drawCommand.shaderProgram)) { + // Destroy the old shader + drawCommand.shaderProgram.destroy(); + } + drawCommand.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : drawVS, + fragmentShaderSource : drawFS, + attributeLocations : attributeLocations + }); + + var pickCommand = content._pickCommand; + if (defined(pickCommand.shaderProgram)) { + // Destroy the old shader + pickCommand.shaderProgram.destroy(); + } + pickCommand.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations + }); + + try { + // Check if the shader compiles correctly. If not there is likely a syntax error with the style. + drawCommand.shaderProgram._bind(); + } catch (error) { + // Rephrase the error. + throw new RuntimeError('Error generating style shader: this may be caused by a type mismatch, index out-of-bounds, or other syntax error.'); + } + } + + function createFeatures(content) { + var tileset = content._tileset; + var featuresLength = content.featuresLength; + if (!defined(content._features) && (featuresLength > 0)) { + var features = new Array(featuresLength); + for (var i = 0; i < featuresLength; ++i) { + features[i] = new Cesium3DTileFeature(tileset, content, i); + } + content._features = features; + } + } /** - * 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 + * @inheritdoc Cesium3DTileContent#hasProperty + */ + PointCloud3DTileContent.prototype.hasProperty = function(batchId, name) { + if (defined(this._batchTable)) { + return this._batchTable.hasProperty(batchId, name); + } + return false; + }; + + /** + * Part of the {@link Cesium3DTileContent} interface. * + * In this context a feature refers to a group of points that share the same BATCH_ID. + * For example all the points that represent a door in a house point cloud would be a feature. * - * @example - * var bing = new Cesium.BingMapsImageryProvider({ - * url : 'https://dev.virtualearth.net', - * key : 'get-yours-at-https://www.bingmapsportal.com/', - * mapStyle : Cesium.BingMapsStyle.AERIAL - * }); + * Features are backed by a batch table and can be colored, shown/hidden, picked, etc like features + * in b3dm and i3dm. * - * @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} + * When the BATCH_ID semantic is omitted and the point cloud stores per-point properties, they + * are not accessible by getFeature. They are only used for dynamic styling. */ - function BingMapsImageryProvider(options) { - options = defaultValue(options, {}); + PointCloud3DTileContent.prototype.getFeature = function(batchId) { + if (!defined(this._batchTable)) { + return undefined; + } + var featuresLength = this.featuresLength; + createFeatures(this); + return this._features[batchId]; + }; - - this._key = BingMapsApi.getKey(options.key); - this._keyErrorCredit = BingMapsApi.getErrorCredit(options.key); + /** + * @inheritdoc Cesium3DTileContent#applyDebugSettings + */ + PointCloud3DTileContent.prototype.applyDebugSettings = function(enabled, color) { + this._highlightColor = enabled ? color : Color.WHITE; + }; - 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'); + /** + * @inheritdoc Cesium3DTileContent#applyStyle + */ + PointCloud3DTileContent.prototype.applyStyle = function(frameState, style) { + if (defined(this._batchTable)) { + this._batchTable.applyStyle(frameState, style); + } else { + createShaders(this, frameState, style); + } + }; - /** - * 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; + var scratchComputedTranslation = new Cartesian4(); + var scratchComputedMatrixIn2D = new Matrix4(); - this._tilingScheme = new WebMercatorTilingScheme({ - numberOfLevelZeroTilesX : 2, - numberOfLevelZeroTilesY : 2, - ellipsoid : options.ellipsoid - }); + /** + * @inheritdoc Cesium3DTileContent#update + */ + PointCloud3DTileContent.prototype.update = function(tileset, frameState) { + var modelMatrix = this._tile.computedTransform; + var modelMatrixChanged = !Matrix4.equals(this._modelMatrix, modelMatrix); + var updateModelMatrix = modelMatrixChanged || this._mode !== frameState.mode; - this._tileWidth = undefined; - this._tileHeight = undefined; - this._maximumLevel = undefined; - this._imageUrlTemplate = undefined; - this._imageUrlSubdomains = undefined; + this._mode = frameState.mode; - this._errorEvent = new Event(); + if (!defined(this._drawCommand)) { + createResources(this, frameState); + createShaders(this, frameState, tileset.style); + updateModelMatrix = true; - this._ready = false; - this._readyPromise = when.defer(); + this._readyPromise.resolve(this); + this._parsedContent = undefined; // Unload + } - var metadataUrl = this._url + '/REST/v1/Imagery/Metadata/' + this._mapStyle + '?incl=ImageryProviders&key=' + this._key; - var that = this; - var metadataError; + if (updateModelMatrix) { + Matrix4.clone(modelMatrix, this._modelMatrix); + if (defined(this._rtcCenter)) { + Matrix4.multiplyByTranslation(modelMatrix, this._rtcCenter, this._drawCommand.modelMatrix); + } else if (defined(this._quantizedVolumeOffset)) { + Matrix4.multiplyByTranslation(modelMatrix, this._quantizedVolumeOffset, this._drawCommand.modelMatrix); + } else { + Matrix4.clone(modelMatrix, this._drawCommand.modelMatrix); + } - function metadataSuccess(data) { - var resource = data.resourceSets[0].resources[0]; + if (frameState.mode !== SceneMode.SCENE3D) { + var projection = frameState.mapProjection; + modelMatrix = this._drawCommand.modelMatrix; + var translation = Matrix4.getColumn(modelMatrix, 3, scratchComputedTranslation); + if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) { + Transforms.basisTo2D(projection, modelMatrix, modelMatrix); + } else { + var center = this._tile.boundingSphere.center; + var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); + Matrix4.multiply(to2D, modelMatrix, modelMatrix); + } + } - 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); + Matrix4.clone(this._drawCommand.modelMatrix, this._pickCommand.modelMatrix); - 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:'; + var boundingVolume; + if (defined(this._tile._contentBoundingVolume)) { + boundingVolume = this._mode === SceneMode.SCENE3D ? this._tile._contentBoundingVolume.boundingSphere : this._tile._contentBoundingVolume2D.boundingSphere; + } else { + boundingVolume = this._mode === SceneMode.SCENE3D ? this._tile._boundingVolume.boundingSphere : this._tile._boundingVolume2D.boundingSphere; } - that._imageUrlTemplate = that._imageUrlTemplate.replace(/^http:/, tileProtocol); + this._drawCommand.boundingVolume = boundingVolume; + this._pickCommand.boundingVolume = boundingVolume; + } - // 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 - }); - } + this._drawCommand.castShadows = ShadowMode.castShadows(tileset.shadows); + this._drawCommand.receiveShadows = ShadowMode.receiveShadows(tileset.shadows); - var attributionList = that._attributionList = resource.imageryProviders; - if (!attributionList) { - attributionList = that._attributionList = []; - } + if (this.backFaceCulling !== this._backFaceCulling) { + this._backFaceCulling = this.backFaceCulling; + createShaders(this, frameState, tileset.style); + } - for (var attributionIndex = 0, attributionLength = attributionList.length; attributionIndex < attributionLength; ++attributionIndex) { - var attribution = attributionList[attributionIndex]; + // Update the render state + var isTranslucent = (this._highlightColor.alpha < 1.0) || (this._constantColor.alpha < 1.0) || this._styleTranslucent; + this._drawCommand.renderState = isTranslucent ? this._translucentRenderState : this._opaqueRenderState; + this._drawCommand.pass = isTranslucent ? Pass.TRANSLUCENT : Pass.CESIUM_3D_TILE; - attribution.credit = new Credit(attribution.attribution); + if (defined(this._batchTable)) { + this._batchTable.update(tileset, frameState); + } - var coverageAreas = attribution.coverageAreas; + var commandList = frameState.commandList; + + var passes = frameState.passes; + if (passes.render) { + commandList.push(this._drawCommand); + } + if (passes.pick) { + commandList.push(this._pickCommand); + } + }; + + /** + * @inheritdoc Cesium3DTileContent#isDestroyed + */ + PointCloud3DTileContent.prototype.isDestroyed = function() { + return false; + }; + + /** + * @inheritdoc Cesium3DTileContent#destroy + */ + PointCloud3DTileContent.prototype.destroy = function() { + var command = this._drawCommand; + var pickCommand = this._pickCommand; + if (defined(command)) { + command.vertexArray = command.vertexArray && command.vertexArray.destroy(); + command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy(); + pickCommand.shaderProgram = pickCommand.shaderProgram && pickCommand.shaderProgram.destroy(); + } + this._batchTable = this._batchTable && this._batchTable.destroy(); + return destroyObject(this); + }; + + return PointCloud3DTileContent; +}); + +define('Scene/Tileset3DTileContent',[ + '../Core/defaultValue', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/getStringFromTypedArray', + '../Core/RuntimeError', + '../ThirdParty/when' + ], function( + defaultValue, + defineProperties, + destroyObject, + getStringFromTypedArray, + RuntimeError, + when) { + 'use strict'; + + /** + * Represents content for a tile in a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset whose + * content points to another 3D Tiles tileset. + *

    + * Implements the {@link Cesium3DTileContent} interface. + *

    + * + * @alias Tileset3DTileContent + * @constructor + * + * @private + */ + function Tileset3DTileContent(tileset, tile, url, arrayBuffer, byteOffset) { + this._tileset = tileset; + this._tile = tile; + this._url = url; + this._readyPromise = when.defer(); + + /** + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty + */ + this.featurePropertiesDirty = false; + + initialize(this, arrayBuffer, byteOffset); + } - 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])); - } + defineProperties(Tileset3DTileContent.prototype, { + /** + * @inheritdoc Cesium3DTileContent#featuresLength + */ + featuresLength : { + get : function() { + return 0; } + }, - 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)); - } + /** + * @inheritdoc Cesium3DTileContent#pointsLength + */ + pointsLength : { + get : function() { + return 0; + } + }, - function requestMetadata() { - var metadata = loadJsonp(metadataUrl, { - callbackParameterName : 'jsonp', - proxy : that._proxy - }); - when(metadata, metadataSuccess, metadataFailure); - } + /** + * @inheritdoc Cesium3DTileContent#trianglesLength + */ + trianglesLength : { + get : function() { + return 0; + } + }, - requestMetadata(); - } + /** + * @inheritdoc Cesium3DTileContent#geometryByteLength + */ + geometryByteLength : { + get : function() { + return 0; + } + }, - defineProperties(BingMapsImageryProvider.prototype, { /** - * Gets the name of the BingMaps server url hosting the imagery. - * @memberof BingMapsImageryProvider.prototype - * @type {String} - * @readonly + * @inheritdoc Cesium3DTileContent#texturesByteLength */ - url : { + texturesByteLength : { get : function() { - return this._url; + return 0; } }, /** - * Gets the proxy used by this provider. - * @memberof BingMapsImageryProvider.prototype - * @type {Proxy} - * @readonly + * @inheritdoc Cesium3DTileContent#batchTableByteLength */ - proxy : { + batchTableByteLength : { get : function() { - return this._proxy; + return 0; } }, + /** + * @inheritdoc Cesium3DTileContent#innerContents + */ + innerContents : { + get : function() { + return undefined; + } + }, /** - * Gets the Bing Maps key. - * @memberof BingMapsImageryProvider.prototype - * @type {String} - * @readonly + * @inheritdoc Cesium3DTileContent#readyPromise */ - key : { + readyPromise : { get : function() { - return this._key; + return this._readyPromise.promise; } }, /** - * Gets the type of Bing Maps imagery to load. - * @memberof BingMapsImageryProvider.prototype - * @type {BingMapsStyle} - * @readonly + * @inheritdoc Cesium3DTileContent#tileset */ - mapStyle : { + tileset : { get : function() { - return this._mapStyle; + return this._tileset; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tile */ - culture : { + tile : { get : function() { - return this._culture; + return this._tile; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#url */ - tileWidth : { + url : { get : function() { - - return this._tileWidth; + return this._url; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#batchTable */ - tileHeight: { + batchTable : { get : function() { - - return this._tileHeight; + return undefined; } + } + }); + + function initialize(content, arrayBuffer, byteOffset) { + byteOffset = defaultValue(byteOffset, 0); + var uint8Array = new Uint8Array(arrayBuffer); + var jsonString = getStringFromTypedArray(uint8Array, byteOffset); + var tilesetJson; + + try { + tilesetJson = JSON.parse(jsonString); + } catch (error) { + content._readyPromise.reject(new RuntimeError('Invalid tile content.')); + return; + } + + content._tileset.loadTileset(content._url, tilesetJson, content._tile); + content._readyPromise.resolve(content); + } + + /** + * Part of the {@link Cesium3DTileContent} interface. Tileset3DTileContent + * always returns false since a tile of this type does not have any features. + */ + Tileset3DTileContent.prototype.hasProperty = function(batchId, name) { + return false; + }; + + /** + * Part of the {@link Cesium3DTileContent} interface. Tileset3DTileContent + * always returns undefined since a tile of this type does not have any features. + */ + Tileset3DTileContent.prototype.getFeature = function(batchId) { + return undefined; + }; + + /** + * @inheritdoc Cesium3DTileContent#applyDebugSettings + */ + Tileset3DTileContent.prototype.applyDebugSettings = function(enabled, color) { + }; + + /** + * @inheritdoc Cesium3DTileContent#applyStyle + */ + Tileset3DTileContent.prototype.applyStyle = function(frameState, style) { + }; + + /** + * @inheritdoc Cesium3DTileContent#update + */ + Tileset3DTileContent.prototype.update = function(tileset, frameState) { + }; + + /** + * @inheritdoc Cesium3DTileContent#isDestroyed + */ + Tileset3DTileContent.prototype.isDestroyed = function() { + return false; + }; + + /** + * @inheritdoc Cesium3DTileContent#destroy + */ + Tileset3DTileContent.prototype.destroy = function() { + return destroyObject(this); + }; + + return Tileset3DTileContent; +}); + +define('Scene/Cesium3DTileContentFactory',[ + './Batched3DModel3DTileContent', + './Composite3DTileContent', + './Instanced3DModel3DTileContent', + './PointCloud3DTileContent', + './Tileset3DTileContent' + ], function( + Batched3DModel3DTileContent, + Composite3DTileContent, + Instanced3DModel3DTileContent, + PointCloud3DTileContent, + Tileset3DTileContent) { + 'use strict'; + + /** + * Maps a tile's magic field in its header to a new content object for the tile's payload. + * + * @private + */ + var Cesium3DTileContentFactory = { + b3dm : function(tileset, tile, url, arrayBuffer, byteOffset) { + return new Batched3DModel3DTileContent(tileset, tile, url, arrayBuffer, byteOffset); + }, + pnts : function(tileset, tile, url, arrayBuffer, byteOffset) { + return new PointCloud3DTileContent(tileset, tile, url, arrayBuffer, byteOffset); + }, + i3dm : function(tileset, tile, url, arrayBuffer, byteOffset) { + return new Instanced3DModel3DTileContent(tileset, tile, url, arrayBuffer, byteOffset); }, + cmpt : function(tileset, tile, url, arrayBuffer, byteOffset) { + // Send in the factory in order to avoid a cyclical dependency + return new Composite3DTileContent(tileset, tile, url, arrayBuffer, byteOffset, Cesium3DTileContentFactory); + }, + json : function(tileset, tile, url, arrayBuffer, byteOffset) { + return new Tileset3DTileContent(tileset, tile, url, arrayBuffer, byteOffset); + } + }; + + return Cesium3DTileContentFactory; +}); + +define('Scene/Cesium3DTileContentState',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * @private + */ + var Cesium3DTileContentState = { + UNLOADED : 0, // Has never been requested + LOADING : 1, // Is waiting on a pending request + PROCESSING : 2, // Request received. Contents are being processed for rendering. Depending on the content, it might make its own requests for external data. + READY : 3, // Ready to render. + EXPIRED : 4, // Is expired and will be unloaded once new content is loaded. + FAILED : 5 // Request failed. + }; + + return freezeObject(Cesium3DTileContentState); +}); + +define('Scene/Cesium3DTileOptimizationHint',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * Hint defining optimization support for a 3D tile + * + * @exports Cesium3DTileOptimizationHint + * + * @private + */ + var Cesium3DTileOptimizationHint = { + NOT_COMPUTED: -1, + USE_OPTIMIZATION: 1, + SKIP_OPTIMIZATION: 0 + }; + + return freezeObject(Cesium3DTileOptimizationHint); +}); +define('Scene/Cesium3DTileRefine',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + /** + * The refinement approach for a tile. + *

    + * See the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/schema/tile.schema.json|tile schema} + * in the 3D Tiles spec. + *

    + * + * @exports Cesium3DTileRefine + * + * @private + */ + var Cesium3DTileRefine = { /** - * 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 + * Render this tile and, if it doesn't meet the screen space error, also refine to its children. + * * @type {Number} - * @readonly + * @constant */ - maximumLevel : { + ADD : 0, + + /** + * Render this tile or, if it doesn't meet the screen space error, refine to its descendants instead. + * + * @type {Number} + * @constant + */ + REPLACE : 1 + }; + + return freezeObject(Cesium3DTileRefine); +}); + +define('Scene/Empty3DTileContent',[ + '../Core/defineProperties', + '../Core/destroyObject' + ], function( + defineProperties, + destroyObject) { + 'use strict'; + + /** + * Represents empty content for tiles in a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset that + * do not have content, e.g., because they are used to optimize hierarchical culling. + *

    + * Implements the {@link Cesium3DTileContent} interface. + *

    + * + * @alias Empty3DTileContent + * @constructor + * + * @private + */ + function Empty3DTileContent(tileset, tile) { + this._tileset = tileset; + this._tile = tile; + + /** + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty + */ + this.featurePropertiesDirty = false; + } + + defineProperties(Empty3DTileContent.prototype, { + /** + * @inheritdoc Cesium3DTileContent#featuresLength + */ + featuresLength : { get : function() { - - return this._maximumLevel; + return 0; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#pointsLength */ - minimumLevel : { + pointsLength : { get : function() { - 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 + * @inheritdoc Cesium3DTileContent#trianglesLength */ - tilingScheme : { + trianglesLength : { get : function() { - - return this._tilingScheme; + return 0; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#geometryByteLength */ - rectangle : { + geometryByteLength : { get : function() { - - return this._tilingScheme.rectangle; + return 0; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#texturesByteLength */ - tileDiscardPolicy : { + texturesByteLength : { get : function() { - - return this._tileDiscardPolicy; + return 0; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#batchTableByteLength */ - errorEvent : { + batchTableByteLength : { get : function() { - return this._errorEvent; + return 0; } }, /** - * Gets a value indicating whether or not the provider is ready for use. - * @memberof BingMapsImageryProvider.prototype - * @type {Boolean} - * @readonly + * @inheritdoc Cesium3DTileContent#innerContents */ - ready : { + innerContents : { get : function() { - return this._ready; + return undefined; } }, /** - * Gets a promise that resolves to true when the provider is ready for use. - * @memberof BingMapsImageryProvider.prototype - * @type {Promise.} - * @readonly + * @inheritdoc Cesium3DTileContent#readyPromise */ readyPromise : { get : function() { - return this._readyPromise.promise; + return undefined; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tileset */ - credit : { + tileset : { get : function() { - return this._credit; + return this._tileset; } }, /** - * 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 + * @inheritdoc Cesium3DTileContent#tile */ - hasAlphaChannel : { + tile : { get : function() { - return false; + return this._tile; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#url + */ + url: { + get: function() { + return undefined; + } + }, + + /** + * @inheritdoc Cesium3DTileContent#batchTable + */ + batchTable : { + get : function() { + return undefined; } } }); - 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. + * Part of the {@link Cesium3DTileContent} interface. Empty3DTileContent + * always returns false since a tile of this type does not have any features. */ - BingMapsImageryProvider.prototype.getTileCredits = function(x, y, level) { - - 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; + Empty3DTileContent.prototype.hasProperty = function(batchId, name) { + return false; }; /** - * 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. + * Part of the {@link Cesium3DTileContent} interface. Empty3DTileContent + * always returns undefined since a tile of this type does not have any features. */ - BingMapsImageryProvider.prototype.requestImage = function(x, y, level) { - - var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + Empty3DTileContent.prototype.getFeature = function(batchId) { + return undefined; }; /** - * 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. + * @inheritdoc Cesium3DTileContent#applyDebugSettings */ - BingMapsImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { - return undefined; + Empty3DTileContent.prototype.applyDebugSettings = function(enabled, color) { }; - 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 + * @inheritdoc Cesium3DTileContent#applyStyle */ - 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; + Empty3DTileContent.prototype.applyStyle = function(frameState, style) { }; /** - * 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 + * @inheritdoc Cesium3DTileContent#update */ - 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 - }; + Empty3DTileContent.prototype.update = function(tileset, frameState) { }; - 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); - } - } + /** + * @inheritdoc Cesium3DTileContent#isDestroyed + */ + Empty3DTileContent.prototype.isDestroyed = function() { + return false; + }; - return result; - } + /** + * @inheritdoc Cesium3DTileContent#destroy + */ + Empty3DTileContent.prototype.destroy = function() { + return destroyObject(this); + }; - return BingMapsImageryProvider; + return Empty3DTileContent; }); -/*global define*/ -define('Scene/PerspectiveOffCenterFrustum',[ +define('Scene/TileBoundingRegion',[ + '../Core/BoundingSphere', '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/defined', + '../Core/Cartographic', + '../Core/Check', + '../Core/ColorGeometryInstanceAttribute', + '../Core/defaultValue', '../Core/defineProperties', - '../Core/DeveloperError', + '../Core/Ellipsoid', + '../Core/GeometryInstance', + '../Core/IntersectionTests', '../Core/Matrix4', - './CullingVolume' + '../Core/OrientedBoundingBox', + '../Core/Plane', + '../Core/Ray', + '../Core/Rectangle', + '../Core/RectangleOutlineGeometry', + './PerInstanceColorAppearance', + './Primitive', + './SceneMode' ], function( + BoundingSphere, Cartesian3, - Cartesian4, - defined, + Cartographic, + Check, + ColorGeometryInstanceAttribute, + defaultValue, defineProperties, - DeveloperError, + Ellipsoid, + GeometryInstance, + IntersectionTests, Matrix4, - CullingVolume) { + OrientedBoundingBox, + Plane, + Ray, + Rectangle, + RectangleOutlineGeometry, + PerInstanceColorAppearance, + Primitive, + SceneMode) { '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 + * A tile bounding volume specified as a longitude/latitude/height region. + * @alias TileBoundingRegion * @constructor * + * @param {Object} options Object with the following properties: + * @param {Rectangle} options.rectangle The rectangle specifying the longitude and latitude range of the region. + * @param {Number} [options.minimumHeight=0.0] The minimum height of the region. + * @param {Number} [options.maximumHeight=0.0] The maximum height of the region. + * @param {Ellipsoid} [options.ellipsoid=Cesium.Ellipsoid.WGS84] The ellipsoid. * - * @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 + * @private */ - function PerspectiveOffCenterFrustum() { + function TileBoundingRegion(options) { + + this.rectangle = Rectangle.clone(options.rectangle); + this.minimumHeight = defaultValue(options.minimumHeight, 0.0); + this.maximumHeight = defaultValue(options.maximumHeight, 0.0); + /** - * Defines the left clipping plane. - * @type {Number} - * @default undefined + * The world coordinates of the southwest corner of the tile's rectangle. + * + * @type {Cartesian3} + * @default Cartesian3() */ - this.left = undefined; - this._left = undefined; + this.southwestCornerCartesian = new Cartesian3(); /** - * Defines the right clipping plane. - * @type {Number} - * @default undefined + * The world coordinates of the northeast corner of the tile's rectangle. + * + * @type {Cartesian3} + * @default Cartesian3() */ - this.right = undefined; - this._right = undefined; + this.northeastCornerCartesian = new Cartesian3(); /** - * Defines the top clipping plane. - * @type {Number} - * @default undefined + * A normal that, along with southwestCornerCartesian, defines a plane at the western edge of + * the tile. Any position above (in the direction of the normal) this plane is outside the tile. + * + * @type {Cartesian3} + * @default Cartesian3() */ - this.top = undefined; - this._top = undefined; + this.westNormal = new Cartesian3(); /** - * Defines the bottom clipping plane. - * @type {Number} - * @default undefined + * A normal that, along with southwestCornerCartesian, defines a plane at the southern edge of + * the tile. Any position above (in the direction of the normal) this plane is outside the tile. + * Because points of constant latitude do not necessary lie in a plane, positions below this + * plane are not necessarily inside the tile, but they are close. + * + * @type {Cartesian3} + * @default Cartesian3() */ - this.bottom = undefined; - this._bottom = undefined; + this.southNormal = new Cartesian3(); /** - * The distance of the near plane. - * @type {Number} - * @default 1.0 + * A normal that, along with northeastCornerCartesian, defines a plane at the eastern edge of + * the tile. Any position above (in the direction of the normal) this plane is outside the tile. + * + * @type {Cartesian3} + * @default Cartesian3() */ - this.near = 1.0; - this._near = this.near; + this.eastNormal = new Cartesian3(); /** - * The distance of the far plane. - * @type {Number} - * @default 500000000.0 + * A normal that, along with northeastCornerCartesian, defines a plane at the eastern edge of + * the tile. Any position above (in the direction of the normal) this plane is outside the tile. + * Because points of constant latitude do not necessary lie in a plane, positions below this + * plane are not necessarily inside the tile, but they are close. + * + * @type {Cartesian3} + * @default Cartesian3() */ - this.far = 500000000.0; - this._far = this.far; - - this._cullingVolume = new CullingVolume(); - this._perspectiveMatrix = new Matrix4(); - this._infinitePerspective = new Matrix4(); - } + this.northNormal = new Cartesian3(); - function update(frustum) { - - var t = frustum.top; - var b = frustum.bottom; - var r = frustum.right; - var l = frustum.left; - var n = frustum.near; - var f = frustum.far; + var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + computeBox(this, options.rectangle, ellipsoid); - if (t !== frustum._top || b !== frustum._bottom || - l !== frustum._left || r !== frustum._right || - n !== frustum._near || f !== frustum._far) { + // An oriented bounding box that encloses this tile's region. This is used to calculate tile visibility. + this._orientedBoundingBox = OrientedBoundingBox.fromRectangle(this.rectangle, this.minimumHeight, this.maximumHeight, ellipsoid); - - 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); - } + this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox); } - defineProperties(PerspectiveOffCenterFrustum.prototype, { + defineProperties(TileBoundingRegion.prototype, { /** - * Gets the perspective projection matrix computed from the view frustum. - * @memberof PerspectiveOffCenterFrustum.prototype - * @type {Matrix4} - * @readonly + * The underlying bounding volume * - * @see PerspectiveOffCenterFrustum#infiniteProjectionMatrix + * @memberof TileBoundingRegion.prototype + * + * @type {Object} + * @readonly */ - projectionMatrix : { + boundingVolume : { get : function() { - update(this); - return this._perspectiveMatrix; + return this._orientedBoundingBox; } }, - /** - * Gets the perspective projection matrix computed from the view frustum with an infinite far plane. - * @memberof PerspectiveOffCenterFrustum.prototype - * @type {Matrix4} - * @readonly + * The underlying bounding sphere * - * @see PerspectiveOffCenterFrustum#projectionMatrix + * @memberof TileBoundingRegion.prototype + * + * @type {BoundingSphere} + * @readonly */ - infiniteProjectionMatrix : { + boundingSphere : { get : function() { - update(this); - return this._infinitePerspective; + return this._boundingSphere; } } }); - 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) { - - var planes = this._cullingVolume.planes; + var cartesian3Scratch = new Cartesian3(); + var cartesian3Scratch2 = new Cartesian3(); + var cartesian3Scratch3 = new Cartesian3(); + var eastWestNormalScratch = new Cartesian3(); + var westernMidpointScratch = new Cartesian3(); + var easternMidpointScratch = new Cartesian3(); + var cartographicScratch = new Cartographic(); + var planeScratch = new Plane(Cartesian3.UNIT_X, 0.0); + var rayScratch = new Ray(); - var t = this.top; - var b = this.bottom; - var r = this.right; - var l = this.left; - var n = this.near; - var f = this.far; + function computeBox(tileBB, rectangle, ellipsoid) { + ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle), tileBB.southwestCornerCartesian); + ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle), tileBB.northeastCornerCartesian); - var right = Cartesian3.cross(direction, up, getPlanesRight); + // The middle latitude on the western edge. + cartographicScratch.longitude = rectangle.west; + cartographicScratch.latitude = (rectangle.south + rectangle.north) * 0.5; + cartographicScratch.height = 0.0; + var westernMidpointCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, westernMidpointScratch); - var nearCenter = getPlanesNearCenter; - Cartesian3.multiplyByScalar(direction, n, nearCenter); - Cartesian3.add(position, nearCenter, nearCenter); + // Compute the normal of the plane on the western edge of the tile. + var westNormal = Cartesian3.cross(westernMidpointCartesian, Cartesian3.UNIT_Z, cartesian3Scratch); + Cartesian3.normalize(westNormal, tileBB.westNormal); - var farCenter = getPlanesFarCenter; - Cartesian3.multiplyByScalar(direction, f, farCenter); - Cartesian3.add(position, farCenter, farCenter); + // The middle latitude on the eastern edge. + cartographicScratch.longitude = rectangle.east; + var easternMidpointCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, easternMidpointScratch); - var normal = getPlanesNormal; + // Compute the normal of the plane on the eastern edge of the tile. + var eastNormal = Cartesian3.cross(Cartesian3.UNIT_Z, easternMidpointCartesian, cartesian3Scratch); + Cartesian3.normalize(eastNormal, tileBB.eastNormal); - //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); - Cartesian3.normalize(normal, normal); + // Compute the normal of the plane bounding the southern edge of the tile. + var westVector = Cartesian3.subtract(westernMidpointCartesian, easternMidpointCartesian, cartesian3Scratch); + var eastWestNormal = Cartesian3.normalize(westVector, eastWestNormalScratch); - 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); + var south = rectangle.south; + var southSurfaceNormal; - //Right plane computation - Cartesian3.multiplyByScalar(right, r, normal); - Cartesian3.add(nearCenter, normal, normal); - Cartesian3.subtract(normal, position, normal); - Cartesian3.cross(up, normal, normal); - Cartesian3.normalize(normal, normal); + if (south > 0.0) { + // Compute a plane that doesn't cut through the tile. + cartographicScratch.longitude = (rectangle.west + rectangle.east) * 0.5; + cartographicScratch.latitude = south; + var southCenterCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, rayScratch.origin); + Cartesian3.clone(eastWestNormal, rayScratch.direction); + var westPlane = Plane.fromPointNormal(tileBB.southwestCornerCartesian, tileBB.westNormal, planeScratch); + // Find a point that is on the west and the south planes + IntersectionTests.rayPlane(rayScratch, westPlane, tileBB.southwestCornerCartesian); + southSurfaceNormal = ellipsoid.geodeticSurfaceNormal(southCenterCartesian, cartesian3Scratch2); - plane = planes[1]; - if (!defined(plane)) { - plane = planes[1] = new Cartesian4(); + } else { + southSurfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.southeast(rectangle), cartesian3Scratch2); } - plane.x = normal.x; - plane.y = normal.y; - plane.z = normal.z; - plane.w = -Cartesian3.dot(normal, position); + var southNormal = Cartesian3.cross(southSurfaceNormal, westVector, cartesian3Scratch3); + Cartesian3.normalize(southNormal, tileBB.southNormal); - //Bottom plane computation - Cartesian3.multiplyByScalar(up, b, normal); - Cartesian3.add(nearCenter, normal, normal); - Cartesian3.subtract(normal, position, normal); - Cartesian3.cross(right, normal, normal); - Cartesian3.normalize(normal, normal); + // Compute the normal of the plane bounding the northern edge of the tile. + var north = rectangle.north; + var northSurfaceNormal; + if (north < 0.0) { + // Compute a plane that doesn't cut through the tile. + cartographicScratch.longitude = (rectangle.west + rectangle.east) * 0.5; + cartographicScratch.latitude = north; + var northCenterCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, rayScratch.origin); + Cartesian3.negate(eastWestNormal, rayScratch.direction); + var eastPlane = Plane.fromPointNormal(tileBB.northeastCornerCartesian, tileBB.eastNormal, planeScratch); + // Find a point that is on the east and the north planes + IntersectionTests.rayPlane(rayScratch, eastPlane, tileBB.northeastCornerCartesian); + northSurfaceNormal = ellipsoid.geodeticSurfaceNormal(northCenterCartesian, cartesian3Scratch2); - plane = planes[2]; - if (!defined(plane)) { - plane = planes[2] = new Cartesian4(); + } else { + northSurfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.northwest(rectangle), cartesian3Scratch2); } - plane.x = normal.x; - plane.y = normal.y; - plane.z = normal.z; - plane.w = -Cartesian3.dot(normal, position); + var northNormal = Cartesian3.cross(westVector, northSurfaceNormal, cartesian3Scratch3); + Cartesian3.normalize(northNormal, tileBB.northNormal); + } - //Top plane computation - Cartesian3.multiplyByScalar(up, t, normal); - Cartesian3.add(nearCenter, normal, normal); - Cartesian3.subtract(normal, position, normal); - Cartesian3.cross(normal, right, normal); - Cartesian3.normalize(normal, normal); + var southwestCornerScratch = new Cartesian3(); + var northeastCornerScratch = new Cartesian3(); + var negativeUnitY = new Cartesian3(0.0, -1.0, 0.0); + var negativeUnitZ = new Cartesian3(0.0, 0.0, -1.0); + var vectorScratch = new Cartesian3(); - 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); + /** + * Gets the distance from the camera to the closest point on the tile. This is used for level of detail selection. + * + * @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. + */ + TileBoundingRegion.prototype.distanceToCamera = function(frameState) { + var camera = frameState.camera; + var cameraCartesianPosition = camera.positionWC; + var cameraCartographicPosition = camera.positionCartographic; - //Near plane computation - plane = planes[4]; - if (!defined(plane)) { - plane = planes[4] = new Cartesian4(); + var result = 0.0; + if (!Rectangle.contains(this.rectangle, cameraCartographicPosition)) { + var southwestCornerCartesian = this.southwestCornerCartesian; + var northeastCornerCartesian = this.northeastCornerCartesian; + var westNormal = this.westNormal; + var southNormal = this.southNormal; + var eastNormal = this.eastNormal; + var northNormal = this.northNormal; + + if (frameState.mode !== SceneMode.SCENE3D) { + southwestCornerCartesian = frameState.mapProjection.project(Rectangle.southwest(this.rectangle), southwestCornerScratch); + southwestCornerCartesian.z = southwestCornerCartesian.y; + southwestCornerCartesian.y = southwestCornerCartesian.x; + southwestCornerCartesian.x = 0.0; + northeastCornerCartesian = frameState.mapProjection.project(Rectangle.northeast(this.rectangle), northeastCornerScratch); + northeastCornerCartesian.z = northeastCornerCartesian.y; + northeastCornerCartesian.y = northeastCornerCartesian.x; + northeastCornerCartesian.x = 0.0; + westNormal = negativeUnitY; + eastNormal = Cartesian3.UNIT_Y; + southNormal = negativeUnitZ; + northNormal = Cartesian3.UNIT_Z; + } + + var vectorFromSouthwestCorner = Cartesian3.subtract(cameraCartesianPosition, southwestCornerCartesian, vectorScratch); + var distanceToWestPlane = Cartesian3.dot(vectorFromSouthwestCorner, westNormal); + var distanceToSouthPlane = Cartesian3.dot(vectorFromSouthwestCorner, southNormal); + + var vectorFromNortheastCorner = Cartesian3.subtract(cameraCartesianPosition, northeastCornerCartesian, vectorScratch); + var distanceToEastPlane = Cartesian3.dot(vectorFromNortheastCorner, eastNormal); + var distanceToNorthPlane = Cartesian3.dot(vectorFromNortheastCorner, northNormal); + + if (distanceToWestPlane > 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; + } } - 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); + var cameraHeight; + if (frameState.mode === SceneMode.SCENE3D) { + cameraHeight = cameraCartographicPosition.height; + } else { + cameraHeight = cameraCartesianPosition.x; + } - plane = planes[5]; - if (!defined(plane)) { - plane = planes[5] = new Cartesian4(); + var maximumHeight = frameState.mode === SceneMode.SCENE3D ? this.maximumHeight : 0.0; + var distanceFromTop = cameraHeight - maximumHeight; + if (distanceFromTop > 0.0) { + result += distanceFromTop * distanceFromTop; } - plane.x = normal.x; - plane.y = normal.y; - plane.z = normal.z; - plane.w = -Cartesian3.dot(normal, farCenter); - return this._cullingVolume; + return Math.sqrt(result); }; /** - * 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()); + * Determines which side of a plane this box is located. * - * @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()); + * @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. */ - PerspectiveOffCenterFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) { - update(this); - - - 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; + TileBoundingRegion.prototype.intersectPlane = function(plane) { + return this._orientedBoundingBox.intersectPlane(plane); }; /** - * Returns a duplicate of a PerspectiveOffCenterFrustum instance. + * Creates a debug primitive that shows the outline of the tile bounding region. * - * @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. + * @param {Color} color The desired color of the primitive's mesh + * @return {Primitive} + * + * @private */ - 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; - }; + TileBoundingRegion.prototype.createDebugVolume = function(color) { + + var modelMatrix = new Matrix4.clone(Matrix4.IDENTITY); + var geometry = new RectangleOutlineGeometry({ + rectangle : this.rectangle, + height : this.minimumHeight, + extrudedHeight: this.maximumHeight + }); + var instance = new GeometryInstance({ + geometry : geometry, + modelMatrix : modelMatrix, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(color) + } + }); - /** - * 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 new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); }; - return PerspectiveOffCenterFrustum; + return TileBoundingRegion; }); -/*global define*/ -define('Scene/PerspectiveFrustum',[ - '../Core/defined', +define('Scene/TileBoundingSphere',[ + '../Core/BoundingSphere', + '../Core/Cartesian3', + '../Core/Check', + '../Core/ColorGeometryInstanceAttribute', '../Core/defineProperties', - '../Core/DeveloperError', - './PerspectiveOffCenterFrustum' + '../Core/GeometryInstance', + '../Core/Matrix4', + '../Core/SphereOutlineGeometry', + './PerInstanceColorAppearance', + './Primitive' ], function( - defined, + BoundingSphere, + Cartesian3, + Check, + ColorGeometryInstanceAttribute, defineProperties, - DeveloperError, - PerspectiveOffCenterFrustum) { + GeometryInstance, + Matrix4, + SphereOutlineGeometry, + PerInstanceColorAppearance, + Primitive) { '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 + * A tile bounding volume specified as a sphere. + * @alias TileBoundingSphere * @constructor * + * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the bounding sphere. + * @param {Number} [radius=0.0] The radius of the bounding sphere. * - * @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 + * @private */ - 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) { - - 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) { - - 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; - } + function TileBoundingSphere(center, radius) { + this._boundingSphere = new BoundingSphere(center, radius); } - defineProperties(PerspectiveFrustum.prototype, { + defineProperties(TileBoundingSphere.prototype, { /** - * Gets the perspective projection matrix computed from the view frustum. - * @memberof PerspectiveFrustum.prototype - * @type {Matrix4} - * @readonly + * The center of the bounding sphere * - * @see PerspectiveFrustum#infiniteProjectionMatrix + * @memberof TileBoundingSphere.prototype + * + * @type {Cartesian3} + * @readonly */ - projectionMatrix : { + center : { get : function() { - update(this); - return this._offCenterFrustum.projectionMatrix; + return this._boundingSphere.center; } }, /** - * The perspective projection matrix computed from the view frustum with an infinite far plane. - * @memberof PerspectiveFrustum.prototype - * @type {Matrix4} - * @readonly + * The radius of the bounding sphere * - * @see PerspectiveFrustum#projectionMatrix + * @memberof TileBoundingSphere.prototype + * + * @type {Number} + * @readonly */ - infiniteProjectionMatrix : { + radius : { get : function() { - update(this); - return this._offCenterFrustum.infiniteProjectionMatrix; + return this._boundingSphere.radius; } }, /** - * Gets the angle of the vertical field of view, in radians. - * @memberof PerspectiveFrustum.prototype - * @type {Number} + * The underlying bounding volume + * + * @memberof TileBoundingSphere.prototype + * + * @type {Object} * @readonly - * @default undefined */ - fovy : { + boundingVolume : { get : function() { - update(this); - return this._fovy; + return this._boundingSphere; } }, - /** + * The underlying bounding sphere + * + * @memberof TileBoundingSphere.prototype + * + * @type {BoundingSphere} * @readonly - * @private */ - sseDenominator : { - get : function () { - update(this); - return this._sseDenominator; + boundingSphere : { + get : function() { + return this._boundingSphere; } } }); /** - * Creates a culling volume for this frustum. + * Computes the distance between this bounding sphere and the camera attached to frameState. * - * @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. + * @param {FrameState} frameState The frameState to which the camera is attached. + * @returns {Number} The distance between the camera and the bounding sphere in meters. Returns 0 if the camera is inside the bounding volume. * - * @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); + TileBoundingSphere.prototype.distanceToCamera = function(frameState) { + var boundingSphere = this._boundingSphere; + return Math.max(0.0, Cartesian3.distance(boundingSphere.center, frameState.camera.positionWC) - boundingSphere.radius); }; /** - * 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()); + * Determines which side of a plane this sphere is located. * - * @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()); + * @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. */ - PerspectiveFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) { - update(this); - return this._offCenterFrustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, result); + TileBoundingSphere.prototype.intersectPlane = function(plane) { + return BoundingSphere.intersectPlane(this._boundingSphere, plane); }; /** - * Returns a duplicate of a PerspectiveFrustum instance. + * Update the bounding sphere after the tile is transformed. * - * @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. + * @param {Cartesian3} center The center of the bounding sphere. + * @param {Number} radius The radius of the bounding sphere. */ - 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; + TileBoundingSphere.prototype.update = function(center, radius) { + Cartesian3.clone(center, this._boundingSphere.center); + this._boundingSphere.radius = radius; }; /** - * Compares the provided PerspectiveFrustum componentwise and returns - * true if they are equal, false otherwise. + * Creates a debug primitive that shows the outline of the sphere. * - * @param {PerspectiveFrustum} [other] The right hand side PerspectiveFrustum. - * @returns {Boolean} true if they are equal, false otherwise. + * @param {Color} color The desired color of the primitive's mesh + * @return {Primitive} */ - PerspectiveFrustum.prototype.equals = function(other) { - if (!defined(other)) { - return false; - } - - update(this); - update(other); + TileBoundingSphere.prototype.createDebugVolume = function(color) { + var geometry = new SphereOutlineGeometry({ + radius: this.radius + }); + var modelMatrix = Matrix4.fromTranslation(this.center, new Matrix4.clone(Matrix4.IDENTITY)); + var instance = new GeometryInstance({ + geometry : geometry, + modelMatrix : modelMatrix, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(color) + } + }); - return (this.fov === other.fov && - this.aspectRatio === other.aspectRatio && - this.near === other.near && - this.far === other.far && - this._offCenterFrustum.equals(other._offCenterFrustum)); + return new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); }; - return PerspectiveFrustum; + return TileBoundingSphere; }); -/*global define*/ -define('Scene/CameraFlightPath',[ - '../Core/Cartesian2', +define('Scene/TileOrientedBoundingBox',[ + '../Core/BoundingSphere', + '../Core/BoxOutlineGeometry', '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/defaultValue', - '../Core/defined', - '../Core/DeveloperError', - '../Core/EasingFunction', - '../Core/Math', - './PerspectiveFrustum', - './PerspectiveOffCenterFrustum', - './SceneMode' + '../Core/Check', + '../Core/ColorGeometryInstanceAttribute', + '../Core/defineProperties', + '../Core/GeometryInstance', + '../Core/Matrix3', + '../Core/Matrix4', + '../Core/OrientedBoundingBox', + './PerInstanceColorAppearance', + './Primitive' ], function( - Cartesian2, + BoundingSphere, + BoxOutlineGeometry, Cartesian3, - Cartographic, - defaultValue, - defined, - DeveloperError, - EasingFunction, - CesiumMath, - PerspectiveFrustum, - PerspectiveOffCenterFrustum, - SceneMode) { + Check, + ColorGeometryInstanceAttribute, + defineProperties, + GeometryInstance, + Matrix3, + Matrix4, + OrientedBoundingBox, + PerInstanceColorAppearance, + Primitive) { 'use strict'; /** - * Creates tweens for camera flights. - *

    - * Mouse interaction is disabled during flights. + * A tile bounding volume specified as an oriented bounding box. + * @alias TileOrientedBoundingBox + * @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. * * @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 createPitchFunction(startPitch, endPitch, heightFunction, pitchAdjustHeight) { - if (defined(pitchAdjustHeight) && heightFunction(0.5) > pitchAdjustHeight) { - var startHeight = heightFunction(0.0); - var endHeight = heightFunction(1.0); - var middleHeight = heightFunction(0.5); - - var d1 = middleHeight - startHeight; - var d2 = middleHeight - endHeight; - - return function(time) { - var altitude = heightFunction(time); - if (time <= 0.5) { - var t1 = (altitude - startHeight) / d1; - return CesiumMath.lerp(startPitch, -CesiumMath.PI_OVER_TWO, t1); - } - - var t2 = (altitude - endHeight) / d2; - return CesiumMath.lerp(-CesiumMath.PI_OVER_TWO, endPitch, 1 - t2); - }; - } - return function(time) { - return CesiumMath.lerp(startPitch, endPitch, time); - }; - } - - 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; - } - - function useLongestFlight(startCart, destCart) { - if (startCart.longitude < destCart.longitude) { - startCart.longitude += CesiumMath.TWO_PI; - } else { - destCart.longitude += CesiumMath.TWO_PI; - } - } - - function useShortestFlight(startCart, destCart) { - 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 scratchStartCart = new Cartographic(); - var scratchEndCart = new Cartographic(); - - function createUpdate3D(scene, duration, destination, heading, pitch, roll, optionAltitude, optionFlyOverLongitude, optionFlyOverLongitudeWeight, optionPitchAdjustHeight) { - 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 useLongFlight = false; - - if (defined(optionFlyOverLongitude)) { - var hitLon = CesiumMath.zeroToTwoPi(optionFlyOverLongitude); - - var lonMin = Math.min(startCart.longitude, destCart.longitude); - var lonMax = Math.max(startCart.longitude, destCart.longitude); - - var hitInside = (hitLon >= lonMin && hitLon <= lonMax); - - if (defined(optionFlyOverLongitudeWeight)) { - // Distance inside (0...2Pi) - var din = Math.abs(startCart.longitude - destCart.longitude); - // Distance outside (0...2Pi) - var dot = CesiumMath.TWO_PI - din; - - var hitDistance = hitInside ? din : dot; - var offDistance = hitInside ? dot : din; - - if (hitDistance < offDistance * optionFlyOverLongitudeWeight && !hitInside) { - useLongFlight = true; - } - } else if (!hitInside) { - useLongFlight = true; - } - } - - if (useLongFlight) { - useLongestFlight(startCart, destCart); - } else { - useShortestFlight(startCart, destCart); - } - - var heightFunction = createHeightFunction(camera, destination, startCart.height, destCart.height, optionAltitude); - var pitchFunction = createPitchFunction(startPitch, pitch, heightFunction, optionPitchAdjustHeight); - - // Isolate scope for update function. - // to have local copies of vars used in lerp - // Othervise, if you call nex - // createUpdate3D (createAnimationTween) - // before you played animation, variables will be overwriten. - function isolateUpdateFunction() { - var startLongitude = startCart.longitude; - var destLongitude = destCart.longitude; - var startLatitude = startCart.latitude; - var destLatitude = destCart.latitude; - - return function update(value) { - var time = value.time / duration; - - var position = Cartesian3.fromRadians( - CesiumMath.lerp(startLongitude, destLongitude, time), - CesiumMath.lerp(startLatitude, destLatitude, time), - heightFunction(time) - ); - - camera.setView({ - destination : position, - orientation: { - heading : CesiumMath.lerp(startHeading, heading, time), - pitch : pitchFunction(time), - roll : CesiumMath.lerp(startRoll, roll, time) - } - }); - }; - } - return isolateUpdateFunction(); - } - - 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 TileOrientedBoundingBox(center, halfAxes) { + this._orientedBoundingBox = new OrientedBoundingBox(center, halfAxes); + this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox); } - function wrapCallback(controller, cb) { - function wrapped() { - if (typeof cb === 'function') { - cb(); + defineProperties(TileOrientedBoundingBox.prototype, { + /** + * The underlying bounding volume. + * + * @memberof TileOrientedBoundingBox.prototype + * + * @type {Object} + * @readonly + */ + boundingVolume : { + get : function() { + return this._orientedBoundingBox; } - - controller.enableInputs = true; - } - return wrapped; - } - - CameraFlightPath.createTween = function(scene, options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var destination = options.destination; - - 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 flyOverLongitude = options.flyOverLongitude; - var flyOverLongitudeWeight = options.flyOverLongitudeWeight; - var pitchAdjustHeight = options.pitchAdjustHeight; - 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, flyOverLongitude, flyOverLongitudeWeight, pitchAdjustHeight); - update({ time: 1.0 }); - - if (typeof complete === 'function') { - complete(); - } - }; - return emptyFlight(newOnComplete, cancel); - } - - var update = updateFunctions[mode](scene, duration, destination, heading, pitch, roll, maximumHeight, flyOverLongitude, flyOverLongitudeWeight, pitchAdjustHeight); - - 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; + }, + /** + * The underlying bounding sphere. + * + * @memberof TileOrientedBoundingBox.prototype + * + * @type {BoundingSphere} + * @readonly + */ + boundingSphere : { + get : function() { + return this._boundingSphere; } } + }); - return { - duration : duration, - easingFunction : easingFunction, - startObject : { - time : 0.0 - }, - stopObject : { - time : duration - }, - update : update, - complete : complete, - cancel: cancel - }; + /** + * Computes the distance between this bounding box and the camera attached to frameState. + * + * @param {FrameState} frameState The frameState to which the camera is attached. + * @returns {Number} The distance between the camera and the bounding box in meters. Returns 0 if the camera is inside the bounding volume. + */ + TileOrientedBoundingBox.prototype.distanceToCamera = function(frameState) { + return Math.sqrt(this._orientedBoundingBox.distanceSquaredTo(frameState.camera.positionWC)); }; - return CameraFlightPath; -}); + /** + * 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. + */ + TileOrientedBoundingBox.prototype.intersectPlane = function(plane) { + return this._orientedBoundingBox.intersectPlane(plane); + }; -/*global define*/ -define('Scene/MapMode2D',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + /** + * Update the bounding box after the tile is transformed. + * + * @param {Cartesian3} center The center of the box. + * @param {Matrix3} halfAxes The three orthogonal half-axes of the bounding box. + * Equivalently, the transformation matrix, to rotate and scale a 2x2x2 + * cube centered at the origin. + */ + TileOrientedBoundingBox.prototype.update = function(center, halfAxes) { + Cartesian3.clone(center, this._orientedBoundingBox.center); + Matrix3.clone(halfAxes, this._orientedBoundingBox.halfAxes); + BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox, this._boundingSphere); + }; /** - * Describes how the map will operate in 2D. + * Creates a debug primitive that shows the outline of the box. * - * @exports MapMode2D + * @param {Color} color The desired color of the primitive's mesh + * @return {Primitive} */ - var MapMode2D = { - /** - * The 2D map can be rotated about the z axis. - * - * @type {Number} - * @constant - */ - ROTATE : 0, + TileOrientedBoundingBox.prototype.createDebugVolume = function(color) { + + var geometry = new BoxOutlineGeometry({ + // Make a 2x2x2 cube + minimum: new Cartesian3(-1.0, -1.0, -1.0), + maximum: new Cartesian3(1.0, 1.0, 1.0) + }); + var modelMatrix = Matrix4.fromRotationTranslation(this.boundingVolume.halfAxes, this.boundingVolume.center); + var instance = new GeometryInstance({ + geometry : geometry, + modelMatrix : modelMatrix, + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(color) + } + }); - /** - * The 2D map can be scrolled infinitely in the horizontal direction. - * - * @type {Number} - * @constant - */ - INFINITE_SCROLL : 1 + return new Primitive({ + geometryInstances : instance, + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); }; - return freezeObject(MapMode2D); + return TileOrientedBoundingBox; }); -/*global define*/ -define('Scene/Camera',[ +define('Scene/Cesium3DTile',[ '../Core/BoundingSphere', - '../Core/Cartesian2', '../Core/Cartesian3', - '../Core/Cartesian4', - '../Core/Cartographic', + '../Core/Color', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/EasingFunction', - '../Core/Ellipsoid', - '../Core/EllipsoidGeodesic', - '../Core/Event', - '../Core/HeadingPitchRange', - '../Core/HeadingPitchRoll', + '../Core/deprecationWarning', + '../Core/destroyObject', + '../Core/getMagic', '../Core/Intersect', - '../Core/IntersectionTests', - '../Core/Math', + '../Core/joinUrls', + '../Core/JulianDate', + '../Core/loadArrayBuffer', '../Core/Matrix3', '../Core/Matrix4', - '../Core/Quaternion', - '../Core/Ray', '../Core/Rectangle', - '../Core/Transforms', - './CameraFlightPath', - './MapMode2D', - './OrthographicFrustum', - './OrthographicOffCenterFrustum', - './PerspectiveFrustum', - './SceneMode' + '../Core/Request', + '../Core/RequestScheduler', + '../Core/RequestState', + '../Core/RequestType', + '../Core/RuntimeError', + '../ThirdParty/when', + './Cesium3DTileChildrenVisibility', + './Cesium3DTileContentFactory', + './Cesium3DTileContentState', + './Cesium3DTileOptimizationHint', + './Cesium3DTileRefine', + './Empty3DTileContent', + './SceneMode', + './TileBoundingRegion', + './TileBoundingSphere', + './TileOrientedBoundingBox' ], function( BoundingSphere, - Cartesian2, Cartesian3, - Cartesian4, - Cartographic, + Color, defaultValue, defined, defineProperties, - DeveloperError, - EasingFunction, - Ellipsoid, - EllipsoidGeodesic, - Event, - HeadingPitchRange, - HeadingPitchRoll, + deprecationWarning, + destroyObject, + getMagic, Intersect, - IntersectionTests, - CesiumMath, + joinUrls, + JulianDate, + loadArrayBuffer, Matrix3, Matrix4, - Quaternion, - Ray, Rectangle, - Transforms, - CameraFlightPath, - MapMode2D, - OrthographicFrustum, - OrthographicOffCenterFrustum, - PerspectiveFrustum, - SceneMode) { + Request, + RequestScheduler, + RequestState, + RequestType, + RuntimeError, + when, + Cesium3DTileChildrenVisibility, + Cesium3DTileContentFactory, + Cesium3DTileContentState, + Cesium3DTileOptimizationHint, + Cesium3DTileRefine, + Empty3DTileContent, + SceneMode, + TileBoundingRegion, + TileBoundingSphere, + TileOrientedBoundingBox) { '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 + * A tile in a {@link Cesium3DTileset}. When a tile is first created, its content is not loaded; + * the content is loaded on-demand when needed based on the view. + *

    + * Do not construct this directly, instead access tiles through {@link Cesium3DTileset#tileVisible}. + *

    * + * @alias Cesium3DTile * @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 undefined if this tile is the root. + *

    + * When a tile's content points to an external tileset.json, the external tileset's + * root tile's parent is not undefined; instead, the parent references + * the tile (with its content pointing to an external tileset.json) as if the two tilesets were merged. + *

    + * + * @type {Cesium3DTile} + * @readonly + */ + this.parent = parent; + + var content; + var hasEmptyContent; + var contentState; + var contentUrl; + var serverKey; + + if (defined(contentHeader)) { + hasEmptyContent = false; + contentState = Cesium3DTileContentState.UNLOADED; + contentUrl = joinUrls(basePath, contentHeader.url); + serverKey = RequestScheduler.getServerKey(contentUrl); + } else { + content = new Empty3DTileContent(tileset, this); + hasEmptyContent = true; + contentState = Cesium3DTileContentState.READY; + } + + this._content = content; + this._contentUrl = contentUrl; + this._contentState = contentState; + this._contentReadyToProcessPromise = undefined; + this._contentReadyPromise = undefined; + this._expiredContent = undefined; + + this._serverKey = serverKey; + + /** + * When true, the tile has no content. + * + * @type {Boolean} + * @readonly + * + * @private + */ + this.hasEmptyContent = hasEmptyContent; - this._transform = Matrix4.clone(Matrix4.IDENTITY); - this._invTransform = Matrix4.clone(Matrix4.IDENTITY); - this._actualTransform = Matrix4.clone(Matrix4.IDENTITY); - this._actualInvTransform = Matrix4.clone(Matrix4.IDENTITY); - this._transformChanged = false; + /** + * When true, the tile's content is renderable. + *

    + * This is false until the tile's content is loaded. + *

    + * + * @type {Boolean} + * @readonly + * + * @private + */ + this.hasRenderableContent = false; /** - * The position of the camera. + * When true, the tile's content points to an external tileset. + *

    + * This is false until the tile's content is loaded. + *

    * - * @type {Cartesian3} + * @type {Boolean} + * @readonly + * + * @private */ - this.position = new Cartesian3(); - this._position = new Cartesian3(); - this._positionWC = new Cartesian3(); - this._positionCartographic = new Cartographic(); + this.hasTilesetContent = false; /** - * The view direction of the camera. + * The corresponding node in the cache replacement list. * - * @type {Cartesian3} + * @type {DoublyLinkedListNode} + * @readonly + * + * @private */ - this.direction = new Cartesian3(); - this._direction = new Cartesian3(); - this._directionWC = new Cartesian3(); + this.replacementNode = undefined; + + var expire = header.expire; + var expireDuration; + var expireDate; + if (defined(expire)) { + expireDuration = expire.duration; + if (defined(expire.date)) { + expireDate = JulianDate.fromIso8601(expire.date); + } + } /** - * The up direction of the camera. + * The time in seconds after the tile's content is ready when the content expires and new content is requested. * - * @type {Cartesian3} + * @type {Number} */ - this.up = new Cartesian3(); - this._up = new Cartesian3(); - this._upWC = new Cartesian3(); + this.expireDuration = expireDuration; /** - * The right direction of the camera. + * The date when the content expires and new content is requested. * - * @type {Cartesian3} + * @type {JulianDate} */ - this.right = new Cartesian3(); - this._right = new Cartesian3(); - this._rightWC = new Cartesian3(); + this.expireDate = expireDate; /** - * The region of space in view. + * Marks if the tile is selected this frame. * - * @type {Frustum} - * @default PerspectiveFrustum() + * @type {Boolean} * - * @see PerspectiveFrustum - * @see PerspectiveOffCenterFrustum - * @see OrthographicFrustum + * @private */ - this.frustum = new PerspectiveFrustum(); - this.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; - this.frustum.fov = CesiumMath.toRadians(60.0); + this.selected = false; /** - * The default amount to move the camera when an argument is not - * provided to the move methods. + * The time when a style was last applied to this tile. + * * @type {Number} - * @default 100000.0; + * + * @private */ - this.defaultMoveAmount = 100000.0; + this.lastStyleTime = 0; + /** - * The default amount to rotate the camera when an argument is not - * provided to the look methods. - * @type {Number} - * @default Math.PI / 60.0 + * Marks whether the tile's children bounds are fully contained within the tile's bounds + * + * @type {Cesium3DTileOptimizationHint} + * + * @private */ - this.defaultLookAmount = Math.PI / 60.0; + this._optimChildrenWithinParent = Cesium3DTileOptimizationHint.NOT_COMPUTED; + + // Members that are updated every frame for tree traversal and rendering optimizations: + this._distanceToCamera = 0; + this._visibilityPlaneMask = 0; + this._childrenVisibility = Cesium3DTileChildrenVisibility.VISIBLE; + this._lastSelectedFrameNumber = -1; + this._screenSpaceError = 0; + this._screenSpaceErrorComputedFrame = -1; + this._finalResolution = true; + this._depth = 0; + this._centerZDepth = 0; + this._stackLength = 0; + this._selectedFrame = -1; + this._selectionDepth = 0; + this._lastSelectionDepth = undefined; + this._requestedFrame = undefined; + this._lastVisitedFrame = undefined; + this._ancestorWithContent = undefined; + this._ancestorWithLoadedContent = undefined; + + this._debugBoundingVolume = undefined; + this._debugContentBoundingVolume = undefined; + this._debugViewerRequestVolume = undefined; + this._debugColor = Color.fromRandom({ alpha : 1.0 }); + this._debugColorizeTiles = false; + + this._commandsLength = 0; + + this._color = undefined; + this._colorDirty = false; + } + + // This can be overridden for testing purposes + Cesium3DTile._deprecationWarning = deprecationWarning; + + defineProperties(Cesium3DTile.prototype, { /** - * The default amount to rotate the camera when an argument is not - * provided to the rotate methods. - * @type {Number} - * @default Math.PI / 3600.0 + * The tileset containing this tile. + * + * @memberof Cesium3DTile.prototype + * + * @type {Cesium3DTileset} + * @readonly */ - this.defaultRotateAmount = Math.PI / 3600.0; + tileset : { + get : function() { + return this._tileset; + } + }, + /** - * The default amount to move the camera when an argument is not - * provided to the zoom methods. - * @type {Number} - * @default 100000.0; + * The tile's content. This represents the actual tile's payload, + * not the content's metadata in tileset.json. + * + * @memberof Cesium3DTile.prototype + * + * @type {Cesium3DTileContent} + * @readonly */ - this.defaultZoomAmount = 100000.0; + content : { + get : function() { + return this._content; + } + }, + /** - * If set, the camera will not be able to rotate past this axis in either direction. - * @type {Cartesian3} - * @default undefined + * Get the bounding volume of the tile's contents. This defaults to the + * tile's bounding volume when the content's bounding volume is + * undefined. + * + * @memberof Cesium3DTile.prototype + * + * @type {TileBoundingVolume} + * @readonly + * @private */ - this.constrainedAxis = undefined; + contentBoundingVolume : { + get : function() { + return defaultValue(this._contentBoundingVolume, this._boundingVolume); + } + }, + /** - * The factor multiplied by the the map size used to determine where to clamp the camera position - * when zooming out from the surface. The default is 1.5. Only valid for 2D and the map is rotatable. - * @type {Number} - * @default 1.5 + * Get the bounding sphere derived from the tile's bounding volume. + * + * @memberof Cesium3DTile.prototype + * + * @type {BoundingSphere} + * @readonly */ - this.maximumZoomFactor = 1.5; + boundingSphere : { + get : function() { + return this._boundingVolume.boundingSphere; + } + }, - this._moveStart = new Event(); - this._moveEnd = new Event(); + /** + * Gets or sets the tile's highlight color. + * + * @memberof Cesium3DTile.prototype + * + * @type {Color} + * + * @default {@link Color.WHITE} + * + * @private + */ + color : { + get : function() { + if (!defined(this._color)) { + this._color = new Color(); + } + return Color.clone(this._color); + }, + set : function(value) { + this._color = Color.clone(value, this._color); + this._colorDirty = true; + } + }, - this._changed = new Event(); - this._changedPosition = undefined; - this._changedDirection = undefined; - this._changedFrustum = undefined; + /** + * Determines if the tile has available content to render. true if the tile's + * content is ready or if it has expired content that renders while new content loads; otherwise, + * false. + * + * @memberof Cesium3DTile.prototype + * + * @type {Boolean} + * @readonly + * + * @private + */ + contentAvailable : { + get : function() { + return this.contentReady || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED); + } + }, /** - * The amount the camera has to change before the changed event is raised. The value is a percentage in the [0, 1] range. - * @type {number} - * @default 0.5 + * Determines if the tile is ready to render. true if the tile + * is ready to render; otherwise, false. + * + * @memberof Cesium3DTile.prototype + * + * @type {Boolean} + * @readonly + * + * @private */ - this.percentageChanged = 0.5; + contentReady : { + get : function() { + return this._contentState === Cesium3DTileContentState.READY; + } + }, - this._viewMatrix = new Matrix4(); - this._invViewMatrix = new Matrix4(); - updateViewMatrix(this); + /** + * Determines if the tile's content has not be requested. true if tile's + * content has not be requested; otherwise, false. + * + * @memberof Cesium3DTile.prototype + * + * @type {Boolean} + * @readonly + * + * @private + */ + contentUnloaded : { + get : function() { + return this._contentState === Cesium3DTileContentState.UNLOADED; + } + }, - this._mode = SceneMode.SCENE3D; - this._modeChanged = true; - var projection = scene.mapProjection; - this._projection = projection; - this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)); - this._max2Dfrustum = undefined; - this._suspendTerrainAdjustment = false; + /** + * Determines if the tile's content is expired. true if tile's + * content is expired; otherwise, false. + * + * @memberof Cesium3DTile.prototype + * + * @type {Boolean} + * @readonly + * + * @private + */ + contentExpired : { + get : function() { + return this._contentState === Cesium3DTileContentState.EXPIRED; + } + }, - // set default view - rectangleCameraPosition3D(this, Camera.DEFAULT_VIEW_RECTANGLE, this.position, true); + /** + * Gets the promise that will be resolved when the tile's content is ready to process. + * This happens after the content is downloaded but before the content is ready + * to render. + *

    + * The promise remains undefined until the tile's content is requested. + *

    + * + * @type {Promise.} + * @readonly + * + * @private + */ + contentReadyToProcessPromise : { + get : function() { + if (defined(this._contentReadyToProcessPromise)) { + return this._contentReadyToProcessPromise.promise; + } + } + }, - var mag = Cartesian3.magnitude(this.position); - mag += mag * Camera.DEFAULT_VIEW_FACTOR; - Cartesian3.normalize(this.position, this.position); - Cartesian3.multiplyByScalar(this.position, mag, this.position); - } + /** + * Gets the promise that will be resolved when the tile's content is ready to render. + *

    + * The promise remains undefined until the tile's content is requested. + *

    + * + * @type {Promise.} + * @readonly + * + * @private + */ + contentReadyPromise : { + get : function() { + if (defined(this._contentReadyPromise)) { + return this._contentReadyPromise.promise; + } + } + }, - /** - * @private - */ - Camera.TRANSFORM_2D = 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); + /** + * Returns the number of draw commands used by this tile. + * + * @readonly + * + * @private + */ + commandsLength : { + get : function() { + return this._commandsLength; + } + } + }); + + var scratchJulianDate = new JulianDate(); /** + * Update whether the tile has expired. + * * @private */ - Camera.TRANSFORM_2D_INVERSE = Matrix4.inverseTransformation(Camera.TRANSFORM_2D, new Matrix4()); + Cesium3DTile.prototype.updateExpiration = function() { + if (defined(this.expireDate) && this.contentReady && !this.hasEmptyContent) { + var now = JulianDate.now(scratchJulianDate); + if (JulianDate.lessThan(this.expireDate, now)) { + this._contentState = Cesium3DTileContentState.EXPIRED; + this._expiredContent = this._content; + } + } + }; - /** - * The default rectangle the camera will view on creation. - * @type Rectangle - */ - Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees(-95.0, -20.0, -70.0, 90.0); + function updateExpireDate(tile) { + if (defined(tile.expireDuration)) { + var expireDurationDate = JulianDate.now(scratchJulianDate); + JulianDate.addSeconds(expireDurationDate, tile.expireDuration, expireDurationDate); - /** - * A scalar to multiply to the camera position and add it back after setting the camera to view the rectangle. - * A value of zero means the camera will view the entire {@link Camera#DEFAULT_VIEW_RECTANGLE}, a value greater than zero - * will move it further away from the extent, and a value less than zero will move it close to the extent. - * @type Number - */ - Camera.DEFAULT_VIEW_FACTOR = 0.5; + if (defined(tile.expireDate)) { + if (JulianDate.lessThan(tile.expireDate, expireDurationDate)) { + JulianDate.clone(expireDurationDate, tile.expireDate); + } + } else { + tile.expireDate = JulianDate.clone(expireDurationDate); + } + } + } - /** - * The default heading/pitch/range that is used when the camera flies to a location that contains a bounding sphere. - * @type HeadingPitchRange - */ - Camera.DEFAULT_OFFSET = new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_FOUR, 0.0); + function getContentFailedFunction(tile) { + return function(error) { + tile._contentState = Cesium3DTileContentState.FAILED; + tile._contentReadyPromise.reject(error); + tile._contentReadyToProcessPromise.reject(error); + }; + } - function updateViewMatrix(camera) { - Matrix4.computeView(camera._position, camera._direction, camera._up, camera._right, camera._viewMatrix); - Matrix4.multiply(camera._viewMatrix, camera._actualInvTransform, camera._viewMatrix); - Matrix4.inverseTransformation(camera._viewMatrix, camera._invViewMatrix); + function createPriorityFunction(tile) { + return function() { + return tile._distanceToCamera; + }; } - Camera.prototype._updateCameraChanged = function() { - var camera = this; + /** + * Requests the tile's content. + *

    + * The request may not be made if the Cesium Request Scheduler can't prioritize it. + *

    + * + * @private + */ + Cesium3DTile.prototype.requestContent = function() { + var that = this; + var tileset = this._tileset; - if (camera._changed.numberOfListeners === 0) { - return; + if (this.hasEmptyContent) { + return false; } - var percentageChanged = camera.percentageChanged; + var url = this._contentUrl; + var expired = this.contentExpired; + if (expired) { + // Append a query parameter of the tile expiration date to prevent caching + var timestampQuery = '?expired=' + this.expireDate.toString(); + url = joinUrls(url, timestampQuery, false); + } - if (camera._mode === SceneMode.SCENE2D) { - if (!defined(camera._changedFrustum)) { - camera._changedPosition = Cartesian3.clone(camera.position, camera._changedPosition); - camera._changedFrustum = camera.frustum.clone(); - return; - } + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.TILES3D, + priorityFunction : createPriorityFunction(this), + serverKey : this._serverKey + }); - var position = camera.position; - var lastPosition = camera._changedPosition; + var promise = loadArrayBuffer(url, undefined, request); - var frustum = camera.frustum; - var lastFrustum = camera._changedFrustum; + if (!defined(promise)) { + return false; + } - var x0 = position.x + frustum.left; - var x1 = position.x + frustum.right; - var x2 = lastPosition.x + lastFrustum.left; - var x3 = lastPosition.x + lastFrustum.right; + var contentState = this._contentState; + this._contentState = Cesium3DTileContentState.LOADING; + this._contentReadyToProcessPromise = when.defer(); + this._contentReadyPromise = when.defer(); - var y0 = position.y + frustum.bottom; - var y1 = position.y + frustum.top; - var y2 = lastPosition.y + lastFrustum.bottom; - var y3 = lastPosition.y + lastFrustum.top; + if (expired) { + this.expireDate = undefined; + } - var leftX = Math.max(x0, x2); - var rightX = Math.min(x1, x3); - var bottomY = Math.max(y0, y2); - var topY = Math.min(y1, y3); + var contentFailedFunction = getContentFailedFunction(this); - var areaPercentage; - if (leftX >= rightX || bottomY >= y1) { - areaPercentage = 1.0; - } else { - var areaRef = lastFrustum; - if (x0 < x2 && x1 > x3 && y0 < y2 && y1 > y3) { - areaRef = frustum; - } - areaPercentage = 1.0 - ((rightX - leftX) * (topY - bottomY)) / ((areaRef.right - areaRef.left) * (areaRef.top - areaRef.bottom)); + promise.then(function(arrayBuffer) { + if (that.isDestroyed()) { + // Tile is unloaded before the content finishes loading + contentFailedFunction(); + return; } + var uint8Array = new Uint8Array(arrayBuffer); + var magic = getMagic(uint8Array); + var contentFactory = Cesium3DTileContentFactory[magic]; + var content; - if (areaPercentage > percentageChanged) { - camera._changed.raiseEvent(areaPercentage); - camera._changedPosition = Cartesian3.clone(camera.position, camera._changedPosition); - camera._changedFrustum = camera.frustum.clone(camera._changedFrustum); + if (defined(contentFactory)) { + content = contentFactory(tileset, that, that._contentUrl, arrayBuffer, 0); + that.hasRenderableContent = true; + } else { + // The content may be json instead + content = Cesium3DTileContentFactory.json(tileset, that, that._contentUrl, arrayBuffer, 0); + that.hasTilesetContent = true; } - return; - } - if (!defined(camera._changedDirection)) { - camera._changedPosition = Cartesian3.clone(camera.positionWC, camera._changedPosition); - camera._changedDirection = Cartesian3.clone(camera.directionWC, camera._changedDirection); - return; - } + that._content = content; + that._contentState = Cesium3DTileContentState.PROCESSING; + that._contentReadyToProcessPromise.resolve(content); - var dirAngle = CesiumMath.acosClamped(Cartesian3.dot(camera.directionWC, camera._changedDirection)); + return content.readyPromise.then(function(content) { + if (that.isDestroyed()) { + // Tile is unloaded before the content finishes processing + contentFailedFunction(); + return; + } + updateExpireDate(that); - var dirPercentage; - if (defined(camera.frustum.fovy)) { - dirPercentage = dirAngle / (camera.frustum.fovy * 0.5); - } else { - dirPercentage = dirAngle; - } + // Refresh style for expired content + that.lastStyleTime = 0; - var distance = Cartesian3.distance(camera.positionWC, camera._changedPosition); - var heightPercentage = distance / camera.positionCartographic.height; + that._contentState = Cesium3DTileContentState.READY; + that._contentReadyPromise.resolve(content); + }); + }).otherwise(function(error) { + if (request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + that._contentState = contentState; + --tileset.statistics.numberOfPendingRequests; + ++tileset.statistics.numberOfAttemptedRequests; + return; + } + contentFailedFunction(error); + }); - if (dirPercentage > percentageChanged || heightPercentage > percentageChanged) { - camera._changed.raiseEvent(Math.max(dirPercentage, heightPercentage)); - camera._changedPosition = Cartesian3.clone(camera.positionWC, camera._changedPosition); - camera._changedDirection = Cartesian3.clone(camera.directionWC, camera._changedDirection); - } + return true; }; - var scratchAdjustHeightTransform = new Matrix4(); - var scratchAdjustHeightCartographic = new Cartographic(); - - Camera.prototype._adjustHeightForTerrain = function() { - var scene = this._scene; - - var screenSpaceCameraController = scene.screenSpaceCameraController; - var enableCollisionDetection = screenSpaceCameraController.enableCollisionDetection; - var minimumCollisionTerrainHeight = screenSpaceCameraController.minimumCollisionTerrainHeight; - var minimumZoomDistance = screenSpaceCameraController.minimumZoomDistance; - - if (this._suspendTerrainAdjustment || !enableCollisionDetection) { + /** + * Unloads the tile's content. + * + * @private + */ + Cesium3DTile.prototype.unloadContent = function() { + if (!this.hasRenderableContent) { return; } - var mode = this._mode; - var globe = scene.globe; + this._content = this._content && this._content.destroy(); + this._contentState = Cesium3DTileContentState.UNLOADED; + this._contentReadyToProcessPromise = undefined; + this._contentReadyPromise = undefined; - if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) { - return; - } + this.replacementNode = undefined; - var ellipsoid = globe.ellipsoid; - var projection = scene.mapProjection; + this.lastStyleTime = 0; - var transform; - var mag; - if (!Matrix4.equals(this.transform, Matrix4.IDENTITY)) { - transform = Matrix4.clone(this.transform, scratchAdjustHeightTransform); - mag = Cartesian3.magnitude(this.position); - this._setTransform(Matrix4.IDENTITY); - } + this._debugColorizeTiles = false; - var cartographic = scratchAdjustHeightCartographic; - if (mode === SceneMode.SCENE3D) { - ellipsoid.cartesianToCartographic(this.position, cartographic); - } else { - projection.unproject(this.position, cartographic); - } + this._debugBoundingVolume = this._debugBoundingVolume && this._debugBoundingVolume.destroy(); + this._debugContentBoundingVolume = this._debugContentBoundingVolume && this._debugContentBoundingVolume.destroy(); + this._debugViewerRequestVolume = this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy(); + }; - var heightUpdated = false; - if (cartographic.height < minimumCollisionTerrainHeight) { - var height = globe.getHeight(cartographic); - if (defined(height)) { - height += minimumZoomDistance; - if (cartographic.height < height) { - cartographic.height = height; - if (mode === SceneMode.SCENE3D) { - ellipsoid.cartographicToCartesian(cartographic, this.position); - } else { - projection.project(cartographic, this.position); - } - heightUpdated = true; - } - } - } + var scratchProjectedBoundingSphere = new BoundingSphere(); - if (defined(transform)) { - this._setTransform(transform); - if (heightUpdated) { - Cartesian3.normalize(this.position, this.position); - Cartesian3.negate(this.position, this.direction); - Cartesian3.multiplyByScalar(this.position, Math.max(mag, minimumZoomDistance), this.position); - Cartesian3.normalize(this.direction, this.direction); - Cartesian3.cross(this.direction, this.up, this.right); - Cartesian3.cross(this.right, this.direction, this.up); - } + function getBoundingVolume(tile, frameState) { + if (frameState.mode !== SceneMode.SCENE3D && !defined(tile._boundingVolume2D)) { + var boundingSphere = tile._boundingVolume.boundingSphere; + var sphere = BoundingSphere.projectTo2D(boundingSphere, frameState.mapProjection, scratchProjectedBoundingSphere); + tile._boundingVolume2D = new TileBoundingSphere(sphere.center, sphere.radius); } - }; - function convertTransformForColumbusView(camera) { - Transforms.basisTo2D(camera._projection, camera._transform, camera._actualTransform); + return frameState.mode !== SceneMode.SCENE3D ? tile._boundingVolume2D : tile._boundingVolume; } - 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(); + function getContentBoundingVolume(tile, frameState) { + if (frameState.mode !== SceneMode.SCENE3D && !defined(tile._contentBoundingVolume2D)) { + var boundingSphere = tile._contentBoundingVolume.boundingSphere; + var sphere = BoundingSphere.projectTo2D(boundingSphere, frameState.mapProjection, scratchProjectedBoundingSphere); + tile._contentBoundingVolume2D = new TileBoundingSphere(sphere.center, sphere.radius); + } + return frameState.mode !== SceneMode.SCENE3D ? tile._contentBoundingVolume2D : tile._contentBoundingVolume; + } - function convertTransformFor2D(camera) { - var projection = camera._projection; - var ellipsoid = projection.ellipsoid; + /** + * Determines whether the tile's bounding volume intersects the culling volume. + * + * @param {FrameState} frameState The frame state. + * @param {Number} parentVisibilityPlaneMask The parent's plane mask to speed up the visibility check. + * @returns {Number} A plane mask as described above in {@link CullingVolume#computeVisibilityWithPlaneMask}. + * + * @private + */ + Cesium3DTile.prototype.visibility = function(frameState, parentVisibilityPlaneMask) { + var cullingVolume = frameState.cullingVolume; + var boundingVolume = getBoundingVolume(this, frameState); + return cullingVolume.computeVisibilityWithPlaneMask(boundingVolume, parentVisibilityPlaneMask); + }; - var origin = Matrix4.getColumn(camera._transform, 3, scratchCartesian4Origin); - var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic); + /** + * Assuming the tile's bounding volume intersects the culling volume, determines + * whether the tile's content's bounding volume intersects the culling volume. + * + * @param {FrameState} frameState The frame state. + * @returns {Intersect} The result of the intersection: the tile's content is completely outside, completely inside, or intersecting the culling volume. + * + * @private + */ + Cesium3DTile.prototype.contentVisibility = function(frameState) { + // Assumes the tile's bounding volume intersects the culling volume already, so + // just return Intersect.INSIDE if there is no content bounding volume. + if (!defined(this._contentBoundingVolume)) { + return Intersect.INSIDE; + } - 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; + // PERFORMANCE_IDEA: is it possible to burn less CPU on this test since we know the + // tile's (not the content's) bounding volume intersects the culling volume? + var cullingVolume = frameState.cullingVolume; + var boundingVolume = getContentBoundingVolume(this, frameState); + return cullingVolume.computeVisibility(boundingVolume); + }; - var newZAxis = Cartesian4.clone(Cartesian4.UNIT_X, scratchCartesian4NewZAxis); + /** + * Computes the (potentially approximate) distance from the closest point of the tile's bounding volume to the camera. + * + * @param {FrameState} frameState The frame state. + * @returns {Number} The distance, in meters, or zero if the camera is inside the bounding volume. + * + * @private + */ + Cesium3DTile.prototype.distanceToTile = function(frameState) { + var boundingVolume = getBoundingVolume(this, frameState); + return boundingVolume.distanceToCamera(frameState); + }; - var xAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 0, scratchCartesian3), origin, scratchCartesian3); - ellipsoid.cartesianToCartographic(xAxis, cartographic); + var scratchToTileCenter = new Cartesian3(); - projection.project(cartographic, projectedPosition); - var newXAxis = scratchCartesian4NewXAxis; - newXAxis.x = projectedPosition.z; - newXAxis.y = projectedPosition.x; - newXAxis.z = projectedPosition.y; - newXAxis.w = 0.0; + /** + * Computes the distance from the center of the tile's bounding volume to the camera. + * + * @param {FrameState} frameState The frame state. + * @returns {Number} The distance, in meters, or zero if the camera is inside the bounding volume. + * + * @private + */ + Cesium3DTile.prototype.distanceToTileCenter = function(frameState) { + var tileBoundingVolume = getBoundingVolume(this, frameState); + var boundingVolume = tileBoundingVolume.boundingVolume; // Gets the underlying OrientedBoundingBox or BoundingSphere + var toCenter = Cartesian3.subtract(boundingVolume.center, frameState.camera.positionWC, scratchToTileCenter); + var distance = Cartesian3.magnitude(toCenter); + Cartesian3.divideByScalar(toCenter, distance, toCenter); + var dot = Cartesian3.dot(frameState.camera.directionWC, toCenter); + return distance * dot; + }; - Cartesian3.subtract(newXAxis, newOrigin, newXAxis); - newXAxis.x = 0.0; + /** + * Checks if the camera is inside the viewer request volume. + * + * @param {FrameState} frameState The frame state. + * @returns {Boolean} Whether the camera is inside the volume. + * + * @private + */ + Cesium3DTile.prototype.insideViewerRequestVolume = function(frameState) { + var viewerRequestVolume = this._viewerRequestVolume; + return !defined(viewerRequestVolume) || (viewerRequestVolume.distanceToCamera(frameState) === 0.0); + }; - var newYAxis = scratchCartesian4NewYAxis; - if (Cartesian3.magnitudeSquared(newXAxis) > CesiumMath.EPSILON10) { - Cartesian3.cross(newZAxis, newXAxis, newYAxis); - } else { - var yAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 1, scratchCartesian3), origin, scratchCartesian3); - ellipsoid.cartesianToCartographic(yAxis, cartographic); + var scratchMatrix = new Matrix3(); + var scratchScale = new Cartesian3(); + var scratchHalfAxes = new Matrix3(); + var scratchCenter = new Cartesian3(); + var scratchRectangle = new Rectangle(); - projection.project(cartographic, projectedPosition); - newYAxis.x = projectedPosition.z; - newYAxis.y = projectedPosition.x; - newYAxis.z = projectedPosition.y; - newYAxis.w = 0.0; + function createBox(box, transform, result) { + var center = Cartesian3.fromElements(box[0], box[1], box[2], scratchCenter); + var halfAxes = Matrix3.fromArray(box, 3, scratchHalfAxes); - Cartesian3.subtract(newYAxis, newOrigin, newYAxis); - newYAxis.x = 0.0; + // Find the transformed center and halfAxes + center = Matrix4.multiplyByPoint(transform, center, center); + var rotationScale = Matrix4.getRotation(transform, scratchMatrix); + halfAxes = Matrix3.multiply(rotationScale, halfAxes, halfAxes); - if (Cartesian3.magnitudeSquared(newYAxis) < CesiumMath.EPSILON10) { - Cartesian4.clone(Cartesian4.UNIT_Y, newXAxis); - Cartesian4.clone(Cartesian4.UNIT_Z, newYAxis); - } + if (defined(result)) { + result.update(center, halfAxes); + return result; } + return new TileOrientedBoundingBox(center, halfAxes); + } - Cartesian3.cross(newYAxis, newZAxis, newXAxis); - Cartesian3.normalize(newXAxis, newXAxis); - Cartesian3.cross(newZAxis, newXAxis, newYAxis); - Cartesian3.normalize(newYAxis, newYAxis); + function createRegion(region, result) { + var rectangleRegion = Rectangle.unpack(region, 0, scratchRectangle); - Matrix4.setColumn(camera._actualTransform, 0, newXAxis, camera._actualTransform); - Matrix4.setColumn(camera._actualTransform, 1, newYAxis, camera._actualTransform); - Matrix4.setColumn(camera._actualTransform, 2, newZAxis, camera._actualTransform); - Matrix4.setColumn(camera._actualTransform, 3, newOrigin, camera._actualTransform); + if (defined(result)) { + // Don't update regions when the transform changes + return result; + } + return new TileBoundingRegion({ + rectangle : rectangleRegion, + minimumHeight : region[4], + maximumHeight : region[5] + }); } - var scratchCartesian = new Cartesian3(); + function createSphere(sphere, transform, result) { + var center = Cartesian3.fromElements(sphere[0], sphere[1], sphere[2], scratchCenter); + var radius = sphere[3]; - function updateMembers(camera) { - var mode = camera._mode; + // Find the transformed center and radius + center = Matrix4.multiplyByPoint(transform, center, center); + var scale = Matrix4.getScale(transform, scratchScale); + var uniformScale = Cartesian3.maximumComponent(scale); + radius *= uniformScale; - var heightChanged = false; - var height = 0.0; - if (mode === SceneMode.SCENE2D) { - height = camera.frustum.right - camera.frustum.left; - heightChanged = height !== camera._positionCartographic.height; + if (defined(result)) { + result.update(center, radius); + return result; } + return new TileBoundingSphere(center, radius); + } - var position = camera._position; - var positionChanged = !Cartesian3.equals(position, camera.position) || heightChanged; - if (positionChanged) { - position = Cartesian3.clone(camera.position, camera._position); + /** + * Create a bounding volume from the tile's bounding volume header. + * + * @param {Object} boundingVolumeHeader The tile's bounding volume header. + * @param {Matrix4} transform The transform to apply to the bounding volume. + * @param {TileBoundingVolume} [result] The object onto which to store the result. + * + * @returns {TileBoundingVolume} The modified result parameter or a new TileBoundingVolume instance if none was provided. + * + * @private + */ + Cesium3DTile.prototype.createBoundingVolume = function(boundingVolumeHeader, transform, result) { + if (!defined(boundingVolumeHeader)) { + throw new RuntimeError('boundingVolume must be defined'); } - - var direction = camera._direction; - var directionChanged = !Cartesian3.equals(direction, camera.direction); - if (directionChanged) { - Cartesian3.normalize(camera.direction, camera.direction); - direction = Cartesian3.clone(camera.direction, camera._direction); + if (defined(boundingVolumeHeader.box)) { + return createBox(boundingVolumeHeader.box, transform, result); } - - var up = camera._up; - var upChanged = !Cartesian3.equals(up, camera.up); - if (upChanged) { - Cartesian3.normalize(camera.up, camera.up); - up = Cartesian3.clone(camera.up, camera._up); + if (defined(boundingVolumeHeader.region)) { + return createRegion(boundingVolumeHeader.region, result); } - - var right = camera._right; - var rightChanged = !Cartesian3.equals(right, camera.right); - if (rightChanged) { - Cartesian3.normalize(camera.right, camera.right); - right = Cartesian3.clone(camera.right, camera._right); + if (defined(boundingVolumeHeader.sphere)) { + return createSphere(boundingVolumeHeader.sphere, transform, result); } + throw new RuntimeError('boundingVolume must contain a sphere, region, or box'); + }; - var transformChanged = camera._transformChanged || camera._modeChanged; - camera._transformChanged = false; - - if (transformChanged) { - Matrix4.inverseTransformation(camera._transform, camera._invTransform); - - if (camera._mode === SceneMode.COLUMBUS_VIEW || camera._mode === SceneMode.SCENE2D) { - if (Matrix4.equals(Matrix4.IDENTITY, camera._transform)) { - Matrix4.clone(Camera.TRANSFORM_2D, camera._actualTransform); - } else if (camera._mode === SceneMode.COLUMBUS_VIEW) { - convertTransformForColumbusView(camera); - } else { - convertTransformFor2D(camera); - } - } else { - Matrix4.clone(camera._transform, camera._actualTransform); - } + var scratchTransform = new Matrix4(); - Matrix4.inverseTransformation(camera._actualTransform, camera._actualInvTransform); + /** + * Update the tile's transform. The transform is applied to the tile's bounding volumes. + * + * @private + */ + Cesium3DTile.prototype.updateTransform = function(parentTransform) { + parentTransform = defaultValue(parentTransform, Matrix4.IDENTITY); + var computedTransform = Matrix4.multiply(parentTransform, this.transform, scratchTransform); + var transformChanged = !Matrix4.equals(computedTransform, this.computedTransform); - camera._modeChanged = false; + if (!transformChanged) { + return; } - var transform = camera._actualTransform; + Matrix4.clone(computedTransform, this.computedTransform); - if (positionChanged || transformChanged) { - camera._positionWC = Matrix4.multiplyByPoint(transform, position, camera._positionWC); + // Update the bounding volumes + var header = this._header; + var content = this._header.content; + this._boundingVolume = this.createBoundingVolume(header.boundingVolume, computedTransform, this._boundingVolume); + if (defined(this._contentBoundingVolume)) { + this._contentBoundingVolume = this.createBoundingVolume(content.boundingVolume, computedTransform, this._contentBoundingVolume); + } + if (defined(this._viewerRequestVolume)) { + this._viewerRequestVolume = this.createBoundingVolume(header.viewerRequestVolume, computedTransform, this._viewerRequestVolume); + } - // Compute the Cartographic position of the camera. - if (mode === SceneMode.SCENE3D || mode === SceneMode.MORPHING) { - camera._positionCartographic = camera._projection.ellipsoid.cartesianToCartographic(camera._positionWC, camera._positionCartographic); - } else { - // The camera position is 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 positionENU = scratchCartesian; - positionENU.x = camera._positionWC.y; - positionENU.y = camera._positionWC.z; - positionENU.z = camera._positionWC.x; + // Destroy the debug bounding volumes. They will be generated fresh. + this._debugBoundingVolume = this._debugBoundingVolume && this._debugBoundingVolume.destroy(); + this._debugContentBoundingVolume = this._debugContentBoundingVolume && this._debugContentBoundingVolume.destroy(); + this._debugViewerRequestVolume = this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy(); + }; - // 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) { - positionENU.z = height; - } + function applyDebugSettings(tile, tileset, frameState) { + var hasContentBoundingVolume = defined(tile._header.content) && defined(tile._header.content.boundingVolume); - camera._projection.unproject(positionENU, camera._positionCartographic); + var showVolume = tileset.debugShowBoundingVolume || (tileset.debugShowContentBoundingVolume && !hasContentBoundingVolume); + if (showVolume) { + if (!defined(tile._debugBoundingVolume)) { + var color = tile._finalResolution ? (hasContentBoundingVolume ? Color.WHITE : Color.RED) : Color.YELLOW; + tile._debugBoundingVolume = tile._boundingVolume.createDebugVolume(color); } + tile._debugBoundingVolume.update(frameState); + } else if (!showVolume && defined(tile._debugBoundingVolume)) { + tile._debugBoundingVolume = tile._debugBoundingVolume.destroy(); } - if (directionChanged || upChanged || rightChanged) { - var det = Cartesian3.dot(direction, Cartesian3.cross(up, right, scratchCartesian)); - if (Math.abs(1.0 - det) > CesiumMath.EPSILON2) { - //orthonormalize axes - var invUpMag = 1.0 / Cartesian3.magnitudeSquared(up); - var scalar = Cartesian3.dot(up, direction) * invUpMag; - var w0 = Cartesian3.multiplyByScalar(direction, scalar, scratchCartesian); - up = Cartesian3.normalize(Cartesian3.subtract(up, w0, camera._up), camera._up); - Cartesian3.clone(up, camera.up); - - right = Cartesian3.cross(direction, up, camera._right); - Cartesian3.clone(right, camera.right); + if (tileset.debugShowContentBoundingVolume && hasContentBoundingVolume) { + if (!defined(tile._debugContentBoundingVolume)) { + tile._debugContentBoundingVolume = tile._contentBoundingVolume.createDebugVolume(Color.BLUE); } + tile._debugContentBoundingVolume.update(frameState); + } else if (!tileset.debugShowContentBoundingVolume && defined(tile._debugContentBoundingVolume)) { + tile._debugContentBoundingVolume = tile._debugContentBoundingVolume.destroy(); } - if (directionChanged || transformChanged) { - camera._directionWC = Matrix4.multiplyByPointAsVector(transform, direction, camera._directionWC); + if (tileset.debugShowViewerRequestVolume && defined(tile._viewerRequestVolume)) { + if (!defined(tile._debugViewerRequestVolume)) { + tile._debugViewerRequestVolume = tile._viewerRequestVolume.createDebugVolume(Color.YELLOW); + } + tile._debugViewerRequestVolume.update(frameState); + } else if (!tileset.debugShowViewerRequestVolume && defined(tile._debugViewerRequestVolume)) { + tile._debugViewerRequestVolume = tile._debugViewerRequestVolume.destroy(); } - if (upChanged || transformChanged) { - camera._upWC = Matrix4.multiplyByPointAsVector(transform, up, camera._upWC); + var debugColorizeTilesOn = tileset.debugColorizeTiles && !tile._debugColorizeTiles; + var debugColorizeTilesOff = !tileset.debugColorizeTiles && tile._debugColorizeTiles; + + if (debugColorizeTilesOn) { + tile._debugColorizeTiles = true; + tile.color = tile._debugColor; + } else if (debugColorizeTilesOff) { + tile._debugColorizeTiles = false; + tile.color = Color.WHITE; } - if (rightChanged || transformChanged) { - camera._rightWC = Matrix4.multiplyByPointAsVector(transform, right, camera._rightWC); + if (tile._colorDirty) { + tile._colorDirty = false; + tile._content.applyDebugSettings(true, tile._color); } - if (positionChanged || directionChanged || upChanged || rightChanged || transformChanged) { - updateViewMatrix(camera); + if (debugColorizeTilesOff) { + tileset.makeStyleDirty(); // Re-apply style now that colorize is switched off } } - function getHeading(direction, up) { - var heading; - if (!CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)) { - heading = Math.atan2(direction.y, direction.x) - CesiumMath.PI_OVER_TWO; - } else { - heading = Math.atan2(up.y, up.x) - CesiumMath.PI_OVER_TWO; + function updateContent(tile, tileset, frameState) { + var content = tile._content; + var expiredContent = tile._expiredContent; + + if (defined(expiredContent)) { + if (!tile.contentReady) { + // Render the expired content while the content loads + expiredContent.update(tileset, frameState); + return; + } + + // New content is ready, destroy expired content + tile._expiredContent.destroy(); + tile._expiredContent = undefined; } - return CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(heading); + content.update(tileset, frameState); } - function getPitch(direction) { - return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(direction.z); - } + /** + * Get the draw commands needed to render this tile. + * + * @private + */ + Cesium3DTile.prototype.update = function(tileset, frameState) { + var initCommandLength = frameState.commandList.length; + applyDebugSettings(this, tileset, frameState); + updateContent(this, tileset, frameState); + this._commandsLength = frameState.commandList.length - initCommandLength; + }; - function getRoll(direction, up, right) { - var roll = 0.0; - if (!CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)) { - roll = Math.atan2(-right.z, up.z); - roll = CesiumMath.zeroToTwoPi(roll + CesiumMath.TWO_PI); - } + var scratchCommandList = []; - return roll; - } + /** + * Processes the tile's content, e.g., create WebGL resources, to move from the PROCESSING to READY state. + * + * @param {Cesium3DTileset} tileset The tileset containing this tile. + * @param {FrameState} frameState The frame state. + * + * @private + */ + Cesium3DTile.prototype.process = function(tileset, frameState) { + var savedCommandList = frameState.commandList; + frameState.commandList = scratchCommandList; - var scratchHPRMatrix1 = new Matrix4(); - var scratchHPRMatrix2 = new Matrix4(); + this._content.update(tileset, frameState); - defineProperties(Camera.prototype, { + scratchCommandList.length = 0; + frameState.commandList = savedCommandList; + }; + + /** + * @private + */ + Cesium3DTile.prototype.isDestroyed = function() { + return false; + }; + + /** + * @private + */ + Cesium3DTile.prototype.destroy = function() { + // For the interval between new content being requested and downloaded, expiredContent === content, so don't destroy twice + this._content = this._content && this._content.destroy(); + this._expiredContent = this._expiredContent && !this._expiredContent.isDestroyed() && this._expiredContent.destroy(); + this._debugBoundingVolume = this._debugBoundingVolume && this._debugBoundingVolume.destroy(); + this._debugContentBoundingVolume = this._debugContentBoundingVolume && this._debugContentBoundingVolume.destroy(); + this._debugViewerRequestVolume = this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy(); + return destroyObject(this); + }; + + return Cesium3DTile; +}); + +define('Scene/Cesium3DTileContent',[ + '../Core/defineProperties', + '../Core/DeveloperError' + ], function( + defineProperties, + DeveloperError) { + 'use strict'; + + /** + * The content of a tile in a {@link Cesium3DTileset}. + *

    + * Derived classes of this interface provide access to individual features in the tile. + * Access derived objects through {@link Cesium3DTile#content}. + *

    + *

    + * This type describes an interface and is not intended to be instantiated directly. + *

    + * + * @alias Cesium3DTileContent + * @constructor + */ + function Cesium3DTileContent(tileset, tile, url, arrayBuffer, byteOffset) { /** - * Gets the camera's reference frame. The inverse of this transformation is appended to the view matrix. - * @memberof Camera.prototype + * Gets or sets if any feature's property changed. Used to + * optimized applying a style when a feature's property changed. + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    * - * @type {Matrix4} - * @readonly + * @type {Boolean} * - * @default {@link Matrix4.IDENTITY} + * @private */ - transform : { + this.featurePropertiesDirty = false; + } + + defineProperties(Cesium3DTileContent.prototype, { + /** + * Gets the number of features in the tile. + * + * @memberof Cesium3DTileContent.prototype + * + * @type {Number} + * @readonly + */ + featuresLength : { get : function() { - return this._transform; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the inverse camera transform. - * @memberof Camera.prototype + * Gets the number of points in the tile. + *

    + * Only applicable for tiles with Point Cloud content. This is different than {@link Cesium3DTileContent#featuresLength} which + * equals the number of groups of points as distinguished by the BATCH_ID feature table semantic. + *

    * - * @type {Matrix4} - * @readonly + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md#batched-points} * - * @default {@link Matrix4.IDENTITY} + * @memberof Cesium3DTileContent.prototype + * + * @type {Number} + * @readonly */ - inverseTransform : { + pointsLength : { get : function() { - updateMembers(this); - return this._invTransform; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the view matrix. - * @memberof Camera.prototype + * Gets the number of triangles in the tile. * - * @type {Matrix4} + * @memberof Cesium3DTileContent.prototype + * + * @type {Number} * @readonly + */ + trianglesLength : { + get : function() { + DeveloperError.throwInstantiationError(); + } + }, + + /** + * Gets the tile's geometry memory in bytes. * - * @see Camera#inverseViewMatrix + * @memberof Cesium3DTileContent.prototype + * + * @type {Number} + * @readonly */ - viewMatrix : { + geometryByteLength : { get : function() { - updateMembers(this); - return this._viewMatrix; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the inverse view matrix. - * @memberof Camera.prototype + * Gets the tile's texture memory in bytes. * - * @type {Matrix4} + * @memberof Cesium3DTileContent.prototype + * + * @type {Number} * @readonly + */ + texturesByteLength : { + get : function() { + DeveloperError.throwInstantiationError(); + } + }, + + /** + * Gets the amount of memory used by the batch table textures, in bytes. * - * @see Camera#viewMatrix + * @memberof Cesium3DTileContent.prototype + * + * @type {Number} + * @readonly */ - inverseViewMatrix : { + batchTableByteLength : { get : function() { - updateMembers(this); - return this._invViewMatrix; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the {@link Cartographic} position of the camera, with longitude and latitude - * expressed in radians and height in meters. In 2D and Columbus View, it is possible - * for the returned longitude and latitude to be outside the range of valid longitudes - * and latitudes when the camera is outside the map. - * @memberof Camera.prototype + * Gets the array of {@link Cesium3DTileContent} objects that represent the + * content a composite's inner tiles, which can also be composites. * - * @type {Cartographic} + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/Composite/README.md} + * + * @memberof Cesium3DTileContent.prototype + * + * @type {Array} * @readonly */ - positionCartographic : { + innerContents : { get : function() { - updateMembers(this); - return this._positionCartographic; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the position of the camera in world coordinates. - * @memberof Camera.prototype + * Gets the promise that will be resolved when the tile's content is ready to render. * - * @type {Cartesian3} + * @memberof Cesium3DTileContent.prototype + * + * @type {Promise.} * @readonly */ - positionWC : { + readyPromise : { get : function() { - updateMembers(this); - return this._positionWC; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the view direction of the camera in world coordinates. - * @memberof Camera.prototype + * Gets the tileset for this tile. * - * @type {Cartesian3} + * @type {Cesium3DTileset} * @readonly */ - directionWC : { + tileset : { get : function() { - updateMembers(this); - return this._directionWC; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the up direction of the camera in world coordinates. - * @memberof Camera.prototype + * Gets the tile containing this content. * - * @type {Cartesian3} + * @type {Cesium3DTile} * @readonly */ - upWC : { + tile : { get : function() { - updateMembers(this); - return this._upWC; + DeveloperError.throwInstantiationError(); } }, /** - * Gets the right direction of the camera in world coordinates. - * @memberof Camera.prototype + * Gets the url of the tile's content. + * @memberof Cesium3DTileContent.prototype * - * @type {Cartesian3} + * @type {String} * @readonly */ - rightWC : { + url : { get : function() { - updateMembers(this); - return this._rightWC; + DeveloperError.throwInstantiationError(); } }, - /** - * Gets the camera heading in radians. - * @memberof Camera.prototype - * - * @type {Number} - * @readonly - */ - heading : { - get : function() { - if (this._mode !== SceneMode.MORPHING) { - var ellipsoid = this._projection.ellipsoid; + /** + * Gets the batch table for this content. + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    + * + * @type {Cesium3DTileBatchTable} + * @readonly + * + * @private + */ + batchTable : { + get : function() { + DeveloperError.throwInstantiationError(); + } + } + }); + + /** + * Determines if the tile's batch table has a property. If it does, each feature in + * the tile will have the property. + * + * @param {Number} batchId The batchId for the feature. + * @param {String} name The case-sensitive name of the property. + * @returns {Boolean} true if the property exists; otherwise, false. + */ + Cesium3DTileContent.prototype.hasProperty = function(batchId, name) { + DeveloperError.throwInstantiationError(); + }; + + /** + * Returns the {@link Cesium3DTileFeature} object for the feature with the + * given batchId. This object is used to get and modify the + * feature's properties. + *

    + * Features in a tile are ordered by batchId, an index used to retrieve their metadata from the batch table. + *

    + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/TileFormats/BatchTable}. + * + * @param {Number} batchId The batchId for the feature. + * @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object. + * + * @exception {DeveloperError} batchId must be between zero and {@link Cesium3DTileContent#featuresLength} - 1. + */ + Cesium3DTileContent.prototype.getFeature = function(batchId) { + DeveloperError.throwInstantiationError(); + }; + + /** + * Called when {@link Cesium3DTileset#debugColorizeTiles} changes. + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    + * + * @param {Boolean} enabled Whether to enable or disable debug settings. + * @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object. + + * @private + */ + Cesium3DTileContent.prototype.applyDebugSettings = function(enabled, color) { + DeveloperError.throwInstantiationError(); + }; + + /** + * Apply a style to the content + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    + * + * @param {FrameSate} frameState The frame state. + * @param {Cesium3DTileStyle} style The style. + * + * @private + */ + Cesium3DTileContent.prototype.applyStyle = function(frameState, style) { + DeveloperError.throwInstantiationError(); + }; + + /** + * Called by the tile during tileset traversal to get the draw commands needed to render this content. + * When the tile's content is in the PROCESSING state, this creates WebGL resources to ultimately + * move to the READY state. + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    + * + * @param {Cesium3DTileset} tileset The tileset containing this tile. + * @param {FrameState} frameState The frame state. + * + * @private + */ + Cesium3DTileContent.prototype.update = function(tileset, frameState) { + 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. + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    + * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see Cesium3DTileContent#destroy + * + * @private + */ + Cesium3DTileContent.prototype.isDestroyed = function() { + 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. + *

    + * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

    + * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * content = content && content.destroy(); + * + * @see Cesium3DTileContent#isDestroyed + * + * @private + */ + Cesium3DTileContent.prototype.destroy = function() { + DeveloperError.throwInstantiationError(); + }; + + return Cesium3DTileContent; +}); + +define('Scene/Cesium3DTileOptimizations',[ + '../Core/Cartesian3', + '../Core/Check', + './Cesium3DTileOptimizationHint', + './TileBoundingRegion', + './TileOrientedBoundingBox' + ], function( + Cartesian3, + Check, + Cesium3DTileOptimizationHint, + TileBoundingRegion, + TileOrientedBoundingBox) { + 'use strict'; + + /** + * Utility functions for computing optimization hints for a {@link Cesium3DTileset}. + * + * @exports Cesium3DTileOptimizations + * + * @private + */ + var Cesium3DTileOptimizations = {}; + + var scratchAxis = new Cartesian3(); + + /** + * Evaluates support for the childrenWithinParent optimization. This is used to more tightly cull tilesets if + * children bounds are fully contained within the parent. Currently, support for the optimization only works for + * oriented bounding boxes, so both the child and parent tile must be either a {@link TileOrientedBoundingBox} or + * {@link TileBoundingRegion}. The purpose of this check is to prevent use of a culling optimization when the child + * bounds exceed those of the parent. If the child bounds are greater, it is more likely that the optimization will + * waste CPU cycles. Bounding spheres are not supported for the reason that the child bounds can very often be + * partially outside of the parent bounds. + * + * @param {Cesium3DTile} tile The tile to check. + * @returns {Boolean} Whether the childrenWithinParent optimization is supported. + */ + Cesium3DTileOptimizations.checkChildrenWithinParent = function(tile) { + + var children = tile.children; + var length = children.length; + + // Check if the parent has an oriented bounding box. + var boundingVolume = tile._boundingVolume; + if (boundingVolume instanceof TileOrientedBoundingBox || boundingVolume instanceof TileBoundingRegion) { + var orientedBoundingBox = boundingVolume._orientedBoundingBox; + tile._optimChildrenWithinParent = Cesium3DTileOptimizationHint.USE_OPTIMIZATION; + for (var i = 0; i < length; ++i) { + var child = children[i]; + + // Check if the child has an oriented bounding box. + var childBoundingVolume = child._boundingVolume; + if (!(childBoundingVolume instanceof TileOrientedBoundingBox || childBoundingVolume instanceof TileBoundingRegion)) { + // Do not support if the parent and child both do not have oriented bounding boxes. + tile._optimChildrenWithinParent = Cesium3DTileOptimizationHint.SKIP_OPTIMIZATION; + break; + } + + var childOrientedBoundingBox = childBoundingVolume._orientedBoundingBox; + + // Compute the axis from the parent to the child. + var axis = Cartesian3.subtract(childOrientedBoundingBox.center, orientedBoundingBox.center, scratchAxis); + var axisLength = Cartesian3.magnitude(axis); + Cartesian3.divideByScalar(axis, axisLength, axis); + + // Project the bounding box of the parent onto the axis. Because the axis is a ray from the parent + // to the child, the projection parameterized along the ray will be (+/- proj1). + var proj1 = Math.abs(orientedBoundingBox.halfAxes[0] * axis.x) + + Math.abs(orientedBoundingBox.halfAxes[1] * axis.y) + + Math.abs(orientedBoundingBox.halfAxes[2] * axis.z) + + Math.abs(orientedBoundingBox.halfAxes[3] * axis.x) + + Math.abs(orientedBoundingBox.halfAxes[4] * axis.y) + + Math.abs(orientedBoundingBox.halfAxes[5] * axis.z) + + Math.abs(orientedBoundingBox.halfAxes[6] * axis.x) + + Math.abs(orientedBoundingBox.halfAxes[7] * axis.y) + + Math.abs(orientedBoundingBox.halfAxes[8] * axis.z); + + // Project the bounding box of the child onto the axis. Because the axis is a ray from the parent + // to the child, the projection parameterized along the ray will be (+/- proj2) + axis.length. + var proj2 = Math.abs(childOrientedBoundingBox.halfAxes[0] * axis.x) + + Math.abs(childOrientedBoundingBox.halfAxes[1] * axis.y) + + Math.abs(childOrientedBoundingBox.halfAxes[2] * axis.z) + + Math.abs(childOrientedBoundingBox.halfAxes[3] * axis.x) + + Math.abs(childOrientedBoundingBox.halfAxes[4] * axis.y) + + Math.abs(childOrientedBoundingBox.halfAxes[5] * axis.z) + + Math.abs(childOrientedBoundingBox.halfAxes[6] * axis.x) + + Math.abs(childOrientedBoundingBox.halfAxes[7] * axis.y) + + Math.abs(childOrientedBoundingBox.halfAxes[8] * axis.z); + + // If the child extends the parent's bounds, the optimization is not valid and we skip it. + if (proj1 <= proj2 + axisLength) { + tile._optimChildrenWithinParent = Cesium3DTileOptimizationHint.SKIP_OPTIMIZATION; + break; + } + } + } + + return tile._optimChildrenWithinParent === Cesium3DTileOptimizationHint.USE_OPTIMIZATION; + }; + + return Cesium3DTileOptimizations; +}); + +define('Scene/Cesium3DTilesetStatistics',[ + '../Core/defined' + ], function( + defined) { + 'use strict'; + + /** + * @private + */ + function Cesium3DTilesetStatistics() { + // Rendering statistics + this.selected = 0; + this.visited = 0; + // Loading statistics + this.numberOfCommands = 0; + this.numberOfAttemptedRequests = 0; + this.numberOfPendingRequests = 0; + this.numberOfTilesProcessing = 0; + this.numberOfTilesWithContentReady = 0; // Number of tiles with content loaded, does not include empty tiles + this.numberOfTilesTotal = 0; // Number of tiles in tileset.json (and other tileset.json files as they are loaded) + // Features statistics + this.numberOfFeaturesSelected = 0; // Number of features rendered + this.numberOfFeaturesLoaded = 0; // Number of features in memory + this.numberOfPointsSelected = 0; + this.numberOfPointsLoaded = 0; + this.numberOfTrianglesSelected = 0; + // Styling statistics + this.numberOfTilesStyled = 0; + this.numberOfFeaturesStyled = 0; + // Optimization statistics + this.numberOfTilesCulledWithChildrenUnion = 0; + // Memory statistics + this.geometryByteLength = 0; + this.texturesByteLength = 0; + this.batchTableByteLength = 0; + } + + Cesium3DTilesetStatistics.prototype.clear = function() { + this.selected = 0; + this.visited = 0; + this.numberOfCommands = 0; + this.numberOfAttemptedRequests = 0; + this.numberOfFeaturesSelected = 0; + this.numberOfPointsSelected = 0; + this.numberOfTrianglesSelected = 0; + this.numberOfTilesStyled = 0; + this.numberOfFeaturesStyled = 0; + this.numberOfTilesCulledWithChildrenUnion = 0; + }; + + function updatePointAndFeatureCounts(statistics, content, decrement, load) { + var contents = content.innerContents; + var pointsLength = content.pointsLength; + var trianglesLength = content.trianglesLength; + var featuresLength = content.featuresLength; + var geometryByteLength = content.geometryByteLength; + var texturesByteLength = content.texturesByteLength; + var batchTableByteLength = content.batchTableByteLength; + + if (load) { + statistics.numberOfFeaturesLoaded += decrement ? -featuresLength : featuresLength; + statistics.numberOfPointsLoaded += decrement ? -pointsLength : pointsLength; + statistics.geometryByteLength += decrement ? -geometryByteLength : geometryByteLength; + statistics.texturesByteLength += decrement ? -texturesByteLength : texturesByteLength; + statistics.batchTableByteLength += decrement ? -batchTableByteLength : batchTableByteLength; + } else { + statistics.numberOfFeaturesSelected += decrement ? -featuresLength : featuresLength; + statistics.numberOfPointsSelected += decrement ? -pointsLength : pointsLength; + statistics.numberOfTrianglesSelected += decrement ? -trianglesLength : trianglesLength; + } + + if (defined(contents)) { + var length = contents.length; + for (var i = 0; i < length; ++i) { + updatePointAndFeatureCounts(statistics, contents[i], decrement, load); + } + } + } + + Cesium3DTilesetStatistics.prototype.incrementSelectionCounts = function(content) { + updatePointAndFeatureCounts(this, content, false, false); + }; + + Cesium3DTilesetStatistics.prototype.incrementLoadCounts = function(content) { + updatePointAndFeatureCounts(this, content, false, true); + }; + + Cesium3DTilesetStatistics.prototype.decrementLoadCounts = function(content) { + updatePointAndFeatureCounts(this, content, true, true); + }; + + Cesium3DTilesetStatistics.clone = function(statistics, result) { + result.selected = statistics.selected; + result.visited = statistics.visited; + result.numberOfCommands = statistics.numberOfCommands; + result.selected = statistics.selected; + result.numberOfAttemptedRequests = statistics.numberOfAttemptedRequests; + result.numberOfPendingRequests = statistics.numberOfPendingRequests; + result.numberOfTilesProcessing = statistics.numberOfTilesProcessing; + result.numberOfTilesWithContentReady = statistics.numberOfTilesWithContentReady; + result.numberOfTilesTotal = statistics.numberOfTilesTotal; + result.numberOfFeaturesSelected = statistics.numberOfFeaturesSelected; + result.numberOfFeaturesLoaded = statistics.numberOfFeaturesLoaded; + result.numberOfPointsSelected = statistics.numberOfPointsSelected; + result.numberOfPointsLoaded = statistics.numberOfPointsLoaded; + result.numberOfTrianglesSelected = statistics.numberOfTrianglesSelected; + result.numberOfTilesStyled = statistics.numberOfTilesStyled; + result.numberOfFeaturesStyled = statistics.numberOfFeaturesStyled; + result.numberOfTilesCulledWithChildrenUnion = statistics.numberOfTilesCulledWithChildrenUnion; + result.geometryByteLength = statistics.geometryByteLength; + result.texturesByteLength = statistics.texturesByteLength; + result.batchTableByteLength = statistics.batchTableByteLength; + }; + + return Cesium3DTilesetStatistics; +}); + +define('Scene/Cesium3DTilesetTraversal',[ + '../Core/CullingVolume', + '../Core/defined', + '../Core/freezeObject', + '../Core/Intersect', + '../Core/ManagedArray', + '../Core/Math', + '../Core/OrthographicFrustum', + './Cesium3DTileChildrenVisibility', + './Cesium3DTileRefine', + './SceneMode' + ], function( + CullingVolume, + defined, + freezeObject, + Intersect, + ManagedArray, + CesiumMath, + OrthographicFrustum, + Cesium3DTileChildrenVisibility, + Cesium3DTileRefine, + SceneMode) { + 'use strict'; + + /** + * @private + */ + var Cesium3DTilesetTraversal = {}; - var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1); - var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); - this._setTransform(transform); + function selectTiles(tileset, frameState, outOfCore) { + if (tileset.debugFreezeFrame) { + return; + } - var heading = getHeading(this.direction, this.up); + var maximumScreenSpaceError = tileset._maximumScreenSpaceError; - this._setTransform(oldTransform); + tileset._desiredTiles.length = 0; + tileset._selectedTiles.length = 0; + tileset._requestedTiles.length = 0; + tileset._selectedTilesToStyle.length = 0; + tileset._hasMixedContent = false; - return heading; - } + // Move sentinel node to the tail so, at the start of the frame, all tiles + // may be potentially replaced. Tiles are moved to the right of the sentinel + // when they are selected so they will not be replaced. + var replacementList = tileset._replacementList; + replacementList.splice(replacementList.tail, tileset._replacementSentinel); - return undefined; - } - }, + var root = tileset._root; + root.updateTransform(tileset._modelMatrix); - /** - * Gets the camera pitch in radians. - * @memberof Camera.prototype - * - * @type {Number} - * @readonly - */ - pitch : { - get : function() { - if (this._mode !== SceneMode.MORPHING) { - var ellipsoid = this._projection.ellipsoid; + if (!root.insideViewerRequestVolume(frameState)) { + return; + } - var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1); - var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); - this._setTransform(transform); + root._distanceToCamera = root.distanceToTile(frameState); - var pitch = getPitch(this.direction); + if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= maximumScreenSpaceError) { + // The SSE of not rendering the tree is small enough that the tree does not need to be rendered + return; + } - this._setTransform(oldTransform); + root._visibilityPlaneMask = root.visibility(frameState, CullingVolume.MASK_INDETERMINATE); + if (root._visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) { + return; + } - return pitch; - } + loadTile(tileset, root, frameState, true); - return undefined; + if (!tileset.skipLevelOfDetail) { + // just execute base traversal and add tiles to _desiredTiles + tileset._baseTraversal.execute(tileset, root, maximumScreenSpaceError, frameState, outOfCore); + var leaves = tileset._baseTraversal.leaves; + var length = leaves.length; + for (var i = 0; i < length; ++i) { + tileset._desiredTiles.push(leaves.get(i)); } - }, + } else if (tileset.immediatelyLoadDesiredLevelOfDetail) { + tileset._skipTraversal.execute(tileset, root, frameState, outOfCore); + } else { + // leaves of the base traversal is where we start the skip traversal + tileset._baseTraversal.leaves = tileset._skipTraversal.queue1; - /** - * Gets the camera roll in radians. - * @memberof Camera.prototype - * - * @type {Number} - * @readonly - */ - roll : { - get : function() { - if (this._mode !== SceneMode.MORPHING) { - var ellipsoid = this._projection.ellipsoid; + // load and select tiles without skipping up to tileset.baseScreenSpaceError + tileset._baseTraversal.execute(tileset, root, tileset.baseScreenSpaceError, frameState, outOfCore); - var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1); - var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); - this._setTransform(transform); + // skip traversal starts from a prepopulated queue from the base traversal + tileset._skipTraversal.execute(tileset, undefined, frameState, outOfCore); + } - var roll = getRoll(this.direction, this.up, this.right); + // mark tiles for selection or their nearest loaded ancestor + markLoadedTilesForSelection(tileset, frameState, outOfCore); - this._setTransform(oldTransform); + // sort selected tiles by distance to camera and call selectTile on each + // set tile._selectionDepth on all tiles + traverseAndSelect(tileset, root, frameState); - return roll; - } + tileset._desiredTiles.trim(); + } - return undefined; - } - }, + var descendantStack = []; - /** - * Gets the event that will be raised at when the camera starts to move. - * @memberof Camera.prototype - * @type {Event} - * @readonly - */ - moveStart : { - get : function() { - return this._moveStart; + function markLoadedTilesForSelection(tileset, frameState, outOfCore) { + var tiles = tileset._desiredTiles; + var length = tiles.length; + for (var i = 0; i < length; ++i) { + var original = tiles.get(i); + + if (hasAdditiveContent(original)) { + original.selected = true; + original._selectedFrame = frameState.frameNumber; + continue; } - }, - /** - * Gets the event that will be raised when the camera has stopped moving. - * @memberof Camera.prototype - * @type {Event} - * @readonly - */ - moveEnd : { - get : function() { - return this._moveEnd; + var loadedTile = original._ancestorWithLoadedContent; + if (original.hasRenderableContent && original.contentAvailable) { + loadedTile = original; } - }, - /** - * Gets the event that will be raised when the camera has changed by percentageChanged. - * @memberof Camera.prototype - * @type {Event} - * @readonly - */ - changed : { - get : function() { - return this._changed; + if (defined(loadedTile)) { + loadedTile.selected = true; + loadedTile._selectedFrame = frameState.frameNumber; + } else { + // if no ancestors are ready, traverse down and select ready tiles to minimize empty regions + descendantStack.push(original); + while (descendantStack.length > 0) { + var tile = descendantStack.pop(); + var children = tile.children; + var childrenLength = children.length; + for (var j = 0; j < childrenLength; ++j) { + var child = children[j]; + touch(tileset, child, outOfCore); + if (child.contentAvailable) { + child.selected = true; + child._finalResolution = true; + child._selectedFrame = frameState.frameNumber; + } + if (child._depth - original._depth < 2) { // prevent traversing too far + if (!child.contentAvailable || child.refine === Cesium3DTileRefine.ADD) { + descendantStack.push(child); + } + } + } + } } } - }); + } + + var scratchStack = []; + var scratchStack2 = []; /** - * @private + * Traverse the tree while tiles are visible and check if their selected frame is the current frame. + * If so, add it to a selection queue. + * Tiles are sorted near to far so we can take advantage of early Z. + * Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles. + * + * The reason for the preorder traversal is so that tiles can easily be marked with their + * selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed. + * This property is important for use in the stencil test because we want to render deeper tiles on top of their + * ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer. + * + * We want to select children before their ancestors because there is no guarantee on the relationship between + * the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top + * of ancestor regardless of true depth. The stencil tests used require children to be drawn first. @see {@link updateTiles} + * + * NOTE: this will no longer work when there is a chain of selected tiles that is longer than the size of the + * stencil buffer (usually 8 bits). In other words, the subset of the tree containing only selected tiles must be + * no deeper than 255. It is very, very unlikely this will cause a problem. */ - Camera.prototype.update = function(mode) { - - var updateFrustum = false; - if (mode !== this._mode) { - this._mode = mode; - this._modeChanged = mode !== SceneMode.MORPHING; - updateFrustum = this._mode === SceneMode.SCENE2D; - } + function traverseAndSelect(tileset, root, frameState) { + var stack = scratchStack; + var ancestorStack = scratchStack2; - if (updateFrustum) { - var frustum = this._max2Dfrustum = this.frustum.clone(); + var lastAncestor; + stack.push(root); + while (stack.length > 0 || ancestorStack.length > 0) { + if (ancestorStack.length > 0) { + var waitingTile = ancestorStack[ancestorStack.length - 1]; + if (waitingTile._stackLength === stack.length) { + ancestorStack.pop(); + if (waitingTile === lastAncestor) { + waitingTile._finalResolution = true; + } + selectTile(tileset, waitingTile, frameState); + continue; + } + } - - var maxZoomOut = 2.0; - var ratio = frustum.top / frustum.right; - frustum.right = this._maxCoord.x * maxZoomOut; - frustum.left = -frustum.right; - frustum.top = ratio * frustum.right; - frustum.bottom = -frustum.top; + var tile = stack.pop(); + if (!defined(tile) || !isVisited(tile, frameState)) { + continue; + } + + var shouldSelect = tile.selected && tile._selectedFrame === frameState.frameNumber && tile.hasRenderableContent; + + var children = tile.children; + var childrenLength = children.length; + + children.sort(sortChildrenByDistanceToCamera); + + if (shouldSelect) { + if (tile.refine === Cesium3DTileRefine.ADD) { + tile._finalResolution = true; + selectTile(tileset, tile, frameState); + } else { + tile._selectionDepth = ancestorStack.length; + + if (tile._selectionDepth > 0) { + tileset._hasMixedContent = true; + } + + lastAncestor = tile; + + if (childrenLength === 0) { + tile._finalResolution = true; + selectTile(tileset, tile, frameState); + continue; + } + + ancestorStack.push(tile); + tile._stackLength = stack.length; + } + } + + for (var i = 0; i < childrenLength; ++i) { + var child = children[i]; + stack.push(child); + } } + } - if (this._mode === SceneMode.SCENE2D) { - clampMove2D(this, this.position); + function selectTile(tileset, tile, frameState) { + // There may also be a tight box around just the tile's contents, e.g., for a city, we may be + // zoomed into a neighborhood and can cull the skyscrapers in the root tile. + if (tile.contentAvailable && ( + (tile._visibilityPlaneMask === CullingVolume.MASK_INSIDE) || + (tile.contentVisibility(frameState) !== Intersect.OUTSIDE) + )) { + tileset._selectedTiles.push(tile); + + var tileContent = tile.content; + if (tileContent.featurePropertiesDirty) { + // A feature's property in this tile changed, the tile needs to be re-styled. + tileContent.featurePropertiesDirty = false; + tile.lastStyleTime = 0; // Force applying the style to this tile + tileset._selectedTilesToStyle.push(tile); + } else if ((tile._lastSelectedFrameNumber !== frameState.frameNumber - 1) || tile.lastStyleTime === 0) { + // Tile is newly selected; it is selected this frame, but was not selected last frame. + tileset._selectedTilesToStyle.push(tile); + } + tile._lastSelectedFrameNumber = frameState.frameNumber; } + } - var globe = this._scene.globe; - var globeFinishedUpdating = !defined(globe) || (globe._surface.tileProvider.ready && globe._surface._tileLoadQueueHigh.length === 0 && globe._surface._tileLoadQueueMedium.length === 0 && globe._surface._tileLoadQueueLow.length === 0 && globe._surface._debug.tilesWaitingForChildren === 0); - if (this._suspendTerrainAdjustment) { - this._suspendTerrainAdjustment = !globeFinishedUpdating; + // PERFORMANCE_IDEA: is it worth exploiting frame-to-frame coherence in the sort, i.e., the + // list of children are probably fully or mostly sorted unless the camera moved significantly? + function sortChildrenByDistanceToCamera(a, b) { + // Sort by farthest child first since this is going on a stack + if (b._distanceToCamera === 0 && a._distanceToCamera === 0) { + return b._centerZDepth - a._centerZDepth; } - this._adjustHeightForTerrain(); - }; - var setTransformPosition = new Cartesian3(); - var setTransformUp = new Cartesian3(); - var setTransformDirection = new Cartesian3(); + return b._distanceToCamera - a._distanceToCamera; + } - Camera.prototype._setTransform = function(transform) { - var position = Cartesian3.clone(this.positionWC, setTransformPosition); - var up = Cartesian3.clone(this.upWC, setTransformUp); - var direction = Cartesian3.clone(this.directionWC, setTransformDirection); + var emptyArray = freezeObject([]); - Matrix4.clone(transform, this._transform); - this._transformChanged = true; - updateMembers(this); - var inverse = this._actualInvTransform; + function BaseTraversal() { + this.tileset = undefined; + this.frameState = undefined; + this.outOfCore = undefined; + this.stack = new ManagedArray(); + this.leaves = new ManagedArray(); + this.baseScreenSpaceError = undefined; + this.internalDFS = new InternalBaseTraversal(); + } - Matrix4.multiplyByPoint(inverse, position, this.position); - Matrix4.multiplyByPointAsVector(inverse, direction, this.direction); - Matrix4.multiplyByPointAsVector(inverse, up, this.up); - Cartesian3.cross(this.direction, this.up, this.right); + BaseTraversal.prototype.execute = function(tileset, root, baseScreenSpaceError, frameState, outOfCore) { + this.tileset = tileset; + this.frameState = frameState; + this.outOfCore = outOfCore; + this.leaves.length = 0; + this.baseScreenSpaceError = Math.max(baseScreenSpaceError, this.tileset._maximumScreenSpaceError); + this.internalDFS.tileset = this.tileset; + this.internalDFS.frameState = this.frameState; + this.internalDFS.outOfCore = this.outOfCore; + this.internalDFS.baseScreenSpaceError = this.baseScreenSpaceError; + depthFirstSearch(root, this); + }; - updateMembers(this); + BaseTraversal.prototype.visitStart = function(tile) { + if (!isVisited(tile, this.frameState)) { + visitTile(this.tileset, tile, this.frameState, this.outOfCore); + } }; - var scratchAdjustOrtghographicFrustumMousePosition = new Cartesian2(); - var pickGlobeScratchRay = new Ray(); - var scratchRayIntersection = new Cartesian3(); - var scratchDepthIntersection = new Cartesian3(); + BaseTraversal.prototype.visitEnd = function(tile) { + tile._lastVisitedFrame = this.frameState.frameNumber; + }; - Camera.prototype._adjustOrthographicFrustum = function(zooming) { - if (!(this.frustum instanceof OrthographicFrustum)) { - return; + BaseTraversal.prototype.getChildren = function(tile) { + var tileset = this.tileset; + var outOfCore = this.outOfCore; + var frameState = this.frameState; + if (!baseUpdateAndCheckChildren(tileset, tile, this.baseScreenSpaceError, frameState)) { + return emptyArray; } - if (!zooming && this._positionCartographic.height < 150000.0) { - return; + var children = tile.children; + var childrenLength = children.length; + var allReady = true; + var replacementWithContent = tile.refine === Cesium3DTileRefine.REPLACE && tile.hasRenderableContent; + for (var i = 0; i < childrenLength; ++i) { + var child = children[i]; + loadTile(tileset, child, frameState, true); + touch(tileset, child, outOfCore); + + // content cannot be replaced until all of the nearest descendants with content are all loaded + if (replacementWithContent) { + if (!child.hasEmptyContent) { + allReady = allReady && child.contentAvailable; + } else { + allReady = allReady && this.internalDFS.execute(child); + } + } } - if (!Matrix4.equals(Matrix4.IDENTITY, this.transform)) { - this.frustum.width = Cartesian3.magnitude(this.position); - return; + if (allReady) { + return children; } - var scene = this._scene; - var globe = scene._globe; - var rayIntersection; - var depthIntersection; + return emptyArray; + }; - if (defined(globe)) { - var mousePosition = scratchAdjustOrtghographicFrustumMousePosition; - mousePosition.x = scene.drawingBufferWidth / 2.0; - mousePosition.y = scene.drawingBufferHeight / 2.0; + function baseUpdateAndCheckChildren(tileset, tile, baseScreenSpaceError, frameState) { + if (hasAdditiveContent(tile)) { + tileset._desiredTiles.push(tile); + } - var ray = this.getPickRay(mousePosition, pickGlobeScratchRay); - rayIntersection = globe.pick(ray, scene, scratchRayIntersection); + // Stop traversal on the subtree since it will be destroyed + if (tile.hasTilesetContent && tile.contentExpired) { + return false; + } - if (scene.pickPositionSupported) { - depthIntersection = scene.pickPositionWorldCoordinates(mousePosition, scratchDepthIntersection); - } + // stop traversal when we've attained the desired level of error + if (tile._screenSpaceError <= baseScreenSpaceError && !tile.hasTilesetContent) { + // update children so the leaf handler can check if any are visible for the children union bound optimization + updateChildren(tile, frameState); + return false; + } - if (defined(rayIntersection) && defined(depthIntersection)) { - var depthDistance = defined(depthIntersection) ? Cartesian3.distance(depthIntersection, this.positionWC) : Number.POSITIVE_INFINITY; - var rayDistance = defined(rayIntersection) ? Cartesian3.distance(rayIntersection, this.positionWC) : Number.POSITIVE_INFINITY; - this.frustum.width = Math.min(depthDistance, rayDistance); - } else if (defined(depthIntersection)) { - this.frustum.width = Cartesian3.distance(depthIntersection, this.positionWC); - } else if (defined(rayIntersection)) { - this.frustum.width = Cartesian3.distance(rayIntersection, this.positionWC); + var childrenVisibility = updateChildren(tile, frameState); + var showAdditive = tile.refine === Cesium3DTileRefine.ADD; + var showReplacement = tile.refine === Cesium3DTileRefine.REPLACE && (childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE_IN_REQUEST_VOLUME) !== 0; + + return showAdditive || showReplacement || tile.hasTilesetContent || !defined(tile._ancestorWithContent); + } + + BaseTraversal.prototype.shouldVisit = function(tile) { + return isVisible(tile._visibilityPlaneMask); + }; + + BaseTraversal.prototype.leafHandler = function(tile) { + // if skipLevelOfDetail is off, leaves of the base traversal get pushed to tileset._desiredTiles. additive tiles have already been pushed + if (this.tileset.skipLevelOfDetail || !hasAdditiveContent(tile)) { + if (tile.refine === Cesium3DTileRefine.REPLACE && !childrenAreVisible(tile)) { + ++this.tileset._statistics.numberOfTilesCulledWithChildrenUnion; + return; } + this.leaves.push(tile); } + }; - if (!defined(globe) || (!defined(rayIntersection) && !defined(depthIntersection))) { - var distance = Math.max(this.positionCartographic.height, 0.0); - this.frustum.width = distance; + function InternalBaseTraversal() { + this.tileset = undefined; + this.frameState = undefined; + this.outOfCore = undefined; + this.baseScreenSpaceError = undefined; + this.stack = new ManagedArray(); + this.allLoaded = undefined; + } + + InternalBaseTraversal.prototype.execute = function(root) { + this.allLoaded = true; + depthFirstSearch(root, this); + return this.allLoaded; + }; + + InternalBaseTraversal.prototype.visitStart = function(tile) { + if (!isVisited(tile, this.frameState)) { + visitTile(this.tileset, tile, this.frameState, this.outOfCore); } }; - var scratchSetViewCartesian = new Cartesian3(); - var scratchSetViewTransform1 = new Matrix4(); - var scratchSetViewTransform2 = new Matrix4(); - var scratchSetViewQuaternion = new Quaternion(); - var scratchSetViewMatrix3 = new Matrix3(); - var scratchSetViewCartographic = new Cartographic(); + InternalBaseTraversal.prototype.visitEnd = BaseTraversal.prototype.visitEnd; - function setView3D(camera, position, hpr) { - var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); - var localTransform = Transforms.eastNorthUpToFixedFrame(position, camera._projection.ellipsoid, scratchSetViewTransform2); - camera._setTransform(localTransform); + // Continue traversing until we have renderable content. We want the first descendants with content of the root to load + InternalBaseTraversal.prototype.shouldVisit = function(tile) { + return !tile.hasRenderableContent && isVisible(tile._visibilityPlaneMask); + }; - Cartesian3.clone(Cartesian3.ZERO, camera.position); - hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO; + InternalBaseTraversal.prototype.getChildren = function(tile) { + var tileset = this.tileset; + var frameState = this.frameState; + var outOfCore = this.outOfCore; - var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion); - var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); + if (!baseUpdateAndCheckChildren(tileset, tile, this.baseScreenSpaceError, frameState)) { + return emptyArray; + } - Matrix3.getColumn(rotMat, 0, camera.direction); - Matrix3.getColumn(rotMat, 2, camera.up); - Cartesian3.cross(camera.direction, camera.up, camera.right); + var children = tile.children; + var childrenLength = children.length; + for (var i = 0; i < childrenLength; ++i) { + var child = children[i]; + loadTile(tileset, child, frameState, true); + touch(tileset, child, outOfCore); + if (!tile.contentAvailable) { + this.allLoaded = false; + } + } + return children; + }; - camera._setTransform(currentTransform); + InternalBaseTraversal.prototype.updateAndCheckChildren = BaseTraversal.prototype.updateAndCheckChildren; - camera._adjustOrthographicFrustum(true); + function SkipTraversal(options) { + this.tileset = undefined; + this.frameState = undefined; + this.outOfCore = undefined; + this.queue1 = new ManagedArray(); + this.queue2 = new ManagedArray(); + this.internalDFS = new InternalSkipTraversal(options.selectionHeuristic); + this.maxChildrenLength = 0; + this.scratchQueue = new ManagedArray(); } - function setViewCV(camera, position,hpr, convert) { - var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); - camera._setTransform(Matrix4.IDENTITY); + SkipTraversal.prototype.execute = function(tileset, root, frameState, outOfCore) { + this.tileset = tileset; + this.frameState = frameState; + this.outOfCore = outOfCore; + this.internalDFS.frameState = frameState; + this.internalDFS.outOfCore = outOfCore; - if (!Cartesian3.equals(position, camera.positionWC)) { - if (convert) { - var projection = camera._projection; - var cartographic = projection.ellipsoid.cartesianToCartographic(position, scratchSetViewCartographic); - position = projection.project(cartographic, scratchSetViewCartesian); - } - Cartesian3.clone(position, camera.position); + this.maxChildrenLength = 0; + breadthFirstSearch(root, this); + this.queue1.length = 0; + this.queue2.length = 0; + this.scratchQueue.length = 0; + this.scratchQueue.trim(this.maxChildrenLength); + }; + + SkipTraversal.prototype.visitStart = function(tile) { + if (!isVisited(tile, this.frameState)) { + visitTile(this.tileset, tile, this.frameState, this.outOfCore); } - hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO; + }; - var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion); - var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); + SkipTraversal.prototype.visitEnd = BaseTraversal.prototype.visitEnd; - Matrix3.getColumn(rotMat, 0, camera.direction); - Matrix3.getColumn(rotMat, 2, camera.up); - Cartesian3.cross(camera.direction, camera.up, camera.right); + SkipTraversal.prototype.getChildren = function(tile) { + this.scratchQueue.length = 0; + this.internalDFS.execute(tile, this.scratchQueue); + this.maxChildrenLength = Math.max(this.maxChildrenLength, this.scratchQueue.length); + return this.scratchQueue; + }; - camera._setTransform(currentTransform); + SkipTraversal.prototype.leafHandler = function(tile) { + // additive tiles have already been pushed + if (!hasAdditiveContent(tile) && !isVisited(tile, this.frameState)) { + this.tileset._desiredTiles.push(tile); + } + }; - camera._adjustOrthographicFrustum(true); + function InternalSkipTraversal(selectionHeuristic) { + this.selectionHeuristic = selectionHeuristic; + this.tileset = undefined; + this.frameState = undefined; + this.outOfCore = undefined; + this.root = undefined; + this.queue = undefined; + this.stack = new ManagedArray(); } - function setView2D(camera, position, hpr, convert) { - var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); - camera._setTransform(Matrix4.IDENTITY); + InternalSkipTraversal.prototype.execute = function(root, queue) { + this.tileset = root._tileset; + this.root = root; + this.queue = queue; + depthFirstSearch(root, this); + }; - if (!Cartesian3.equals(position, camera.positionWC)) { - if (convert) { - var projection = camera._projection; - var cartographic = projection.ellipsoid.cartesianToCartographic(position, scratchSetViewCartographic); - position = projection.project(cartographic, scratchSetViewCartesian); - } + InternalSkipTraversal.prototype.visitStart = function(tile) { + if (!isVisited(tile, this.frameState)) { + visitTile(this.tileset, tile, this.frameState, this.outOfCore); + } + }; - Cartesian2.clone(position, camera.position); + InternalSkipTraversal.prototype.visitEnd = BaseTraversal.prototype.visitEnd; - var newLeft = -position.z * 0.5; - var newRight = -newLeft; + InternalSkipTraversal.prototype.getChildren = function(tile) { + var tileset = this.tileset; + var maximumScreenSpaceError = tileset._maximumScreenSpaceError; - var frustum = camera.frustum; - if (newRight > newLeft) { - var ratio = frustum.top / frustum.right; - frustum.right = newRight; - frustum.left = newLeft; - frustum.top = frustum.right * ratio; - frustum.bottom = -frustum.top; + // Stop traversal on the subtree since it will be destroyed + if (tile.hasTilesetContent && tile.contentExpired) { + return emptyArray; + } + + if (!tile.hasTilesetContent) { + if (tile.refine === Cesium3DTileRefine.ADD) { + // Always load additive tiles + loadTile(tileset, tile, this.frameState, true); + if (hasAdditiveContent(tile)) { + tileset._desiredTiles.push(tile); + } + } + + // stop traversal when we've attained the desired level of error + if (tile._screenSpaceError <= maximumScreenSpaceError) { + updateChildren(tile, this.frameState); + return emptyArray; + } + + // if we have reached the skipping threshold without any loaded ancestors, return empty so this tile is loaded + if ( + (!tile.hasEmptyContent && tile.contentUnloaded) && + defined(tile._ancestorWithLoadedContent) && + this.selectionHeuristic(tileset, tile._ancestorWithLoadedContent, tile)) { + updateChildren(tile, this.frameState); + return emptyArray; } } - if (camera._scene.mapMode2D === MapMode2D.ROTATE) { - hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO; - hpr.pitch = -CesiumMath.PI_OVER_TWO; - hpr.roll = 0.0; - var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion); - var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); + var childrenVisibility = updateChildren(tile, this.frameState); + var showAdditive = tile.refine === Cesium3DTileRefine.ADD && tile._screenSpaceError > maximumScreenSpaceError; + var showReplacement = tile.refine === Cesium3DTileRefine.REPLACE && (childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE_IN_REQUEST_VOLUME) !== 0; - Matrix3.getColumn(rotMat, 2, camera.up); - Cartesian3.cross(camera.direction, camera.up, camera.right); + // at least one child is visible, but is not in request volume. the parent must be selected + if (childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE_NOT_IN_REQUEST_VOLUME && tile.refine === Cesium3DTileRefine.REPLACE) { + this.tileset._desiredTiles.push(tile); } - camera._setTransform(currentTransform); - } + if (showAdditive || showReplacement || tile.hasTilesetContent) { + var children = tile.children; + var childrenLength = children.length; + for (var i = 0; i < childrenLength; ++i) { + touch(tileset, children[i], this.outOfCore); + } + return children; + } - var scratchToHPRDirection = new Cartesian3(); - var scratchToHPRUp = new Cartesian3(); - var scratchToHPRRight = new Cartesian3(); + return emptyArray; + }; - function directionUpToHeadingPitchRoll(camera, position, orientation, result) { - var direction = Cartesian3.clone(orientation.direction, scratchToHPRDirection); - var up = Cartesian3.clone(orientation.up, scratchToHPRUp); + InternalSkipTraversal.prototype.shouldVisit = function(tile) { + return isVisibleAndMeetsSSE(this.tileset, tile, this.frameState); + }; - if (camera._scene.mode === SceneMode.SCENE3D) { - var ellipsoid = camera._projection.ellipsoid; - var transform = Transforms.eastNorthUpToFixedFrame(position, ellipsoid, scratchHPRMatrix1); - var invTransform = Matrix4.inverseTransformation(transform, scratchHPRMatrix2); + InternalSkipTraversal.prototype.leafHandler = function(tile) { + if (tile !== this.root) { + if (tile.refine === Cesium3DTileRefine.REPLACE && !childrenAreVisible(tile)) { + ++this.tileset._statistics.numberOfTilesCulledWithChildrenUnion; + return; + } + if (!tile.hasEmptyContent) { + if (this.tileset.loadSiblings) { + var parent = tile.parent; + var tiles = parent.children; + var length = tiles.length; + for (var i = 0; i < length; ++i) { + loadTile(this.tileset, tiles[i], this.frameState, false); + touch(this.tileset, tiles[i], this.outOfCore); + } + } else { + loadTile(this.tileset, tile, this.frameState, true); + touch(this.tileset, tile, this.outOfCore); + } + } + this.queue.push(tile); + } else if (!hasAdditiveContent(tile)) { + // additive tiles have already been pushed + this.tileset._desiredTiles.push(tile); + } + }; - Matrix4.multiplyByPointAsVector(invTransform, direction, direction); - Matrix4.multiplyByPointAsVector(invTransform, up, up); + function updateChildren(tile, frameState) { + if (isVisited(tile, frameState)) { + return tile._childrenVisibility; } - var right = Cartesian3.cross(direction, up, scratchToHPRRight); + var children = tile.children; - result.heading = getHeading(direction, up); - result.pitch = getPitch(direction); - result.roll = getRoll(direction, up, right); + updateTransforms(children, tile.computedTransform); + computeDistanceToCamera(children, frameState); - return result; + return computeChildrenVisibility(tile, frameState); } - var scratchSetViewOptions = { - destination : undefined, - orientation : { - direction : undefined, - up : undefined, - heading : undefined, - pitch : undefined, - roll : undefined - }, - convert : undefined, - endTransform : undefined - }; + function isVisited(tile, frameState) { + // because the leaves of one tree traversal are the root of the subsequent traversal, avoid double visitation + return tile._lastVisitedFrame === frameState.frameNumber; + } - var scratchHpr = new HeadingPitchRoll(); - /** - * Sets the camera position, orientation and transform. - * - * @param {Object} options Object with the following properties: - * @param {Cartesian3|Rectangle} [options.destination] The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view. - * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pitch and roll properties. By default, the direction will point - * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive - * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode. - * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame of the camera. - * - * @example - * // 1. Set position with a top-down view - * viewer.camera.setView({ - * destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0) - * }); - * - * // 2 Set view with heading, pitch and roll - * viewer.camera.setView({ - * destination : cartesianPosition, - * orientation: { - * heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north) - * pitch : Cesium.Math.toRadians(-90), // default value (looking down) - * roll : 0.0 // default value - * } - * }); - * - * // 3. Change heading, pitch and roll with the camera position remaining the same. - * viewer.camera.setView({ - * orientation: { - * heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north) - * pitch : Cesium.Math.toRadians(-90), // default value (looking down) - * roll : 0.0 // default value - * } - * }); - * - * - * // 4. View rectangle with a top-down view - * viewer.camera.setView({ - * destination : Cesium.Rectangle.fromDegrees(west, south, east, north) - * }); - * - * // 5. Set position with an orientation using unit vectors. - * viewer.camera.setView({ - * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0), - * orientation : { - * direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734), - * up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339) - * } - * }); - */ - Camera.prototype.setView = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var orientation = defaultValue(options.orientation, defaultValue.EMPTY_OBJECT); + function visitTile(tileset, tile, frameState, outOfCore) { + ++tileset._statistics.visited; + tile.selected = false; + tile._finalResolution = false; + computeSSE(tile, frameState); + touch(tileset, tile, outOfCore); + tile.updateExpiration(); + tile._ancestorWithContent = undefined; + tile._ancestorWithLoadedContent = undefined; + var parent = tile.parent; + if (defined(parent)) { + var replace = parent.refine === Cesium3DTileRefine.REPLACE; + tile._ancestorWithContent = (replace && parent.hasRenderableContent) ? parent : parent._ancestorWithContent; + tile._ancestorWithLoadedContent = (replace && parent.hasRenderableContent && parent.contentAvailable) ? parent : parent._ancestorWithLoadedContent; + } + } - var mode = this._mode; - if (mode === SceneMode.MORPHING) { + function touch(tileset, tile, outOfCore) { + if (!outOfCore) { return; } + var node = tile.replacementNode; + if (defined(node)) { + tileset._replacementList.splice(tileset._replacementSentinel, node); + } + } - if (defined(options.endTransform)) { - this._setTransform(options.endTransform); + function computeSSE(tile, frameState) { + if (tile._screenSpaceErrorComputedFrame !== frameState.frameNumber) { + tile._screenSpaceErrorComputedFrame = frameState.frameNumber; + tile._screenSpaceError = getScreenSpaceError(tile._tileset, tile.geometricError, tile, frameState); } + } - var convert = defaultValue(options.convert, true); - var destination = defaultValue(options.destination, Cartesian3.clone(this.positionWC, scratchSetViewCartesian)); - if (defined(destination) && defined(destination.west)) { - destination = this.getRectangleCameraCoordinates(destination, scratchSetViewCartesian); - convert = false; + function checkAdditiveVisibility(tileset, tile, frameState) { + if (defined(tile.parent) && (tile.parent.refine === Cesium3DTileRefine.ADD)) { + return isVisibleAndMeetsSSE(tileset, tile, frameState); } + return true; + } - if (defined(orientation.direction)) { - orientation = directionUpToHeadingPitchRoll(this, destination, orientation, scratchSetViewOptions.orientation); + function loadTile(tileset, tile, frameState, checkVisibility) { + if ((tile.contentUnloaded || tile.contentExpired) && tile._requestedFrame !== frameState.frameNumber) { + if (!checkVisibility || checkAdditiveVisibility(tileset, tile, frameState)) { + tile._requestedFrame = frameState.frameNumber; + tileset._requestedTiles.push(tile); + } } + } - scratchHpr.heading = defaultValue(orientation.heading, 0.0); - scratchHpr.pitch = defaultValue(orientation.pitch, -CesiumMath.PI_OVER_TWO); - scratchHpr.roll = defaultValue(orientation.roll, 0.0); + function computeChildrenVisibility(tile, frameState) { + var flag = Cesium3DTileChildrenVisibility.NONE; + var children = tile.children; + var childrenLength = children.length; + var visibilityPlaneMask = tile._visibilityPlaneMask; + for (var k = 0; k < childrenLength; ++k) { + var child = children[k]; - this._suspendTerrainAdjustment = true; + var visibilityMask = child.visibility(frameState, visibilityPlaneMask); - if (mode === SceneMode.SCENE3D) { - setView3D(this, destination, scratchHpr); - } else if (mode === SceneMode.SCENE2D) { - setView2D(this, destination, scratchHpr, convert); + if (isVisible(visibilityMask)) { + flag |= Cesium3DTileChildrenVisibility.VISIBLE; + } + + if (!child.insideViewerRequestVolume(frameState)) { + if (isVisible(visibilityMask)) { + flag |= Cesium3DTileChildrenVisibility.VISIBLE_NOT_IN_REQUEST_VOLUME; + } + visibilityMask = CullingVolume.MASK_OUTSIDE; + } else { + flag |= Cesium3DTileChildrenVisibility.IN_REQUEST_VOLUME; + if (isVisible(visibilityMask)) { + flag |= Cesium3DTileChildrenVisibility.VISIBLE_IN_REQUEST_VOLUME; + } + } + + child._visibilityPlaneMask = visibilityMask; + } + + tile._childrenVisibility = flag; + + return flag; + } + + function getScreenSpaceError(tileset, geometricError, tile, frameState) { + if (geometricError === 0.0) { + // Leaf tiles do not have any error so save the computation + return 0.0; + } + + // Avoid divide by zero when viewer is inside the tile + var camera = frameState.camera; + var frustum = camera.frustum; + var context = frameState.context; + var height = context.drawingBufferHeight; + + var error; + if (frameState.mode === SceneMode.SCENE2D || frustum instanceof OrthographicFrustum) { + if (defined(frustum._offCenterFrustum)) { + frustum = frustum._offCenterFrustum; + } + var width = context.drawingBufferWidth; + var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height); + error = geometricError / pixelSize; } else { - setViewCV(this, destination, scratchHpr, convert); + var distance = Math.max(tile._distanceToCamera, CesiumMath.EPSILON7); + var sseDenominator = camera.frustum.sseDenominator; + error = (geometricError * height) / (distance * sseDenominator); + + if (tileset.dynamicScreenSpaceError) { + var density = tileset._dynamicScreenSpaceErrorComputedDensity; + var factor = tileset.dynamicScreenSpaceErrorFactor; + var dynamicError = CesiumMath.fog(distance, density) * factor; + error -= dynamicError; + } } - }; - var pitchScratch = new Cartesian3(); - /** - * Fly the camera to the home view. Use {@link Camera#.DEFAULT_VIEW_RECTANGLE} to set - * the default view for the 3D scene. The home view for 2D and columbus view shows the - * entire map. - * - * @param {Number} [duration] The number of seconds to complete the camera flight to home. See {@link Camera#flyTo} - */ - Camera.prototype.flyHome = function(duration) { - var mode = this._mode; + return error; + } - if (mode === SceneMode.MORPHING) { - this._scene.completeMorph(); + function computeDistanceToCamera(children, frameState) { + var length = children.length; + for (var i = 0; i < length; ++i) { + var child = children[i]; + child._distanceToCamera = child.distanceToTile(frameState); + child._centerZDepth = child.distanceToTileCenter(frameState); } + } - if (mode === SceneMode.SCENE2D) { - this.flyTo({ - destination : Rectangle.MAX_VALUE, - duration : duration, - endTransform : Matrix4.IDENTITY - }); - } else if (mode === SceneMode.SCENE3D) { - var destination = this.getRectangleCameraCoordinates(Camera.DEFAULT_VIEW_RECTANGLE); + function updateTransforms(children, parentTransform) { + var length = children.length; + for (var i = 0; i < length; ++i) { + var child = children[i]; + child.updateTransform(parentTransform); + } + } - var mag = Cartesian3.magnitude(destination); - mag += mag * Camera.DEFAULT_VIEW_FACTOR; - Cartesian3.normalize(destination, destination); - Cartesian3.multiplyByScalar(destination, mag, destination); + function isVisible(visibilityPlaneMask) { + return visibilityPlaneMask !== CullingVolume.MASK_OUTSIDE; + } - this.flyTo({ - destination : destination, - duration : duration, - endTransform : Matrix4.IDENTITY - }); - } else if (mode === SceneMode.COLUMBUS_VIEW) { - var maxRadii = this._projection.ellipsoid.maximumRadius; - var position = new Cartesian3(0.0, -1.0, 1.0); - position = Cartesian3.multiplyByScalar(Cartesian3.normalize(position, position), 5.0 * maxRadii, position); - this.flyTo({ - destination : position, - duration : duration, - orientation : { - heading : 0.0, - pitch : -Math.acos(Cartesian3.normalize(position, pitchScratch).z), - roll : 0.0 - }, - endTransform : Matrix4.IDENTITY, - convert : false - }); + function isVisibleAndMeetsSSE(tileset, tile, frameState) { + var maximumScreenSpaceError = tileset._maximumScreenSpaceError; + var parent = tile.parent; + if (!defined(parent)) { + return isVisible(tile._visibilityPlaneMask); } - }; + var showAdditive = parent.refine === Cesium3DTileRefine.ADD && parent._screenSpaceError > maximumScreenSpaceError; - /** - * Transform a vector or point from world coordinates to the camera's reference frame. - * - * @param {Cartesian4} cartesian The vector or point to transform. - * @param {Cartesian4} [result] The object onto which to store the result. - * @returns {Cartesian4} The transformed vector or point. - */ - Camera.prototype.worldToCameraCoordinates = function(cartesian, result) { - - if (!defined(result)) { - result = new Cartesian4(); + return isVisible(tile._visibilityPlaneMask) && (!showAdditive || getScreenSpaceError(tileset, parent.geometricError, tile, frameState) > maximumScreenSpaceError); + } + + function childrenAreVisible(tile) { + // optimization does not apply for additive refinement + return tile.refine === Cesium3DTileRefine.ADD || tile.children.length === 0 || tile._childrenVisibility & Cesium3DTileChildrenVisibility.VISIBLE !== 0; + } + + function hasAdditiveContent(tile) { + return tile.refine === Cesium3DTileRefine.ADD && tile.hasRenderableContent; + } + + function depthFirstSearch(root, options) { + var stack = options.stack; + + if (defined(root) && (!defined(options.shouldVisit) || options.shouldVisit(root))) { + stack.push(root); } - updateMembers(this); - return Matrix4.multiplyByVector(this._actualInvTransform, cartesian, result); - }; - /** - * Transform a point from world coordinates to the camera's reference frame. - * - * @param {Cartesian3} cartesian The point to transform. - * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} The transformed point. - */ - Camera.prototype.worldToCameraCoordinatesPoint = function(cartesian, result) { - - if (!defined(result)) { - result = new Cartesian3(); + var maxLength = 0; + while (stack.length > 0) { + maxLength = Math.max(maxLength, stack.length); + + var tile = stack.pop(); + options.visitStart(tile); + var children = options.getChildren(tile); + var isNativeArray = !defined(children.get); + var length = children.length; + for (var i = 0; i < length; ++i) { + var child = isNativeArray ? children[i] : children.get(i); + + if (!defined(options.shouldVisit) || options.shouldVisit(child)) { + stack.push(child); + } + } + + if (length === 0 && defined(options.leafHandler)) { + options.leafHandler(tile); + } + options.visitEnd(tile); } - updateMembers(this); - return Matrix4.multiplyByPoint(this._actualInvTransform, cartesian, result); - }; - /** - * Transform a vector from world coordinates to the camera's reference frame. - * - * @param {Cartesian3} cartesian The vector to transform. - * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} The transformed vector. - */ - Camera.prototype.worldToCameraCoordinatesVector = function(cartesian, result) { - - if (!defined(result)) { - result = new Cartesian3(); + stack.trim(maxLength); + } + + function breadthFirstSearch(root, options) { + var queue1 = options.queue1; + var queue2 = options.queue2; + + if (defined(root) && (!defined(options.shouldVisit) || options.shouldVisit(root))) { + queue1.push(root); } - updateMembers(this); - return Matrix4.multiplyByPointAsVector(this._actualInvTransform, cartesian, result); - }; + + var maxLength = 0; + while (queue1.length > 0) { + var length = queue1.length; + maxLength = Math.max(maxLength, length); + + for (var i = 0; i < length; ++i) { + var tile = queue1.get(i); + options.visitStart(tile); + var children = options.getChildren(tile); + var isNativeArray = !defined(children.get); + var childrenLength = children.length; + for (var j = 0; j < childrenLength; ++j) { + var child = isNativeArray ? children[j] : children.get(j); + + if (!defined(options.shouldVisit) || options.shouldVisit(child)) { + queue2.push(child); + } + } + + if (childrenLength === 0 && defined(options.leafHandler)) { + options.leafHandler(tile); + } + options.visitEnd(tile); + } + + queue1.length = 0; + var temp = queue1; + queue1 = queue2; + queue2 = temp; + options.queue1 = queue1; + options.queue2 = queue2; + } + + queue1.length = 0; + queue2.length = 0; + + queue1.trim(maxLength); + queue2.trim(maxLength); + } + + Cesium3DTilesetTraversal.selectTiles = selectTiles; + + Cesium3DTilesetTraversal.BaseTraversal = BaseTraversal; + + Cesium3DTilesetTraversal.SkipTraversal = SkipTraversal; + + return Cesium3DTilesetTraversal; +}); + +define('Scene/Cesium3DTileStyleEngine',[ + '../Core/defined', + '../Core/defineProperties' + ], function( + defined, + defineProperties) { + 'use strict'; /** - * Transform a vector or point from the camera's reference frame to world coordinates. - * - * @param {Cartesian4} cartesian The vector or point to transform. - * @param {Cartesian4} [result] The object onto which to store the result. - * @returns {Cartesian4} The transformed vector or point. + * @private */ - Camera.prototype.cameraToWorldCoordinates = function(cartesian, result) { - - if (!defined(result)) { - result = new Cartesian4(); + function Cesium3DTileStyleEngine() { + this._style = undefined; // The style provided by the user + this._styleDirty = false; // true when the style is reassigned + this._lastStyleTime = 0; // The "time" when the last style was assigned + } + + defineProperties(Cesium3DTileStyleEngine.prototype, { + style : { + get : function() { + return this._style; + }, + set : function(value) { + this._style = value; + this._styleDirty = true; + } } - updateMembers(this); - return Matrix4.multiplyByVector(this._actualTransform, cartesian, result); + }); + + Cesium3DTileStyleEngine.prototype.makeDirty = function() { + this._styleDirty = true; }; - /** - * Transform a point from the camera's reference frame to world coordinates. - * - * @param {Cartesian3} cartesian The point to transform. - * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} The transformed point. - */ - Camera.prototype.cameraToWorldCoordinatesPoint = function(cartesian, result) { - - if (!defined(result)) { - result = new Cartesian3(); + Cesium3DTileStyleEngine.prototype.applyStyle = function(tileset, frameState) { + if (!tileset.ready) { + return; + } + + if (defined(this._style) && !this._style.ready) { + return; + } + + var styleDirty = this._styleDirty; + + if (frameState.passes.render) { + // Don't reset until the color pass, e.g., for mouse-over picking + this._styleDirty = false; + } + + if (styleDirty) { + // Increase "time", so the style is applied to all visible tiles + ++this._lastStyleTime; + } + + var lastStyleTime = this._lastStyleTime; + var statistics = tileset._statistics; + + // If a new style was assigned, loop through all the visible tiles; otherwise, loop through + // only the tiles that are newly visible, i.e., they are visible this frame, but were not + // visible last frame. In many cases, the newly selected tiles list will be short or empty. + var tiles = styleDirty ? tileset._selectedTiles : tileset._selectedTilesToStyle; + // PERFORMANCE_IDEA: does mouse-over picking basically trash this? We need to style on + // pick, for example, because a feature's show may be false. + + var length = tiles.length; + for (var i = 0; i < length; ++i) { + var tile = tiles[i]; + if (tile.selected && (tile.lastStyleTime !== lastStyleTime)) { + // Apply the style to this tile if it wasn't already applied because: + // 1) the user assigned a new style to the tileset + // 2) this tile is now visible, but it wasn't visible when the style was first assigned + var content = tile.content; + tile.lastStyleTime = lastStyleTime; + content.applyStyle(frameState, this._style); + statistics.numberOfFeaturesStyled += content.featuresLength; + ++statistics.numberOfTilesStyled; + } } - updateMembers(this); - return Matrix4.multiplyByPoint(this._actualTransform, cartesian, result); }; + return Cesium3DTileStyleEngine; +}); + +define('Scene/Cesium3DTileset',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/Check', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/DoublyLinkedList', + '../Core/Event', + '../Core/getBaseUri', + '../Core/getExtensionFromUri', + '../Core/Heap', + '../Core/isDataUri', + '../Core/joinUrls', + '../Core/JulianDate', + '../Core/loadJson', + '../Core/ManagedArray', + '../Core/Math', + '../Core/Matrix4', + '../Core/RuntimeError', + '../Renderer/ClearCommand', + '../Renderer/Pass', + '../ThirdParty/when', + './Axis', + './Cesium3DTile', + './Cesium3DTileColorBlendMode', + './Cesium3DTileOptimizations', + './Cesium3DTileRefine', + './Cesium3DTilesetStatistics', + './Cesium3DTilesetTraversal', + './Cesium3DTileStyleEngine', + './LabelCollection', + './SceneMode', + './ShadowMode', + './TileBoundingRegion', + './TileBoundingSphere', + './TileOrientedBoundingBox' + ], function( + Cartesian2, + Cartesian3, + Cartographic, + Check, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + DoublyLinkedList, + Event, + getBaseUri, + getExtensionFromUri, + Heap, + isDataUri, + joinUrls, + JulianDate, + loadJson, + ManagedArray, + CesiumMath, + Matrix4, + RuntimeError, + ClearCommand, + Pass, + when, + Axis, + Cesium3DTile, + Cesium3DTileColorBlendMode, + Cesium3DTileOptimizations, + Cesium3DTileRefine, + Cesium3DTilesetStatistics, + Cesium3DTilesetTraversal, + Cesium3DTileStyleEngine, + LabelCollection, + SceneMode, + ShadowMode, + TileBoundingRegion, + TileBoundingSphere, + TileOrientedBoundingBox) { + 'use strict'; + /** - * Transform a vector from the camera's reference frame to world coordinates. + * A {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles tileset}, + * used for streaming massive heterogeneous 3D geospatial datasets. * - * @param {Cartesian3} cartesian The vector to transform. - * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} The transformed vector. + * @alias Cesium3DTileset + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} options.url The url to a tileset.json file or to a directory containing a tileset.json file. + * @param {Boolean} [options.show=true] Determines if the tileset will be shown. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the tileset's root tile. + * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from each light source. + * @param {Number} [options.maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement. + * @param {Number} [options.maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. + * @param {Boolean} [options.cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes. + * @param {Boolean} [options.dynamicScreenSpaceError=false] Optimization option. Reduce the screen space error for tiles that are further away from the camera. + * @param {Number} [options.dynamicScreenSpaceErrorDensity=0.00278] Density used to adjust the dynamic screen space error, similar to fog density. + * @param {Number} [options.dynamicScreenSpaceErrorFactor=4.0] A factor used to increase the computed dynamic screen space error. + * @param {Number} [options.dynamicScreenSpaceErrorHeightFalloff=0.25] A ratio of the tileset's height at which the density starts to falloff. + * @param {Boolean} [options.skipLevelOfDetail=true] Optimization option. Determines if level of detail skipping should be applied during the traversal. + * @param {Number} [options.baseScreenSpaceError=1024] When skipLevelOfDetail is true, the screen space error that must be reached before skipping levels of detail. + * @param {Number} [options.skipScreenSpaceErrorFactor=16] When skipLevelOfDetail is true, a multiplier defining the minimum screen space error to skip. Used in conjunction with skipLevels to determine which tiles to load. + * @param {Number} [options.skipLevels=1] When skipLevelOfDetail is true, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with skipScreenSpaceErrorFactor to determine which tiles to load. + * @param {Boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When skipLevelOfDetail is true, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded. + * @param {Boolean} [options.loadSiblings=false] When skipLevelOfDetail is true, determines whether siblings of visible tiles are always downloaded during traversal. + * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. + * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. + * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile. + * @param {Boolean} [options.debugShowContentBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile's content. + * @param {Boolean} [options.debugShowViewerRequestVolume=false] For debugging only. When true, renders the viewer request volume for each tile. + * @param {Boolean} [options.debugShowGeometricError=false] For debugging only. When true, draws labels to indicate the geometric error of each tile. + * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. + * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. + * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * + * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. See {@link https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status} + * + * @example + * var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ + * url : 'http://localhost:8002/tilesets/Seattle' + * })); + * + * @example + * // Common setting for the skipLevelOfDetail optimization + * var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ + * url : 'http://localhost:8002/tilesets/Seattle', + * skipLevelOfDetail : true, + * baseScreenSpaceError : 1024, + * skipScreenSpaceErrorFactor : 16, + * skipLevels : 1, + * immediatelyLoadDesiredLevelOfDetail : false, + * loadSiblings : false, + * cullWithChildrenBounds : true + * })); + * + * @example + * // Common settings for the dynamicScreenSpaceError optimization + * var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ + * url : 'http://localhost:8002/tilesets/Seattle', + * dynamicScreenSpaceError : true, + * dynamicScreenSpaceErrorDensity : 0.00278, + * dynamicScreenSpaceErrorFactor : 4.0, + * dynamicScreenSpaceErrorHeightFalloff : 0.25 + * })); + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles specification} */ - Camera.prototype.cameraToWorldCoordinatesVector = function(cartesian, result) { + function Cesium3DTileset(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var url = options.url; + - if (!defined(result)) { - result = new Cartesian3(); + var tilesetUrl; + var basePath; + + if (getExtensionFromUri(url) === 'json') { + tilesetUrl = url; + basePath = getBaseUri(url, true); + } else if (isDataUri(url)) { + tilesetUrl = url; + basePath = ''; + } else { + basePath = url; + tilesetUrl = joinUrls(basePath, 'tileset.json'); } - updateMembers(this); - return Matrix4.multiplyByPointAsVector(this._actualTransform, cartesian, result); - }; - function clampMove2D(camera, position) { - var rotatable2D = camera._scene.mapMode2D === MapMode2D.ROTATE; - var maxProjectedX = camera._maxCoord.x; - var maxProjectedY = camera._maxCoord.y; + this._url = url; + this._basePath = basePath; + this._tilesetUrl = tilesetUrl; + this._root = undefined; + this._asset = undefined; // Metadata for the entire tileset + this._properties = undefined; // Metadata for per-model/point/etc properties + this._geometricError = undefined; // Geometric error when the tree is not rendered at all + this._gltfUpAxis = undefined; + this._processingQueue = []; + this._selectedTiles = []; + this._requestedTiles = []; + this._desiredTiles = new ManagedArray(); + this._selectedTilesToStyle = []; + this._loadTimestamp = undefined; + this._timeSinceLoad = 0.0; + + var replacementList = new DoublyLinkedList(); + + // [head, sentinel) -> tiles that weren't selected this frame and may be replaced + // (sentinel, tail] -> tiles that were selected this frame + this._replacementList = replacementList; // Tiles with content loaded. For cache management. + this._replacementSentinel = replacementList.add(); + this._trimTiles = false; + + this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true); + + this._hasMixedContent = false; + + this._baseTraversal = new Cesium3DTilesetTraversal.BaseTraversal(); + this._skipTraversal = new Cesium3DTilesetTraversal.SkipTraversal({ + selectionHeuristic : selectionHeuristic + }); + + this._backfaceCommands = new ManagedArray(); + + this._maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 16); + this._maximumMemoryUsage = defaultValue(options.maximumMemoryUsage, 512); + + this._styleEngine = new Cesium3DTileStyleEngine(); + + this._modelMatrix = defined(options.modelMatrix) ? Matrix4.clone(options.modelMatrix) : Matrix4.clone(Matrix4.IDENTITY); + + this._statistics = new Cesium3DTilesetStatistics(); + this._statisticsLastColor = new Cesium3DTilesetStatistics(); + this._statisticsLastPick = new Cesium3DTilesetStatistics(); + + this._tilesLoaded = false; + + this._tileDebugLabels = undefined; + + this._readyPromise = when.defer(); + + /** + * Optimization option. Whether the tileset should refine based on a dynamic screen space error. Tiles that are further + * away will be rendered with lower detail than closer tiles. This improves performance by rendering fewer + * tiles and making less requests, but may result in a slight drop in visual quality for tiles in the distance. + * The algorithm is biased towards "street views" where the camera is close to the ground plane of the tileset and looking + * at the horizon. In addition results are more accurate for tightly fitting bounding volumes like box and region. + * + * @type {Boolean} + * @default false + */ + this.dynamicScreenSpaceError = defaultValue(options.dynamicScreenSpaceError, false); + + /** + * A scalar that determines the density used to adjust the dynamic screen space error, similar to {@link Fog}. Increasing this + * value has the effect of increasing the maximum screen space error for all tiles, but in a non-linear fashion. + * The error starts at 0.0 and increases exponentially until a midpoint is reached, and then approaches 1.0 asymptotically. + * This has the effect of keeping high detail in the closer tiles and lower detail in the further tiles, with all tiles + * beyond a certain distance all roughly having an error of 1.0. + *

    + * The dynamic error is in the range [0.0, 1.0) and is multiplied by dynamicScreenSpaceErrorFactor to produce the + * final dynamic error. This dynamic error is then subtracted from the tile's actual screen space error. + *

    + *

    + * Increasing dynamicScreenSpaceErrorDensity has the effect of moving the error midpoint closer to the camera. + * It is analogous to moving fog closer to the camera. + *

    + * + * @type {Number} + * @default 0.00278 + */ + this.dynamicScreenSpaceErrorDensity = 0.00278; + + /** + * A factor used to increase the screen space error of tiles for dynamic screen space error. As this value increases less tiles + * are requested for rendering and tiles in the distance will have lower detail. If set to zero, the feature will be disabled. + * + * @type {Number} + * @default 4.0 + */ + this.dynamicScreenSpaceErrorFactor = 4.0; + + /** + * A ratio of the tileset's height at which the density starts to falloff. If the camera is below this height the + * full computed density is applied, otherwise the density falls off. This has the effect of higher density at + * street level views. + *

    + * Valid values are between 0.0 and 1.0. + *

    + * + * @type {Number} + * @default 0.25 + */ + this.dynamicScreenSpaceErrorHeightFalloff = 0.25; + + this._dynamicScreenSpaceErrorComputedDensity = 0.0; // Updated based on the camera position and direction + + /** + * Determines whether the tileset casts or receives shadows from each light source. + *

    + * Enabling shadows has a performance impact. A tileset that casts shadows must be rendered twice, once from the camera and again from the light's point of view. + *

    + *

    + * Shadows are rendered only when {@link Viewer#shadows} is true. + *

    + * + * @type {ShadowMode} + * @default ShadowMode.ENABLED + */ + this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); + + /** + * Determines if the tileset will be shown. + * + * @type {Boolean} + * @default true + */ + this.show = defaultValue(options.show, true); + + /** + * Defines how per-feature colors set from the Cesium API or declarative styling blend with the source colors from + * the original feature, e.g. glTF material or per-point color in the tile. + * + * @type {Cesium3DTileColorBlendMode} + * @default Cesium3DTileColorBlendMode.HIGHLIGHT + */ + this.colorBlendMode = Cesium3DTileColorBlendMode.HIGHLIGHT; + + /** + * Defines the value used to linearly interpolate between the source color and feature color when the {@link Cesium3DTileset#colorBlendMode} is MIX. + * A value of 0.0 results in the source color while a value of 1.0 results in the feature color, with any value in-between + * resulting in a mix of the source color and feature color. + * + * @type {Number} + * @default 0.5 + */ + this.colorBlendAmount = 0.5; + + /** + * The event fired to indicate progress of loading new tiles. This event is fired when a new tile + * is requested, when a requested tile is finished downloading, and when a downloaded tile has been + * processed and is ready to render. + *

    + * The number of pending tile requests, numberOfPendingRequests, and number of tiles + * processing, numberOfTilesProcessing are passed to the event listener. + *

    + *

    + * This event is fired at the end of the frame after the scene is rendered. + *

    + * + * @type {Event} + * @default new Event() + * + * @example + * tileset.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) { + * if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) { + * console.log('Stopped loading'); + * return; + * } + * + * console.log('Loading: requests: ' + numberOfPendingRequests + ', processing: ' + numberOfTilesProcessing); + * }); + */ + this.loadProgress = new Event(); + + /** + * The event fired to indicate that all tiles that meet the screen space error this frame are loaded. The tileset + * is completely loaded for this view. + *

    + * This event is fired at the end of the frame after the scene is rendered. + *

    + * + * @type {Event} + * @default new Event() + * + * @example + * tileset.allTilesLoaded.addEventListener(function() { + * console.log('All tiles are loaded'); + * }); + * + * @see Cesium3DTileset#tilesLoaded + */ + this.allTilesLoaded = new Event(); + + /** + * The event fired to indicate that a tile's content was loaded. + *

    + * The loaded {@link Cesium3DTile} is passed to the event listener. + *

    + *

    + * This event is fired during the tileset traversal while the frame is being rendered + * so that updates to the tile take effect in the same frame. Do not create or modify + * Cesium entities or primitives during the event listener. + *

    + * + * @type {Event} + * @default new Event() + * + * @example + * tileset.tileLoad.addEventListener(function(tile) { + * console.log('A tile was loaded.'); + * }); + */ + this.tileLoad = new Event(); + + /** + * The event fired to indicate that a tile's content was unloaded. + *

    + * The unloaded {@link Cesium3DTile} is passed to the event listener. + *

    + *

    + * This event is fired immediately before the tile's content is unloaded while the frame is being + * rendered so that the event listener has access to the tile's content. Do not create + * or modify Cesium entities or primitives during the event listener. + *

    + * + * @type {Event} + * @default new Event() + * + * @example + * tileset.tileUnload.addEventListener(function(tile) { + * console.log('A tile was unloaded from the cache.'); + * }); + * + * @see Cesium3DTileset#maximumMemoryUsage + * @see Cesium3DTileset#trimLoadedTiles + */ + this.tileUnload = new Event(); + + /** + * This event fires once for each visible tile in a frame. This can be used to manually + * style a tileset. + *

    + * The visible {@link Cesium3DTile} is passed to the event listener. + *

    + *

    + * This event is fired during the tileset traversal while the frame is being rendered + * so that updates to the tile take effect in the same frame. Do not create or modify + * Cesium entities or primitives during the event listener. + *

    + * + * @type {Event} + * @default new Event() + * + * @example + * tileset.tileVisible.addEventListener(function(tile) { + * if (tile.content instanceof Cesium.Batched3DModel3DTileContent) { + * console.log('A Batched 3D Model tile is visible.'); + * } + * }); + * + * @example + * // Apply a red style and then manually set random colors for every other feature when the tile becomes visible. + * tileset.style = new Cesium.Cesium3DTileStyle({ + * color : 'color("red")' + * }); + * tileset.tileVisible.addEventListener(function(tile) { + * var content = tile.content; + * var featuresLength = content.featuresLength; + * for (var i = 0; i < featuresLength; i+=2) { + * content.getFeature(i).color = Cesium.Color.fromRandom(); + * } + * }); + */ + this.tileVisible = new Event(); + + /** + * Optimization option. Determines if level of detail skipping should be applied during the traversal. + *

    + * The common strategy for replacement-refinement traversal is to store all levels of the tree in memory and require + * all children to be loaded before the parent can refine. With this optimization levels of the tree can be skipped + * entirely and children can be rendered alongside their parents. The tileset requires significantly less memory when + * using this optimization. + *

    + * + * @type {Boolean} + * @default true + */ + this.skipLevelOfDetail = defaultValue(options.skipLevelOfDetail, true); + + /** + * The screen space error that must be reached before skipping levels of detail. + *

    + * Only used when {@link Cesium3DTileset#skipLevelOfDetail} is true. + *

    + * + * @type {Number} + * @default 1024 + */ + this.baseScreenSpaceError = defaultValue(options.baseScreenSpaceError, 1024); + + /** + * Multiplier defining the minimum screen space error to skip. + * For example, if a tile has screen space error of 100, no tiles will be loaded unless they + * are leaves or have a screen space error <= 100 / skipScreenSpaceErrorFactor. + *

    + * Only used when {@link Cesium3DTileset#skipLevelOfDetail} is true. + *

    + * + * @type {Number} + * @default 16 + */ + this.skipScreenSpaceErrorFactor = defaultValue(options.skipScreenSpaceErrorFactor, 16); + + /** + * Constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. + * For example, if a tile is level 1, no tiles will be loaded unless they are at level greater than 2. + *

    + * Only used when {@link Cesium3DTileset#skipLevelOfDetail} is true. + *

    + * + * @type {Number} + * @default 1 + */ + this.skipLevels = defaultValue(options.skipLevels, 1); + + /** + * When true, only tiles that meet the maximum screen space error will ever be downloaded. + * Skipping factors are ignored and just the desired tiles are loaded. + *

    + * Only used when {@link Cesium3DTileset#skipLevelOfDetail} is true. + *

    + * + * @type {Boolean} + * @default false + */ + this.immediatelyLoadDesiredLevelOfDetail = defaultValue(options.immediatelyLoadDesiredLevelOfDetail, false); + + /** + * Determines whether siblings of visible tiles are always downloaded during traversal. + * This may be useful for ensuring that tiles are already available when the viewer turns left/right. + *

    + * Only used when {@link Cesium3DTileset#skipLevelOfDetail} is true. + *

    + * + * @type {Boolean} + * @default false + */ + this.loadSiblings = defaultValue(options.loadSiblings, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * Determines if only the tiles from last frame should be used for rendering. This + * effectively "freezes" the tileset to the previous frame so it is possible to zoom + * out and see what was rendered. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugFreezeFrame = defaultValue(options.debugFreezeFrame, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, assigns a random color to each tile. This is useful for visualizing + * what features belong to what tiles, especially with additive refinement where features + * from parent tiles may be interleaved with features from child tiles. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugColorizeTiles = defaultValue(options.debugColorizeTiles, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, renders each tile's content as a wireframe. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugWireframe = defaultValue(options.debugWireframe, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, renders the bounding volume for each visible tile. The bounding volume is + * white if the tile has a content bounding volume; otherwise, it is red. Tiles that don't meet the + * screen space error and are still refining to their descendants are yellow. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, renders the bounding volume for each visible tile's content. The bounding volume is + * blue if the tile has a content bounding volume; otherwise it is red. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowContentBoundingVolume = defaultValue(options.debugShowContentBoundingVolume, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, renders the viewer request volume for each tile. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowViewerRequestVolume = defaultValue(options.debugShowViewerRequestVolume, false); + + this._tileDebugLabels = undefined; + this.debugPickedTileLabelOnly = false; + this.debugPickedTile = undefined; + this.debugPickPosition = undefined; + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, draws labels to indicate the geometric error of each tile. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowGeometricError = defaultValue(options.debugShowGeometricError, false); + + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, draws labels to indicate the number of commands, points, triangles and features of each tile. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowRenderingStatistics = defaultValue(options.debugShowRenderingStatistics, false); - var minX; - var maxX; - if (rotatable2D) { - maxX = maxProjectedX; - minX = -maxX; - } else { - maxX = position.x - maxProjectedX * 2.0; - minX = position.x + maxProjectedX * 2.0; - } + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, draws labels to indicate the geometry and texture memory usage of each tile. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowMemoryUsage = defaultValue(options.debugShowMemoryUsage, false); - if (position.x > maxProjectedX) { - position.x = maxX; - } - if (position.x < -maxProjectedX) { - position.x = minX; - } + /** + * This property is for debugging only; it is not optimized for production use. + *

    + * When true, draws labels to indicate the url of each tile. + *

    + * + * @type {Boolean} + * @default false + */ + this.debugShowUrl = defaultValue(options.debugShowUrl, false); - if (position.y > maxProjectedY) { - position.y = maxProjectedY; - } - if (position.y < -maxProjectedY) { - position.y = -maxProjectedY; - } + var that = this; + + // We don't know the distance of the tileset until tileset.json is loaded, so use the default distance for now + Cesium3DTileset.loadJson(tilesetUrl).then(function(tilesetJson) { + that._root = that.loadTileset(tilesetUrl, tilesetJson); + var gltfUpAxis = defined(tilesetJson.asset.gltfUpAxis) ? Axis.fromName(tilesetJson.asset.gltfUpAxis) : Axis.Y; + that._asset = tilesetJson.asset; + that._properties = tilesetJson.properties; + that._geometricError = tilesetJson.geometricError; + that._gltfUpAxis = gltfUpAxis; + that._readyPromise.resolve(that); + }).otherwise(function(error) { + that._readyPromise.reject(error); + }); } - var moveScratch = new Cartesian3(); - /** - * Translates the camera's position by amount along direction. - * - * @param {Cartesian3} direction The direction to move. - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveBackward - * @see Camera#moveForward - * @see Camera#moveLeft - * @see Camera#moveRight - * @see Camera#moveUp - * @see Camera#moveDown - */ - Camera.prototype.move = function(direction, amount) { - - var cameraPosition = this.position; - Cartesian3.multiplyByScalar(direction, amount, moveScratch); - Cartesian3.add(cameraPosition, moveScratch, cameraPosition); + defineProperties(Cesium3DTileset.prototype, { + /** + * Gets the tileset's asset object property, which contains metadata about the tileset. + *

    + * See the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/schema/asset.schema.json|asset schema} + * in the 3D Tiles spec for the full set of properties. + *

    + * + * @memberof Cesium3DTileset.prototype + * + * @type {Object} + * @readonly + * + * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. + */ + asset : { + get : function() { + + return this._asset; + } + }, - if (this._mode === SceneMode.SCENE2D) { - clampMove2D(this, cameraPosition); - } - this._adjustOrthographicFrustum(true); - }; + /** + * Gets the tileset's properties dictionary object, which contains metadata about per-feature properties. + *

    + * See the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/schema/properties.schema.json|properties schema} + * in the 3D Tiles spec for the full set of properties. + *

    + * + * @memberof Cesium3DTileset.prototype + * + * @type {Object} + * @readonly + * + * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. + * + * @example + * console.log('Maximum building height: ' + tileset.properties.height.maximum); + * console.log('Minimum building height: ' + tileset.properties.height.minimum); + * + * @see Cesium3DTileFeature#getProperty + * @see Cesium3DTileFeature#setProperty + */ + properties : { + get : function() { + + return this._properties; + } + }, - /** - * Translates the camera's position by amount along the camera's view vector. - * - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveBackward - */ - Camera.prototype.moveForward = function(amount) { - amount = defaultValue(amount, this.defaultMoveAmount); - this.move(this.direction, amount); - }; + /** + * When true, the tileset's root tile is loaded and the tileset is ready to render. + * This is set to true right before {@link Cesium3DTileset#readyPromise} is resolved. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + ready : { + get : function() { + return defined(this._root); + } + }, - /** - * Translates the camera's position by amount along the opposite direction - * of the camera's view vector. - * - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveForward - */ - Camera.prototype.moveBackward = function(amount) { - amount = defaultValue(amount, this.defaultMoveAmount); - this.move(this.direction, -amount); - }; + /** + * Gets the promise that will be resolved when the tileset's root tile is loaded and the tileset is ready to render. + *

    + * This promise is resolved at the end of the frame before the first frame the tileset is rendered in. + *

    + * + * @memberof Cesium3DTileset.prototype + * + * @type {Promise.} + * @readonly + * + * @example + * tileset.readyPromise.then(function(tileset) { + * // tile.properties is not defined until readyPromise resolves. + * var properties = tileset.properties; + * if (Cesium.defined(properties)) { + * for (var name in properties) { + * console.log(properties[name]); + * } + * } + * }); + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + }, - /** - * Translates the camera's position by amount along the camera's up vector. - * - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveDown - */ - Camera.prototype.moveUp = function(amount) { - amount = defaultValue(amount, this.defaultMoveAmount); - this.move(this.up, amount); - }; + /** + * When true, all tiles that meet the screen space error this frame are loaded. The tileset is + * completely loaded for this view. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + * + * @see Cesium3DTileset#allTilesLoaded + */ + tilesLoaded : { + get : function() { + return this._tilesLoaded; + } + }, - /** - * Translates the camera's position by amount along the opposite direction - * of the camera's up vector. - * - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveUp - */ - Camera.prototype.moveDown = function(amount) { - amount = defaultValue(amount, this.defaultMoveAmount); - this.move(this.up, -amount); - }; + /** + * The url to a tileset.json file or to a directory containing a tileset.json file. + * + * @memberof Cesium3DTileset.prototype + * + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._url; + } + }, - /** - * Translates the camera's position by amount along the camera's right vector. - * - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveLeft - */ - Camera.prototype.moveRight = function(amount) { - amount = defaultValue(amount, this.defaultMoveAmount); - this.move(this.right, amount); - }; + /** + * The base path that non-absolute paths in tileset.json are relative to. + * + * @memberof Cesium3DTileset.prototype + * + * @type {String} + * @readonly + */ + basePath : { + get : function() { + return this._basePath; + } + }, - /** - * Translates the camera's position by amount along the opposite direction - * of the camera's right vector. - * - * @param {Number} [amount] The amount, in meters, to move. Defaults to defaultMoveAmount. - * - * @see Camera#moveRight - */ - Camera.prototype.moveLeft = function(amount) { - amount = defaultValue(amount, this.defaultMoveAmount); - this.move(this.right, -amount); - }; + /** + * The style, defined using the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}, + * applied to each feature in the tileset. + *

    + * Assign undefined to remove the style, which will restore the visual + * appearance of the tileset to its default when no style was applied. + *

    + *

    + * The style is applied to a tile before the {@link Cesium3DTileset#tileVisible} + * event is raised, so code in tileVisible can manually set a feature's + * properties (e.g. color and show) after the style is applied. When + * a new style is assigned any manually set properties are overwritten. + *

    + * + * @memberof Cesium3DTileset.prototype + * + * @type {Cesium3DTileStyle} + * + * @default undefined + * + * @example + * tileset.style = new Cesium.Cesium3DTileStyle({ + * color : { + * conditions : [ + * ['${Height} >= 100', 'color("purple", 0.5)'], + * ['${Height} >= 50', 'color("red")'], + * ['true', 'color("blue")'] + * ] + * }, + * show : '${Height} > 0', + * meta : { + * description : '"Building id ${id} has height ${Height}."' + * } + * }); + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + */ + style : { + get : function() { + return this._styleEngine.style; + }, + set : function(value) { + this._styleEngine.style = value; + } + }, - /** - * Rotates the camera around its up vector by amount, in radians, in the opposite direction - * of its right vector. - * - * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#lookRight - */ - Camera.prototype.lookLeft = function(amount) { - amount = defaultValue(amount, this.defaultLookAmount); - this.look(this.up, -amount); - }; + /** + * The maximum screen space error used to drive level of detail refinement. This value helps determine when a tile + * refines to its descendants, and therefore plays a major role in balancing performance with visual quality. + *

    + * A tile's screen space error is roughly equivalent to the number of pixels wide that would be drawn if a sphere with a + * radius equal to the tile's geometric error were rendered at the tile's position. If this value exceeds + * maximumScreenSpaceError the tile refines to its descendants. + *

    + *

    + * Depending on the tileset, maximumScreenSpaceError may need to be tweaked to achieve the right balance. + * Higher values provide better performance but lower visual quality. + *

    + * + * @memberof Cesium3DTileset.prototype + * + * @type {Number} + * @default 16 + * + * @exception {DeveloperError} maximumScreenSpaceError must be greater than or equal to zero. + */ + maximumScreenSpaceError : { + get : function() { + return this._maximumScreenSpaceError; + }, + set : function(value) { + + this._maximumScreenSpaceError = value; + } + }, - /** - * Rotates the camera around its up vector by amount, in radians, in the direction - * of its right vector. - * - * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#lookLeft - */ - Camera.prototype.lookRight = function(amount) { - amount = defaultValue(amount, this.defaultLookAmount); - this.look(this.up, amount); - }; + /** + * The maximum amount of GPU memory (in MB) that may be used to cache tiles. This value is estimated from + * geometry, textures, and batch table textures of loaded tiles. For point clouds, this value also + * includes per-point metadata. + *

    + * Tiles not in view are unloaded to enforce this. + *

    + *

    + * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame. + *

    + *

    + * If tiles sized more than maximumMemoryUsage are needed + * to meet the desired screen space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError}, + * for the current view, then the memory usage of the tiles loaded will exceed + * maximumMemoryUsage. For example, if the maximum is 256 MB, but + * 300 MB of tiles are needed to meet the screen space error, then 300 MB of tiles may be loaded. When + * these tiles go out of view, they will be unloaded. + *

    + * + * @memberof Cesium3DTileset.prototype + * + * @type {Number} + * @default 512 + * + * @exception {DeveloperError} maximumMemoryUsage must be greater than or equal to zero. + * @see Cesium3DTileset#totalMemoryUsageInBytes + */ + maximumMemoryUsage : { + get : function() { + return this._maximumMemoryUsage; + }, + set : function(value) { + + this._maximumMemoryUsage = value; + } + }, - /** - * Rotates the camera around its right vector by amount, in radians, in the direction - * of its up vector. - * - * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#lookDown - */ - Camera.prototype.lookUp = function(amount) { - amount = defaultValue(amount, this.defaultLookAmount); - this.look(this.right, -amount); - }; + /** + * The tileset's bounding sphere. + * + * @memberof Cesium3DTileset.prototype + * + * @type {BoundingSphere} + * @readonly + * + * @exception {DeveloperError} The tileset is not loaded. Use Cesium3DTileset.readyPromise or wait for Cesium3DTileset.ready to be true. + * + * @example + * var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ + * url : 'http://localhost:8002/tilesets/Seattle' + * })); + * + * tileset.readyPromise.then(function(tileset) { + * // Set the camera to view the newly added tileset + * viewer.camera.viewBoundingSphere(tileset.boundingSphere, new Cesium.HeadingPitchRange(0, -0.5, 0)); + * }); + */ + boundingSphere : { + get : function() { + + return this._root.boundingSphere; + } + }, - /** - * Rotates the camera around its right vector by amount, in radians, in the opposite direction - * of its up vector. - * - * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#lookUp - */ - Camera.prototype.lookDown = function(amount) { - amount = defaultValue(amount, this.defaultLookAmount); - this.look(this.right, amount); - }; + /** + * A 4x4 transformation matrix that transforms the entire tileset. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Matrix4} + * @default Matrix4.IDENTITY + * + * @example + * // Adjust a tileset's height from the globe's surface. + * var heightOffset = 20.0; + * var boundingSphere = tileset.boundingSphere; + * var cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center); + * var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0); + * var offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset); + * var translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3()); + * tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); + */ + modelMatrix : { + get : function() { + return this._modelMatrix; + }, + set : function(value) { + this._modelMatrix = Matrix4.clone(value, this._modelMatrix); + if (defined(this._root)) { + // Update the root transform right away instead of waiting for the next update loop. + // Useful, for example, when setting the modelMatrix and then having the camera view the tileset. + this._root.updateTransform(this._modelMatrix); + } + } + }, - var lookScratchQuaternion = new Quaternion(); - var lookScratchMatrix = new Matrix3(); - /** - * Rotate each of the camera's orientation vectors around axis by angle - * - * @param {Cartesian3} axis The axis to rotate around. - * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#lookUp - * @see Camera#lookDown - * @see Camera#lookLeft - * @see Camera#lookRight - */ - Camera.prototype.look = function(axis, angle) { - - var turnAngle = defaultValue(angle, this.defaultLookAmount); - var quaternion = Quaternion.fromAxisAngle(axis, -turnAngle, lookScratchQuaternion); - var rotation = Matrix3.fromQuaternion(quaternion, lookScratchMatrix); + /** + * Returns the time, in milliseconds, since the tileset was loaded and first updated. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Number} + * @readonly + */ + timeSinceLoad : { + get : function() { + return this._timeSinceLoad; + } + }, - var direction = this.direction; - var up = this.up; - var right = this.right; + /** + * The total amount of GPU memory in bytes used by the tileset. This value is estimated from + * geometry, texture, and batch table textures of loaded tiles. For point clouds, this value also + * includes per-point metadata. + * + * @memberof Cesium3DTileset.prototype + * + * @type {Number} + * @readonly + * + * @see Cesium3DTileset#maximumMemoryUsage + */ + totalMemoryUsageInBytes : { + get : function() { + var statistics = this._statistics; + return statistics.texturesByteLength + statistics.geometryByteLength + statistics.batchTableByteLength; + } + }, - Matrix3.multiplyByVector(rotation, direction, direction); - Matrix3.multiplyByVector(rotation, up, up); - Matrix3.multiplyByVector(rotation, right, right); - }; + /** + * @private + */ + styleEngine : { + get : function() { + return this._styleEngine; + } + }, + + /** + * @private + */ + statistics : { + get : function() { + return this._statistics; + } + } + }); /** - * Rotate the camera counter-clockwise around its direction vector by amount, in radians. - * - * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#twistRight + * Provides a hook to override the method used to request the tileset json + * useful when fetching tilesets from remote servers + * @param {String} tilesetUrl The url of the json file to be fetched + * @returns {Promise.} A promise that resolves with the fetched json data */ - Camera.prototype.twistLeft = function(amount) { - amount = defaultValue(amount, this.defaultLookAmount); - this.look(this.direction, amount); + Cesium3DTileset.loadJson = function(tilesetUrl) { + return loadJson(tilesetUrl); }; /** - * Rotate the camera clockwise around its direction vector by amount, in radians. - * - * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to defaultLookAmount. - * - * @see Camera#twistLeft + * Marks the tileset's {@link Cesium3DTileset#style} as dirty, which forces all + * features to re-evaluate the style in the next frame each is visible. */ - Camera.prototype.twistRight = function(amount) { - amount = defaultValue(amount, this.defaultLookAmount); - this.look(this.direction, -amount); + Cesium3DTileset.prototype.makeStyleDirty = function() { + this._styleEngine.makeDirty(); }; - var rotateScratchQuaternion = new Quaternion(); - var rotateScratchMatrix = new Matrix3(); /** - * Rotates the camera around axis by angle. The distance - * of the camera's position to the center of the camera's reference frame remains the same. - * - * @param {Cartesian3} axis The axis to rotate around given in world coordinates. - * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to defaultRotateAmount. + * Loads the main tileset.json or a tileset.json referenced from a tile. * - * @see Camera#rotateUp - * @see Camera#rotateDown - * @see Camera#rotateLeft - * @see Camera#rotateRight + * @private */ - Camera.prototype.rotate = function(axis, angle) { - - var turnAngle = defaultValue(angle, this.defaultRotateAmount); - var quaternion = Quaternion.fromAxisAngle(axis, -turnAngle, rotateScratchQuaternion); - var rotation = Matrix3.fromQuaternion(quaternion, rotateScratchMatrix); - Matrix3.multiplyByVector(rotation, this.position, this.position); - Matrix3.multiplyByVector(rotation, this.direction, this.direction); - Matrix3.multiplyByVector(rotation, this.up, this.up); - Cartesian3.cross(this.direction, this.up, this.right); - Cartesian3.cross(this.right, this.direction, this.up); + Cesium3DTileset.prototype.loadTileset = function(tilesetUrl, tilesetJson, parentTile) { + var asset = tilesetJson.asset; + if (!defined(asset)) { + throw new RuntimeError('Tileset must have an asset property.'); + } + if (asset.version !== '0.0' && asset.version !== '1.0') { + throw new RuntimeError('The tileset must be 3D Tiles version 0.0 or 1.0. See https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status'); + } - this._adjustOrthographicFrustum(false); - }; + var statistics = this._statistics; - /** - * Rotates the camera around the center of the camera's reference frame by angle downwards. - * - * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to defaultRotateAmount. - * - * @see Camera#rotateUp - * @see Camera#rotate - */ - Camera.prototype.rotateDown = function(angle) { - angle = defaultValue(angle, this.defaultRotateAmount); - rotateVertical(this, angle); - }; + // Append the tileset version to the basePath + var hasVersionQuery = /[?&]v=/.test(tilesetUrl); + if (!hasVersionQuery) { + var versionQuery = '?v=' + defaultValue(asset.tilesetVersion, '0.0'); + this._basePath = joinUrls(this._basePath, versionQuery); + tilesetUrl = joinUrls(tilesetUrl, versionQuery, false); + } - /** - * Rotates the camera around the center of the camera's reference frame by angle upwards. - * - * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to defaultRotateAmount. - * - * @see Camera#rotateDown - * @see Camera#rotate - */ - Camera.prototype.rotateUp = function(angle) { - angle = defaultValue(angle, this.defaultRotateAmount); - rotateVertical(this, -angle); - }; + // A tileset.json referenced from a tile may exist in a different directory than the root tileset. + // Get the basePath relative to the external tileset. + var basePath = getBaseUri(tilesetUrl, true); + var rootTile = new Cesium3DTile(this, basePath, tilesetJson.root, parentTile); - var rotateVertScratchP = new Cartesian3(); - var rotateVertScratchA = new Cartesian3(); - var rotateVertScratchTan = new Cartesian3(); - var rotateVertScratchNegate = new Cartesian3(); - function rotateVertical(camera, angle) { - var position = camera.position; - var p = Cartesian3.normalize(position, rotateVertScratchP); - if (defined(camera.constrainedAxis)) { - var northParallel = Cartesian3.equalsEpsilon(p, camera.constrainedAxis, CesiumMath.EPSILON2); - var southParallel = Cartesian3.equalsEpsilon(p, Cartesian3.negate(camera.constrainedAxis, rotateVertScratchNegate), CesiumMath.EPSILON2); - if ((!northParallel && !southParallel)) { - var constrainedAxis = Cartesian3.normalize(camera.constrainedAxis, rotateVertScratchA); + // If there is a parentTile, add the root of the currently loading tileset + // to parentTile's children, and update its _depth. + if (defined(parentTile)) { + parentTile.children.push(rootTile); + rootTile._depth = parentTile._depth + 1; + } - var dot = Cartesian3.dot(p, constrainedAxis); - var angleToAxis = CesiumMath.acosClamped(dot); - if (angle > 0 && angle > angleToAxis) { - angle = angleToAxis - CesiumMath.EPSILON4; - } + ++statistics.numberOfTilesTotal; - dot = Cartesian3.dot(p, Cartesian3.negate(constrainedAxis, rotateVertScratchNegate)); - angleToAxis = CesiumMath.acosClamped(dot); - if (angle < 0 && -angle > angleToAxis) { - angle = -angleToAxis + CesiumMath.EPSILON4; + var stack = []; + stack.push({ + header : tilesetJson.root, + tile3D : rootTile + }); + + while (stack.length > 0) { + var tile = stack.pop(); + var tile3D = tile.tile3D; + var children = tile.header.children; + if (defined(children)) { + var length = children.length; + for (var i = 0; i < length; ++i) { + var childHeader = children[i]; + var childTile = new Cesium3DTile(this, basePath, childHeader, tile3D); + tile3D.children.push(childTile); + childTile._depth = tile3D._depth + 1; + ++statistics.numberOfTilesTotal; + stack.push({ + header : childHeader, + tile3D : childTile + }); } + } - var tangent = Cartesian3.cross(constrainedAxis, p, rotateVertScratchTan); - camera.rotate(tangent, angle); - } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) { - camera.rotate(camera.right, angle); + if (this._cullWithChildrenBounds) { + Cesium3DTileOptimizations.checkChildrenWithinParent(tile3D); } - } else { - camera.rotate(camera.right, angle); } - } - /** - * Rotates the camera around the center of the camera's reference frame by angle to the right. - * - * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to defaultRotateAmount. - * - * @see Camera#rotateLeft - * @see Camera#rotate - */ - Camera.prototype.rotateRight = function(angle) { - angle = defaultValue(angle, this.defaultRotateAmount); - rotateHorizontal(this, -angle); + return rootTile; }; - /** - * Rotates the camera around the center of the camera's reference frame by angle to the left. - * - * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to defaultRotateAmount. - * - * @see Camera#rotateRight - * @see Camera#rotate - */ - Camera.prototype.rotateLeft = function(angle) { - angle = defaultValue(angle, this.defaultRotateAmount); - rotateHorizontal(this, angle); - }; + var scratchPositionNormal = new Cartesian3(); + var scratchCartographic = new Cartographic(); + var scratchMatrix = new Matrix4(); + var scratchCenter = new Cartesian3(); + var scratchPosition = new Cartesian3(); + var scratchDirection = new Cartesian3(); - function rotateHorizontal(camera, angle) { - if (defined(camera.constrainedAxis)) { - camera.rotate(camera.constrainedAxis, angle); + function updateDynamicScreenSpaceError(tileset, frameState) { + var up; + var direction; + var height; + var minimumHeight; + var maximumHeight; + + var camera = frameState.camera; + var root = tileset._root; + var tileBoundingVolume = root.contentBoundingVolume; + + if (tileBoundingVolume instanceof TileBoundingRegion) { + up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal); + direction = camera.directionWC; + height = camera.positionCartographic.height; + minimumHeight = tileBoundingVolume.minimumHeight; + maximumHeight = tileBoundingVolume.maximumHeight; } else { - camera.rotate(camera.up, angle); + // Transform camera position and direction into the local coordinate system of the tileset + var transformLocal = Matrix4.inverseTransformation(root.computedTransform, scratchMatrix); + var ellipsoid = frameState.mapProjection.ellipsoid; + var boundingVolume = tileBoundingVolume.boundingVolume; + var centerLocal = Matrix4.multiplyByPoint(transformLocal, boundingVolume.center, scratchCenter); + if (Cartesian3.magnitude(centerLocal) > ellipsoid.minimumRadius) { + // The tileset is defined in WGS84. Approximate the minimum and maximum height. + var centerCartographic = Cartographic.fromCartesian(centerLocal, ellipsoid, scratchCartographic); + up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal); + direction = camera.directionWC; + height = camera.positionCartographic.height; + minimumHeight = 0.0; + maximumHeight = centerCartographic.height * 2.0; + } else { + // The tileset is defined in local coordinates (z-up) + var positionLocal = Matrix4.multiplyByPoint(transformLocal, camera.positionWC, scratchPosition); + up = Cartesian3.UNIT_Z; + direction = Matrix4.multiplyByPointAsVector(transformLocal, camera.directionWC, scratchDirection); + direction = Cartesian3.normalize(direction, direction); + height = positionLocal.z; + if (tileBoundingVolume instanceof TileOrientedBoundingBox) { + // Assuming z-up, the last component stores the half-height of the box + var boxHeight = root._header.boundingVolume.box[11]; + minimumHeight = centerLocal.z - boxHeight; + maximumHeight = centerLocal.z + boxHeight; + } else if (tileBoundingVolume instanceof TileBoundingSphere) { + var radius = boundingVolume.radius; + minimumHeight = centerLocal.z - radius; + maximumHeight = centerLocal.z + radius; + } + } } - } - function zoom2D(camera, amount) { - var frustum = camera.frustum; + // The range where the density starts to lessen. Start at the quarter height of the tileset. + var heightFalloff = tileset.dynamicScreenSpaceErrorHeightFalloff; + var heightClose = minimumHeight + (maximumHeight - minimumHeight) * heightFalloff; + var heightFar = maximumHeight; - - amount = amount * 0.5; - var newRight = frustum.right - amount; - var newLeft = frustum.left + amount; + var t = CesiumMath.clamp((height - heightClose) / (heightFar - heightClose), 0.0, 1.0); - var maxRight = camera._maxCoord.x; - if (camera._scene.mapMode2D === MapMode2D.ROTATE) { - maxRight *= camera.maximumZoomFactor; - } + // Increase density as the camera tilts towards the horizon + var dot = Math.abs(Cartesian3.dot(direction, up)); + var horizonFactor = 1.0 - dot; - if (newRight > maxRight) { - newRight = maxRight; - newLeft = -maxRight; - } + // Weaken the horizon factor as the camera height increases, implying the camera is further away from the tileset. + // The goal is to increase density for the "street view", not when viewing the tileset from a distance. + horizonFactor = horizonFactor * (1.0 - t); - if (newRight <= newLeft) { - newRight = 1.0; - newLeft = -1.0; - } + var density = tileset.dynamicScreenSpaceErrorDensity; + density *= horizonFactor; - var ratio = frustum.top / frustum.right; - frustum.right = newRight; - frustum.left = newLeft; - frustum.top = frustum.right * ratio; - frustum.bottom = -frustum.top; + tileset._dynamicScreenSpaceErrorComputedDensity = density; } - function zoom3D(camera, amount) { - camera.move(camera.direction, amount); - } + function selectionHeuristic(tileset, ancestor, tile) { + var skipLevels = tileset.skipLevelOfDetail ? tileset.skipLevels : 0; + var skipScreenSpaceErrorFactor = tileset.skipLevelOfDetail ? tileset.skipScreenSpaceErrorFactor : 1.0; - /** - * Zooms amount along the camera's view vector. - * - * @param {Number} [amount] The amount to move. Defaults to defaultZoomAmount. - * - * @see Camera#zoomOut - */ - Camera.prototype.zoomIn = function(amount) { - amount = defaultValue(amount, this.defaultZoomAmount); - if (this._mode === SceneMode.SCENE2D) { - zoom2D(this, amount); - } else { - zoom3D(this, amount); - } - }; + return (ancestor !== tile && !tile.hasEmptyContent && !tileset.immediatelyLoadDesiredLevelOfDetail) && + (tile._screenSpaceError < ancestor._screenSpaceError / skipScreenSpaceErrorFactor) && + (tile._depth > ancestor._depth + skipLevels); + } - /** - * Zooms amount along the opposite direction of - * the camera's view vector. - * - * @param {Number} [amount] The amount to move. Defaults to defaultZoomAmount. - * - * @see Camera#zoomIn - */ - Camera.prototype.zoomOut = function(amount) { - amount = defaultValue(amount, this.defaultZoomAmount); - if (this._mode === SceneMode.SCENE2D) { - zoom2D(this, -amount); - } else { - zoom3D(this, -amount); - } - }; + /////////////////////////////////////////////////////////////////////////// - /** - * Gets the magnitude of the camera position. In 3D, this is the vector magnitude. In 2D and - * Columbus view, this is the distance to the map. - * - * @returns {Number} The magnitude of the position. - */ - Camera.prototype.getMagnitude = function() { - if (this._mode === SceneMode.SCENE3D) { - return Cartesian3.magnitude(this.position); - } else if (this._mode === SceneMode.COLUMBUS_VIEW) { - return Math.abs(this.position.z); - } else if (this._mode === SceneMode.SCENE2D) { - return Math.max(this.frustum.right - this.frustum.left, this.frustum.top - this.frustum.bottom); + function requestContent(tileset, tile) { + if (tile.hasEmptyContent) { + return; } - }; - var scratchLookAtMatrix4 = new Matrix4(); - - /** - * Sets the camera position and orientation using a target and offset. The target must be given in - * world coordinates. The offset can be either a cartesian or heading/pitch/range in the local east-north-up reference frame centered at the target. - * If the offset is a cartesian, then it is an offset from the center of the reference frame defined by the transformation matrix. If the offset - * is heading/pitch/range, then the heading and the pitch angles are defined in the reference frame defined by the transformation matrix. - * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch - * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. - * - * In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the - * target will be the magnitude of the offset. The heading will be determined from the offset. If the heading cannot be - * determined from the offset, the heading will be north. - * - * @param {Cartesian3} target The target position in world coordinates. - * @param {Cartesian3|HeadingPitchRange} offset The offset from the target in the local east-north-up reference frame centered at the target. - * - * @exception {DeveloperError} lookAt is not supported while morphing. - * - * @example - * // 1. Using a cartesian offset - * var center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0); - * viewer.camera.lookAt(center, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0)); - * - * // 2. Using a HeadingPitchRange offset - * var center = Cesium.Cartesian3.fromDegrees(-72.0, 40.0); - * var heading = Cesium.Math.toRadians(50.0); - * var pitch = Cesium.Math.toRadians(-20.0); - * var range = 5000.0; - * viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, range)); - */ - Camera.prototype.lookAt = function(target, offset) { - - var transform = Transforms.eastNorthUpToFixedFrame(target, Ellipsoid.WGS84, scratchLookAtMatrix4); - this.lookAtTransform(transform, offset); - }; + var statistics = tileset._statistics; + var expired = tile.contentExpired; + var requested = tile.requestContent(); - var scratchLookAtHeadingPitchRangeOffset = new Cartesian3(); - var scratchLookAtHeadingPitchRangeQuaternion1 = new Quaternion(); - var scratchLookAtHeadingPitchRangeQuaternion2 = new Quaternion(); - var scratchHeadingPitchRangeMatrix3 = new Matrix3(); + if (!requested) { + ++statistics.numberOfAttemptedRequests; + return; + } - function offsetFromHeadingPitchRange(heading, pitch, range) { - pitch = CesiumMath.clamp(pitch, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); - heading = CesiumMath.zeroToTwoPi(heading) - CesiumMath.PI_OVER_TWO; + if (expired) { + if (tile.hasRenderableContent) { + statistics.decrementLoadCounts(tile.content); + --tileset._statistics.numberOfTilesWithContentReady; + } else if (tile.hasTilesetContent) { + destroySubtree(tileset, tile); + } + } - var pitchQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -pitch, scratchLookAtHeadingPitchRangeQuaternion1); - var headingQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -heading, scratchLookAtHeadingPitchRangeQuaternion2); - var rotQuat = Quaternion.multiply(headingQuat, pitchQuat, headingQuat); - var rotMatrix = Matrix3.fromQuaternion(rotQuat, scratchHeadingPitchRangeMatrix3); + ++statistics.numberOfPendingRequests; - var offset = Cartesian3.clone(Cartesian3.UNIT_X, scratchLookAtHeadingPitchRangeOffset); - Matrix3.multiplyByVector(rotMatrix, offset, offset); - Cartesian3.negate(offset, offset); - Cartesian3.multiplyByScalar(offset, range, offset); - return offset; + var removeFunction = removeFromProcessingQueue(tileset, tile); + tile.contentReadyToProcessPromise.then(addToProcessingQueue(tileset, tile)); + tile.contentReadyPromise.then(function() { + removeFunction(); + tileset.tileLoad.raiseEvent(tile); + }).otherwise(removeFunction); } - /** - * Sets the camera position and orientation using a target and transformation matrix. The offset can be either a cartesian or heading/pitch/range. - * If the offset is a cartesian, then it is an offset from the center of the reference frame defined by the transformation matrix. If the offset - * is heading/pitch/range, then the heading and the pitch angles are defined in the reference frame defined by the transformation matrix. - * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch - * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. - * - * In 2D, there must be a top down view. The camera will be placed above the center of the reference frame. The height above the - * target will be the magnitude of the offset. The heading will be determined from the offset. If the heading cannot be - * determined from the offset, the heading will be north. - * - * @param {Matrix4} transform The transformation matrix defining the reference frame. - * @param {Cartesian3|HeadingPitchRange} [offset] The offset from the target in a reference frame centered at the target. - * - * @exception {DeveloperError} lookAtTransform is not supported while morphing. - * - * @example - * // 1. Using a cartesian offset - * var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-98.0, 40.0)); - * viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0)); - * - * // 2. Using a HeadingPitchRange offset - * var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-72.0, 40.0)); - * var heading = Cesium.Math.toRadians(50.0); - * var pitch = Cesium.Math.toRadians(-20.0); - * var range = 5000.0; - * viewer.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(heading, pitch, range)); - */ - Camera.prototype.lookAtTransform = function(transform, offset) { - - this._setTransform(transform); - if (!defined(offset)) { + function requestTiles(tileset, outOfCore) { + if (!outOfCore) { return; } - - var cartesianOffset; - if (defined(offset.heading)) { - cartesianOffset = offsetFromHeadingPitchRange(offset.heading, offset.pitch, offset.range); - } else { - cartesianOffset = offset; + var requestedTiles = tileset._requestedTiles; + var length = requestedTiles.length; + for (var i = 0; i < length; ++i) { + requestContent(tileset, requestedTiles[i]); } + } - if (this._mode === SceneMode.SCENE2D) { - Cartesian2.clone(Cartesian2.ZERO, this.position); + function addToProcessingQueue(tileset, tile) { + return function() { + tileset._processingQueue.push(tile); - Cartesian3.negate(cartesianOffset, this.up); - this.up.z = 0.0; + --tileset._statistics.numberOfPendingRequests; + ++tileset._statistics.numberOfTilesProcessing; + }; + } - if (Cartesian3.magnitudeSquared(this.up) < CesiumMath.EPSILON10) { - Cartesian3.clone(Cartesian3.UNIT_Y, this.up); + function removeFromProcessingQueue(tileset, tile) { + return function() { + var index = tileset._processingQueue.indexOf(tile); + if (index === -1) { + // Not in processing queue + // For example, when a url request fails and the ready promise is rejected + --tileset._statistics.numberOfPendingRequests; + return; } - Cartesian3.normalize(this.up, this.up); - - this._setTransform(Matrix4.IDENTITY); - - Cartesian3.negate(Cartesian3.UNIT_Z, this.direction); - Cartesian3.cross(this.direction, this.up, this.right); - Cartesian3.normalize(this.right, this.right); - - var frustum = this.frustum; - var ratio = frustum.top / frustum.right; - frustum.right = Cartesian3.magnitude(cartesianOffset) * 0.5; - frustum.left = -frustum.right; - frustum.top = ratio * frustum.right; - frustum.bottom = -frustum.top; + // Remove from processing queue + tileset._processingQueue.splice(index, 1); + --tileset._statistics.numberOfTilesProcessing; - this._setTransform(transform); + if (tile.hasRenderableContent) { + // RESEARCH_IDEA: ability to unload tiles (without content) for an + // external tileset when all the tiles are unloaded. + tileset._statistics.incrementLoadCounts(tile.content); + ++tileset._statistics.numberOfTilesWithContentReady; - return; - } + // Add to the tile cache. Previously expired tiles are already in the cache. + if (!defined(tile.replacementNode)) { + tile.replacementNode = tileset._replacementList.add(tile); + } + } + }; + } - Cartesian3.clone(cartesianOffset, this.position); - Cartesian3.negate(this.position, this.direction); - Cartesian3.normalize(this.direction, this.direction); - Cartesian3.cross(this.direction, Cartesian3.UNIT_Z, this.right); + function processTiles(tileset, frameState) { + var tiles = tileset._processingQueue; + var length = tiles.length; - if (Cartesian3.magnitudeSquared(this.right) < CesiumMath.EPSILON10) { - Cartesian3.clone(Cartesian3.UNIT_X, this.right); + // Process tiles in the PROCESSING state so they will eventually move to the READY state. + // Traverse backwards in case a tile is removed as a result of calling process() + for (var i = length - 1; i >= 0; --i) { + tiles[i].process(tileset, frameState); } + } - Cartesian3.normalize(this.right, this.right); - Cartesian3.cross(this.right, this.direction, this.up); - Cartesian3.normalize(this.up, this.up); + /////////////////////////////////////////////////////////////////////////// - this._adjustOrthographicFrustum(true); - }; + var scratchCartesian = new Cartesian3(); - var viewRectangle3DCartographic1 = new Cartographic(); - var viewRectangle3DCartographic2 = new Cartographic(); - var viewRectangle3DNorthEast = new Cartesian3(); - var viewRectangle3DSouthWest = new Cartesian3(); - var viewRectangle3DNorthWest = new Cartesian3(); - var viewRectangle3DSouthEast = new Cartesian3(); - var viewRectangle3DNorthCenter = new Cartesian3(); - var viewRectangle3DSouthCenter = new Cartesian3(); - var viewRectangle3DCenter = new Cartesian3(); - var viewRectangle3DEquator = new Cartesian3(); - var defaultRF = { - direction : new Cartesian3(), - right : new Cartesian3(), - up : new Cartesian3() + var stringOptions = { + maximumFractionDigits : 3 }; - var viewRectangle3DEllipsoidGeodesic; - function computeD(direction, upOrRight, corner, tanThetaOrPhi) { - var opposite = Math.abs(Cartesian3.dot(upOrRight, corner)); - return opposite / tanThetaOrPhi - Cartesian3.dot(direction, corner); + function formatMemoryString(memorySizeInBytes) { + var memoryInMegabytes = memorySizeInBytes / 1048576; + if (memoryInMegabytes < 1.0) { + return memoryInMegabytes.toLocaleString(undefined, stringOptions); + } + return Math.round(memoryInMegabytes).toLocaleString(); } - function rectangleCameraPosition3D(camera, rectangle, result, updateCamera) { - var ellipsoid = camera._projection.ellipsoid; - var cameraRF = updateCamera ? camera : defaultRF; + function computeTileLabelPosition(tile) { + var boundingVolume = tile._boundingVolume.boundingVolume; + var halfAxes = boundingVolume.halfAxes; + var radius = boundingVolume.radius; - var north = rectangle.north; - var south = rectangle.south; - var east = rectangle.east; - var west = rectangle.west; + var position = Cartesian3.clone(boundingVolume.center, scratchCartesian); + if (defined(halfAxes)) { + position.x += 0.75 * (halfAxes[0] + halfAxes[3] + halfAxes[6]); + position.y += 0.75 * (halfAxes[1] + halfAxes[4] + halfAxes[7]); + position.z += 0.75 * (halfAxes[2] + halfAxes[5] + halfAxes[8]); + } else if (defined(radius)) { + var normal = Cartesian3.normalize(boundingVolume.center, scratchCartesian); + normal = Cartesian3.multiplyByScalar(normal, 0.75 * radius, scratchCartesian); + position = Cartesian3.add(normal, boundingVolume.center, scratchCartesian); + } + return position; + } - // If we go across the International Date Line - if (west > east) { - east += CesiumMath.TWO_PI; + function addTileDebugLabel(tile, tileset, position) { + var labelString = ''; + var attributes = 0; + + if (tileset.debugShowGeometricError) { + labelString += '\nGeometric error: ' + tile.geometricError; + attributes++; } - // Find the midpoint latitude. - // - // EllipsoidGeodesic will fail if the north and south edges are very close to being on opposite sides of the ellipsoid. - // Ideally we'd just call EllipsoidGeodesic.setEndPoints and let it throw when it detects this case, but sadly it doesn't - // even look for this case in optimized builds, so we have to test for it here instead. - // - // Fortunately, this case can only happen (here) when north is very close to the north pole and south is very close to the south pole, - // so handle it just by using 0 latitude as the center. It's certainliy possible to use a smaller tolerance - // than one degree here, but one degree is safe and putting the center at 0 latitude should be good enough for any - // rectangle that spans 178+ of the 180 degrees of latitude. - var longitude = (west + east) * 0.5; - var latitude; - if (south < -CesiumMath.PI_OVER_TWO + CesiumMath.RADIANS_PER_DEGREE && north > CesiumMath.PI_OVER_TWO - CesiumMath.RADIANS_PER_DEGREE) { - latitude = 0.0; - } else { - var northCartographic = viewRectangle3DCartographic1; - northCartographic.longitude = longitude; - northCartographic.latitude = north; - northCartographic.height = 0.0; + if (tileset.debugShowRenderingStatistics) { + labelString += '\nCommands: ' + tile.commandsLength; + attributes++; - var southCartographic = viewRectangle3DCartographic2; - southCartographic.longitude = longitude; - southCartographic.latitude = south; - southCartographic.height = 0.0; + // Don't display number of points or triangles if 0. + var numberOfPoints = tile.content.pointsLength; + if (numberOfPoints > 0) { + labelString += '\nPoints: ' + tile.content.pointsLength; + attributes++; + } - var ellipsoidGeodesic = viewRectangle3DEllipsoidGeodesic; - if (!defined(ellipsoidGeodesic) || ellipsoidGeodesic.ellipsoid !== ellipsoid) { - viewRectangle3DEllipsoidGeodesic = ellipsoidGeodesic = new EllipsoidGeodesic(undefined, undefined, ellipsoid); + var numberOfTriangles = tile.content.trianglesLength; + if (numberOfTriangles > 0) { + labelString += '\nTriangles: ' + tile.content.trianglesLength; + attributes++; } - ellipsoidGeodesic.setEndPoints(northCartographic, southCartographic); - latitude = ellipsoidGeodesic.interpolateUsingFraction(0.5, viewRectangle3DCartographic1).latitude; + labelString += '\nFeatures: ' + tile.content.featuresLength; + attributes ++; } - var centerCartographic = viewRectangle3DCartographic1; - centerCartographic.longitude = longitude; - centerCartographic.latitude = latitude; - centerCartographic.height = 0.0; - - var center = ellipsoid.cartographicToCartesian(centerCartographic, viewRectangle3DCenter); + if (tileset.debugShowMemoryUsage) { + labelString += '\nTexture Memory: ' + formatMemoryString(tile.content.texturesByteLength); + labelString += '\nGeometry Memory: ' + formatMemoryString(tile.content.geometryByteLength); + attributes += 2; + } - var cart = viewRectangle3DCartographic1; - cart.longitude = east; - cart.latitude = north; - var northEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthEast); - cart.longitude = west; - var northWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthWest); - cart.longitude = longitude; - var northCenter = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthCenter); - cart.latitude = south; - var southCenter = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthCenter); - cart.longitude = east; - var southEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthEast); - cart.longitude = west; - var southWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthWest); + if (tileset.debugShowUrl) { + labelString += '\nUrl: ' + tile._header.content.url; + attributes++; + } - Cartesian3.subtract(northWest, center, northWest); - Cartesian3.subtract(southEast, center, southEast); - Cartesian3.subtract(northEast, center, northEast); - Cartesian3.subtract(southWest, center, southWest); - Cartesian3.subtract(northCenter, center, northCenter); - Cartesian3.subtract(southCenter, center, southCenter); + var newLabel = { + text : labelString.substring(1), + position : position, + font : (19-attributes) + 'px sans-serif', + showBackground : true, + disableDepthTestDistance : Number.POSITIVE_INFINITY + }; - var direction = ellipsoid.geodeticSurfaceNormal(center, cameraRF.direction); - Cartesian3.negate(direction, direction); - var right = Cartesian3.cross(direction, Cartesian3.UNIT_Z, cameraRF.right); - Cartesian3.normalize(right, right); - var up = Cartesian3.cross(right, direction, cameraRF.up); + return tileset._tileDebugLabels.add(newLabel); + } - var d; - if (camera.frustum instanceof OrthographicFrustum) { - var width = Math.max(Cartesian3.distance(northEast, northWest), Cartesian3.distance(southEast, southWest)); - var height = Math.max(Cartesian3.distance(northEast, southEast), Cartesian3.distance(northWest, southWest)); + function updateTileDebugLabels(tileset, frameState) { + var selectedTiles = tileset._selectedTiles; + var length = selectedTiles.length; + tileset._tileDebugLabels.removeAll(); - var rightScalar; - var topScalar; - var ratio = camera.frustum._offCenterFrustum.right / camera.frustum._offCenterFrustum.top; - var heightRatio = height * ratio; - if (width > heightRatio) { - rightScalar = width; - topScalar = rightScalar / ratio; - } else { - topScalar = height; - rightScalar = heightRatio; + if (tileset.debugPickedTileLabelOnly) { + if (defined(tileset.debugPickedTile)) { + var position = (defined(tileset.debugPickPosition)) ? tileset.debugPickPosition : computeTileLabelPosition(tileset.debugPickedTile); + var label = addTileDebugLabel(tileset.debugPickedTile, tileset, position); + label.pixelOffset = new Cartesian2(15, -15); // Offset to avoid picking the label. } - - d = Math.max(rightScalar, topScalar); } else { - var tanPhi = Math.tan(camera.frustum.fovy * 0.5); - var tanTheta = camera.frustum.aspectRatio * tanPhi; + for (var i = 0; i < length; ++i) { + var tile = selectedTiles[i]; + addTileDebugLabel(tile, tileset, computeTileLabelPosition(tile)); + } + } + tileset._tileDebugLabels.update(frameState); + } - d = Math.max( - computeD(direction, up, northWest, tanPhi), - computeD(direction, up, southEast, tanPhi), - computeD(direction, up, northEast, tanPhi), - computeD(direction, up, southWest, tanPhi), - computeD(direction, up, northCenter, tanPhi), - computeD(direction, up, southCenter, tanPhi), - computeD(direction, right, northWest, tanTheta), - computeD(direction, right, southEast, tanTheta), - computeD(direction, right, northEast, tanTheta), - computeD(direction, right, southWest, tanTheta), - computeD(direction, right, northCenter, tanTheta), - computeD(direction, right, southCenter, tanTheta)); + var stencilClearCommand = new ClearCommand({ + stencil : 0, + pass : Pass.CESIUM_3D_TILE + }); - // If the rectangle crosses the equator, compute D at the equator, too, because that's the - // widest part of the rectangle when projected onto the globe. - if (south < 0 && north > 0) { - var equatorCartographic = viewRectangle3DCartographic1; - equatorCartographic.longitude = west; - equatorCartographic.latitude = 0.0; - equatorCartographic.height = 0.0; - var equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator); - Cartesian3.subtract(equatorPosition, center, equatorPosition); - d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta)); + function updateTiles(tileset, frameState) { + tileset._styleEngine.applyStyle(tileset, frameState); - equatorCartographic.longitude = east; - equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator); - Cartesian3.subtract(equatorPosition, center, equatorPosition); - d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta)); + var statistics = tileset._statistics; + var commandList = frameState.commandList; + var numberOfInitialCommands = commandList.length; + var selectedTiles = tileset._selectedTiles; + var length = selectedTiles.length; + var tileVisible = tileset.tileVisible; + var i; + + var bivariateVisibilityTest = tileset.skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer && length > 0; + + tileset._backfaceCommands.length = 0; + + if (bivariateVisibilityTest) { + commandList.push(stencilClearCommand); + } + + var lengthBeforeUpdate = commandList.length; + for (i = 0; i < length; ++i) { + var tile = selectedTiles[i]; + // tiles may get unloaded and destroyed between selection and update + if (tile.selected) { + // Raise the tileVisible event before update in case the tileVisible event + // handler makes changes that update needs to apply to WebGL resources + tileVisible.raiseEvent(tile); + tile.update(tileset, frameState); + statistics.incrementSelectionCounts(tile.content); + ++statistics.selected; } } + var lengthAfterUpdate = commandList.length; - return Cartesian3.add(center, Cartesian3.multiplyByScalar(direction, -d, viewRectangle3DEquator), result); - } + tileset._backfaceCommands.trim(); - var viewRectangleCVCartographic = new Cartographic(); - var viewRectangleCVNorthEast = new Cartesian3(); - var viewRectangleCVSouthWest = new Cartesian3(); - function rectangleCameraPositionColumbusView(camera, rectangle, result) { - var projection = camera._projection; - if (rectangle.west > rectangle.east) { - rectangle = Rectangle.MAX_VALUE; - } - var transform = camera._actualTransform; - var invTransform = camera._actualInvTransform; + if (bivariateVisibilityTest) { + /** + * Consider 'effective leaf' tiles as selected tiles that have no selected descendants. They may have children, + * but they are currently our effective leaves because they do not have selected descendants. These tiles + * are those where with tile._finalResolution === true. + * Let 'unresolved' tiles be those with tile._finalResolution === false. + * + * 1. Render just the backfaces of unresolved tiles in order to lay down z + * 2. Render all frontfaces wherever tile._selectionDepth > stencilBuffer. + * Replace stencilBuffer with tile._selectionDepth, when passing the z test. + * Because children are always drawn before ancestors {@link Cesium3DTilesetTraversal#traverseAndSelect}, + * this effectively draws children first and does not draw ancestors if a descendant has already + * been drawn at that pixel. + * Step 1 prevents child tiles from appearing on top when they are truly behind ancestor content. + * If they are behind the backfaces of the ancestor, then they will not be drawn. + * + * NOTE: Step 2 sometimes causes visual artifacts when backfacing child content has some faces that + * partially face the camera and are inside of the ancestor content. Because they are inside, they will + * not be culled by the depth writes in Step 1, and because they partially face the camera, the stencil tests + * will draw them on top of the ancestor content. + * + * NOTE: Because we always render backfaces of unresolved tiles, if the camera is looking at the backfaces + * of an object, they will always be drawn while loading, even if backface culling is enabled. + */ - var cart = viewRectangleCVCartographic; - cart.longitude = rectangle.east; - cart.latitude = rectangle.north; - var northEast = projection.project(cart, viewRectangleCVNorthEast); - Matrix4.multiplyByPoint(transform, northEast, northEast); - Matrix4.multiplyByPoint(invTransform, northEast, northEast); + var backfaceCommands = tileset._backfaceCommands.values; + var addedCommandsLength = (lengthAfterUpdate - lengthBeforeUpdate); + var backfaceCommandsLength = backfaceCommands.length; - cart.longitude = rectangle.west; - cart.latitude = rectangle.south; - var southWest = projection.project(cart, viewRectangleCVSouthWest); - Matrix4.multiplyByPoint(transform, southWest, southWest); - Matrix4.multiplyByPoint(invTransform, southWest, southWest); + commandList.length += backfaceCommands.length; - result.x = (northEast.x - southWest.x) * 0.5 + southWest.x; - result.y = (northEast.y - southWest.y) * 0.5 + southWest.y; + // copy commands to the back of the commandList + for (i = addedCommandsLength - 1; i >= 0; --i) { + commandList[lengthBeforeUpdate + backfaceCommandsLength + i] = commandList[lengthBeforeUpdate + i]; + } - if (defined(camera.frustum.fovy)) { - var tanPhi = Math.tan(camera.frustum.fovy * 0.5); - var tanTheta = camera.frustum.aspectRatio * tanPhi; - result.z = Math.max((northEast.x - southWest.x) / tanTheta, (northEast.y - southWest.y) / tanPhi) * 0.5; + // move backface commands to the front of the commandList + for (i = 0; i < backfaceCommandsLength; ++i) { + commandList[lengthBeforeUpdate + i] = backfaceCommands[i]; + } + } + + // Number of commands added by each update above + statistics.numberOfCommands = (commandList.length - numberOfInitialCommands); + + if (tileset.debugShowGeometricError || tileset.debugShowRenderingStatistics || tileset.debugShowMemoryUsage || tileset.debugShowUrl) { + if (!defined(tileset._tileDebugLabels)) { + tileset._tileDebugLabels = new LabelCollection(); + } + updateTileDebugLabels(tileset, frameState); } else { - var width = northEast.x - southWest.x; - var height = northEast.y - southWest.y; - result.z = Math.max(width, height); + tileset._tileDebugLabels = tileset._tileDebugLabels && tileset._tileDebugLabels.destroy(); } + } - return result; + var scratchStack = []; + + function destroySubtree(tileset, tile) { + var root = tile; + var statistics = tileset._statistics; + var stack = scratchStack; + stack.push(tile); + while (stack.length > 0) { + tile = stack.pop(); + var children = tile.children; + var length = children.length; + for (var i = 0; i < length; ++i) { + stack.push(children[i]); + } + if (tile !== root) { + unloadTileFromCache(tileset, tile); + tile.destroy(); + --statistics.numberOfTilesTotal; + } + } + root.children = []; } - var viewRectangle2DCartographic = new Cartographic(); - var viewRectangle2DNorthEast = new Cartesian3(); - var viewRectangle2DSouthWest = new Cartesian3(); - function rectangleCameraPosition2D(camera, rectangle, result) { - var projection = camera._projection; - if (rectangle.west > rectangle.east) { - rectangle = Rectangle.MAX_VALUE; + function unloadTileFromCache(tileset, tile) { + var node = tile.replacementNode; + if (!defined(node)) { + return; } - var cart = viewRectangle2DCartographic; - cart.longitude = rectangle.east; - cart.latitude = rectangle.north; - var northEast = projection.project(cart, viewRectangle2DNorthEast); - cart.longitude = rectangle.west; - cart.latitude = rectangle.south; - var southWest = projection.project(cart, viewRectangle2DSouthWest); + var statistics = tileset._statistics; + var replacementList = tileset._replacementList; + var tileUnload = tileset.tileUnload; - var width = Math.abs(northEast.x - southWest.x) * 0.5; - var height = Math.abs(northEast.y - southWest.y) * 0.5; + tileUnload.raiseEvent(tile); + replacementList.remove(node); + statistics.decrementLoadCounts(tile.content); + --statistics.numberOfTilesWithContentReady; + } - var right, top; - var ratio = camera.frustum.right / camera.frustum.top; - var heightRatio = height * ratio; - if (width > heightRatio) { - right = width; - top = right / ratio; - } else { - top = height; - right = heightRatio; + function unloadTiles(tileset) { + var trimTiles = tileset._trimTiles; + tileset._trimTiles = false; + + var replacementList = tileset._replacementList; + + var totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes; + var maximumMemoryUsageInBytes = tileset._maximumMemoryUsage * 1024 * 1024; + + // Traverse the list only to the sentinel since tiles/nodes to the + // right of the sentinel were used this frame. + // + // The sub-list to the left of the sentinel is ordered from LRU to MRU. + var sentinel = tileset._replacementSentinel; + var node = replacementList.head; + while ((node !== sentinel) && ((totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) { + var tile = node.item; + node = node.next; + unloadTileFromCache(tileset, tile); + tile.unloadContent(); + totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes; } + } + + /** + * Unloads all tiles that weren't selected the previous frame. This can be used to + * explicitly manage the tile cache and reduce the total number of tiles loaded below + * {@link Cesium3DTileset#maximumMemoryUsage}. + *

    + * Tile unloads occur at the next frame to keep all the WebGL delete calls + * within the render loop. + *

    + */ + Cesium3DTileset.prototype.trimLoadedTiles = function() { + // Defer to next frame so WebGL delete calls happen inside the render loop + this._trimTiles = true; + }; - height = Math.max(2.0 * right, 2.0 * top); + /////////////////////////////////////////////////////////////////////////// - result.x = (northEast.x - southWest.x) * 0.5 + southWest.x; - result.y = (northEast.y - southWest.y) * 0.5 + southWest.y; + function raiseLoadProgressEvent(tileset, frameState) { + var statistics = tileset._statistics; + var statisticsLast = tileset._statisticsLastColor; + var numberOfPendingRequests = statistics.numberOfPendingRequests; + var numberOfTilesProcessing = statistics.numberOfTilesProcessing; + var lastNumberOfPendingRequest = statisticsLast.numberOfPendingRequests; + var lastNumberOfTilesProcessing = statisticsLast.numberOfTilesProcessing; - cart = projection.unproject(result, cart); - cart.height = height; - result = projection.project(cart, result); + var progressChanged = (numberOfPendingRequests !== lastNumberOfPendingRequest) || (numberOfTilesProcessing !== lastNumberOfTilesProcessing); - return result; + if (progressChanged) { + frameState.afterRender.push(function() { + tileset.loadProgress.raiseEvent(numberOfPendingRequests, numberOfTilesProcessing); + }); + } + + tileset._tilesLoaded = (statistics.numberOfPendingRequests === 0) && (statistics.numberOfTilesProcessing === 0) && (statistics.numberOfAttemptedRequests === 0); + + if (progressChanged && tileset._tilesLoaded) { + frameState.afterRender.push(function() { + tileset.allTilesLoaded.raiseEvent(); + }); + } } + /////////////////////////////////////////////////////////////////////////// + /** - * Get the camera position needed to view a rectangle on an ellipsoid or map - * - * @param {Rectangle} rectangle The rectangle to view. - * @param {Cartesian3} [result] The camera position needed to view the rectangle - * @returns {Cartesian3} The camera position needed to view the rectangle + * 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: + *

    */ - Camera.prototype.getRectangleCameraCoordinates = function(rectangle, result) { - var mode = this._mode; + Cesium3DTileset.prototype.update = function(frameState) { + if (frameState.mode === SceneMode.MORPHING) { + return; + } - if (!defined(result)) { - result = new Cartesian3(); + if (!this.show || !this.ready) { + return; } - if (mode === SceneMode.SCENE3D) { - return rectangleCameraPosition3D(this, rectangle, result); - } else if (mode === SceneMode.COLUMBUS_VIEW) { - return rectangleCameraPositionColumbusView(this, rectangle, result); - } else if (mode === SceneMode.SCENE2D) { - return rectangleCameraPosition2D(this, rectangle, result); + if (!defined(this._loadTimestamp)) { + this._loadTimestamp = JulianDate.clone(frameState.time); } - return undefined; - }; + this._timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0); - var pickEllipsoid3DRay = new Ray(); - function pickEllipsoid3D(camera, windowPosition, ellipsoid, result) { - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - var ray = camera.getPickRay(windowPosition, pickEllipsoid3DRay); - var intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid); - if (!intersection) { - return undefined; - } + // Do not do out-of-core operations (new content requests, cache removal, + // process new tiles) during the pick pass. + var passes = frameState.passes; + var isPick = (passes.pick && !passes.render); + var outOfCore = !isPick; - var t = intersection.start > 0.0 ? intersection.start : intersection.stop; - return Ray.getPoint(ray, t, result); - } + var statistics = this._statistics; + statistics.clear(); - var pickEllipsoid2DRay = new Ray(); - function pickMap2D(camera, windowPosition, projection, result) { - var ray = camera.getPickRay(windowPosition, pickEllipsoid2DRay); - var position = ray.origin; - position.z = 0.0; - var cart = projection.unproject(position); + if (outOfCore) { + processTiles(this, frameState); + } - if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO) { - return undefined; + if (this.dynamicScreenSpaceError) { + updateDynamicScreenSpaceError(this, frameState); } - return projection.ellipsoid.cartographicToCartesian(cart, result); - } + Cesium3DTilesetTraversal.selectTiles(this, frameState, outOfCore); + requestTiles(this, outOfCore); + updateTiles(this, frameState); - var pickEllipsoidCVRay = new Ray(); - function pickMapColumbusView(camera, windowPosition, projection, result) { - var ray = camera.getPickRay(windowPosition, pickEllipsoidCVRay); - var scalar = -ray.origin.x / ray.direction.x; - Ray.getPoint(ray, scalar, result); + if (outOfCore) { + unloadTiles(this); + } - var cart = projection.unproject(new Cartesian3(result.y, result.z, 0.0)); + // Events are raised (added to the afterRender queue) here since promises + // may resolve outside of the update loop that then raise events, e.g., + // model's readyPromise. + raiseLoadProgressEvent(this, frameState); - if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO || - cart.longitude < -Math.PI || cart.longitude > Math.PI) { - return undefined; - } + // Update last statistics + var statisticsLast = isPick ? this._statisticsLastPick : this._statisticsLastColor; + Cesium3DTilesetStatistics.clone(statistics, statisticsLast); + }; - return projection.ellipsoid.cartographicToCartesian(cart, result); - } + /** + * 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 Cesium3DTileset#destroy + */ + Cesium3DTileset.prototype.isDestroyed = function() { + return false; + }; /** - * Pick an ellipsoid or map. + * 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. * - * @param {Cartesian2} windowPosition The x and y coordinates of a pixel. - * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to pick. - * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} If the ellipsoid or map was picked, returns the point on the surface of the ellipsoid or map - * in world coordinates. If the ellipsoid or map was not picked, returns undefined. + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * tileset = tileset && tileset.destroy(); + * + * @see Cesium3DTileset#isDestroyed */ - Camera.prototype.pickEllipsoid = function(windowPosition, ellipsoid, result) { - - var canvas = this._scene.canvas; - if (canvas.clientWidth === 0 || canvas.clientHeight === 0) { - return undefined; - } + Cesium3DTileset.prototype.destroy = function() { + // Destroy debug labels + this._tileDebugLabels = this._tileDebugLabels && this._tileDebugLabels.destroy(); - if (!defined(result)) { - result = new Cartesian3(); - } + // Traverse the tree and destroy all tiles + if (defined(this._root)) { + var stack = scratchStack; + stack.push(this._root); - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + while (stack.length > 0) { + var tile = stack.pop(); + tile.destroy(); - if (this._mode === SceneMode.SCENE3D) { - result = pickEllipsoid3D(this, windowPosition, ellipsoid, result); - } else if (this._mode === SceneMode.SCENE2D) { - result = pickMap2D(this, windowPosition, this._projection, result); - } else if (this._mode === SceneMode.COLUMBUS_VIEW) { - result = pickMapColumbusView(this, windowPosition, this._projection, result); - } else { - return undefined; + var children = tile.children; + var length = children.length; + for (var i = 0; i < length; ++i) { + stack.push(children[i]); + } + } } - return result; + this._root = undefined; + return destroyObject(this); }; - var pickPerspCenter = new Cartesian3(); - var pickPerspXDir = new Cartesian3(); - var pickPerspYDir = new Cartesian3(); - function getPickRayPerspective(camera, windowPosition, result) { - var canvas = camera._scene.canvas; - var width = canvas.clientWidth; - var height = canvas.clientHeight; + return Cesium3DTileset; +}); + +// JavaScript Expression Parser (JSEP) 0.3.1 +// JSEP may be freely distributed under the MIT License +// http://jsep.from.so/ + +define('ThirdParty/jsep',[],function() { + +/*global module: true, exports: true, console: true */ +(function (root) { + 'use strict'; + // Node Types + // ---------- + + // This is the full set of types that any JSEP node can be. + // Store them here to save space when minified + var COMPOUND = 'Compound', + IDENTIFIER = 'Identifier', + MEMBER_EXP = 'MemberExpression', + LITERAL = 'Literal', + THIS_EXP = 'ThisExpression', + CALL_EXP = 'CallExpression', + UNARY_EXP = 'UnaryExpression', + BINARY_EXP = 'BinaryExpression', + LOGICAL_EXP = 'LogicalExpression', + CONDITIONAL_EXP = 'ConditionalExpression', + ARRAY_EXP = 'ArrayExpression', + + PERIOD_CODE = 46, // '.' + COMMA_CODE = 44, // ',' + SQUOTE_CODE = 39, // single quote + DQUOTE_CODE = 34, // double quotes + OPAREN_CODE = 40, // ( + CPAREN_CODE = 41, // ) + OBRACK_CODE = 91, // [ + CBRACK_CODE = 93, // ] + QUMARK_CODE = 63, // ? + SEMCOL_CODE = 59, // ; + COLON_CODE = 58, // : + + throwError = function(message, index) { + var error = new Error(message + ' at character ' + index); + error.index = index; + error.description = message; + throw error; + }, - var tanPhi = Math.tan(camera.frustum.fovy * 0.5); - var tanTheta = camera.frustum.aspectRatio * tanPhi; - var near = camera.frustum.near; + // Operations + // ---------- + + // Set `t` to `true` to save space (when minified, not gzipped) + t = true, + // Use a quickly-accessible map to store all of the unary operators + // Values are set to `true` (it really doesn't matter) + unary_ops = {'-': t, '!': t, '~': t, '+': t}, + // Also use a map for the binary operations but set their values to their + // binary precedence for quick reference: + // see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language) + binary_ops = { + '||': 1, '&&': 2, '|': 3, '^': 4, '&': 5, + '==': 6, '!=': 6, '===': 6, '!==': 6, + '<': 7, '>': 7, '<=': 7, '>=': 7, + '<<':8, '>>': 8, '>>>': 8, + '+': 9, '-': 9, + '*': 10, '/': 10, '%': 10 + }, + // Get return the longest key length of any object + getMaxKeyLen = function(obj) { + var max_len = 0, len; + for(var key in obj) { + if((len = key.length) > max_len && obj.hasOwnProperty(key)) { + max_len = len; + } + } + return max_len; + }, + max_unop_len = getMaxKeyLen(unary_ops), + max_binop_len = getMaxKeyLen(binary_ops), + // Literals + // ---------- + // Store the values to return for the various literals we may encounter + literals = { + 'true': true, + 'false': false, + 'null': null + }, + // Except for `this`, which is special. This could be changed to something like `'self'` as well + this_str = 'this', + // Returns the precedence of a binary operator or `0` if it isn't a binary operator + binaryPrecedence = function(op_val) { + return binary_ops[op_val] || 0; + }, + // Utility function (gets called from multiple places) + // Also note that `a && b` and `a || b` are *logical* expressions, not binary expressions + createBinaryExpression = function (operator, left, right) { + var type = (operator === '||' || operator === '&&') ? LOGICAL_EXP : BINARY_EXP; + return { + type: type, + operator: operator, + left: left, + right: right + }; + }, + // `ch` is a character code in the next three functions + isDecimalDigit = function(ch) { + return (ch >= 48 && ch <= 57); // 0...9 + }, + isIdentifierStart = function(ch) { + return (ch === 36) || (ch === 95) || // `$` and `_` + (ch >= 65 && ch <= 90) || // A...Z + (ch >= 97 && ch <= 122) || // a...z + (ch >= 128 && !binary_ops[String.fromCharCode(ch)]); // any non-ASCII that is not an operator + }, + isIdentifierPart = function(ch) { + return (ch === 36) || (ch === 95) || // `$` and `_` + (ch >= 65 && ch <= 90) || // A...Z + (ch >= 97 && ch <= 122) || // a...z + (ch >= 48 && ch <= 57) || // 0...9 + (ch >= 128 && !binary_ops[String.fromCharCode(ch)]); // any non-ASCII that is not an operator + }, - var x = (2.0 / width) * windowPosition.x - 1.0; - var y = (2.0 / height) * (height - windowPosition.y) - 1.0; + // Parsing + // ------- + // `expr` is a string with the passed in expression + jsep = function(expr) { + // `index` stores the character number we are currently at while `length` is a constant + // All of the gobbles below will modify `index` as we move along + var index = 0, + charAtFunc = expr.charAt, + charCodeAtFunc = expr.charCodeAt, + exprI = function(i) { return charAtFunc.call(expr, i); }, + exprICode = function(i) { return charCodeAtFunc.call(expr, i); }, + length = expr.length, + + // Push `index` up to the next non-space character + gobbleSpaces = function() { + var ch = exprICode(index); + // space or tab + while(ch === 32 || ch === 9) { + ch = exprICode(++index); + } + }, + + // The main parsing function. Much of this code is dedicated to ternary expressions + gobbleExpression = function() { + var test = gobbleBinaryExpression(), + consequent, alternate; + gobbleSpaces(); + if(exprICode(index) === QUMARK_CODE) { + // Ternary expression: test ? consequent : alternate + index++; + consequent = gobbleExpression(); + if(!consequent) { + throwError('Expected expression', index); + } + gobbleSpaces(); + if(exprICode(index) === COLON_CODE) { + index++; + alternate = gobbleExpression(); + if(!alternate) { + throwError('Expected expression', index); + } + return { + type: CONDITIONAL_EXP, + test: test, + consequent: consequent, + alternate: alternate + }; + } else { + throwError('Expected :', index); + } + } else { + return test; + } + }, + + // Search for the operation portion of the string (e.g. `+`, `===`) + // Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`) + // and move down from 3 to 2 to 1 character until a matching binary operation is found + // then, return that binary operation + gobbleBinaryOp = function() { + gobbleSpaces(); + var biop, to_check = expr.substr(index, max_binop_len), tc_len = to_check.length; + while(tc_len > 0) { + if(binary_ops.hasOwnProperty(to_check)) { + index += tc_len; + return to_check; + } + to_check = to_check.substr(0, --tc_len); + } + return false; + }, + + // This function is responsible for gobbling an individual expression, + // e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)` + gobbleBinaryExpression = function() { + var ch_i, node, biop, prec, stack, biop_info, left, right, i; + + // First, try to get the leftmost thing + // Then, check to see if there's a binary operator operating on that leftmost thing + left = gobbleToken(); + biop = gobbleBinaryOp(); + + // If there wasn't a binary operator, just return the leftmost node + if(!biop) { + return left; + } - var position = camera.positionWC; - Cartesian3.clone(position, result.origin); + // Otherwise, we need to start a stack to properly place the binary operations in their + // precedence structure + biop_info = { value: biop, prec: binaryPrecedence(biop)}; - var nearCenter = Cartesian3.multiplyByScalar(camera.directionWC, near, pickPerspCenter); - Cartesian3.add(position, nearCenter, nearCenter); - var xDir = Cartesian3.multiplyByScalar(camera.rightWC, x * near * tanTheta, pickPerspXDir); - var yDir = Cartesian3.multiplyByScalar(camera.upWC, y * near * tanPhi, pickPerspYDir); - var direction = Cartesian3.add(nearCenter, xDir, result.direction); - Cartesian3.add(direction, yDir, direction); - Cartesian3.subtract(direction, position, direction); - Cartesian3.normalize(direction, direction); + right = gobbleToken(); + if(!right) { + throwError("Expected expression after " + biop, index); + } + stack = [left, biop_info, right]; - return result; - } + // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm) + while((biop = gobbleBinaryOp())) { + prec = binaryPrecedence(biop); - var scratchDirection = new Cartesian3(); + if(prec === 0) { + break; + } + biop_info = { value: biop, prec: prec }; + + // Reduce: make a binary expression from the three topmost entries. + while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { + right = stack.pop(); + biop = stack.pop().value; + left = stack.pop(); + node = createBinaryExpression(biop, left, right); + stack.push(node); + } - function getPickRayOrthographic(camera, windowPosition, result) { - var canvas = camera._scene.canvas; - var width = canvas.clientWidth; - var height = canvas.clientHeight; + node = gobbleToken(); + if(!node) { + throwError("Expected expression after " + biop, index); + } + stack.push(biop_info, node); + } - var frustum = camera.frustum; - if (defined(frustum._offCenterFrustum)) { - frustum = frustum._offCenterFrustum; - } - var x = (2.0 / width) * windowPosition.x - 1.0; - x *= (frustum.right - frustum.left) * 0.5; - var y = (2.0 / height) * (height - windowPosition.y) - 1.0; - y *= (frustum.top - frustum.bottom) * 0.5; + i = stack.length - 1; + node = stack[i]; + while(i > 1) { + node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node); + i -= 2; + } + return node; + }, + + // An individual part of a binary expression: + // e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis) + gobbleToken = function() { + var ch, to_check, tc_len; + + gobbleSpaces(); + ch = exprICode(index); + + if(isDecimalDigit(ch) || ch === PERIOD_CODE) { + // Char code 46 is a dot `.` which can start off a numeric literal + return gobbleNumericLiteral(); + } else if(ch === SQUOTE_CODE || ch === DQUOTE_CODE) { + // Single or double quotes + return gobbleStringLiteral(); + } else if(isIdentifierStart(ch) || ch === OPAREN_CODE) { // open parenthesis + // `foo`, `bar.baz` + return gobbleVariable(); + } else if (ch === OBRACK_CODE) { + return gobbleArray(); + } else { + to_check = expr.substr(index, max_unop_len); + tc_len = to_check.length; + while(tc_len > 0) { + if(unary_ops.hasOwnProperty(to_check)) { + index += tc_len; + return { + type: UNARY_EXP, + operator: to_check, + argument: gobbleToken(), + prefix: true + }; + } + to_check = to_check.substr(0, --tc_len); + } - var origin = result.origin; - Cartesian3.clone(camera.position, origin); + return false; + } + }, + // Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to + // keep track of everything in the numeric literal and then calling `parseFloat` on that string + gobbleNumericLiteral = function() { + var number = '', ch, chCode; + while(isDecimalDigit(exprICode(index))) { + number += exprI(index++); + } - Cartesian3.multiplyByScalar(camera.right, x, scratchDirection); - Cartesian3.add(scratchDirection, origin, origin); - Cartesian3.multiplyByScalar(camera.up, y, scratchDirection); - Cartesian3.add(scratchDirection, origin, origin); + if(exprICode(index) === PERIOD_CODE) { // can start with a decimal marker + number += exprI(index++); - Cartesian3.clone(camera.directionWC, result.direction); + while(isDecimalDigit(exprICode(index))) { + number += exprI(index++); + } + } - if (camera._mode === SceneMode.COLUMBUS_VIEW) { - Cartesian3.fromElements(result.origin.z, result.origin.x, result.origin.y, result.origin); - } + ch = exprI(index); + if(ch === 'e' || ch === 'E') { // exponent marker + number += exprI(index++); + ch = exprI(index); + if(ch === '+' || ch === '-') { // exponent sign + number += exprI(index++); + } + while(isDecimalDigit(exprICode(index))) { //exponent itself + number += exprI(index++); + } + if(!isDecimalDigit(exprICode(index-1)) ) { + throwError('Expected exponent (' + number + exprI(index) + ')', index); + } + } - return result; + + chCode = exprICode(index); + // Check to make sure this isn't a variable name that start with a number (123abc) + if(isIdentifierStart(chCode)) { + throwError('Variable names cannot start with a number (' + + number + exprI(index) + ')', index); + } else if(chCode === PERIOD_CODE) { + throwError('Unexpected period', index); + } + + return { + type: LITERAL, + value: parseFloat(number), + raw: number + }; + }, + + // Parses a string literal, staring with single or double quotes with basic support for escape codes + // e.g. `"hello world"`, `'this is\nJSEP'` + gobbleStringLiteral = function() { + var str = '', quote = exprI(index++), closed = false, ch; + + while(index < length) { + ch = exprI(index++); + if(ch === quote) { + closed = true; + break; + } else if(ch === '\\') { + // Check for all of the common escape codes + ch = exprI(index++); + switch(ch) { + case 'n': str += '\n'; break; + case 'r': str += '\r'; break; + case 't': str += '\t'; break; + case 'b': str += '\b'; break; + case 'f': str += '\f'; break; + case 'v': str += '\x0B'; break; + default : str += '\\' + ch; + } + } else { + str += ch; + } + } + + if(!closed) { + throwError('Unclosed quote after "'+str+'"', index); + } + + return { + type: LITERAL, + value: str, + raw: quote + str + quote + }; + }, + + // Gobbles only identifiers + // e.g.: `foo`, `_value`, `$x1` + // Also, this function checks if that identifier is a literal: + // (e.g. `true`, `false`, `null`) or `this` + gobbleIdentifier = function() { + var ch = exprICode(index), start = index, identifier; + + if(isIdentifierStart(ch)) { + index++; + } else { + throwError('Unexpected ' + exprI(index), index); + } + + while(index < length) { + ch = exprICode(index); + if(isIdentifierPart(ch)) { + index++; + } else { + break; + } + } + identifier = expr.slice(start, index); + + if(literals.hasOwnProperty(identifier)) { + return { + type: LITERAL, + value: literals[identifier], + raw: identifier + }; + } else if(identifier === this_str) { + return { type: THIS_EXP }; + } else { + return { + type: IDENTIFIER, + name: identifier + }; + } + }, + + // Gobbles a list of arguments within the context of a function call + // or array literal. This function also assumes that the opening character + // `(` or `[` has already been gobbled, and gobbles expressions and commas + // until the terminator character `)` or `]` is encountered. + // e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]` + gobbleArguments = function(termination) { + var ch_i, args = [], node, closed = false; + while(index < length) { + gobbleSpaces(); + ch_i = exprICode(index); + if(ch_i === termination) { // done parsing + closed = true; + index++; + break; + } else if (ch_i === COMMA_CODE) { // between expressions + index++; + } else { + node = gobbleExpression(); + if(!node || node.type === COMPOUND) { + throwError('Expected comma', index); + } + args.push(node); + } + } + if (!closed) { + throwError('Expected ' + String.fromCharCode(termination), index); + } + return args; + }, + + // Gobble a non-literal variable name. This variable name may include properties + // e.g. `foo`, `bar.baz`, `foo['bar'].baz` + // It also gobbles function calls: + // e.g. `Math.acos(obj.angle)` + gobbleVariable = function() { + var ch_i, node; + ch_i = exprICode(index); + + if(ch_i === OPAREN_CODE) { + node = gobbleGroup(); + } else { + node = gobbleIdentifier(); + } + gobbleSpaces(); + ch_i = exprICode(index); + while(ch_i === PERIOD_CODE || ch_i === OBRACK_CODE || ch_i === OPAREN_CODE) { + index++; + if(ch_i === PERIOD_CODE) { + gobbleSpaces(); + node = { + type: MEMBER_EXP, + computed: false, + object: node, + property: gobbleIdentifier() + }; + } else if(ch_i === OBRACK_CODE) { + node = { + type: MEMBER_EXP, + computed: true, + object: node, + property: gobbleExpression() + }; + gobbleSpaces(); + ch_i = exprICode(index); + if(ch_i !== CBRACK_CODE) { + throwError('Unclosed [', index); + } + index++; + } else if(ch_i === OPAREN_CODE) { + // A function call is being made; gobble all the arguments + node = { + type: CALL_EXP, + 'arguments': gobbleArguments(CPAREN_CODE), + callee: node + }; + } + gobbleSpaces(); + ch_i = exprICode(index); + } + return node; + }, + + // Responsible for parsing a group of things within parentheses `()` + // This function assumes that it needs to gobble the opening parenthesis + // and then tries to gobble everything within that parenthesis, assuming + // that the next thing it should see is the close parenthesis. If not, + // then the expression probably doesn't have a `)` + gobbleGroup = function() { + index++; + var node = gobbleExpression(); + gobbleSpaces(); + if(exprICode(index) === CPAREN_CODE) { + index++; + return node; + } else { + throwError('Unclosed (', index); + } + }, + + // Responsible for parsing Array literals `[1, 2, 3]` + // This function assumes that it needs to gobble the opening bracket + // and then tries to gobble the expressions as arguments. + gobbleArray = function() { + index++; + return { + type: ARRAY_EXP, + elements: gobbleArguments(CBRACK_CODE) + }; + }, + + nodes = [], ch_i, node; + + while(index < length) { + ch_i = exprICode(index); + + // Expressions can be separated by semicolons, commas, or just inferred without any + // separators + if(ch_i === SEMCOL_CODE || ch_i === COMMA_CODE) { + index++; // ignore separators + } else { + // Try to gobble each expression individually + if((node = gobbleExpression())) { + nodes.push(node); + // If we weren't able to find a binary expression and are out of room, then + // the expression passed in probably has too much + } else if(index < length) { + throwError('Unexpected "' + exprI(index) + '"', index); + } + } + } + + // If there's only one expression just try returning the expression + if(nodes.length === 1) { + return nodes[0]; + } else { + return { + type: COMPOUND, + body: nodes + }; + } + }; + + // To be filled in by the template + jsep.version = '0.3.1'; + jsep.toString = function() { return 'JavaScript Expression Parser (JSEP) v' + jsep.version; }; + + /** + * @method jsep.addUnaryOp + * @param {string} op_name The name of the unary op to add + * @return jsep + */ + jsep.addUnaryOp = function(op_name) { + max_unop_len = Math.max(op_name.length, max_unop_len); + unary_ops[op_name] = t; return this; + }; + + /** + * @method jsep.addBinaryOp + * @param {string} op_name The name of the binary op to add + * @param {number} precedence The precedence of the binary op (can be a float) + * @return jsep + */ + jsep.addBinaryOp = function(op_name, precedence) { + max_binop_len = Math.max(op_name.length, max_binop_len); + binary_ops[op_name] = precedence; + return this; + }; + + /** + * @method jsep.addLiteral + * @param {string} literal_name The name of the literal to add + * @param {*} literal_value The value of the literal + * @return jsep + */ + jsep.addLiteral = function(literal_name, literal_value) { + literals[literal_name] = literal_value; + return this; + }; + + /** + * @method jsep.removeUnaryOp + * @param {string} op_name The name of the unary op to remove + * @return jsep + */ + jsep.removeUnaryOp = function(op_name) { + delete unary_ops[op_name]; + if(op_name.length === max_unop_len) { + max_unop_len = getMaxKeyLen(unary_ops); + } + return this; + }; + + /** + * @method jsep.removeAllUnaryOps + * @return jsep + */ + jsep.removeAllUnaryOps = function() { + unary_ops = {}; + max_unop_len = 0; + + return this; + }; + + /** + * @method jsep.removeBinaryOp + * @param {string} op_name The name of the binary op to remove + * @return jsep + */ + jsep.removeBinaryOp = function(op_name) { + delete binary_ops[op_name]; + if(op_name.length === max_binop_len) { + max_binop_len = getMaxKeyLen(binary_ops); + } + return this; + }; + + /** + * @method jsep.removeAllBinaryOps + * @return jsep + */ + jsep.removeAllBinaryOps = function() { + binary_ops = {}; + max_binop_len = 0; + + return this; + }; + + /** + * @method jsep.removeLiteral + * @param {string} literal_name The name of the literal to remove + * @return jsep + */ + jsep.removeLiteral = function(literal_name) { + delete literals[literal_name]; + return this; + }; + + /** + * @method jsep.removeAllLiterals + * @return jsep + */ + jsep.removeAllLiterals = function() { + literals = {}; + + return this; + }; + + // In desktop environments, have a way to restore the old value for `jsep` + if (typeof exports === 'undefined') { + var old_jsep = root.jsep; + // The star of the show! It's a function! + root.jsep = jsep; + // And a courteous function willing to move out of the way for other similarly-named objects! + jsep.noConflict = function() { + if(root.jsep === jsep) { + root.jsep = old_jsep; + } + return jsep; + }; + } else { + // In Node.JS environments + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = jsep; + } else { + exports.parse = jsep; + } + } +}(this)); + + // `jsep` only exists when running in the browser + if (typeof jsep !== 'undefined') { + return jsep.noConflict(); } +}); + +define('Scene/ExpressionNodeType',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; /** - * Create a ray from the camera position through the pixel at windowPosition - * in world coordinates. + * @private + */ + var ExpressionNodeType = { + VARIABLE : 0, + UNARY : 1, + BINARY : 2, + TERNARY : 3, + CONDITIONAL : 4, + MEMBER : 5, + FUNCTION_CALL : 6, + ARRAY : 7, + REGEX: 8, + VARIABLE_IN_STRING : 9, + LITERAL_NULL : 10, + LITERAL_BOOLEAN : 11, + LITERAL_NUMBER : 12, + LITERAL_STRING : 13, + LITERAL_COLOR : 14, + LITERAL_VECTOR : 15, + LITERAL_REGEX : 16, + LITERAL_UNDEFINED : 17, + BUILTIN_VARIABLE : 18 + }; + + return freezeObject(ExpressionNodeType); +}); + +define('Scene/Expression',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Check', + '../Core/Color', + '../Core/defined', + '../Core/defineProperties', + '../Core/isArray', + '../Core/Math', + '../Core/RuntimeError', + '../ThirdParty/jsep', + './ExpressionNodeType' + ], function( + Cartesian2, + Cartesian3, + Cartesian4, + Check, + Color, + defined, + defineProperties, + isArray, + CesiumMath, + RuntimeError, + jsep, + ExpressionNodeType) { + 'use strict'; + + /** + * An expression for a style applied to a {@link Cesium3DTileset}. + *

    + * Evaluates an expression defined using the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. + *

    + *

    + * Implements the {@link StyleExpression} interface. + *

    * - * @param {Cartesian2} windowPosition The x and y coordinates of a pixel. - * @param {Ray} [result] The object onto which to store the result. - * @returns {Ray} Returns the {@link Cartesian3} position and direction of the ray. + * @alias Expression + * @constructor + * + * @param {String} [expression] The expression defined using the 3D Tiles Styling language. + * @param {Object} [defines] Defines in the style. + * + * @example + * var expression = new Cesium.Expression('(regExp("^Chest").test(${County})) && (${YearBuilt} >= 1970)'); + * expression.evaluate(frameState, feature); // returns true or false depending on the feature's properties + * + * @example + * var expression = new Cesium.Expression('(${Temperature} > 90) ? color("red") : color("white")'); + * expression.evaluateColor(frameState, feature, result); // returns a Cesium.Color object */ - Camera.prototype.getPickRay = function(windowPosition, result) { + function Expression(expression, defines) { - if (!defined(result)) { - result = new Ray(); + this._expression = expression; + expression = replaceDefines(expression, defines); + expression = replaceVariables(removeBackslashes(expression)); + + // customize jsep operators + jsep.addBinaryOp('=~', 0); + jsep.addBinaryOp('!~', 0); + + var ast; + try { + ast = jsep(expression); + } catch (e) { + throw new RuntimeError(e); } - var frustum = this.frustum; - if (defined(frustum.aspectRatio) && defined(frustum.fov) && defined(frustum.near)) { - return getPickRayPerspective(this, windowPosition, result); + this._runtimeAst = createRuntimeAst(this, ast); + } + + defineProperties(Expression.prototype, { + /** + * Gets the expression defined in the 3D Tiles Styling language. + * + * @memberof Expression.prototype + * + * @type {String} + * @readonly + * + * @default undefined + */ + expression : { + get : function() { + return this._expression; + } } + }); - return getPickRayOrthographic(this, windowPosition, result); + // Scratch storage manager while evaluating deep expressions. + // For example, an expression like dot(vec4(${red}), vec4(${green}) * vec4(${blue}) requires 3 scratch Cartesian4's + var scratchStorage = { + arrayIndex : 0, + arrayArray : [[]], + cartesian2Index : 0, + cartesian3Index : 0, + cartesian4Index : 0, + cartesian2Array : [new Cartesian2()], + cartesian3Array : [new Cartesian3()], + cartesian4Array : [new Cartesian4()], + reset : function() { + this.arrayIndex = 0; + this.cartesian2Index = 0; + this.cartesian3Index = 0; + this.cartesian4Index = 0; + }, + getArray : function() { + if (this.arrayIndex >= this.arrayArray.length) { + this.arrayArray.push([]); + } + var array = this.arrayArray[this.arrayIndex++]; + array.length = 0; + return array; + }, + getCartesian2 : function() { + if (this.cartesian2Index >= this.cartesian2Array.length) { + this.cartesian2Array.push(new Cartesian2()); + } + return this.cartesian2Array[this.cartesian2Index++]; + }, + getCartesian3 : function() { + if (this.cartesian3Index >= this.cartesian3Array.length) { + this.cartesian3Array.push(new Cartesian3()); + } + return this.cartesian3Array[this.cartesian3Index++]; + }, + getCartesian4 : function() { + if (this.cartesian4Index >= this.cartesian4Array.length) { + this.cartesian4Array.push(new Cartesian4()); + } + return this.cartesian4Array[this.cartesian4Index++]; + } }; - var scratchToCenter = new Cartesian3(); - var scratchProj = new Cartesian3(); + /** + * Evaluates the result of an expression, optionally using the provided feature's properties. If the result of + * the expression in the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + * is of type Boolean, Number, or String, the corresponding JavaScript + * primitive type will be returned. If the result is a RegExp, a Javascript RegExp + * object will be returned. If the result is a Cartesian2, Cartesian3, or Cartesian4, + * a {@link Cartesian2}, {@link Cartesian3}, or {@link Cartesian4} object will be returned. If the result argument is + * a {@link Color}, the {@link Cartesian4} value is converted to a {@link Color} and then returned. + * + * @param {FrameState} frameState The frame state. + * @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression. + * @param {Object} [result] The object onto which to store the result. + * @returns {Boolean|Number|String|RegExp|Cartesian2|Cartesian3|Cartesian4|Color} The result of evaluating the expression. + */ + Expression.prototype.evaluate = function(frameState, feature, result) { + scratchStorage.reset(); + var value = this._runtimeAst.evaluate(frameState, feature); + if ((result instanceof Color) && (value instanceof Cartesian4)) { + return Color.fromCartesian4(value, result); + } + if ((value instanceof Cartesian2) || (value instanceof Cartesian3) || (value instanceof Cartesian4)) { + return value.clone(result); + } + return value; + }; /** - * Return the distance from the camera to the front of the bounding sphere. + * Evaluates the result of a Color expression, optionally using the provided feature's properties. + *

    + * This is equivalent to {@link Expression#evaluate} but always returns a {@link Color} object. + *

    * - * @param {BoundingSphere} boundingSphere The bounding sphere in world coordinates. - * @returns {Number} The distance to the bounding sphere. + * @param {FrameState} frameState The frame state. + * @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression. + * @param {Color} [result] The object in which to store the result + * @returns {Color} The modified result parameter or a new Color instance if one was not provided. */ - Camera.prototype.distanceToBoundingSphere = function(boundingSphere) { - - var toCenter = Cartesian3.subtract(this.positionWC, boundingSphere.center, scratchToCenter); - var proj = Cartesian3.multiplyByScalar(this.directionWC, Cartesian3.dot(toCenter, this.directionWC), scratchProj); - return Math.max(0.0, Cartesian3.magnitude(proj) - boundingSphere.radius); + Expression.prototype.evaluateColor = function(frameState, feature, result) { + scratchStorage.reset(); + var color = this._runtimeAst.evaluate(frameState, feature); + return Color.fromCartesian4(color, result); }; - var scratchPixelSize = new Cartesian2(); + /** + * Gets the shader function for this expression. + * Returns undefined if the shader function can't be generated from this expression. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * @param {String} returnType The return type of the generated function. + * + * @returns {String} The shader function. + * + * @private + */ + Expression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { + var shaderExpression = this.getShaderExpression(attributePrefix, shaderState); + + shaderExpression = returnType + ' ' + functionName + '() \n' + + '{ \n' + + ' return ' + shaderExpression + '; \n' + + '} \n'; + + return shaderExpression; + }; /** - * Return the pixel size in meters. + * Gets the shader expression for this expression. + * Returns undefined if the shader expression can't be generated from this expression. * - * @param {BoundingSphere} boundingSphere The bounding sphere in world coordinates. - * @param {Number} drawingBufferWidth The drawing buffer width. - * @param {Number} drawingBufferHeight The drawing buffer height. - * @returns {Number} The pixel size in meters. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * + * @returns {String} The shader expression. + * + * @private */ - Camera.prototype.getPixelSize = function(boundingSphere, drawingBufferWidth, drawingBufferHeight) { - - var distance = this.distanceToBoundingSphere(boundingSphere); - var pixelSize = this.frustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, scratchPixelSize); - return Math.max(pixelSize.x, pixelSize.y); + Expression.prototype.getShaderExpression = function(attributePrefix, shaderState) { + return this._runtimeAst.getShaderExpression(attributePrefix, shaderState); }; - function createAnimationTemplateCV(camera, position, center, maxX, maxY, duration) { - var newPosition = Cartesian3.clone(position); + var unaryOperators = ['!', '-', '+']; + var binaryOperators = ['+', '-', '*', '/', '%', '===', '!==', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; - if (center.y > maxX) { - newPosition.y -= center.y - maxX; - } else if (center.y < -maxX) { - newPosition.y += -maxX - center.y; - } + var variableRegex = /\${(.*?)}/g; // Matches ${variable_name} + var backslashRegex = /\\/g; + var backslashReplacement = '@#%'; + var replacementRegex = /@#%/g; - if (center.z > maxY) { - newPosition.z -= center.z - maxY; - } else if (center.z < -maxY) { - newPosition.z += -maxY - center.z; - } + var scratchColor = new Color(); - function updateCV(value) { - var interp = Cartesian3.lerp(position, newPosition, value.time, new Cartesian3()); - camera.worldToCameraCoordinatesPoint(interp, camera.position); - } - return { - easingFunction : EasingFunction.EXPONENTIAL_OUT, - startObject : { - time : 0.0 - }, - stopObject : { - time : 1.0 - }, - duration : duration, - update : updateCV + var unaryFunctions = { + abs : getEvaluateUnaryComponentwise(Math.abs), + sqrt : getEvaluateUnaryComponentwise(Math.sqrt), + cos : getEvaluateUnaryComponentwise(Math.cos), + sin : getEvaluateUnaryComponentwise(Math.sin), + tan : getEvaluateUnaryComponentwise(Math.tan), + acos : getEvaluateUnaryComponentwise(Math.acos), + asin : getEvaluateUnaryComponentwise(Math.asin), + atan : getEvaluateUnaryComponentwise(Math.atan), + radians : getEvaluateUnaryComponentwise(CesiumMath.toRadians), + degrees : getEvaluateUnaryComponentwise(CesiumMath.toDegrees), + sign : getEvaluateUnaryComponentwise(CesiumMath.sign), + floor : getEvaluateUnaryComponentwise(Math.floor), + ceil : getEvaluateUnaryComponentwise(Math.ceil), + round : getEvaluateUnaryComponentwise(Math.round), + exp : getEvaluateUnaryComponentwise(Math.exp), + exp2 : getEvaluateUnaryComponentwise(exp2), + log : getEvaluateUnaryComponentwise(Math.log), + log2 : getEvaluateUnaryComponentwise(log2), + fract : getEvaluateUnaryComponentwise(fract), + length : length, + normalize: normalize + }; + + var binaryFunctions = { + atan2 : getEvaluateBinaryCommponentwise(Math.atan2, false), + pow : getEvaluateBinaryCommponentwise(Math.pow, false), + min : getEvaluateBinaryCommponentwise(Math.min, true), + max : getEvaluateBinaryCommponentwise(Math.max, true), + distance : distance, + dot : dot, + cross : cross + }; + + var ternaryFunctions = { + clamp : getEvaluateTernaryCommponentwise(CesiumMath.clamp, true), + mix : getEvaluateTernaryCommponentwise(CesiumMath.lerp, true) + }; + + function fract(number) { + return number - Math.floor(number); + } + + function exp2(exponent) { + return Math.pow(2.0,exponent); + } + + function log2(number) { + return CesiumMath.logBase(number, 2.0); + } + + function getEvaluateUnaryComponentwise(operation) { + return function(call, left) { + if (typeof left === 'number') { + return operation(left); + } else if (left instanceof Cartesian2) { + return Cartesian2.fromElements(operation(left.x), operation(left.y), scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3) { + return Cartesian3.fromElements(operation(left.x), operation(left.y), operation(left.z), scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4) { + return Cartesian4.fromElements(operation(left.x), operation(left.y), operation(left.z), operation(left.w), scratchStorage.getCartesian4()); + } + throw new RuntimeError('Function "' + call + '" requires a vector or number argument. Argument is ' + left + '.'); }; } - var normalScratch = new Cartesian3(); - var centerScratch = new Cartesian3(); - var posScratch = new Cartesian3(); - var scratchCartesian3Subtract = new Cartesian3(); + function getEvaluateBinaryCommponentwise(operation, allowScalar) { + return function(call, left, right) { + if (allowScalar && typeof right === 'number') { + if (typeof left === 'number') { + return operation(left, right); + } else if (left instanceof Cartesian2) { + return Cartesian2.fromElements(operation(left.x, right), operation(left.y, right), scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3) { + return Cartesian3.fromElements(operation(left.x, right), operation(left.y, right), operation(left.z, right), scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4) { + return Cartesian4.fromElements(operation(left.x, right), operation(left.y, right), operation(left.z, right), operation(left.w, right), scratchStorage.getCartesian4()); + } + } - function createAnimationCV(camera, duration) { - var position = camera.position; - var direction = camera.direction; + if (typeof left === 'number' && typeof right === 'number') { + return operation(left, right); + } else if (left instanceof Cartesian2 && right instanceof Cartesian2) { + return Cartesian2.fromElements(operation(left.x, right.x), operation(left.y, right.y), scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3 && right instanceof Cartesian3) { + return Cartesian3.fromElements(operation(left.x, right.x), operation(left.y, right.y), operation(left.z, right.z), scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4 && right instanceof Cartesian4) { + return Cartesian4.fromElements(operation(left.x, right.x), operation(left.y, right.y), operation(left.z, right.z), operation(left.w, right.w), scratchStorage.getCartesian4()); + } - var normal = camera.worldToCameraCoordinatesVector(Cartesian3.UNIT_X, normalScratch); - var scalar = -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction); - var center = Cartesian3.add(position, Cartesian3.multiplyByScalar(direction, scalar, centerScratch), centerScratch); - camera.cameraToWorldCoordinatesPoint(center, center); + throw new RuntimeError('Function "' + call + '" requires vector or number arguments of matching types. Arguments are ' + left + ' and ' + right + '.'); + }; + } - position = camera.cameraToWorldCoordinatesPoint(camera.position, posScratch); + function getEvaluateTernaryCommponentwise(operation, allowScalar) { + return function(call, left, right, test) { + if (allowScalar && typeof test === 'number') { + if (typeof left === 'number' && typeof right === 'number') { + return operation(left, right, test); + } else if (left instanceof Cartesian2 && right instanceof Cartesian2) { + return Cartesian2.fromElements(operation(left.x, right.x, test), operation(left.y, right.y, test), scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3 && right instanceof Cartesian3) { + return Cartesian3.fromElements(operation(left.x, right.x, test), operation(left.y, right.y, test), operation(left.z, right.z, test), scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4 && right instanceof Cartesian4) { + return Cartesian4.fromElements(operation(left.x, right.x, test), operation(left.y, right.y, test), operation(left.z, right.z, test), operation(left.w, right.w, test), scratchStorage.getCartesian4()); + } + } - var tanPhi = Math.tan(camera.frustum.fovy * 0.5); - var tanTheta = camera.frustum.aspectRatio * tanPhi; - var distToC = Cartesian3.magnitude(Cartesian3.subtract(position, center, scratchCartesian3Subtract)); - var dWidth = tanTheta * distToC; - var dHeight = tanPhi * distToC; + if (typeof left === 'number' && typeof right === 'number' && typeof test === 'number') { + return operation(left, right, test); + } else if (left instanceof Cartesian2 && right instanceof Cartesian2 && test instanceof Cartesian2) { + return Cartesian2.fromElements(operation(left.x, right.x, test.x), operation(left.y, right.y, test.y), scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3 && right instanceof Cartesian3 && test instanceof Cartesian3) { + return Cartesian3.fromElements(operation(left.x, right.x, test.x), operation(left.y, right.y, test.y), operation(left.z, right.z, test.z), scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4 && right instanceof Cartesian4 && test instanceof Cartesian4) { + return Cartesian4.fromElements(operation(left.x, right.x, test.x), operation(left.y, right.y, test.y), operation(left.z, right.z, test.z), operation(left.w, right.w, test.w), scratchStorage.getCartesian4()); + } - var mapWidth = camera._maxCoord.x; - var mapHeight = camera._maxCoord.y; + throw new RuntimeError('Function "' + call + '" requires vector or number arguments of matching types. Arguments are ' + left + ', ' + right + ', and ' + test + '.'); + }; + } - var maxX = Math.max(dWidth - mapWidth, mapWidth); - var maxY = Math.max(dHeight - mapHeight, mapHeight); + function length(call, left) { + if (typeof left === 'number') { + return Math.abs(left); + } else if (left instanceof Cartesian2) { + return Cartesian2.magnitude(left); + } else if (left instanceof Cartesian3) { + return Cartesian3.magnitude(left); + } else if (left instanceof Cartesian4) { + return Cartesian4.magnitude(left); + } - if (position.z < -maxX || position.z > maxX || position.y < -maxY || position.y > maxY) { - var translateX = center.y < -maxX || center.y > maxX; - var translateY = center.z < -maxY || center.z > maxY; - if (translateX || translateY) { - return createAnimationTemplateCV(camera, position, center, maxX, maxY, duration); - } + throw new RuntimeError('Function "' + call + '" requires a vector or number argument. Argument is ' + left + '.'); + } + + function normalize(call, left) { + if (typeof left === 'number') { + return 1.0; + } else if (left instanceof Cartesian2) { + return Cartesian2.normalize(left, scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3) { + return Cartesian3.normalize(left, scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4) { + return Cartesian4.normalize(left, scratchStorage.getCartesian4()); } - return undefined; + throw new RuntimeError('Function "' + call + '" requires a vector or number argument. Argument is ' + left + '.'); } - /** - * Create an animation to move the map into view. This method is only valid for 2D and Columbus modes. - * - * @param {Number} duration The duration, in seconds, of the animation. - * @returns {Object} The animation or undefined if the scene mode is 3D or the map is already ion view. - * - * @private - */ - Camera.prototype.createCorrectPositionTween = function(duration) { - - if (this._mode === SceneMode.COLUMBUS_VIEW) { - return createAnimationCV(this, duration); + function distance(call, left, right) { + if (typeof left === 'number' && typeof right === 'number') { + return Math.abs(left - right); + } else if (left instanceof Cartesian2 && right instanceof Cartesian2) { + return Cartesian2.distance(left, right); + } else if (left instanceof Cartesian3 && right instanceof Cartesian3) { + return Cartesian3.distance(left, right); + } else if (left instanceof Cartesian4 && right instanceof Cartesian4) { + return Cartesian4.distance(left, right); } - return undefined; - }; + throw new RuntimeError('Function "' + call + '" requires vector or number arguments of matching types. Arguments are ' + left + ' and ' + right + '.'); + } - var scratchFlyToDestination = new Cartesian3(); - var newOptions = { - destination : undefined, - heading : undefined, - pitch : undefined, - roll : undefined, - duration : undefined, - complete : undefined, - cancel : undefined, - endTransform : undefined, - maximumHeight : undefined, - easingFunction : undefined - }; + function dot(call, left, right) { + if (typeof left === 'number' && typeof right === 'number') { + return left * right; + } else if (left instanceof Cartesian2 && right instanceof Cartesian2) { + return Cartesian2.dot(left, right); + } else if (left instanceof Cartesian3 && right instanceof Cartesian3) { + return Cartesian3.dot(left, right); + } else if (left instanceof Cartesian4 && right instanceof Cartesian4) { + return Cartesian4.dot(left, right); + } - /** - * Cancels the current camera flight if one is in progress. - * The camera is left at it's current location. - */ - Camera.prototype.cancelFlight = function () { - if (defined(this._currentFlight)) { - this._currentFlight.cancelTween(); - this._currentFlight = undefined; + throw new RuntimeError('Function "' + call + '" requires vector or number arguments of matching types. Arguments are ' + left + ' and ' + right + '.'); + } + + function cross(call, left, right) { + if (left instanceof Cartesian3 && right instanceof Cartesian3) { + return Cartesian3.cross(left, right, scratchStorage.getCartesian3()); } - }; - /** - * Flies the camera from its current position to a new position. - * - * @param {Object} options Object with the following properties: - * @param {Cartesian3|Rectangle} options.destination The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view. - * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pith and roll properties. By default, the direction will point - * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive - * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode. - * @param {Number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight. - * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete. - * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled. - * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed. - * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight. - * @param {Number} [options.pitchAdjustHeight] If camera flyes higher than that value, adjust pitch duiring the flight to look down, and keep Earth in viewport. - * @param {Number} [options.flyOverLongitude] There are always two ways between 2 points on globe. This option force camera to choose fight direction to fly over that longitude. - * @param {Number} [options.flyOverLongitudeWeight] Fly over the lon specifyed via flyOverLongitude only if that way is not longer than short way times flyOverLongitudeWeight. - * @param {EasingFunction|EasingFunction~Callback} [options.easingFunction] Controls how the time is interpolated over the duration of the flight. - * - * @exception {DeveloperError} If either direction or up is given, then both are required. - * - * @example - * // 1. Fly to a position with a top-down view - * viewer.camera.flyTo({ - * destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0) - * }); - * - * // 2. Fly to a Rectangle with a top-down view - * viewer.camera.flyTo({ - * destination : Cesium.Rectangle.fromDegrees(west, south, east, north) - * }); - * - * // 3. Fly to a position with an orientation using unit vectors. - * viewer.camera.flyTo({ - * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0), - * orientation : { - * direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734), - * up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339) - * } - * }); - * - * // 4. Fly to a position with an orientation using heading, pitch and roll. - * viewer.camera.flyTo({ - * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0), - * orientation : { - * heading : Cesium.Math.toRadians(175.0), - * pitch : Cesium.Math.toRadians(-35.0), - * roll : 0.0 - * } - * }); - */ - Camera.prototype.flyTo = function(options) { - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - var destination = options.destination; - - var mode = this._mode; - if (mode === SceneMode.MORPHING) { - return; + throw new RuntimeError('Function "' + call + '" requires vec3 arguments. Arguments are ' + left + ' and ' + right + '.'); + } + + function Node(type, value, left, right, test) { + this._type = type; + this._value = value; + this._left = left; + this._right = right; + this._test = test; + this.evaluate = undefined; + + setEvaluateFunction(this); + } + + function replaceDefines(expression, defines) { + if (!defined(defines)) { + return expression; + } + for (var key in defines) { + if (defines.hasOwnProperty(key)) { + var definePlaceholder = new RegExp('\\$\\{' + key + '\\}', 'g'); + var defineReplace = '(' + defines[key] + ')'; + if (defined(defineReplace)) { + expression = expression.replace(definePlaceholder, defineReplace); + } + } } + return expression; + } - this.cancelFlight(); + function removeBackslashes(expression) { + return expression.replace(backslashRegex, backslashReplacement); + } - var orientation = defaultValue(options.orientation, defaultValue.EMPTY_OBJECT); - if (defined(orientation.direction)) { - orientation = directionUpToHeadingPitchRoll(this, destination, orientation, scratchSetViewOptions.orientation); + function replaceBackslashes(expression) { + return expression.replace(replacementRegex, '\\'); + } + + function replaceVariables(expression) { + var exp = expression; + var result = ''; + var i = exp.indexOf('${'); + while (i >= 0) { + // Check if string is inside quotes + var openSingleQuote = exp.indexOf('\''); + var openDoubleQuote = exp.indexOf('"'); + var closeQuote; + if (openSingleQuote >= 0 && openSingleQuote < i) { + closeQuote = exp.indexOf('\'', openSingleQuote + 1); + result += exp.substr(0, closeQuote + 1); + exp = exp.substr(closeQuote + 1); + i = exp.indexOf('${'); + } else if (openDoubleQuote >= 0 && openDoubleQuote < i) { + closeQuote = exp.indexOf('"', openDoubleQuote + 1); + result += exp.substr(0, closeQuote + 1); + exp = exp.substr(closeQuote + 1); + i = exp.indexOf('${'); + } else { + result += exp.substr(0, i); + var j = exp.indexOf('}'); + if (j < 0) { + throw new RuntimeError('Unmatched {.'); + } + result += 'czm_' + exp.substr(i + 2, j - (i + 2)); + exp = exp.substr(j + 1); + i = exp.indexOf('${'); + } } + result += exp; + return result; + } - if (defined(options.duration) && options.duration <= 0.0) { - var setViewOptions = scratchSetViewOptions; - setViewOptions.destination = options.destination; - setViewOptions.orientation.heading = orientation.heading; - setViewOptions.orientation.pitch = orientation.pitch; - setViewOptions.orientation.roll = orientation.roll; - setViewOptions.convert = options.convert; - setViewOptions.endTransform = options.endTransform; - this.setView(setViewOptions); - if (typeof options.complete === 'function') { - options.complete(); + function parseLiteral(ast) { + var type = typeof ast.value; + if (ast.value === null) { + return new Node(ExpressionNodeType.LITERAL_NULL, null); + } else if (type === 'boolean') { + return new Node(ExpressionNodeType.LITERAL_BOOLEAN, ast.value); + } else if (type === 'number') { + return new Node(ExpressionNodeType.LITERAL_NUMBER, ast.value); + } else if (type === 'string') { + if (ast.value.indexOf('${') >= 0) { + return new Node(ExpressionNodeType.VARIABLE_IN_STRING, ast.value); } - return; + return new Node(ExpressionNodeType.LITERAL_STRING, replaceBackslashes(ast.value)); } + } - var isRectangle = defined(destination.west); - if (isRectangle) { - destination = this.getRectangleCameraCoordinates(destination, scratchFlyToDestination); + function parseCall(expression, ast) { + var args = ast.arguments; + var argsLength = args.length; + var call; + var val, left, right; + + // Member function calls + if (ast.callee.type === 'MemberExpression') { + call = ast.callee.property.name; + var object = ast.callee.object; + if (call === 'test' || call === 'exec') { + // Make sure this is called on a valid type + if (object.callee.name !== 'regExp') { + throw new RuntimeError(call + ' is not a function.'); + } + if (argsLength === 0) { + if (call === 'test') { + return new Node(ExpressionNodeType.LITERAL_BOOLEAN, false); + } + return new Node(ExpressionNodeType.LITERAL_NULL, null); + } + left = createRuntimeAst(expression, object); + right = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.FUNCTION_CALL, call, left, right); + } else if (call === 'toString') { + val = createRuntimeAst(expression, object); + return new Node(ExpressionNodeType.FUNCTION_CALL, call, val); + } + + throw new RuntimeError('Unexpected function call "' + call + '".'); } - var that = this; - var flightTween; + // Non-member function calls + call = ast.callee.name; + if (call === 'color') { + if (argsLength === 0) { + return new Node(ExpressionNodeType.LITERAL_COLOR, call); + } + val = createRuntimeAst(expression, args[0]); + if (defined(args[1])) { + var alpha = createRuntimeAst(expression, args[1]); + return new Node(ExpressionNodeType.LITERAL_COLOR, call, [val, alpha]); + } + return new Node(ExpressionNodeType.LITERAL_COLOR, call, [val]); + } else if (call === 'rgb' || call === 'hsl') { + if (argsLength < 3) { + throw new RuntimeError(call + ' requires three arguments.'); + } + val = [ + createRuntimeAst(expression, args[0]), + createRuntimeAst(expression, args[1]), + createRuntimeAst(expression, args[2]) + ]; + return new Node(ExpressionNodeType.LITERAL_COLOR, call, val); + } else if (call === 'rgba' || call === 'hsla') { + if (argsLength < 4) { + throw new RuntimeError(call + ' requires four arguments.'); + } + val = [ + createRuntimeAst(expression, args[0]), + createRuntimeAst(expression, args[1]), + createRuntimeAst(expression, args[2]), + createRuntimeAst(expression, args[3]) + ]; + return new Node(ExpressionNodeType.LITERAL_COLOR, call, val); + } else if (call === 'vec2' || call === 'vec3' || call === 'vec4') { + // Check for invalid constructors at evaluation time + val = new Array(argsLength); + for (var i = 0; i < argsLength; ++i) { + val[i] = createRuntimeAst(expression, args[i]); + } + return new Node(ExpressionNodeType.LITERAL_VECTOR, call, val); + } else if (call === 'isNaN' || call === 'isFinite') { + if (argsLength === 0) { + if (call === 'isNaN') { + return new Node(ExpressionNodeType.LITERAL_BOOLEAN, true); + } + return new Node(ExpressionNodeType.LITERAL_BOOLEAN, false); + } + val = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.UNARY, call, val); + } else if (call === 'isExactClass' || call === 'isClass') { + if (argsLength < 1 || argsLength > 1) { + throw new RuntimeError(call + ' requires exactly one argument.'); + } + val = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.UNARY, call, val); + } else if (call === 'getExactClassName') { + if (argsLength > 0) { + throw new RuntimeError(call + ' does not take any argument.'); + } + return new Node(ExpressionNodeType.UNARY, call); + } else if (defined(unaryFunctions[call])) { + if (argsLength !== 1) { + throw new RuntimeError(call + ' requires exactly one argument.'); + } + val = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.UNARY, call, val); + } else if (defined(binaryFunctions[call])) { + if (argsLength !== 2) { + throw new RuntimeError(call + ' requires exactly two arguments.'); + } + left = createRuntimeAst(expression, args[0]); + right = createRuntimeAst(expression, args[1]); + return new Node(ExpressionNodeType.BINARY, call, left, right); + } else if (defined(ternaryFunctions[call])) { + if (argsLength !== 3) { + throw new RuntimeError(call + ' requires exactly three arguments.'); + } + left = createRuntimeAst(expression, args[0]); + right = createRuntimeAst(expression, args[1]); + var test = createRuntimeAst(expression, args[2]); + return new Node(ExpressionNodeType.TERNARY, call, left, right, test); + } else if (call === 'Boolean') { + if (argsLength === 0) { + return new Node(ExpressionNodeType.LITERAL_BOOLEAN, false); + } + val = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.UNARY, call, val); + } else if (call === 'Number') { + if (argsLength === 0) { + return new Node(ExpressionNodeType.LITERAL_NUMBER, 0); + } + val = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.UNARY, call, val); + } else if (call === 'String') { + if (argsLength === 0) { + return new Node(ExpressionNodeType.LITERAL_STRING, ''); + } + val = createRuntimeAst(expression, args[0]); + return new Node(ExpressionNodeType.UNARY, call, val); + } else if (call === 'regExp') { + return parseRegex(expression, ast); + } + + throw new RuntimeError('Unexpected function call "' + call + '".'); + } + + function parseRegex(expression, ast) { + var args = ast.arguments; + // no arguments, return default regex + if (args.length === 0) { + return new Node(ExpressionNodeType.LITERAL_REGEX, new RegExp()); + } + + var pattern = createRuntimeAst(expression, args[0]); + var exp; + + // optional flag argument supplied + if (args.length > 1) { + var flags = createRuntimeAst(expression, args[1]); + if (isLiteralType(pattern) && isLiteralType(flags)) { + try { + exp = new RegExp(replaceBackslashes(String(pattern._value)), flags._value); + } catch (e) { + throw new RuntimeError(e); + } + return new Node(ExpressionNodeType.LITERAL_REGEX, exp); + } + return new Node(ExpressionNodeType.REGEX, pattern, flags); + } - newOptions.destination = destination; - newOptions.heading = orientation.heading; - newOptions.pitch = orientation.pitch; - newOptions.roll = orientation.roll; - newOptions.duration = options.duration; - newOptions.complete = function () { - if(flightTween === that._currentFlight){ - that._currentFlight = undefined; + // only pattern argument supplied + if (isLiteralType(pattern)) { + try { + exp = new RegExp(replaceBackslashes(String(pattern._value))); + } catch (e) { + throw new RuntimeError(e); } - if (defined(options.complete)) { - options.complete(); + return new Node(ExpressionNodeType.LITERAL_REGEX, exp); + } + return new Node(ExpressionNodeType.REGEX, pattern); + } + + function parseKeywordsAndVariables(ast) { + if (isVariable(ast.name)) { + var name = getPropertyName(ast.name); + if (name.substr(0, 8) === 'tiles3d_') { + return new Node(ExpressionNodeType.BUILTIN_VARIABLE, name); } - }; - newOptions.cancel = options.cancel; - newOptions.endTransform = options.endTransform; - newOptions.convert = isRectangle ? false : options.convert; - newOptions.maximumHeight = options.maximumHeight; - newOptions.pitchAdjustHeight = options.pitchAdjustHeight; - newOptions.flyOverLongitude = options.flyOverLongitude; - newOptions.flyOverLongitudeWeight = options.flyOverLongitudeWeight; - newOptions.easingFunction = options.easingFunction; + return new Node(ExpressionNodeType.VARIABLE, name); + } else if (ast.name === 'NaN') { + return new Node(ExpressionNodeType.LITERAL_NUMBER, NaN); + } else if (ast.name === 'Infinity') { + return new Node(ExpressionNodeType.LITERAL_NUMBER, Infinity); + } else if (ast.name === 'undefined') { + return new Node(ExpressionNodeType.LITERAL_UNDEFINED, undefined); + } - var scene = this._scene; - flightTween = scene.tweens.add(CameraFlightPath.createTween(scene, newOptions)); - this._currentFlight = flightTween; - }; + throw new RuntimeError(ast.name + ' is not defined.'); + } - function distanceToBoundingSphere3D(camera, radius) { - var frustum = camera.frustum; - var tanPhi = Math.tan(frustum.fovy * 0.5); - var tanTheta = frustum.aspectRatio * tanPhi; - return Math.max(radius / tanTheta, radius / tanPhi); + function parseMathConstant(ast) { + var name = ast.property.name; + if (name === 'PI') { + return new Node(ExpressionNodeType.LITERAL_NUMBER, Math.PI); + } else if (name === 'E') { + return new Node(ExpressionNodeType.LITERAL_NUMBER, Math.E); + } } - function distanceToBoundingSphere2D(camera, radius) { - var frustum = camera.frustum; - if (defined(frustum._offCenterFrustum)) { - frustum = frustum._offCenterFrustum; + function parseMemberExpression(expression, ast) { + if (ast.object.name === 'Math') { + return parseMathConstant(ast); } - var right, top; - var ratio = frustum.right / frustum.top; - var heightRatio = radius * ratio; - if (radius > heightRatio) { - right = radius; - top = right / ratio; - } else { - top = radius; - right = heightRatio; + var val; + var obj = createRuntimeAst(expression, ast.object); + if (ast.computed) { + val = createRuntimeAst(expression, ast.property); + return new Node(ExpressionNodeType.MEMBER, 'brackets', obj, val); } - return Math.max(right, top) * 1.50; + val = new Node(ExpressionNodeType.LITERAL_STRING, ast.property.name); + return new Node(ExpressionNodeType.MEMBER, 'dot', obj, val); } - var MINIMUM_ZOOM = 100.0; + function isLiteralType(node) { + return (node._type >= ExpressionNodeType.LITERAL_NULL); + } - function adjustBoundingSphereOffset(camera, boundingSphere, offset) { - if (!defined(offset)) { - offset = HeadingPitchRange.clone(Camera.DEFAULT_OFFSET); - } + function isVariable(name) { + return (name.substr(0, 4) === 'czm_'); + } - var range = offset.range; - if (!defined(range) || range === 0.0) { - var radius = boundingSphere.radius; - if (radius === 0.0) { - offset.range = MINIMUM_ZOOM; - } else if (camera.frustum instanceof OrthographicFrustum || camera._mode === SceneMode.SCENE2D) { - offset.range = distanceToBoundingSphere2D(camera, radius); + function getPropertyName(variable) { + return variable.substr(4); + } + + function createRuntimeAst(expression, ast) { + var node; + var op; + var left; + var right; + + if (ast.type === 'Literal') { + node = parseLiteral(ast); + } else if (ast.type === 'CallExpression') { + node = parseCall(expression, ast); + } else if (ast.type === 'Identifier') { + node = parseKeywordsAndVariables(ast); + } else if (ast.type === 'UnaryExpression') { + op = ast.operator; + var child = createRuntimeAst(expression, ast.argument); + if (unaryOperators.indexOf(op) > -1) { + node = new Node(ExpressionNodeType.UNARY, op, child); } else { - offset.range = distanceToBoundingSphere3D(camera, radius); + throw new RuntimeError('Unexpected operator "' + op + '".'); + } + } else if (ast.type === 'BinaryExpression') { + op = ast.operator; + left = createRuntimeAst(expression, ast.left); + right = createRuntimeAst(expression, ast.right); + if (binaryOperators.indexOf(op) > -1) { + node = new Node(ExpressionNodeType.BINARY, op, left, right); + } else { + throw new RuntimeError('Unexpected operator "' + op + '".'); + } + } else if (ast.type === 'LogicalExpression') { + op = ast.operator; + left = createRuntimeAst(expression, ast.left); + right = createRuntimeAst(expression, ast.right); + if (binaryOperators.indexOf(op) > -1) { + node = new Node(ExpressionNodeType.BINARY, op, left, right); + } + } else if (ast.type === 'ConditionalExpression') { + var test = createRuntimeAst(expression, ast.test); + left = createRuntimeAst(expression, ast.consequent); + right = createRuntimeAst(expression, ast.alternate); + node = new Node(ExpressionNodeType.CONDITIONAL, '?', left, right, test); + } else if (ast.type === 'MemberExpression') { + node = parseMemberExpression(expression, ast); + } else if (ast.type === 'ArrayExpression') { + var val = []; + for (var i = 0; i < ast.elements.length; i++) { + val[i] = createRuntimeAst(expression, ast.elements[i]); + } + node = new Node(ExpressionNodeType.ARRAY, val); + } else if (ast.type === 'Compound') { + // empty expression or multiple expressions + throw new RuntimeError('Provide exactly one expression.'); + } else { + throw new RuntimeError('Cannot parse expression.'); + } + + return node; + } + + function setEvaluateFunction(node) { + if (node._type === ExpressionNodeType.CONDITIONAL) { + node.evaluate = node._evaluateConditional; + } else if (node._type === ExpressionNodeType.FUNCTION_CALL) { + if (node._value === 'test') { + node.evaluate = node._evaluateRegExpTest; + } else if (node._value === 'exec') { + node.evaluate = node._evaluateRegExpExec; + } else if (node._value === 'toString') { + node.evaluate = node._evaluateToString; + } + } else if (node._type === ExpressionNodeType.UNARY) { + if (node._value === '!') { + node.evaluate = node._evaluateNot; + } else if (node._value === '-') { + node.evaluate = node._evaluateNegative; + } else if (node._value === '+') { + node.evaluate = node._evaluatePositive; + } else if (node._value === 'isNaN') { + node.evaluate = node._evaluateNaN; + } else if (node._value === 'isFinite') { + node.evaluate = node._evaluateIsFinite; + } else if (node._value === 'isExactClass') { + node.evaluate = node._evaluateIsExactClass; + } else if (node._value === 'isClass') { + node.evaluate = node._evaluateIsClass; + } else if (node._value === 'getExactClassName') { + node.evaluate = node._evaluategetExactClassName; + } else if (node._value === 'Boolean') { + node.evaluate = node._evaluateBooleanConversion; + } else if (node._value === 'Number') { + node.evaluate = node._evaluateNumberConversion; + } else if (node._value === 'String') { + node.evaluate = node._evaluateStringConversion; + } else if (defined(unaryFunctions[node._value])) { + node.evaluate = getEvaluateUnaryFunction(node._value); + } + } else if (node._type === ExpressionNodeType.BINARY) { + if (node._value === '+') { + node.evaluate = node._evaluatePlus; + } else if (node._value === '-') { + node.evaluate = node._evaluateMinus; + } else if (node._value === '*') { + node.evaluate = node._evaluateTimes; + } else if (node._value === '/') { + node.evaluate = node._evaluateDivide; + } else if (node._value === '%') { + node.evaluate = node._evaluateMod; + } else if (node._value === '===') { + node.evaluate = node._evaluateEqualsStrict; + } else if (node._value === '!==') { + node.evaluate = node._evaluateNotEqualsStrict; + } else if (node._value === '<') { + node.evaluate = node._evaluateLessThan; + } else if (node._value === '<=') { + node.evaluate = node._evaluateLessThanOrEquals; + } else if (node._value === '>') { + node.evaluate = node._evaluateGreaterThan; + } else if (node._value === '>=') { + node.evaluate = node._evaluateGreaterThanOrEquals; + } else if (node._value === '&&') { + node.evaluate = node._evaluateAnd; + } else if (node._value === '||') { + node.evaluate = node._evaluateOr; + } else if (node._value === '=~') { + node.evaluate = node._evaluateRegExpMatch; + } else if (node._value === '!~') { + node.evaluate = node._evaluateRegExpNotMatch; + } else if (defined(binaryFunctions[node._value])) { + node.evaluate = getEvaluateBinaryFunction(node._value); + } + } else if (node._type === ExpressionNodeType.TERNARY) { + node.evaluate = getEvaluateTernaryFunction(node._value); + } else if (node._type === ExpressionNodeType.MEMBER) { + if (node._value === 'brackets') { + node.evaluate = node._evaluateMemberBrackets; + } else { + node.evaluate = node._evaluateMemberDot; + } + } else if (node._type === ExpressionNodeType.ARRAY) { + node.evaluate = node._evaluateArray; + } else if (node._type === ExpressionNodeType.VARIABLE) { + node.evaluate = node._evaluateVariable; + } else if (node._type === ExpressionNodeType.VARIABLE_IN_STRING) { + node.evaluate = node._evaluateVariableString; + } else if (node._type === ExpressionNodeType.LITERAL_COLOR) { + node.evaluate = node._evaluateLiteralColor; + } else if (node._type === ExpressionNodeType.LITERAL_VECTOR) { + node.evaluate = node._evaluateLiteralVector; + } else if (node._type === ExpressionNodeType.LITERAL_STRING) { + node.evaluate = node._evaluateLiteralString; + } else if (node._type === ExpressionNodeType.REGEX) { + node.evaluate = node._evaluateRegExp; + } else if (node._type === ExpressionNodeType.BUILTIN_VARIABLE) { + if (node._value === 'tiles3d_tileset_time') { + node.evaluate = evaluateTilesetTime; } + } else { + node.evaluate = node._evaluateLiteral; } + } - return offset; + function evaluateTilesetTime(frameState, feature) { + return feature.content.tileset.timeSinceLoad; } - /** - * Sets the camera so that the current view contains the provided bounding sphere. - * - *

    The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere. - * The heading and the pitch angles are defined in the local east-north-up reference frame. - * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch - * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. If the range is - * zero, a range will be computed such that the whole bounding sphere is visible.

    - * - *

    In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the - * target will be the range. The heading will be determined from the offset. If the heading cannot be - * determined from the offset, the heading will be north.

    - * - * @param {BoundingSphere} boundingSphere The bounding sphere to view, in world coordinates. - * @param {HeadingPitchRange} [offset] The offset from the target in the local east-north-up reference frame centered at the target. - * - * @exception {DeveloperError} viewBoundingSphere is not supported while morphing. - */ - Camera.prototype.viewBoundingSphere = function(boundingSphere, offset) { - - offset = adjustBoundingSphereOffset(this, boundingSphere, offset); - this.lookAt(boundingSphere.center, offset); - }; + function getEvaluateUnaryFunction(call) { + var evaluate = unaryFunctions[call]; + return function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + return evaluate(call, left); + }; + } - var scratchflyToBoundingSphereTransform = new Matrix4(); - var scratchflyToBoundingSphereDestination = new Cartesian3(); - var scratchflyToBoundingSphereDirection = new Cartesian3(); - var scratchflyToBoundingSphereUp = new Cartesian3(); - var scratchflyToBoundingSphereRight = new Cartesian3(); - var scratchFlyToBoundingSphereCart4 = new Cartesian4(); - var scratchFlyToBoundingSphereQuaternion = new Quaternion(); - var scratchFlyToBoundingSphereMatrix3 = new Matrix3(); + function getEvaluateBinaryFunction(call) { + var evaluate = binaryFunctions[call]; + return function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + return evaluate(call, left, right); + }; + } - /** - * Flies the camera to a location where the current view contains the provided bounding sphere. - * - *

    The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere. - * The heading and the pitch angles are defined in the local east-north-up reference frame. - * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch - * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. If the range is - * zero, a range will be computed such that the whole bounding sphere is visible.

    - * - *

    In 2D and Columbus View, there must be a top down view. The camera will be placed above the target looking down. The height above the - * target will be the range. The heading will be aligned to local north.

    - * - * @param {BoundingSphere} boundingSphere The bounding sphere to view, in world coordinates. - * @param {Object} [options] Object with the following properties: - * @param {Number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight. - * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target. - * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete. - * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled. - * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed. - * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight. - * @param {Number} [options.pitchAdjustHeight] If camera flyes higher than that value, adjust pitch duiring the flight to look down, and keep Earth in viewport. - * @param {Number} [options.flyOverLongitude] There are always two ways between 2 points on globe. This option force camera to choose fight direction to fly over that longitude. - * @param {Number} [options.flyOverLongitudeWeight] Fly over the lon specifyed via flyOverLongitude only if that way is not longer than short way times flyOverLongitudeWeight. - * @param {EasingFunction|EasingFunction~Callback} [options.easingFunction] Controls how the time is interpolated over the duration of the flight. - */ - Camera.prototype.flyToBoundingSphere = function(boundingSphere, options) { - - options = defaultValue(options, defaultValue.EMPTY_OBJECT); + function getEvaluateTernaryFunction(call) { + var evaluate = ternaryFunctions[call]; + return function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + var test = this._test.evaluate(frameState, feature); + return evaluate(call, left, right, test); + }; + } - var scene2D = this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW; - this._setTransform(Matrix4.IDENTITY); - var offset = adjustBoundingSphereOffset(this, boundingSphere, options.offset); + Node.prototype._evaluateLiteral = function(frameState, feature) { + return this._value; + }; - var position; - if (scene2D) { - position = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Z, offset.range, scratchflyToBoundingSphereDestination); - } else { - position = offsetFromHeadingPitchRange(offset.heading, offset.pitch, offset.range); + Node.prototype._evaluateLiteralColor = function(frameState, feature) { + var color = scratchColor; + var args = this._left; + if (this._value === 'color') { + if (!defined(args)) { + Color.fromBytes(255, 255, 255, 255, color); + } else if (args.length > 1) { + Color.fromCssColorString(args[0].evaluate(frameState, feature), color); + color.alpha = args[1].evaluate(frameState, feature); + } else { + Color.fromCssColorString(args[0].evaluate(frameState, feature), color); + } + } else if (this._value === 'rgb') { + Color.fromBytes( + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), + 255, color); + } else if (this._value === 'rgba') { + // convert between css alpha (0 to 1) and cesium alpha (0 to 255) + var a = args[3].evaluate(frameState, feature) * 255; + Color.fromBytes( + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), + a, color); + } else if (this._value === 'hsl') { + Color.fromHsl( + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), + 1.0, color); + } else if (this._value === 'hsla') { + Color.fromHsl( + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), + args[3].evaluate(frameState, feature), + color); + } + return Cartesian4.fromColor(color, scratchStorage.getCartesian4()); + }; + + Node.prototype._evaluateLiteralVector = function(frameState, feature) { + // Gather the components that make up the vector, which includes components from interior vectors. + // For example vec3(1, 2, 3) or vec3(vec2(1, 2), 3) are both valid. + // + // If the number of components does not equal the vector's size, then a RuntimeError is thrown - with two exceptions: + // 1. A vector may be constructed from a larger vector and drop the extra components. + // 2. A vector may be constructed from a single component - vec3(1) will become vec3(1, 1, 1). + // + // Examples of invalid constructors include: + // vec4(1, 2) // not enough components + // vec3(vec2(1, 2)) // not enough components + // vec3(1, 2, 3, 4) // too many components + // vec2(vec4(1), 1) // too many components + + var components = scratchStorage.getArray(); + var call = this._value; + var args = this._left; + var argsLength = args.length; + for (var i = 0; i < argsLength; ++i) { + var value = args[i].evaluate(frameState, feature); + if (typeof value === 'number') { + components.push(value); + } else if (value instanceof Cartesian2) { + components.push(value.x, value.y); + } else if (value instanceof Cartesian3) { + components.push(value.x, value.y, value.z); + } else if (value instanceof Cartesian4) { + components.push(value.x, value.y, value.z, value.w); + } else { + throw new RuntimeError(call + ' argument must be a vector or number. Argument is ' + value + '.'); + } } - var transform = Transforms.eastNorthUpToFixedFrame(boundingSphere.center, Ellipsoid.WGS84, scratchflyToBoundingSphereTransform); - Matrix4.multiplyByPoint(transform, position, position); + var componentsLength = components.length; + var vectorLength = parseInt(call.charAt(3)); - var direction; - var up; + if (componentsLength === 0) { + throw new RuntimeError('Invalid ' + call + ' constructor. No valid arguments.'); + } else if ((componentsLength < vectorLength) && (componentsLength > 1)) { + throw new RuntimeError('Invalid ' + call + ' constructor. Not enough arguments.'); + } else if ((componentsLength > vectorLength) && (argsLength > 1)) { + throw new RuntimeError('Invalid ' + call + ' constructor. Too many arguments.'); + } - if (!scene2D) { - direction = Cartesian3.subtract(boundingSphere.center, position, scratchflyToBoundingSphereDirection); - Cartesian3.normalize(direction, direction); + if (componentsLength === 1) { + // Add the same component 3 more times + var component = components[0]; + components.push(component, component, component); + } - up = Matrix4.multiplyByPointAsVector(transform, Cartesian3.UNIT_Z, scratchflyToBoundingSphereUp); - if (1.0 - Math.abs(Cartesian3.dot(direction, up)) < CesiumMath.EPSILON6) { - var rotateQuat = Quaternion.fromAxisAngle(direction, offset.heading, scratchFlyToBoundingSphereQuaternion); - var rotation = Matrix3.fromQuaternion(rotateQuat, scratchFlyToBoundingSphereMatrix3); + if (call === 'vec2') { + return Cartesian2.fromArray(components, 0, scratchStorage.getCartesian2()); + } else if (call === 'vec3') { + return Cartesian3.fromArray(components, 0, scratchStorage.getCartesian3()); + } else if (call === 'vec4') { + return Cartesian4.fromArray(components, 0, scratchStorage.getCartesian4()); + } + }; - Cartesian3.fromCartesian4(Matrix4.getColumn(transform, 1, scratchFlyToBoundingSphereCart4), up); - Matrix3.multiplyByVector(rotation, up, up); - } + Node.prototype._evaluateLiteralString = function(frameState, feature) { + return this._value; + }; - var right = Cartesian3.cross(direction, up, scratchflyToBoundingSphereRight); - Cartesian3.cross(right, direction, up); - Cartesian3.normalize(up, up); + Node.prototype._evaluateVariableString = function(frameState, feature) { + var result = this._value; + var match = variableRegex.exec(result); + while (match !== null) { + var placeholder = match[0]; + var variableName = match[1]; + var property = feature.getProperty(variableName); + if (!defined(property)) { + property = ''; + } + result = result.replace(placeholder, property); + match = variableRegex.exec(result); } - - this.flyTo({ - destination : position, - orientation : { - direction : direction, - up : up - }, - duration : options.duration, - complete : options.complete, - cancel : options.cancel, - endTransform : options.endTransform, - maximumHeight : options.maximumHeight, - easingFunction : options.easingFunction, - flyOverLongitude : options.flyOverLongitude, - flyOverLongitudeWeight : options.flyOverLongitudeWeight, - pitchAdjustHeight : options.pitchAdjustHeight - }); + return result; }; - var scratchCartesian3_1 = new Cartesian3(); - var scratchCartesian3_2 = new Cartesian3(); - var scratchCartesian3_3 = new Cartesian3(); - var scratchCartesian3_4 = new Cartesian3(); - var horizonPoints = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()]; + Node.prototype._evaluateVariable = function(frameState, feature) { + // evaluates to undefined if the property name is not defined for that feature + return feature.getProperty(this._value); + }; - function computeHorizonQuad(camera, ellipsoid) { - var radii = ellipsoid.radii; - var p = camera.positionWC; + function checkFeature (ast) { + return (ast._value === 'feature'); + } - // Find the corresponding position in the scaled space of the ellipsoid. - var q = Cartesian3.multiplyComponents(ellipsoid.oneOverRadii, p, scratchCartesian3_1); + // PERFORMANCE_IDEA: Determine if parent property needs to be computed before runtime + Node.prototype._evaluateMemberDot = function(frameState, feature) { + if (checkFeature(this._left)) { + return feature.getProperty(this._right.evaluate(frameState, feature)); + } + var property = this._left.evaluate(frameState, feature); + if (!defined(property)) { + return undefined; + } - var qMagnitude = Cartesian3.magnitude(q); - var qUnit = Cartesian3.normalize(q, scratchCartesian3_2); + var member = this._right.evaluate(frameState, feature); + if ((property instanceof Cartesian2) || (property instanceof Cartesian3) || (property instanceof Cartesian4)) { + // Vector components may be accessed with .r, .g, .b, .a and implicitly with .x, .y, .z, .w + if (member === 'r') { + return property.x; + } else if (member === 'g') { + return property.y; + } else if (member === 'b') { + return property.z; + } else if (member === 'a') { + return property.w; + } + } + return property[member]; + }; - // Determine the east and north directions at q. - var eUnit; - var nUnit; - if (Cartesian3.equalsEpsilon(qUnit, Cartesian3.UNIT_Z, CesiumMath.EPSILON10)) { - eUnit = new Cartesian3(0, 1, 0); - nUnit = new Cartesian3(0, 0, 1); - } else { - eUnit = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, qUnit, scratchCartesian3_3), scratchCartesian3_3); - nUnit = Cartesian3.normalize(Cartesian3.cross(qUnit, eUnit, scratchCartesian3_4), scratchCartesian3_4); + Node.prototype._evaluateMemberBrackets = function(frameState, feature) { + if (checkFeature(this._left)) { + return feature.getProperty(this._right.evaluate(frameState, feature)); + } + var property = this._left.evaluate(frameState, feature); + if (!defined(property)) { + return undefined; } - // Determine the radius of the 'limb' of the ellipsoid. - var wMagnitude = Math.sqrt(Cartesian3.magnitudeSquared(q) - 1.0); + var member = this._right.evaluate(frameState, feature); + if ((property instanceof Cartesian2) || (property instanceof Cartesian3) || (property instanceof Cartesian4)) { + // Vector components may be accessed with [0][1][2][3], ['r']['g']['b']['a'] and implicitly with ['x']['y']['z']['w'] + // For Cartesian2 and Cartesian3 out-of-range components will just return undefined + if (member === 0 || member === 'r') { + return property.x; + } else if (member === 1 || member === 'g') { + return property.y; + } else if (member === 2 || member === 'b') { + return property.z; + } else if (member === 3 || member === 'a') { + return property.w; + } + } + return property[member]; + }; - // Compute the center and offsets. - var center = Cartesian3.multiplyByScalar(qUnit, 1.0 / qMagnitude, scratchCartesian3_1); - var scalar = wMagnitude / qMagnitude; - var eastOffset = Cartesian3.multiplyByScalar(eUnit, scalar, scratchCartesian3_2); - var northOffset = Cartesian3.multiplyByScalar(nUnit, scalar, scratchCartesian3_3); + Node.prototype._evaluateArray = function(frameState, feature) { + var array = []; + for (var i = 0; i < this._value.length; i++) { + array[i] = this._value[i].evaluate(frameState, feature); + } + return array; + }; - // A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum. - var upperLeft = Cartesian3.add(center, northOffset, horizonPoints[0]); - Cartesian3.subtract(upperLeft, eastOffset, upperLeft); - Cartesian3.multiplyComponents(radii, upperLeft, upperLeft); + // PERFORMANCE_IDEA: Have "fast path" functions that deal only with specific types + // that we can assign if we know the types before runtime - var lowerLeft = Cartesian3.subtract(center, northOffset, horizonPoints[1]); - Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft); - Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft); + Node.prototype._evaluateNot = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + if (typeof left !== 'boolean') { + throw new RuntimeError('Operator "!" requires a boolean argument. Argument is ' + left + '.'); + } + return !left; + }; - var lowerRight = Cartesian3.subtract(center, northOffset, horizonPoints[2]); - Cartesian3.add(lowerRight, eastOffset, lowerRight); - Cartesian3.multiplyComponents(radii, lowerRight, lowerRight); + Node.prototype._evaluateNegative = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + if (left instanceof Cartesian2) { + return Cartesian2.negate(left, scratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3) { + return Cartesian3.negate(left, scratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4) { + return Cartesian4.negate(left, scratchStorage.getCartesian4()); + } else if (typeof left === 'number') { + return -left; + } - var upperRight = Cartesian3.add(center, northOffset, horizonPoints[3]); - Cartesian3.add(upperRight, eastOffset, upperRight); - Cartesian3.multiplyComponents(radii, upperRight, upperRight); + throw new RuntimeError('Operator "-" requires a vector or number argument. Argument is ' + left + '.'); + }; - return horizonPoints; - } + Node.prototype._evaluatePositive = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); - var scratchPickCartesian2 = new Cartesian2(); - var scratchRectCartesian = new Cartesian3(); - var cartoArray = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; - function addToResult(x, y, index, camera, ellipsoid, computedHorizonQuad) { - scratchPickCartesian2.x = x; - scratchPickCartesian2.y = y; - var r = camera.pickEllipsoid(scratchPickCartesian2, ellipsoid, scratchRectCartesian); - if (defined(r)) { - cartoArray[index] = ellipsoid.cartesianToCartographic(r, cartoArray[index]); - return 1; - } - cartoArray[index] = ellipsoid.cartesianToCartographic(computedHorizonQuad[index], cartoArray[index]); - return 0; - } - /** - * Computes the approximate visible rectangle on the ellipsoid. - * - * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid that you want to know the visible region. - * @param {Rectangle} [result] The rectangle in which to store the result - * - * @returns {Rectangle|undefined} The visible rectangle or undefined if the ellipsoid isn't visible at all. - */ - Camera.prototype.computeViewRectangle = function(ellipsoid, result) { - ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); - var cullingVolume = this.frustum.computeCullingVolume(this.positionWC, this.directionWC, this.upWC); - var boundingSphere = new BoundingSphere(Cartesian3.ZERO, ellipsoid.maximumRadius); - var visibility = cullingVolume.computeVisibility(boundingSphere); - if (visibility === Intersect.OUTSIDE) { - return undefined; + if (!((left instanceof Cartesian2) || (left instanceof Cartesian3) || (left instanceof Cartesian4) || (typeof left === 'number'))) { + throw new RuntimeError('Operator "+" requires a vector or number argument. Argument is ' + left + '.'); } - var canvas = this._scene.canvas; - var width = canvas.clientWidth; - var height = canvas.clientHeight; + return left; + }; - var successfulPickCount = 0; + Node.prototype._evaluateLessThan = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); - var computedHorizonQuad = computeHorizonQuad(this, ellipsoid); + if ((typeof left !== 'number') || (typeof right !== 'number')) { + throw new RuntimeError('Operator "<" requires number arguments. Arguments are ' + left + ' and ' + right + '.'); + } - successfulPickCount += addToResult(0, 0, 0, this, ellipsoid, computedHorizonQuad); - successfulPickCount += addToResult(0, height, 1, this, ellipsoid, computedHorizonQuad); - successfulPickCount += addToResult(width, height, 2, this, ellipsoid, computedHorizonQuad); - successfulPickCount += addToResult(width, 0, 3, this, ellipsoid, computedHorizonQuad); + return left < right; + }; - if (successfulPickCount < 2) { - // If we have space non-globe in 3 or 4 corners then return the whole globe - return Rectangle.MAX_VALUE; + Node.prototype._evaluateLessThanOrEquals = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + + if ((typeof left !== 'number') || (typeof right !== 'number')) { + throw new RuntimeError('Operator "<=" requires number arguments. Arguments are ' + left + ' and ' + right + '.'); } - result = Rectangle.fromCartographicArray(cartoArray, result); + return left <= right; + }; - // Detect if we go over the poles - var distance = 0; - var lastLon = cartoArray[3].longitude; - for (var i = 0; i < 4; ++i) { - var lon = cartoArray[i].longitude; - var diff = Math.abs(lon - lastLon); - if (diff > CesiumMath.PI) { - // Crossed the dateline - distance += CesiumMath.TWO_PI - diff; - } else { - distance += diff; - } + Node.prototype._evaluateGreaterThan = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); - lastLon = lon; + if ((typeof left !== 'number') || (typeof right !== 'number')) { + throw new RuntimeError('Operator ">" requires number arguments. Arguments are ' + left + ' and ' + right + '.'); } - // We are over one of the poles so adjust the rectangle accordingly - if (CesiumMath.equalsEpsilon(Math.abs(distance), CesiumMath.TWO_PI, CesiumMath.EPSILON9)) { - result.west = -CesiumMath.PI; - result.east = CesiumMath.PI; - if (cartoArray[0].latitude >= 0.0) { - result.north = CesiumMath.PI_OVER_TWO; - } else { - result.south = -CesiumMath.PI_OVER_TWO; - } + return left > right; + }; + + Node.prototype._evaluateGreaterThanOrEquals = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + + if ((typeof left !== 'number') || (typeof right !== 'number')) { + throw new RuntimeError('Operator ">=" requires number arguments. Arguments are ' + left + ' and ' + right + '.'); } - return result; + return left >= right; }; - /** - * Switches the frustum/projection to perspective. - * - * This function is a no-op in 2D which must always be orthographic. - */ - Camera.prototype.switchToPerspectiveFrustum = function() { - if (this._mode === SceneMode.SCENE2D || this.frustum instanceof PerspectiveFrustum) { - return; + Node.prototype._evaluateOr = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + if (typeof left !== 'boolean') { + throw new RuntimeError('Operator "||" requires boolean arguments. First argument is ' + left + '.'); } - var scene = this._scene; - this.frustum = new PerspectiveFrustum(); - this.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; - this.frustum.fov = CesiumMath.toRadians(60.0); + // short circuit the expression + if (left) { + return true; + } + + var right = this._right.evaluate(frameState, feature); + if (typeof right !== 'boolean') { + throw new RuntimeError('Operator "||" requires boolean arguments. Second argument is ' + right + '.'); + } + + return left || right; }; - /** - * Switches the frustum/projection to orthographic. - * - * This function is a no-op in 2D which will always be orthographic. - */ - Camera.prototype.switchToOrthographicFrustum = function() { - if (this._mode === SceneMode.SCENE2D || this.frustum instanceof OrthographicFrustum) { - return; + Node.prototype._evaluateAnd = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + if (typeof left !== 'boolean') { + throw new RuntimeError('Operator "&&" requires boolean arguments. First argument is ' + left + '.'); } - var scene = this._scene; - this.frustum = new OrthographicFrustum(); - this.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + // short circuit the expression + if (!left) { + return false; + } - // It doesn't matter what we set this to. The adjust below will correct the width based on the camera position. - this.frustum.width = Cartesian3.magnitude(this.position); + var right = this._right.evaluate(frameState, feature); + if (typeof right !== 'boolean') { + throw new RuntimeError('Operator "&&" requires boolean arguments. Second argument is ' + right + '.'); + } + + return left && right; + }; + + Node.prototype._evaluatePlus = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.add(left, right, scratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.add(left, right, scratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.add(left, right, scratchStorage.getCartesian4()); + } else if ((typeof left === 'string') || (typeof right === 'string')) { + // If only one argument is a string the other argument calls its toString function. + return left + right; + } else if ((typeof left === 'number') && (typeof right === 'number')) { + return left + right; + } + + throw new RuntimeError('Operator "+" requires vector or number arguments of matching types, or at least one string argument. Arguments are ' + left + ' and ' + right + '.'); + }; + + Node.prototype._evaluateMinus = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.subtract(left, right, scratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.subtract(left, right, scratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.subtract(left, right, scratchStorage.getCartesian4()); + } else if ((typeof left === 'number') && (typeof right === 'number')) { + return left - right; + } + + throw new RuntimeError('Operator "-" requires vector or number arguments of matching types. Arguments are ' + left + ' and ' + right + '.'); + }; + + Node.prototype._evaluateTimes = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.multiplyComponents(left, right, scratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian2) && (typeof left === 'number')) { + return Cartesian2.multiplyByScalar(right, left, scratchStorage.getCartesian2()); + } else if ((left instanceof Cartesian2) && (typeof right === 'number')) { + return Cartesian2.multiplyByScalar(left, right, scratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.multiplyComponents(left, right, scratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian3) && (typeof left === 'number')) { + return Cartesian3.multiplyByScalar(right, left, scratchStorage.getCartesian3()); + } else if ((left instanceof Cartesian3) && (typeof right === 'number')) { + return Cartesian3.multiplyByScalar(left, right, scratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.multiplyComponents(left, right, scratchStorage.getCartesian4()); + } else if ((right instanceof Cartesian4) && (typeof left === 'number')) { + return Cartesian4.multiplyByScalar(right, left, scratchStorage.getCartesian4()); + } else if ((left instanceof Cartesian4) && (typeof right === 'number')) { + return Cartesian4.multiplyByScalar(left, right, scratchStorage.getCartesian4()); + } else if ((typeof left === 'number') && (typeof right === 'number')) { + return left * right; + } + + throw new RuntimeError('Operator "*" requires vector or number arguments. If both arguments are vectors they must be matching types. Arguments are ' + left + ' and ' + right + '.'); + }; + + Node.prototype._evaluateDivide = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.divideComponents(left, right, scratchStorage.getCartesian2()); + } else if ((left instanceof Cartesian2) && (typeof right === 'number')) { + return Cartesian2.divideByScalar(left, right, scratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.divideComponents(left, right, scratchStorage.getCartesian3()); + } else if ((left instanceof Cartesian3) && (typeof right === 'number')) { + return Cartesian3.divideByScalar(left, right, scratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.divideComponents(left, right, scratchStorage.getCartesian4()); + } else if ((left instanceof Cartesian4) && (typeof right === 'number')) { + return Cartesian4.divideByScalar(left, right, scratchStorage.getCartesian4()); + } else if ((typeof left === 'number') && (typeof right === 'number')) { + return left / right; + } + + throw new RuntimeError('Operator "/" requires vector or number arguments of matching types, or a number as the second argument. Arguments are ' + left + ' and ' + right + '.'); + }; + + Node.prototype._evaluateMod = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.fromElements(left.x % right.x, left.y % right.y, scratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.fromElements(left.x % right.x, left.y % right.y, left.z % right.z, scratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.fromElements(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w, scratchStorage.getCartesian4()); + } else if ((typeof left === 'number') && (typeof right === 'number')) { + return left % right; + } + + throw new RuntimeError('Operator "%" requires vector or number arguments of matching types. Arguments are ' + left + ' and ' + right + '.'); + }; + + Node.prototype._evaluateEqualsStrict = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return left.equals(right); + } + return left === right; + }; + + Node.prototype._evaluateNotEqualsStrict = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + if ((right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return !left.equals(right); + } + return left !== right; + }; + + Node.prototype._evaluateConditional = function(frameState, feature) { + var test = this._test.evaluate(frameState, feature); - // Check the projection matrix. It will always be defined, but we need to force an off-center update. - var projectionMatrix = this.frustum.projectionMatrix; - if (defined(projectionMatrix)) { - this._adjustOrthographicFrustum(true); + if (typeof test !== 'boolean') { + throw new RuntimeError('Conditional argument of conditional expression must be a boolean. Argument is ' + test + '.'); } - }; - /** - * @private - */ - Camera.clone = function(camera, result) { - if (!defined(result)) { - result = new Camera(camera._scene); + if (test) { + return this._left.evaluate(frameState, feature); } + return this._right.evaluate(frameState, feature); + }; - Cartesian3.clone(camera.position, result.position); - Cartesian3.clone(camera.direction, result.direction); - Cartesian3.clone(camera.up, result.up); - Cartesian3.clone(camera.right, result.right); - Matrix4.clone(camera._transform, result.transform); - result._transformChanged = true; + Node.prototype._evaluateNaN = function(frameState, feature) { + return isNaN(this._left.evaluate(frameState, feature)); + }; - return result; + Node.prototype._evaluateIsFinite = function(frameState, feature) { + return isFinite(this._left.evaluate(frameState, feature)); }; - /** - * A function that will execute when a flight completes. - * @callback Camera~FlightCompleteCallback - */ + Node.prototype._evaluateIsExactClass = function(frameState, feature) { + return feature.isExactClass(this._left.evaluate(frameState, feature)); + }; - /** - * A function that will execute when a flight is cancelled. - * @callback Camera~FlightCancelledCallback - */ + Node.prototype._evaluateIsClass = function(frameState, feature) { + return feature.isClass(this._left.evaluate(frameState, feature)); + }; - return Camera; -}); + Node.prototype._evaluategetExactClassName = function(frameState, feature) { + return feature.getExactClassName(); + }; -/*global define*/ -define('Scene/CameraEventType',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + Node.prototype._evaluateBooleanConversion = function(frameState, feature) { + return Boolean(this._left.evaluate(frameState, feature)); + }; - /** - * Enumerates the available input for interacting with the camera. - * - * @exports CameraEventType - */ - var CameraEventType = { - /** - * A left mouse button press followed by moving the mouse and releasing the button. - * - * @type {Number} - * @constant - */ - LEFT_DRAG : 0, + Node.prototype._evaluateNumberConversion = function(frameState, feature) { + return Number(this._left.evaluate(frameState, feature)); + }; - /** - * A right mouse button press followed by moving the mouse and releasing the button. - * - * @type {Number} - * @constant - */ - RIGHT_DRAG : 1, + Node.prototype._evaluateStringConversion = function(frameState, feature) { + return String(this._left.evaluate(frameState, feature)); + }; - /** - * A middle mouse button press followed by moving the mouse and releasing the button. - * - * @type {Number} - * @constant - */ - MIDDLE_DRAG : 2, + Node.prototype._evaluateRegExp = function(frameState, feature) { + var pattern = this._value.evaluate(frameState, feature); + var flags = ''; - /** - * Scrolling the middle mouse button. - * - * @type {Number} - * @constant - */ - WHEEL : 3, + if (defined(this._left)) { + flags = this._left.evaluate(frameState, feature); + } - /** - * A two-finger touch on a touch surface. - * - * @type {Number} - * @constant - */ - PINCH : 4 + var exp; + try { + exp = new RegExp(pattern, flags); + } catch (e) { + throw new RuntimeError(e); + } + return exp; }; - return freezeObject(CameraEventType); -}); + Node.prototype._evaluateRegExpTest = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); -/*global define*/ -define('Scene/CameraEventAggregator',[ - '../Core/Cartesian2', - '../Core/defined', - '../Core/defineProperties', - '../Core/destroyObject', - '../Core/DeveloperError', - '../Core/KeyboardEventModifier', - '../Core/Math', - '../Core/ScreenSpaceEventHandler', - '../Core/ScreenSpaceEventType', - './CameraEventType' - ], function( - Cartesian2, - defined, - defineProperties, - destroyObject, - DeveloperError, - KeyboardEventModifier, - CesiumMath, - ScreenSpaceEventHandler, - ScreenSpaceEventType, - CameraEventType) { - 'use strict'; + if (!((left instanceof RegExp) && (typeof right === 'string'))) { + throw new RuntimeError('RegExp.test requires the first argument to be a RegExp and the second argument to be a string. Arguments are ' + left + ' and ' + right + '.'); + } - function getKey(type, modifier) { - var key = type; - if (defined(modifier)) { - key += '+' + modifier; + return left.test(right); + }; + + Node.prototype._evaluateRegExpMatch = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); + + if ((left instanceof RegExp) && (typeof right === 'string')) { + return left.test(right); + } else if ((right instanceof RegExp) && (typeof left === 'string')) { + return right.test(left); } - return key; - } - function clonePinchMovement(pinchMovement, result) { - Cartesian2.clone(pinchMovement.distance.startPosition, result.distance.startPosition); - Cartesian2.clone(pinchMovement.distance.endPosition, result.distance.endPosition); + throw new RuntimeError('Operator "=~" requires one RegExp argument and one string argument. Arguments are ' + left + ' and ' + right + '.'); + }; - Cartesian2.clone(pinchMovement.angleAndHeight.startPosition, result.angleAndHeight.startPosition); - Cartesian2.clone(pinchMovement.angleAndHeight.endPosition, result.angleAndHeight.endPosition); - } + Node.prototype._evaluateRegExpNotMatch = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); - function listenToPinch(aggregator, modifier, canvas) { - var key = getKey(CameraEventType.PINCH, modifier); + if ((left instanceof RegExp) && (typeof right === 'string')) { + return !(left.test(right)); + } else if ((right instanceof RegExp) && (typeof left === 'string')) { + return !(right.test(left)); + } - var update = aggregator._update; - var isDown = aggregator._isDown; - var eventStartPosition = aggregator._eventStartPosition; - var pressTime = aggregator._pressTime; - var releaseTime = aggregator._releaseTime; + throw new RuntimeError('Operator "!~" requires one RegExp argument and one string argument. Arguments are ' + left + ' and ' + right + '.'); + }; - update[key] = true; - isDown[key] = false; - eventStartPosition[key] = new Cartesian2(); + Node.prototype._evaluateRegExpExec = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + var right = this._right.evaluate(frameState, feature); - var movement = aggregator._movement[key]; - if (!defined(movement)) { - movement = aggregator._movement[key] = {}; + if (!((left instanceof RegExp) && (typeof right === 'string'))) { + throw new RuntimeError('RegExp.exec requires the first argument to be a RegExp and the second argument to be a string. Arguments are ' + left + ' and ' + right + '.'); } - movement.distance = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() - }; - movement.angleAndHeight = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() - }; - movement.prevAngle = 0.0; + var exec = left.exec(right); + if (!defined(exec)) { + return null; + } + return exec[1]; + }; - aggregator._eventHandler.setInputAction(function(event) { - aggregator._buttonsDown++; - isDown[key] = true; - pressTime[key] = new Date(); - // Compute center position and store as start point. - Cartesian2.lerp(event.position1, event.position2, 0.5, eventStartPosition[key]); - }, ScreenSpaceEventType.PINCH_START, modifier); + Node.prototype._evaluateToString = function(frameState, feature) { + var left = this._left.evaluate(frameState, feature); + if ((left instanceof RegExp) || (left instanceof Cartesian2) || (left instanceof Cartesian3) || (left instanceof Cartesian4)) { + return String(left); + } - aggregator._eventHandler.setInputAction(function() { - aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0); - isDown[key] = false; - releaseTime[key] = new Date(); - }, ScreenSpaceEventType.PINCH_END, modifier); + throw new RuntimeError('Unexpected function call "' + this._value + '".'); + }; - aggregator._eventHandler.setInputAction(function(mouseMovement) { - if (isDown[key]) { - // Aggregate several input events into a single animation frame. - if (!update[key]) { - Cartesian2.clone(mouseMovement.distance.endPosition, movement.distance.endPosition); - Cartesian2.clone(mouseMovement.angleAndHeight.endPosition, movement.angleAndHeight.endPosition); - } else { - clonePinchMovement(mouseMovement, movement); - update[key] = false; - movement.prevAngle = movement.angleAndHeight.startPosition.x; - } - // Make sure our aggregation of angles does not "flip" over 360 degrees. - var angle = movement.angleAndHeight.endPosition.x; - var prevAngle = movement.prevAngle; - var TwoPI = Math.PI * 2; - while (angle >= (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; + function convertHSLToRGB(ast) { + // Check if the color contains any nested expressions to see if the color can be converted here. + // E.g. "hsl(0.9, 0.6, 0.7)" is able to convert directly to rgb, "hsl(0.9, 0.6, ${Height})" is not. + var channels = ast._left; + var length = channels.length; + for (var i = 0; i < length; ++i) { + if (channels[i]._type !== ExpressionNodeType.LITERAL_NUMBER) { + return undefined; } - }, ScreenSpaceEventType.PINCH_MOVE, modifier); + } + var h = channels[0]._value; + var s = channels[1]._value; + var l = channels[2]._value; + var a = (length === 4) ? channels[3]._value : 1.0; + return Color.fromHsl(h, s, l, a, scratchColor); } - function listenToWheel(aggregator, modifier) { - var key = getKey(CameraEventType.WHEEL, modifier); + function convertRGBToColor(ast) { + // Check if the color contains any nested expressions to see if the color can be converted here. + // E.g. "rgb(255, 255, 255)" is able to convert directly to Color, "rgb(255, 255, ${Height})" is not. + var channels = ast._left; + var length = channels.length; + for (var i = 0; i < length; ++i) { + if (channels[i]._type !== ExpressionNodeType.LITERAL_NUMBER) { + return undefined; + } + } + var color = scratchColor; + color.red = channels[0]._value / 255.0; + color.green = channels[1]._value / 255.0; + color.blue = channels[2]._value / 255.0; + color.alpha = (length === 4) ? channels[3]._value : 1.0; + return color; + } - var update = aggregator._update; - update[key] = true; + function numberToString(number) { + if (number % 1 === 0) { + // Add a .0 to whole numbers + return number.toFixed(1); + } - var movement = aggregator._movement[key]; - if (!defined(movement)) { - movement = aggregator._movement[key] = {}; + return number.toString(); + } + + function colorToVec3(color) { + var r = numberToString(color.red); + var g = numberToString(color.green); + var b = numberToString(color.blue); + return 'vec3(' + r + ', ' + g + ', ' + b + ')'; + } + + function colorToVec4(color) { + var r = numberToString(color.red); + var g = numberToString(color.green); + var b = numberToString(color.blue); + var a = numberToString(color.alpha); + return 'vec4(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; + } + + function getExpressionArray(array, attributePrefix, shaderState, parent) { + var length = array.length; + var expressions = new Array(length); + for (var i = 0; i < length; ++i) { + expressions[i] = array[i].getShaderExpression(attributePrefix, shaderState, parent); } + return expressions; + } - movement.startPosition = new Cartesian2(); - movement.endPosition = new Cartesian2(); + Node.prototype.getShaderExpression = function(attributePrefix, shaderState, parent) { + var color; + var left; + var right; + var test; - 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; + var type = this._type; + var value = this._value; + + if (defined(this._left)) { + if (isArray(this._left)) { + // Left can be an array if the type is LITERAL_COLOR or LITERAL_VECTOR + left = getExpressionArray(this._left, attributePrefix, shaderState, this); } else { - Cartesian2.clone(Cartesian2.ZERO, movement.startPosition); - movement.endPosition.x = 0.0; - movement.endPosition.y = arcLength; - update[key] = false; + left = this._left.getShaderExpression(attributePrefix, shaderState, this); } - }, 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; + if (defined(this._right)) { + right = this._right.getShaderExpression(attributePrefix, shaderState, this); + } - isDown[key] = false; - eventStartPosition[key] = new Cartesian2(); + if (defined(this._test)) { + test = this._test.getShaderExpression(attributePrefix, shaderState, this); + } - var lastMovement = aggregator._lastMovement[key]; - if (!defined(lastMovement)) { - lastMovement = aggregator._lastMovement[key] = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2(), - valid : false - }; + if (isArray(this._value)) { + // For ARRAY type + value = getExpressionArray(this._value, attributePrefix, shaderState, this); } - 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; + switch (type) { + case ExpressionNodeType.VARIABLE: + return attributePrefix + value; + case ExpressionNodeType.UNARY: + // Supported types: +, -, !, Boolean, Number + if (value === 'Boolean') { + return 'bool(' + left + ')'; + } else if (value === 'Number') { + return 'float(' + left + ')'; + } else if (value === 'round') { + return 'floor(' + left + ' + 0.5)'; + } else if (defined(unaryFunctions[value])) { + return value + '(' + left + ')'; + } else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) { + throw new RuntimeError('Error generating style shader: "' + value + '" is not supported.'); + } else if (defined(unaryFunctions[value])) { + return value + '(' + left + ')'; + } + return value + left; + case ExpressionNodeType.BINARY: + // Supported types: ||, &&, ===, !==, <, >, <=, >=, +, -, *, /, % + if (value === '%') { + return 'mod(' + left + ', ' + right + ')'; + } else if (value === '===') { + return '(' + left + ' == ' + right + ')'; + } else if (value === '!==') { + return '(' + left + ' != ' + right + ')'; + } else if (value === 'atan2') { + return 'atan(' + left + ', ' + right + ')'; + } else if (defined(binaryFunctions[value])) { + return value + '(' + left + ', ' + right + ')'; + } + return '(' + left + ' ' + value + ' ' + right + ')'; + case ExpressionNodeType.TERNARY: + if (defined(ternaryFunctions[value])) { + return value + '(' + left + ', ' + right + ', ' + test + ')'; + } + break; + case ExpressionNodeType.CONDITIONAL: + return '(' + test + ' ? ' + left + ' : ' + right + ')'; + case ExpressionNodeType.MEMBER: + // This is intended for accessing the components of vector properties. String members aren't supported. + // Check for 0.0 rather than 0 because all numbers are previously converted to decimals. + if (right === 'r' || right === 'x' || right === '0.0') { + return left + '[0]'; + } else if (right === 'g' || right === 'y' || right === '1.0') { + return left + '[1]'; + } else if (right === 'b' || right === 'z' || right === '2.0') { + return left + '[2]'; + } else if (right === 'a' || right === 'w' || right === '3.0') { + return left + '[3]'; + } + return left + '[int(' + right + ')]'; + case ExpressionNodeType.FUNCTION_CALL: + throw new RuntimeError('Error generating style shader: "' + value + '" is not supported.'); + case ExpressionNodeType.ARRAY: + if (value.length === 4) { + return 'vec4(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', ' + value[3] + ')'; + } else if (value.length === 3) { + return 'vec3(' + value[0] + ', ' + value[1] + ', ' + value[2] + ')'; + } else if (value.length === 2) { + return 'vec2(' + value[0] + ', ' + value[1] + ')'; + } + throw new RuntimeError('Error generating style shader: Invalid array length. Array length should be 2, 3, or 4.'); + case ExpressionNodeType.REGEX: + throw new RuntimeError('Error generating style shader: Regular expressions are not supported.'); + case ExpressionNodeType.VARIABLE_IN_STRING: + throw new RuntimeError('Error generating style shader: Converting a variable to a string is not supported.'); + case ExpressionNodeType.LITERAL_NULL: + throw new RuntimeError('Error generating style shader: null is not supported.'); + case ExpressionNodeType.LITERAL_BOOLEAN: + return value ? 'true' : 'false'; + case ExpressionNodeType.LITERAL_NUMBER: + return numberToString(value); + case ExpressionNodeType.LITERAL_STRING: + if (defined(parent) && (parent._type === ExpressionNodeType.MEMBER)) { + if (value === 'r' || value === 'g' || value === 'b' || value === 'a' || + value === 'x' || value === 'y' || value === 'z' || value === 'w') { + return value; + } + } + // Check for css color strings + color = Color.fromCssColorString(value, scratchColor); + if (defined(color)) { + return colorToVec3(color); + } + throw new RuntimeError('Error generating style shader: String literals are not supported.'); + case ExpressionNodeType.LITERAL_COLOR: + var args = left; + if (value === 'color') { + if (!defined(args)) { + return 'vec4(1.0)'; + } else if (args.length > 1) { + var rgb = args[0]; + var alpha = args[1]; + if (alpha !== '1.0') { + shaderState.translucent = true; + } + return 'vec4(' + rgb + ', ' + alpha + ')'; + } + return 'vec4(' + args[0] + ', 1.0)'; + } else if (value === 'rgb') { + color = convertRGBToColor(this); + if (defined(color)) { + return colorToVec4(color); + } + return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, 1.0)'; + } else if (value === 'rgba') { + if (args[3] !== '1.0') { + shaderState.translucent = true; + } + color = convertRGBToColor(this); + if (defined(color)) { + return colorToVec4(color); + } + return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, ' + args[3] + ')'; + } else if (value === 'hsl') { + color = convertHSLToRGB(this); + if (defined(color)) { + return colorToVec4(color); + } + return 'vec4(czm_HSLToRGB(vec3(' + args[0] + ', ' + args[1] + ', ' + args[2] + ')), 1.0)'; + } else if (value === 'hsla') { + color = convertHSLToRGB(this); + if (defined(color)) { + if (color.alpha !== 1.0) { + shaderState.translucent = true; + } + return colorToVec4(color); + } + if (args[3] !== '1.0') { + shaderState.translucent = true; + } + return 'vec4(czm_HSLToRGB(vec3(' + args[0] + ', ' + args[1] + ', ' + args[2] + ')), ' + args[3] + ')'; + } + break; + case ExpressionNodeType.LITERAL_VECTOR: + var length = left.length; + var vectorExpression = value + '('; + for (var i = 0; i < length; ++i) { + vectorExpression += left[i]; + if (i < (length - 1)) { + vectorExpression += ', '; + } + } + vectorExpression += ')'; + return vectorExpression; + case ExpressionNodeType.LITERAL_REGEX: + throw new RuntimeError('Error generating style shader: Regular expressions are not supported.'); + case ExpressionNodeType.LITERAL_UNDEFINED: + throw new RuntimeError('Error generating style shader: undefined is not supported.'); + case ExpressionNodeType.BUILTIN_VARIABLE: + if (value === 'tiles3d_tileset_time') { + return 'u_tilesetTime'; + } } + }; - 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); + return Expression; +}); - aggregator._eventHandler.setInputAction(function() { - aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0); - isDown[key] = false; - releaseTime[key] = new Date(); - }, up, modifier); - } +define('Scene/ConditionsExpression',[ + '../Core/clone', + '../Core/defined', + '../Core/defineProperties', + './Expression' + ], function( + clone, + defined, + defineProperties, + Expression) { + 'use strict'; - function cloneMouseMovement(mouseMovement, result) { - Cartesian2.clone(mouseMovement.startPosition, result.startPosition); - Cartesian2.clone(mouseMovement.endPosition, result.endPosition); + /** + * An expression for a style applied to a {@link Cesium3DTileset}. + *

    + * Evaluates a conditions expression defined using the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. + *

    + *

    + * Implements the {@link StyleExpression} interface. + *

    + * + * @alias ConditionsExpression + * @constructor + * + * @param {Object} [conditionsExpression] The conditions expression defined using the 3D Tiles Styling language. + * @param {Object} [defines] Defines in the style. + * + * @example + * var expression = new Cesium.ConditionsExpression({ + * conditions : [ + * ['${Area} > 10, 'color("#FF0000")'], + * ['${id} !== "1"', 'color("#00FF00")'], + * ['true', 'color("#FFFFFF")'] + * ] + * }); + * expression.evaluateColor(frameState, feature, result); // returns a Cesium.Color object + */ + function ConditionsExpression(conditionsExpression, defines) { + this._conditionsExpression = clone(conditionsExpression, true); + this._conditions = conditionsExpression.conditions; + this._runtimeConditions = undefined; + + setRuntime(this, defines); } - function listenMouseMove(aggregator, modifier) { - var update = aggregator._update; - var movement = aggregator._movement; - var lastMovement = aggregator._lastMovement; - var isDown = aggregator._isDown; + defineProperties(ConditionsExpression.prototype, { + /** + * Gets the conditions expression defined in the 3D Tiles Styling language. + * + * @memberof ConditionsExpression.prototype + * + * @type {Object} + * @readonly + * + * @default undefined + */ + conditionsExpression : { + get : function() { + return this._conditionsExpression; + } + } + }); - for ( var typeName in CameraEventType) { - if (CameraEventType.hasOwnProperty(typeName)) { - var type = CameraEventType[typeName]; - if (defined(type)) { - var key = getKey(type, modifier); - update[key] = true; + function Statement(condition, expression) { + this.condition = condition; + this.expression = expression; + } - if (!defined(aggregator._lastMovement[key])) { - aggregator._lastMovement[key] = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2(), - valid : false - }; - } + function setRuntime(expression, defines) { + var runtimeConditions = []; + var conditions = expression._conditions; + if (!defined(conditions)) { + return; + } + var length = conditions.length; + for (var i = 0; i < length; ++i) { + var statement = conditions[i]; + var cond = String(statement[0]); + var condExpression = String(statement[1]); + runtimeConditions.push(new Statement( + new Expression(cond, defines), + new Expression(condExpression, defines) + )); + } + expression._runtimeConditions = runtimeConditions; + } - if (!defined(aggregator._movement[key])) { - aggregator._movement[key] = { - startPosition : new Cartesian2(), - endPosition : new Cartesian2() - }; - } - } + /** + * Evaluates the result of an expression, optionally using the provided feature's properties. If the result of + * the expression in the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + * is of type Boolean, Number, or String, the corresponding JavaScript + * primitive type will be returned. If the result is a RegExp, a Javascript RegExp + * object will be returned. If the result is a Cartesian2, Cartesian3, or Cartesian4, + * a {@link Cartesian2}, {@link Cartesian3}, or {@link Cartesian4} object will be returned. If the result argument is + * a {@link Color}, the {@link Cartesian4} value is converted to a {@link Color} and then returned. + * + * @param {FrameState} frameState The frame state. + * @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression. + * @param {Object} [result] The object onto which to store the result. + * @returns {Boolean|Number|String|RegExp|Cartesian2|Cartesian3|Cartesian4|Color} The result of evaluating the expression. + */ + ConditionsExpression.prototype.evaluate = function(frameState, feature, result) { + var conditions = this._runtimeConditions; + if (!defined(conditions)) { + return undefined; + } + var length = conditions.length; + for (var i = 0; i < length; ++i) { + var statement = conditions[i]; + if (statement.condition.evaluate(frameState, feature)) { + return statement.expression.evaluate(frameState, feature, result); } } + }; - 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; - } - } - } - } + /** + * Evaluates the result of a Color expression, using the values defined by a feature. + *

    + * This is equivalent to {@link ConditionsExpression#evaluate} but always returns a {@link Color} object. + *

    + * @param {FrameState} frameState The frame state. + * @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression. + * @param {Color} [result] The object in which to store the result + * @returns {Color} The modified result parameter or a new Color instance if one was not provided. + */ + ConditionsExpression.prototype.evaluateColor = function(frameState, feature, result) { + var conditions = this._runtimeConditions; + if (!defined(conditions)) { + return undefined; + } + var length = conditions.length; + for (var i = 0; i < length; ++i) { + var statement = conditions[i]; + if (statement.condition.evaluate(frameState, feature)) { + return statement.expression.evaluateColor(frameState, feature, result); } + } + }; - Cartesian2.clone(mouseMovement.endPosition, aggregator._currentMousePosition); - }, ScreenSpaceEventType.MOUSE_MOVE, modifier); - } + /** + * Gets the shader function for this expression. + * Returns undefined if the shader function can't be generated from this expression. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * @param {String} returnType The return type of the generated function. + * + * @returns {String} The shader function. + * + * @private + */ + ConditionsExpression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { + var conditions = this._runtimeConditions; + if (!defined(conditions) || conditions.length === 0) { + return undefined; + } + + var shaderFunction = ''; + var length = conditions.length; + for (var i = 0; i < length; ++i) { + var statement = conditions[i]; + + var condition = statement.condition.getShaderExpression(attributePrefix, shaderState); + var expression = statement.expression.getShaderExpression(attributePrefix, shaderState); + + // Build the if/else chain from the list of conditions + shaderFunction += + ' ' + ((i === 0) ? 'if' : 'else if') + ' (' + condition + ') \n' + + ' { \n' + + ' return ' + expression + '; \n' + + ' } \n'; + } + + shaderFunction = returnType + ' ' + functionName + '() \n' + + '{ \n' + + shaderFunction + + ' return ' + returnType + '(1.0); \n' + // Return a default value if no conditions are met + '} \n'; + + return shaderFunction; + }; + + return ConditionsExpression; +}); + +define('Scene/Cesium3DTileStyle',[ + '../Core/clone', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/loadJson', + '../Core/RequestScheduler', + '../ThirdParty/when', + './ConditionsExpression', + './Expression' + ], function( + clone, + defaultValue, + defined, + defineProperties, + DeveloperError, + loadJson, + RequestScheduler, + when, + ConditionsExpression, + Expression) { + 'use strict'; /** - * 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. + * A style that is applied to a {@link Cesium3DTileset}. + *

    + * Evaluates an expression defined using the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. + *

    * - * @alias CameraEventAggregator + * @alias Cesium3DTileStyle * @constructor * - * @param {Canvas} [canvas=document] The element to handle events for. + * @param {String|Object} [style] The url of a style or an object defining a style. * - * @see ScreenSpaceEventHandler + * @example + * tileset.style = new Cesium.Cesium3DTileStyle({ + * color : { + * conditions : [ + * ['${Height} >= 100', 'color("purple", 0.5)'], + * ['${Height} >= 50', 'color("red")'], + * ['true', 'color("blue")'] + * ] + * }, + * show : '${Height} > 0', + * meta : { + * description : '"Building id ${id} has height ${Height}."' + * } + * }); + * + * @example + * tileset.style = new Cesium.Cesium3DTileStyle({ + * color : 'vec4(${Temperature})', + * pointSize : '${Temperature} * 2.0' + * }); + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} */ - function CameraEventAggregator(canvas) { - - this._eventHandler = new ScreenSpaceEventHandler(canvas, true); + function Cesium3DTileStyle(style) { + this._style = undefined; + this._ready = false; + this._color = undefined; + this._show = undefined; + this._pointSize = undefined; + this._meta = undefined; - this._update = {}; - this._movement = {}; - this._lastMovement = {}; - this._isDown = {}; - this._eventStartPosition = {}; - this._pressTime = {}; - this._releaseTime = {}; + this._colorShaderFunction = undefined; + this._showShaderFunction = undefined; + this._pointSizeShaderFunction = undefined; + this._colorShaderFunctionReady = false; + this._showShaderFunctionReady = false; + this._pointSizeShaderFunctionReady = false; - this._buttonsDown = 0; + var promise; + if (typeof style === 'string') { + promise = loadJson(style); + } else { + promise = when.resolve(style); + } - this._currentMousePosition = new Cartesian2(); + var that = this; + this._readyPromise = promise.then(function(styleJson) { + setup(that, styleJson); + return that; + }); + } - 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); + function setup(that, styleJson) { + that._style = clone(styleJson, true); - 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); + styleJson = defaultValue(styleJson, defaultValue.EMPTY_OBJECT); + + that.color = styleJson.color; + that.show = styleJson.show; + that.pointSize = styleJson.pointSize; + + var meta = {}; + if (defined(styleJson.meta)) { + var defines = styleJson.defines; + var metaJson = defaultValue(styleJson.meta, defaultValue.EMPTY_OBJECT); + for (var property in metaJson) { + if (metaJson.hasOwnProperty(property)) { + meta[property] = new Expression(metaJson[property], defines); } } } + + that._meta = meta; + + that._ready = true; } - defineProperties(CameraEventAggregator.prototype, { + defineProperties(Cesium3DTileStyle.prototype, { /** - * Gets the current mouse position. - * @memberof CameraEventAggregator.prototype - * @type {Cartesian2} + * Gets the object defining the style using the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. + * + * @memberof Cesium3DTileStyle.prototype + * + * @type {Object} + * @readonly + * + * @default undefined + * + * @exception {DeveloperError} The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true. */ - currentMousePosition : { + style : { get : function() { - return this._currentMousePosition; + + return this._style; } }, /** - * Gets whether any mouse button is down, a touch has started, or the wheel has been moved. - * @memberof CameraEventAggregator.prototype + * When true, the style is ready and its expressions can be evaluated. When + * a style is constructed with an object, as opposed to a url, this is true immediately. + * + * @memberof Cesium3DTileStyle.prototype + * * @type {Boolean} + * @readonly + * + * @default false */ - anyButtonDown : { + ready : { 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; + return this._ready; + } + }, + + /** + * Gets the promise that will be resolved when the the style is ready and its expressions can be evaluated. + * + * @memberof Cesium3DTileStyle.prototype + * + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise; + } + }, + + /** + * Gets or sets the {@link StyleExpression} object used to evaluate the style's show property. Alternatively a boolean, string, or object defining a show style can be used. + * The getter will return the internal {@link Expression} or {@link ConditionsExpression}, which may differ from the value provided to the setter. + *

    + * The expression must return or convert to a Boolean. + *

    + * + * @memberof Cesium3DTileStyle.prototype + * + * @type {StyleExpression} + * + * @exception {DeveloperError} The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true. + * + * @example + * var style = new Cesium3DTileStyle({ + * show : '(regExp("^Chest").test(${County})) && (${YearBuilt} >= 1970)' + * }); + * style.show.evaluate(frameState, feature); // returns true or false depending on the feature's properties + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a custom function + * style.show = { + * evaluate : function(frameState, feature) { + * return true; + * } + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a boolean + * style.show = true; + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a string + * style.show = '${Height} > 0'; + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override show expression with a condition + * style.show = { + * conditions: [ + * ['${height} > 2', 'false'], + * ['true', 'true'] + * ]; + * }; + */ + show : { + get : function() { + + return this._show; + }, + set : function(value) { + var defines = defaultValue(this._style, defaultValue.EMPTY_OBJECT).defines; + if (!defined(value)) { + this._show = undefined; + } else if (typeof value === 'boolean') { + this._show = new Expression(String(value)); + } else if (typeof value === 'string') { + this._show = new Expression(value, defines); + } else if (defined(value.conditions)) { + this._show = new ConditionsExpression(value, defines); + } else { + this._show = value; + } + this._showShaderFunctionReady = false; + } + }, + + /** + * Gets or sets the {@link StyleExpression} object used to evaluate the style's color property. Alternatively a string or object defining a color style can be used. + * The getter will return the internal {@link Expression} or {@link ConditionsExpression}, which may differ from the value provided to the setter. + *

    + * The expression must return a Color. + *

    + * + * @memberof Cesium3DTileStyle.prototype + * + * @type {StyleExpression} + * + * @exception {DeveloperError} The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true. + * + * @example + * var style = new Cesium3DTileStyle({ + * color : '(${Temperature} > 90) ? color("red") : color("white")' + * }); + * style.color.evaluateColor(frameState, feature, result); // returns a Cesium.Color object + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override color expression with a custom function + * style.color = { + * evaluateColor : function(frameState, feature, result) { + * return Cesium.Color.clone(Cesium.Color.WHITE, result); + * } + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override color expression with a string + * style.color = 'color("blue")'; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override color expression with a condition + * style.color = { + * conditions : [ + * ['${height} > 2', 'color("cyan")'], + * ['true', 'color("blue")'] + * ] + * }; + */ + color : { + get : function() { + + return this._color; + }, + set : function(value) { + var defines = defaultValue(this._style, defaultValue.EMPTY_OBJECT).defines; + if (!defined(value)) { + this._color = undefined; + } else if (typeof value === 'string') { + this._color = new Expression(value, defines); + } else if (defined(value.conditions)) { + this._color = new ConditionsExpression(value, defines); + } else { + this._color = value; + } + this._colorShaderFunctionReady = false; + } + }, + + /** + * Gets or sets the {@link StyleExpression} object used to evaluate the style's pointSize property. Alternatively a number, string, or object defining a pointSize style can be used. + * The getter will return the internal {@link Expression} or {@link ConditionsExpression}, which may differ from the value provided to the setter. + *

    + * The expression must return or convert to a Number. + *

    + * + * @memberof Cesium3DTileStyle.prototype + * + * @type {StyleExpression} + * + * @exception {DeveloperError} The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true. + * + * @example + * var style = new Cesium3DTileStyle({ + * pointSize : '(${Temperature} > 90) ? 2.0 : 1.0' + * }); + * style.pointSize.evaluate(frameState, feature); // returns a Number + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a custom function + * style.pointSize = { + * evaluate : function(frameState, feature) { + * return 1.0; + * } + * }; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a number + * style.pointSize = 1.0; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a string + * style.pointSize = '${height} / 10'; + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a condition + * style.pointSize = { + * conditions : [ + * ['${height} > 2', '1.0'], + * ['true', '2.0'] + * ] + * }; + */ + pointSize : { + get : function() { + + return this._pointSize; + }, + set : function(value) { + var defines = defaultValue(this._style, defaultValue.EMPTY_OBJECT).defines; + if (!defined(value)) { + this._pointSize = undefined; + } else if (typeof value === 'number') { + this._pointSize = new Expression(String(value)); + } else if (typeof value === 'string') { + this._pointSize = new Expression(value, defines); + } else if (defined(value.conditions)) { + this._pointSize = new ConditionsExpression(value, defines); + } else { + this._pointSize = value; + } + this._pointSizeShaderFunctionReady = false; + } + }, + + /** + * Gets or sets the object containing application-specific expression that can be explicitly + * evaluated, e.g., for display in a UI. + * + * @memberof Cesium3DTileStyle.prototype + * + * @type {StyleExpression} + * + * @exception {DeveloperError} The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true. + * + * @example + * var style = new Cesium3DTileStyle({ + * meta : { + * description : '"Building id ${id} has height ${Height}."' + * } + * }); + * style.meta.description.evaluate(frameState, feature); // returns a String with the substituted variables + */ + meta : { + get : function() { + + return this._meta; + }, + set : function(value) { + this._meta = value; } } }); /** - * Gets if a mouse button down or touch has started and has been moved. + * Gets the color shader function for this style. * - * @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) { - - var key = getKey(type, modifier); - return !this._update[key]; - }; - - /** - * Gets the aggregated start and end position of the current event. + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. * - * @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) { - - 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). + * @returns {String} The shader function. * - * @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. + * @private */ - CameraEventAggregator.prototype.getLastMovement = function(type, modifier) { - - var key = getKey(type, modifier); - var lastMovement = this._lastMovement[key]; - if (lastMovement.valid) { - return lastMovement; + Cesium3DTileStyle.prototype.getColorShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._colorShaderFunctionReady) { + // Return the cached result, may be undefined + return this._colorShaderFunction; } - return undefined; + this._colorShaderFunctionReady = true; + this._colorShaderFunction = defined(this.color) ? this.color.getShaderFunction(functionName, attributePrefix, shaderState, 'vec4') : undefined; + return this._colorShaderFunction; }; /** - * Gets whether the mouse button is down or a touch has started. + * Gets the show shader function for this style. * - * @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) { - - var key = getKey(type, modifier); - return this._isDown[key]; - }; - - /** - * Gets the mouse position that started the aggregation. + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. * - * @param {CameraEventType} type The camera event type. - * @param {KeyboardEventModifier} [modifier] The keyboard modifier. - * @returns {Cartesian2} The mouse position. + * @returns {String} The shader function. + * + * @private */ - CameraEventAggregator.prototype.getStartMousePosition = function(type, modifier) { - - if (type === CameraEventType.WHEEL) { - return this._currentMousePosition; + Cesium3DTileStyle.prototype.getShowShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._showShaderFunctionReady) { + // Return the cached result, may be undefined + return this._showShaderFunction; } - var key = getKey(type, modifier); - return this._eventStartPosition[key]; + this._showShaderFunctionReady = true; + this._showShaderFunction = defined(this.show) ? this.show.getShaderFunction(functionName, attributePrefix, shaderState, 'bool') : undefined; + return this._showShaderFunction; }; /** - * Gets the time the button was pressed or the touch was started. + * Gets the pointSize shader function for this style. * - * @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. + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * + * @returns {String} The shader function. + * + * @private */ - CameraEventAggregator.prototype.getButtonPressTime = function(type, modifier) { - - var key = getKey(type, modifier); - return this._pressTime[key]; + Cesium3DTileStyle.prototype.getPointSizeShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._pointSizeShaderFunctionReady) { + // Return the cached result, may be undefined + return this._pointSizeShaderFunction; + } + + this._pointSizeShaderFunctionReady = true; + this._pointSizeShaderFunction = defined(this.pointSize) ? this.pointSize.getShaderFunction(functionName, attributePrefix, shaderState, 'float') : undefined; + return this._pointSizeShaderFunction; }; + return Cesium3DTileStyle; +}); + +define('Scene/CircleEmitter',[ + '../Core/Cartesian3', + '../Core/Check', + '../Core/defaultValue', + '../Core/defineProperties', + '../Core/Math' + ], function( + Cartesian3, + Check, + defaultValue, + defineProperties, + CesiumMath) { + 'use strict'; + /** - * Gets the time the button was released or the touch was ended. + * A ParticleEmitter that emits particles from a circle. + * Particles will be positioned within a circle and have initial velocities going along the z vector. * - * @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. + * @alias CircleEmitter + * @constructor + * + * @param {Number} [radius=1.0] The radius of the circle in meters. */ - CameraEventAggregator.prototype.getButtonReleaseTime = function(type, modifier) { + function CircleEmitter(radius) { + radius = defaultValue(radius, 1.0); + - var key = getKey(type, modifier); - return this._releaseTime[key]; - }; + this._radius = defaultValue(radius, 1.0); + } - /** - * 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; + defineProperties(CircleEmitter.prototype, { + /** + * The radius of the circle in meters. + * @memberof CircleEmitter.prototype + * @type {Number} + * @default 1.0 + */ + radius : { + get : function() { + return this._radius; + }, + set : function(value) { + this._radius = value; } } - }; + }); /** - * 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. + * Initializes the given {@link Particle} by setting it's position and velocity. * - * @see CameraEventAggregator#destroy + * @private + * @param {Particle} particle The particle to initialize. */ - CameraEventAggregator.prototype.isDestroyed = function() { - return false; + CircleEmitter.prototype.emit = function(particle) { + var theta = CesiumMath.randomBetween(0.0, CesiumMath.TWO_PI); + var rad = CesiumMath.randomBetween(0.0, this._radius); + + var x = rad * Math.cos(theta); + var y = rad * Math.sin(theta); + var z = 0.0; + + particle.position = Cartesian3.fromElements(x, y, z, particle.position); + particle.velocity = Cartesian3.clone(Cartesian3.UNIT_Z, particle.velocity); }; + return CircleEmitter; +}); + +define('Scene/ConeEmitter',[ + '../Core/Cartesian3', + '../Core/Check', + '../Core/defaultValue', + '../Core/defineProperties', + '../Core/Math' + ], function( + Cartesian3, + Check, + defaultValue, + defineProperties, + CesiumMath) { + 'use strict'; + + var defaultAngle = CesiumMath.toRadians(30.0); + /** - * 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. + * A ParticleEmitter that emits particles within a cone. + * Particles will be positioned at the tip of the cone and have initial velocities going towards the base. * + * @alias ConeEmitter + * @constructor * - * @example - * handler = handler && handler.destroy(); + * @param {Number} [angle=Cesium.Math.toRadians(30.0)] The angle of the cone in radians. + */ + function ConeEmitter(angle) { + this._angle = defaultValue(angle, defaultAngle); + } + + defineProperties(ConeEmitter.prototype, { + /** + * The angle of the cone in radians. + * @memberof CircleEmitter.prototype + * @type {Number} + * @default Cesium.Math.toRadians(30.0) + */ + angle : { + get : function() { + return this._angle; + }, + set : function(value) { + this._angle = value; + } + } + }); + + /** + * Initializes the given {Particle} by setting it's position and velocity. * - * @see CameraEventAggregator#isDestroyed + * @private + * @param {Particle} particle The particle to initialize */ - CameraEventAggregator.prototype.destroy = function() { - this._eventHandler = this._eventHandler && this._eventHandler.destroy(); - return destroyObject(this); + ConeEmitter.prototype.emit = function(particle) { + var radius = Math.tan(this._angle); + + // Compute a random point on the cone's base + var theta = CesiumMath.randomBetween(0.0, CesiumMath.TWO_PI); + var rad = CesiumMath.randomBetween(0.0, radius); + + var x = rad * Math.cos(theta); + var y = rad * Math.sin(theta); + var z = 1.0; + + particle.velocity = Cartesian3.fromElements(x, y, z, particle.velocity); + Cartesian3.normalize(particle.velocity, particle.velocity); + particle.position = Cartesian3.clone(Cartesian3.ZERO, particle.position); }; - return CameraEventAggregator; + return ConeEmitter; }); -/*global define*/ define('Scene/UrlTemplateImageryProvider',[ '../Core/Cartesian2', '../Core/Cartesian3', @@ -157507,7 +178608,7 @@ define('Scene/UrlTemplateImageryProvider',[ * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider * @see createTileMapServiceImageryProvider @@ -157603,7 +178704,6 @@ define('Scene/UrlTemplateImageryProvider',[ } }, - /** * 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 @@ -157855,8 +178955,8 @@ define('Scene/UrlTemplateImageryProvider',[ } that._credit = credit; - that._urlParts = urlTemplateToParts(that._url, tags); - that._pickFeaturesUrlParts = urlTemplateToParts(that._pickFeaturesUrl, pickFeaturesTags); + that._urlParts = urlTemplateToParts(that._url, tags); //eslint-disable-line no-use-before-define + that._pickFeaturesUrlParts = urlTemplateToParts(that._pickFeaturesUrl, pickFeaturesTags); //eslint-disable-line no-use-before-define return true; }); }; @@ -157882,14 +178982,15 @@ define('Scene/UrlTemplateImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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) { + UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level, request) { var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, request); }; /** @@ -157937,17 +179038,21 @@ define('Scene/UrlTemplateImageryProvider',[ 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 loadWithXhr({ + url : url, + responseType : format.format + }).then(handleResponse.bind(undefined, format)).otherwise(doRequest); } return doRequest(); }; + var degreesScratchComputed = false; + var degreesScratch = new Rectangle(); + var projectedScratchComputed = false; + var projectedScratch = new Rectangle(); + function buildImageUrl(imageryProvider, x, y, level) { degreesScratchComputed = false; projectedScratchComputed = false; @@ -157957,6 +179062,10 @@ define('Scene/UrlTemplateImageryProvider',[ }); } + var ijScratchComputed = false; + var ijScratch = new Cartesian2(); + var longitudeLatitudeProjectedScratchComputed = false; + function buildPickFeaturesUrl(imageryProvider, x, y, level, longitude, latitude, format) { degreesScratchComputed = false; projectedScratchComputed = false; @@ -158075,9 +179184,6 @@ define('Scene/UrlTemplateImageryProvider',[ return imageryProvider._subdomains[index]; } - var degreesScratchComputed = false; - var degreesScratch = new Rectangle(); - function computeDegrees(imageryProvider, x, y, level) { if (degreesScratchComputed) { return; @@ -158112,9 +179218,6 @@ define('Scene/UrlTemplateImageryProvider',[ return degreesScratch.north; } - var projectedScratchComputed = false; - var projectedScratch = new Rectangle(); - function computeProjected(imageryProvider, x, y, level) { if (projectedScratchComputed) { return; @@ -158153,9 +179256,6 @@ define('Scene/UrlTemplateImageryProvider',[ 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; @@ -158177,6 +179277,7 @@ define('Scene/UrlTemplateImageryProvider',[ } var rectangleScratch = new Rectangle(); + var longitudeLatitudeProjectedScratch = new Cartesian3(); function computeIJ(imageryProvider, x, y, level, longitude, latitude, format) { if (ijScratchComputed) { @@ -158200,9 +179301,6 @@ define('Scene/UrlTemplateImageryProvider',[ 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; @@ -158272,7 +179370,6 @@ define('Scene/UrlTemplateImageryProvider',[ return UrlTemplateImageryProvider; }); -/*global define*/ define('Scene/createOpenStreetMapImageryProvider',[ '../Core/Credit', '../Core/defaultValue', @@ -158315,7 +179412,7 @@ define('Scene/createOpenStreetMapImageryProvider',[ * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see SingleTileImageryProvider * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider @@ -158364,7 +179461,7 @@ define('Scene/createOpenStreetMapImageryProvider',[ credit = new Credit(credit); } - var templateUrl = url + "{z}/{x}/{y}." + fileExtension; + var templateUrl = url + '{z}/{x}/{y}.' + fileExtension; return new UrlTemplateImageryProvider({ url: templateUrl, @@ -158382,7 +179479,6 @@ define('Scene/createOpenStreetMapImageryProvider',[ return createOpenStreetMapImageryProvider; }); -/*global define*/ define('Scene/createTangentSpaceDebugPrimitive',[ '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', @@ -158489,7 +179585,6 @@ define('Scene/createTangentSpaceDebugPrimitive',[ return createTangentSpaceDebugPrimitive; }); -/*global define*/ define('Scene/createTileMapServiceImageryProvider',[ '../Core/Cartesian2', '../Core/Cartographic', @@ -158552,7 +179647,7 @@ define('Scene/createTileMapServiceImageryProvider',[ * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider * @see WebMapServiceImageryProvider @@ -158759,7 +179854,6 @@ define('Scene/createTileMapServiceImageryProvider',[ return createTileMapServiceImageryProvider; }); -/*global define*/ define('Scene/CreditDisplay',[ '../Core/Credit', '../Core/defaultValue', @@ -159063,7 +180157,6 @@ define('Scene/CreditDisplay',[ return CreditDisplay; }); -/*global define*/ define('Scene/DebugAppearance',[ '../Core/defaultValue', '../Core/defined', @@ -159321,41 +180414,42 @@ define('Scene/DebugAppearance',[ 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/FrustumGeometry', + '../Core/FrustumOutlineGeometry', '../Core/GeometryInstance', - '../Core/Matrix4', - '../Core/PrimitiveType', + '../Core/Matrix3', + '../Core/OrthographicFrustum', + '../Core/OrthographicOffCenterFrustum', + '../Core/PerspectiveFrustum', + '../Core/PerspectiveOffCenterFrustum', + '../Core/Quaternion', './PerInstanceColorAppearance', './Primitive' ], function( - BoundingSphere, Cartesian3, - Cartesian4, Color, ColorGeometryInstanceAttribute, - ComponentDatatype, defaultValue, defined, destroyObject, DeveloperError, - GeometryAttribute, - GeometryAttributes, + FrustumGeometry, + FrustumOutlineGeometry, GeometryInstance, - Matrix4, - PrimitiveType, + Matrix3, + OrthographicFrustum, + OrthographicOffCenterFrustum, + PerspectiveFrustum, + PerspectiveOffCenterFrustum, + Quaternion, PerInstanceColorAppearance, Primitive) { 'use strict'; @@ -159406,24 +180500,21 @@ define('Scene/DebugCameraPrimitive',[ this.id = options.id; this._id = undefined; - this._outlinePrimitive = undefined; - this._planesPrimitive = undefined; + this._outlinePrimitives = []; + this._planesPrimitives = []; } - var frustumCornersNDC = new Array(4); - 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); - - var scratchMatrix = new Matrix4(); - var scratchFrustumCorners = new Array(4); - for (var i = 0; i < 4; ++i) { - scratchFrustumCorners[i] = new Cartesian4(); - } + var scratchRight = new Cartesian3(); + var scratchRotation = new Matrix3(); + var scratchOrientation = new Quaternion(); + var scratchPerspective = new PerspectiveFrustum(); + var scratchPerspectiveOffCenter = new PerspectiveOffCenterFrustum(); + var scratchOrthographic = new OrthographicFrustum(); + var scratchOrthographicOffCenter = new OrthographicOffCenterFrustum(); var scratchColor = new Color(); var scratchSplits = [1.0, 100000.0]; + /** * @private */ @@ -159432,15 +180523,36 @@ define('Scene/DebugCameraPrimitive',[ return; } + var planesPrimitives = this._planesPrimitives; + var outlinePrimitives = this._outlinePrimitives; + var i; + var length; + if (this._updateOnChange) { // Recreate the primitive every frame - this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy(); - this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy(); + length = planesPrimitives.length; + for (i = 0; i < length; ++i) { + outlinePrimitives[i] = outlinePrimitives[i] && outlinePrimitives[i].destroy(); + planesPrimitives[i] = planesPrimitives[i] && planesPrimitives[i].destroy(); + } + planesPrimitives.length = 0; + outlinePrimitives.length = 0; } - if (!defined(this._outlinePrimitive)) { + if (planesPrimitives.length === 0) { var camera = this._camera; - var frustum = camera.frustum; + var cameraFrustum = camera.frustum; + var frustum; + if (cameraFrustum instanceof PerspectiveFrustum) { + frustum = scratchPerspective; + } else if (cameraFrustum instanceof PerspectiveOffCenterFrustum) { + frustum = scratchPerspectiveOffCenter; + } else if (cameraFrustum instanceof OrthographicFrustum) { + frustum = scratchOrthographic; + } else { + frustum = scratchOrthographicOffCenter; + } + frustum = cameraFrustum.clone(frustum); var frustumSplits = frameState.frustumSplits; var numFrustums = frustumSplits.length - 1; @@ -159451,200 +180563,74 @@ define('Scene/DebugCameraPrimitive',[ numFrustums = 1; } - var view = this._camera.viewMatrix; - var inverseView; - var inverseViewProjection; - if (defined(camera.frustum.fovy)) { - var projection = this._camera.frustum.projectionMatrix; - var viewProjection = Matrix4.multiply(projection, view, scratchMatrix); - inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix); - } else { - inverseView = Matrix4.inverseTransformation(view, scratchMatrix); - } - - - var positions = new Float64Array(3 * 4 * (numFrustums + 1)); - var f; - for (f = 0; f < numFrustums + 1; ++f) { - for (var i = 0; i < 4; ++i) { - var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]); + var position = camera.positionWC; + var direction = camera.directionWC; + var up = camera.upWC; + var right = camera.rightWC; + right = Cartesian3.negate(right, scratchRight); - if (!defined(inverseViewProjection)) { - if (defined(frustum._offCenterFrustum)) { - frustum = frustum._offCenterFrustum; - } - - var near; - var far; - if (f === numFrustums) { - near = frustumSplits[f - 1]; - far = frustumSplits[f]; - } else { - near = frustumSplits[f]; - far = frustumSplits[f + 1]; - } - corner.x = (corner.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5; - corner.y = (corner.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5; - corner.z = (corner.z * (near - far) - near - far) * 0.5; - corner.w = 1.0; + var rotation = scratchRotation; + Matrix3.setColumn(rotation, 0, right, rotation); + Matrix3.setColumn(rotation, 1, up, rotation); + Matrix3.setColumn(rotation, 2, direction, rotation); - Matrix4.multiplyByVector(inverseView, corner, corner); - } else { - corner = Matrix4.multiplyByVector(inverseViewProjection, corner, corner); + var orientation = Quaternion.fromRotationMatrix(rotation, scratchOrientation); - // Reverse perspective divide - var w = 1.0 / corner.w; - Cartesian3.multiplyByScalar(corner, w, corner); + planesPrimitives.length = outlinePrimitives.length = numFrustums; - Cartesian3.subtract(corner, this._camera.positionWC, corner); - Cartesian3.normalize(corner, corner); + for (i = 0; i < numFrustums; ++i) { + frustum.near = frustumSplits[i]; + frustum.far = frustumSplits[i + 1]; - var fac = Cartesian3.dot(this._camera.directionWC, corner); - Cartesian3.multiplyByScalar(corner, frustumSplits[f] / fac, corner); - Cartesian3.add(corner, this._camera.positionWC, corner); - } + planesPrimitives[i] = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new FrustumGeometry({ + origin : position, + orientation : orientation, + frustum : frustum, + _drawNearPlane : i === 0 + }), + 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 + }); - positions[12 * f + i * 3] = corner.x; - positions[12 * f + i * 3 + 1] = corner.y; - positions[12 * f + i * 3 + 2] = corner.z; - } + outlinePrimitives[i] = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new FrustumOutlineGeometry({ + origin : position, + orientation : orientation, + frustum : frustum, + _drawNearPlane : i === 0 + }), + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(this._color) + }, + id : this.id, + pickPrimitive : this + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); } - - var boundingSphere = new BoundingSphere.fromVertices(positions); - - var attributes = new GeometryAttributes(); - attributes.position = new GeometryAttribute({ - componentDatatype : ComponentDatatype.DOUBLE, - componentsPerAttribute : 3, - values : positions - }); - - var offset, index; - - // Create the outline primitive - var outlineIndices = new Uint16Array(8 * (2 * numFrustums + 1)); - // Build the far planes - for (f = 0; f < numFrustums + 1; ++f) { - offset = f * 8; - index = f * 4; - - outlineIndices[offset] = index; - outlineIndices[offset + 1] = index + 1; - outlineIndices[offset + 2] = index + 1; - outlineIndices[offset + 3] = index + 2; - outlineIndices[offset + 4] = index + 2; - outlineIndices[offset + 5] = index + 3; - outlineIndices[offset + 6] = index + 3; - outlineIndices[offset + 7] = index; - } - // Build the sides of the frustums - for (f = 0; f < numFrustums; ++f) { - offset = (numFrustums + 1 + f) * 8; - index = f * 4; - - outlineIndices[offset] = index; - outlineIndices[offset + 1] = index + 4; - outlineIndices[offset + 2] = index + 1; - outlineIndices[offset + 3] = index + 5; - outlineIndices[offset + 4] = index + 2; - outlineIndices[offset + 5] = index + 6; - outlineIndices[offset + 6] = index + 3; - outlineIndices[offset + 7] = index + 7; - } - - 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(6 * (5 * numFrustums + 1)); - // Build the far planes - for (f = 0; f < numFrustums + 1; ++f) { - offset = f * 6; - index = f * 4; - - planesIndices[offset] = index; - planesIndices[offset + 1] = index + 1; - planesIndices[offset + 2] = index + 2; - planesIndices[offset + 3] = index; - planesIndices[offset + 4] = index + 2; - planesIndices[offset + 5] = index + 3; - } - // Build the sides of the frustums - for (f = 0; f < numFrustums; ++f) { - offset = (numFrustums + 1 + 4 * f) * 6; - index = f * 4; - - planesIndices[offset] = index + 4; - planesIndices[offset + 1] = index; - planesIndices[offset + 2] = index + 3; - planesIndices[offset + 3] = index + 4; - planesIndices[offset + 4] = index + 3; - planesIndices[offset + 5] = index + 7; - - planesIndices[offset + 6] = index + 4; - planesIndices[offset + 7] = index; - planesIndices[offset + 8] = index + 1; - planesIndices[offset + 9] = index + 4; - planesIndices[offset + 10] = index + 1; - planesIndices[offset + 11] = index + 5; - - planesIndices[offset + 12] = index + 7; - planesIndices[offset + 13] = index + 3; - planesIndices[offset + 14] = index + 2; - planesIndices[offset + 15] = index + 7; - planesIndices[offset + 16] = index + 2; - planesIndices[offset + 17] = index + 6; - - planesIndices[offset + 18] = index + 6; - planesIndices[offset + 19] = index + 2; - planesIndices[offset + 20] = index + 1; - planesIndices[offset + 21] = index + 6; - planesIndices[offset + 22] = index + 1; - planesIndices[offset + 23] = index + 5; - } - - 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); + length = planesPrimitives.length; + for (i = 0; i < length; ++i) { + outlinePrimitives[i].update(frameState); + planesPrimitives[i].update(frameState); + } }; /** @@ -159681,15 +180667,17 @@ define('Scene/DebugCameraPrimitive',[ * @see DebugCameraPrimitive#isDestroyed */ DebugCameraPrimitive.prototype.destroy = function() { - this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy(); - this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy(); + var length = this._planesPrimitives.length; + for (var i = 0; i < length; ++i) { + this._outlinePrimitives[i] = this._outlinePrimitives[i] && this._outlinePrimitives[i].destroy(); + this._planesPrimitives[i] = this._planesPrimitives[i] && this._planesPrimitives[i].destroy(); + } return destroyObject(this); }; return DebugCameraPrimitive; }); -/*global define*/ define('Scene/DebugModelMatrixPrimitive',[ '../Core/Cartesian3', '../Core/Color', @@ -159929,7 +180917,6 @@ define('Scene/DebugModelMatrixPrimitive',[ }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/DepthPlaneFS',[],function() { 'use strict'; return "varying vec4 positionEC;\n\ @@ -159955,7 +180942,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/DepthPlaneVS',[],function() { 'use strict'; return "attribute vec4 position;\n\ @@ -159969,7 +180955,6 @@ void main()\n\ }\n\ "; }); -/*global define*/ define('Scene/DepthPlane',[ '../Core/BoundingSphere', '../Core/Cartesian3', @@ -160089,8 +181074,7 @@ define('Scene/DepthPlane',[ enabled : true }, depthTest : { - enabled : true, - func : DepthFunction.ALWAYS + enabled : true }, colorMask : { red : false, @@ -160168,7 +181152,6 @@ define('Scene/DepthPlane',[ return DepthPlane; }); -/*global define*/ define('Scene/DeviceOrientationCameraController',[ '../Core/defined', '../Core/destroyObject', @@ -160299,7 +181282,6 @@ define('Scene/DeviceOrientationCameraController',[ }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/EllipsoidFS',[],function() { 'use strict'; return "#ifdef WRITE_DEPTH\n\ @@ -160412,7 +181394,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/EllipsoidVS',[],function() { 'use strict'; return "attribute vec3 position;\n\ @@ -160443,7 +181424,6 @@ void main() \n\ }\n\ "; }); -/*global define*/ define('Scene/EllipsoidPrimitive',[ '../Core/BoundingSphere', '../Core/BoxGeometry', @@ -160549,7 +181529,7 @@ define('Scene/EllipsoidPrimitive',[ * @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); @@ -160600,7 +181580,7 @@ define('Scene/EllipsoidPrimitive',[ * * // 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)); @@ -160903,7 +181883,7 @@ define('Scene/EllipsoidPrimitive',[ * * @example * e = e && e.destroy(); - * + * * @see EllipsoidPrimitive#isDestroyed */ EllipsoidPrimitive.prototype.destroy = function() { @@ -160917,7 +181897,6 @@ define('Scene/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\ @@ -160956,7 +181935,6 @@ void main()\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\ @@ -160980,7 +181958,6 @@ void main() \n\ }\n\ "; }); -/*global define*/ define('Scene/EllipsoidSurfaceAppearance',[ '../Core/defaultValue', '../Core/defined', @@ -161263,7 +182240,6 @@ define('Scene/EllipsoidSurfaceAppearance',[ return EllipsoidSurfaceAppearance; }); -/*global define*/ define('Scene/Fog',[ '../Core/Cartesian3', '../Core/defined', @@ -161406,7 +182382,6 @@ define('Scene/Fog',[ return Fog; }); -/*global define*/ define('Scene/FrameRateMonitor',[ '../Core/defaultValue', '../Core/defined', @@ -161735,7 +182710,6 @@ define('Scene/FrameRateMonitor',[ return FrameRateMonitor; }); -/*global define*/ define('Scene/FrameState',[ './SceneMode' ], function( @@ -161746,7 +182720,7 @@ define('Scene/FrameState',[ * State information about the current frame. An instance of this class * is provided to update functions. * - * @param {Context} context The rendering context. + * @param {Context} context The rendering context * @param {CreditDisplay} creditDisplay Handles adding and removing credits from an HTML element * @param {JobScheduler} jobScheduler The job scheduler * @@ -161758,12 +182732,14 @@ define('Scene/FrameState',[ function FrameState(context, creditDisplay, jobScheduler) { /** * The rendering context. + * * @type {Context} */ this.context = context; /** * An array of rendering commands. + * * @type {DrawCommand[]} */ this.commandList = []; @@ -161774,8 +182750,21 @@ define('Scene/FrameState',[ */ this.shadowMaps = []; + /** + * The BRDF look up texture generator used for image-based lighting for PBR models + * @type {BrdfLutGenerator} + */ + this.brdfLutGenerator = undefined; + + /** + * The environment map used for image-based lighting for PBR models + * @type {CubeMap} + */ + this.environmentMap = undefined; + /** * The current mode of the scene. + * * @type {SceneMode} * @default {@link SceneMode.SCENE3D} */ @@ -161822,6 +182811,7 @@ define('Scene/FrameState',[ /** * The current camera. + * * @type {Camera} * @default undefined */ @@ -161829,6 +182819,7 @@ define('Scene/FrameState',[ /** * The culling volume. + * * @type {CullingVolume} * @default undefined */ @@ -161836,6 +182827,7 @@ define('Scene/FrameState',[ /** * The current occluder. + * * @type {Occluder} * @default undefined */ @@ -161853,12 +182845,14 @@ define('Scene/FrameState',[ 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 */ @@ -161874,6 +182868,7 @@ define('Scene/FrameState',[ /** * The credit display. + * * @type {CreditDisplay} */ this.creditDisplay = creditDisplay; @@ -161899,6 +182894,7 @@ define('Scene/FrameState',[ /** * Gets whether or not to optimized for 3D only. + * * @type {Boolean} * @default false */ @@ -161913,12 +182909,14 @@ define('Scene/FrameState',[ 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 */ @@ -162012,19 +183010,15 @@ define('Scene/FrameState',[ this.minimumDisableDepthTestDistance = undefined; } - FrameState.prototype.addCommand = function(command) { - this.commandList.push(command); - }; - /** * 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' @@ -162064,7 +183058,6 @@ define('Scene/FrustumCommands',[ }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/PostProcessFilters/FXAA',[],function() { 'use strict'; return "varying vec2 v_textureCoordinates;\n\ @@ -162119,7 +183112,6 @@ void main()\n\ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('ThirdParty/Shaders/FXAA3_11',[],function() { 'use strict'; return "/**\n\ @@ -162772,7 +183764,6 @@ FxaaFloat4 FxaaPixelShader(\n\ }\n\ "; }); -/*global define*/ define('Scene/FXAA',[ '../Core/BoundingRectangle', '../Core/Cartesian2', @@ -162992,7 +183983,6 @@ define('Scene/FXAA',[ return FXAA; }); -/*global define*/ define('Scene/GetFeatureInfoFormat',[ '../Core/Cartographic', '../Core/defined', @@ -163311,7 +184301,6 @@ define('Scene/GetFeatureInfoFormat',[ }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/GlobeFS',[],function() { 'use strict'; return "//#define SHOW_TILE_BOUNDARIES\n\ @@ -163382,7 +184371,7 @@ varying vec3 v_mieColor;\n\ \n\ vec4 sampleAndBlend(\n\ vec4 previousColor,\n\ - sampler2D texture,\n\ + sampler2D textureToSample,\n\ vec2 tileTextureCoordinates,\n\ vec4 textureCoordinateRectangle,\n\ vec4 textureCoordinateTranslationAndScale,\n\ @@ -163410,7 +184399,7 @@ vec4 sampleAndBlend(\n\ vec2 translation = textureCoordinateTranslationAndScale.xy;\n\ vec2 scale = textureCoordinateTranslationAndScale.zw;\n\ vec2 textureCoordinates = tileTextureCoordinates * scale + translation;\n\ - vec4 value = texture2D(texture, textureCoordinates);\n\ + vec4 value = texture2D(textureToSample, textureCoordinates);\n\ vec3 color = value.rgb;\n\ float alpha = value.a;\n\ \n\ @@ -163616,7 +184605,6 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/GlobeVS',[],function() { 'use strict'; return "#ifdef QUANTIZATION_BITS12\n\ @@ -163792,7 +184780,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/GroundAtmosphere',[],function() { 'use strict'; return "/*!\n\ @@ -163926,7 +184913,6 @@ AtmosphereColor computeGroundAtmosphereFromSpace(vec3 v3Pos)\n\ \n\ "; }); -/*global define*/ define('Scene/GlobeSurfaceShaderSet',[ '../Core/defined', '../Core/destroyObject', @@ -164211,7 +185197,6 @@ define('Scene/GlobeSurfaceShaderSet',[ return GlobeSurfaceShaderSet; }); -/*global define*/ define('Scene/ImageryState',[ '../Core/freezeObject' ], function( @@ -164235,7 +185220,6 @@ define('Scene/ImageryState',[ return freezeObject(ImageryState); }); -/*global define*/ define('Scene/QuadtreeTileLoadState',[ '../Core/freezeObject' ], function( @@ -164284,7 +185268,6 @@ define('Scene/QuadtreeTileLoadState',[ return freezeObject(QuadtreeTileLoadState); }); -/*global define*/ define('Scene/TerrainState',[ '../Core/freezeObject' ], function( @@ -164307,368 +185290,6 @@ define('Scene/TerrainState',[ return freezeObject(TerrainState); }); -/*global define*/ -define('Scene/TileBoundingRegion',[ - '../Core/BoundingSphere', - '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/Check', - '../Core/ColorGeometryInstanceAttribute', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/Ellipsoid', - '../Core/GeometryInstance', - '../Core/IntersectionTests', - '../Core/Matrix4', - '../Core/OrientedBoundingBox', - '../Core/Plane', - '../Core/Ray', - '../Core/Rectangle', - '../Core/RectangleOutlineGeometry', - './PerInstanceColorAppearance', - './Primitive', - './SceneMode' - ], function( - BoundingSphere, - Cartesian3, - Cartographic, - Check, - ColorGeometryInstanceAttribute, - defaultValue, - defined, - defineProperties, - Ellipsoid, - GeometryInstance, - IntersectionTests, - Matrix4, - OrientedBoundingBox, - Plane, - Ray, - Rectangle, - RectangleOutlineGeometry, - PerInstanceColorAppearance, - Primitive, - SceneMode) { - 'use strict'; - - /** - * A tile bounding volume specified as a longitude/latitude/height region. - * @alias TileBoundingRegion - * @constructor - * - * @param {Object} options Object with the following properties: - * @param {Rectangle} options.rectangle The rectangle specifying the longitude and latitude range of the region. - * @param {Number} [options.minimumHeight=0.0] The minimum height of the region. - * @param {Number} [options.maximumHeight=0.0] The maximum height of the region. - * @param {Ellipsoid} [options.ellipsoid=Cesium.Ellipsoid.WGS84] The ellipsoid. - * - * @private - */ - function TileBoundingRegion(options) { - - this.rectangle = Rectangle.clone(options.rectangle); - this.minimumHeight = defaultValue(options.minimumHeight, 0.0); - this.maximumHeight = defaultValue(options.maximumHeight, 0.0); - - /** - * The world coordinates of the southwest corner of the tile's rectangle. - * - * @type {Cartesian3} - * @default Cartesian3() - */ - this.southwestCornerCartesian = new Cartesian3(); - - /** - * The world coordinates of the northeast corner of the tile's rectangle. - * - * @type {Cartesian3} - * @default Cartesian3() - */ - this.northeastCornerCartesian = new Cartesian3(); - - /** - * A normal that, along with southwestCornerCartesian, defines a plane at the western edge of - * the tile. Any position above (in the direction of the normal) this plane is outside the tile. - * - * @type {Cartesian3} - * @default Cartesian3() - */ - this.westNormal = new Cartesian3(); - - /** - * A normal that, along with southwestCornerCartesian, defines a plane at the southern edge of - * the tile. Any position above (in the direction of the normal) this plane is outside the tile. - * Because points of constant latitude do not necessary lie in a plane, positions below this - * plane are not necessarily inside the tile, but they are close. - * - * @type {Cartesian3} - * @default Cartesian3() - */ - this.southNormal = new Cartesian3(); - - /** - * A normal that, along with northeastCornerCartesian, defines a plane at the eastern edge of - * the tile. Any position above (in the direction of the normal) this plane is outside the tile. - * - * @type {Cartesian3} - * @default Cartesian3() - */ - this.eastNormal = new Cartesian3(); - - /** - * A normal that, along with northeastCornerCartesian, defines a plane at the eastern edge of - * the tile. Any position above (in the direction of the normal) this plane is outside the tile. - * Because points of constant latitude do not necessary lie in a plane, positions below this - * plane are not necessarily inside the tile, but they are close. - * - * @type {Cartesian3} - * @default Cartesian3() - */ - this.northNormal = new Cartesian3(); - - var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - computeBox(this, options.rectangle, ellipsoid); - - // An oriented bounding box that encloses this tile's region. This is used to calculate tile visibility. - this._orientedBoundingBox = OrientedBoundingBox.fromRectangle(this.rectangle, this.minimumHeight, this.maximumHeight, ellipsoid); - - this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox); - } - - defineProperties(TileBoundingRegion.prototype, { - /** - * The underlying bounding volume - * - * @memberof TileBoundingRegion.prototype - * - * @type {Object} - * @readonly - */ - boundingVolume : { - get : function() { - return this._orientedBoundingBox; - } - }, - /** - * The underlying bounding sphere - * - * @memberof TileBoundingRegion.prototype - * - * @type {BoundingSphere} - * @readonly - */ - boundingSphere : { - get : function() { - return this._boundingSphere; - } - } - }); - - var cartesian3Scratch = new Cartesian3(); - var cartesian3Scratch2 = new Cartesian3(); - var cartesian3Scratch3 = new Cartesian3(); - var eastWestNormalScratch = new Cartesian3(); - var westernMidpointScratch = new Cartesian3(); - var easternMidpointScratch = new Cartesian3(); - var cartographicScratch = new Cartographic(); - var planeScratch = new Plane(Cartesian3.UNIT_X, 0.0); - var rayScratch = new Ray(); - - function computeBox(tileBB, rectangle, ellipsoid) { - ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle), tileBB.southwestCornerCartesian); - ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle), tileBB.northeastCornerCartesian); - - // The middle latitude on the western edge. - cartographicScratch.longitude = rectangle.west; - cartographicScratch.latitude = (rectangle.south + rectangle.north) * 0.5; - cartographicScratch.height = 0.0; - var westernMidpointCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, westernMidpointScratch); - - // Compute the normal of the plane on the western edge of the tile. - var westNormal = Cartesian3.cross(westernMidpointCartesian, Cartesian3.UNIT_Z, cartesian3Scratch); - Cartesian3.normalize(westNormal, tileBB.westNormal); - - // The middle latitude on the eastern edge. - cartographicScratch.longitude = rectangle.east; - var easternMidpointCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, easternMidpointScratch); - - // Compute the normal of the plane on the eastern edge of the tile. - var eastNormal = Cartesian3.cross(Cartesian3.UNIT_Z, easternMidpointCartesian, cartesian3Scratch); - Cartesian3.normalize(eastNormal, tileBB.eastNormal); - - // Compute the normal of the plane bounding the southern edge of the tile. - var westVector = Cartesian3.subtract(westernMidpointCartesian, easternMidpointCartesian, cartesian3Scratch); - var eastWestNormal = Cartesian3.normalize(westVector, eastWestNormalScratch); - - var south = rectangle.south; - var southSurfaceNormal; - - if (south > 0.0) { - // Compute a plane that doesn't cut through the tile. - cartographicScratch.longitude = (rectangle.west + rectangle.east) * 0.5; - cartographicScratch.latitude = south; - var southCenterCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, rayScratch.origin); - Cartesian3.clone(eastWestNormal, rayScratch.direction); - var westPlane = Plane.fromPointNormal(tileBB.southwestCornerCartesian, tileBB.westNormal, planeScratch); - // Find a point that is on the west and the south planes - IntersectionTests.rayPlane(rayScratch, westPlane, tileBB.southwestCornerCartesian); - southSurfaceNormal = ellipsoid.geodeticSurfaceNormal(southCenterCartesian, cartesian3Scratch2); - - } else { - southSurfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.southeast(rectangle), cartesian3Scratch2); - } - var southNormal = Cartesian3.cross(southSurfaceNormal, westVector, cartesian3Scratch3); - Cartesian3.normalize(southNormal, tileBB.southNormal); - - // Compute the normal of the plane bounding the northern edge of the tile. - var north = rectangle.north; - var northSurfaceNormal; - if (north < 0.0) { - // Compute a plane that doesn't cut through the tile. - cartographicScratch.longitude = (rectangle.west + rectangle.east) * 0.5; - cartographicScratch.latitude = north; - var northCenterCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, rayScratch.origin); - Cartesian3.negate(eastWestNormal, rayScratch.direction); - var eastPlane = Plane.fromPointNormal(tileBB.northeastCornerCartesian, tileBB.eastNormal, planeScratch); - // Find a point that is on the east and the north planes - IntersectionTests.rayPlane(rayScratch, eastPlane, tileBB.northeastCornerCartesian); - northSurfaceNormal = ellipsoid.geodeticSurfaceNormal(northCenterCartesian, cartesian3Scratch2); - - } else { - northSurfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.northwest(rectangle), cartesian3Scratch2); - } - var northNormal = Cartesian3.cross(westVector, northSurfaceNormal, cartesian3Scratch3); - Cartesian3.normalize(northNormal, tileBB.northNormal); - } - - var southwestCornerScratch = new Cartesian3(); - var northeastCornerScratch = new Cartesian3(); - var negativeUnitY = new Cartesian3(0.0, -1.0, 0.0); - var negativeUnitZ = new Cartesian3(0.0, 0.0, -1.0); - var vectorScratch = new Cartesian3(); - - /** - * Gets the distance from the camera to the closest point on the tile. This is used for level-of-detail selection. - * - * @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. - */ - TileBoundingRegion.prototype.distanceToCamera = function(frameState) { - var camera = frameState.camera; - var cameraCartesianPosition = camera.positionWC; - var cameraCartographicPosition = camera.positionCartographic; - - var result = 0.0; - if (!Rectangle.contains(this.rectangle, cameraCartographicPosition)) { - var southwestCornerCartesian = this.southwestCornerCartesian; - var northeastCornerCartesian = this.northeastCornerCartesian; - var westNormal = this.westNormal; - var southNormal = this.southNormal; - var eastNormal = this.eastNormal; - var northNormal = this.northNormal; - - if (frameState.mode !== SceneMode.SCENE3D) { - southwestCornerCartesian = frameState.mapProjection.project(Rectangle.southwest(this.rectangle), southwestCornerScratch); - southwestCornerCartesian.z = southwestCornerCartesian.y; - southwestCornerCartesian.y = southwestCornerCartesian.x; - southwestCornerCartesian.x = 0.0; - northeastCornerCartesian = frameState.mapProjection.project(Rectangle.northeast(this.rectangle), northeastCornerScratch); - northeastCornerCartesian.z = northeastCornerCartesian.y; - northeastCornerCartesian.y = northeastCornerCartesian.x; - northeastCornerCartesian.x = 0.0; - westNormal = negativeUnitY; - eastNormal = Cartesian3.UNIT_Y; - southNormal = negativeUnitZ; - northNormal = Cartesian3.UNIT_Z; - } - - var vectorFromSouthwestCorner = Cartesian3.subtract(cameraCartesianPosition, southwestCornerCartesian, vectorScratch); - var distanceToWestPlane = Cartesian3.dot(vectorFromSouthwestCorner, westNormal); - var distanceToSouthPlane = Cartesian3.dot(vectorFromSouthwestCorner, southNormal); - - var vectorFromNortheastCorner = Cartesian3.subtract(cameraCartesianPosition, northeastCornerCartesian, vectorScratch); - var distanceToEastPlane = Cartesian3.dot(vectorFromNortheastCorner, eastNormal); - var distanceToNorthPlane = Cartesian3.dot(vectorFromNortheastCorner, northNormal); - - if (distanceToWestPlane > 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); - }; - - /** - * 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. - */ - TileBoundingRegion.prototype.intersectPlane = function(plane) { - return this._orientedBoundingBox.intersectPlane(plane); - }; - - /** - * Creates a debug primitive that shows the outline of the tile bounding region. - * - * @param {Color} color The desired color of the primitive's mesh - * @return {Primitive} - */ - TileBoundingRegion.prototype.createDebugVolume = function(color) { - - var modelMatrix = new Matrix4.clone(Matrix4.IDENTITY); - var geometry = new RectangleOutlineGeometry({ - rectangle : this.rectangle, - height : this.minimumHeight, - extrudedHeight: this.maximumHeight - }); - var instance = new GeometryInstance({ - geometry : geometry, - modelMatrix : modelMatrix, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(color) - } - }); - - return new Primitive({ - geometryInstances : instance, - appearance : new PerInstanceColorAppearance({ - translucent : false, - flat : true - }), - asynchronous : false - }); - }; - - return TileBoundingRegion; -}); - -/*global define*/ define('Scene/TileTerrain',[ '../Core/BoundingSphere', '../Core/Cartesian3', @@ -164676,13 +185297,15 @@ define('Scene/TileTerrain',[ '../Core/DeveloperError', '../Core/IndexDatatype', '../Core/OrientedBoundingBox', + '../Core/Request', + '../Core/RequestState', + '../Core/RequestType', '../Core/TileProviderError', '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/VertexArray', '../ThirdParty/when', - './TerrainState', - './TileBoundingRegion' + './TerrainState' ], function( BoundingSphere, Cartesian3, @@ -164690,13 +185313,15 @@ define('Scene/TileTerrain',[ DeveloperError, IndexDatatype, OrientedBoundingBox, + Request, + RequestState, + RequestType, TileProviderError, Buffer, BufferUsage, VertexArray, when, - TerrainState, - TileBoundingRegion) { + TerrainState) { 'use strict'; /** @@ -164722,6 +185347,7 @@ define('Scene/TileTerrain',[ this.mesh = undefined; this.vertexArray = undefined; this.upsampleDetails = upsampleDetails; + this.request = undefined; } TileTerrain.prototype.freeResources = function() { @@ -164753,18 +185379,14 @@ define('Scene/TileTerrain',[ surfaceTile.maximumHeight = mesh.maximumHeight; surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D); surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox); - surfaceTile.tileBoundingRegion = new TileBoundingRegion({ - rectangle : tile.rectangle, - minimumHeight : mesh.minimumHeight, - maximumHeight : mesh.maximumHeight, - ellipsoid : tile.tilingScheme.ellipsoid - }); + surfaceTile.tileBoundingRegion.minimumHeight = mesh.minimumHeight; + surfaceTile.tileBoundingRegion.maximumHeight = mesh.maximumHeight; tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace); }; - TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level) { + TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level, priorityFunction) { if (this.state === TerrainState.UNLOADED) { - requestTileGeometry(this, terrainProvider, x, y, level); + requestTileGeometry(this, terrainProvider, x, y, level, priorityFunction); } if (this.state === TerrainState.RECEIVED) { @@ -164776,16 +185398,26 @@ define('Scene/TileTerrain',[ } }; - function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) { + function requestTileGeometry(tileTerrain, terrainProvider, x, y, level, priorityFunction) { function success(terrainData) { tileTerrain.data = terrainData; tileTerrain.state = TerrainState.RECEIVED; + tileTerrain.request = undefined; } function failure() { + if (tileTerrain.request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + tileTerrain.data = undefined; + tileTerrain.state = TerrainState.UNLOADED; + tileTerrain.request = undefined; + return; + } + // Initially assume failure. handleError may retry, in which case the state will // change to RECEIVING or UNLOADED. tileTerrain.state = TerrainState.FAILED; + tileTerrain.request = undefined; var message = 'Failed to obtain terrain tile X: ' + x + ' Y: ' + y + ' Level: ' + level + '.'; terrainProvider._requestError = TileProviderError.handleError( @@ -164799,17 +185431,24 @@ define('Scene/TileTerrain',[ function doRequest() { // Request the terrain from the terrain provider. - tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level); + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.TERRAIN, + priorityFunction : priorityFunction + }); + tileTerrain.request = request; + tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level, request); // 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; + tileTerrain.request = undefined; } } @@ -164913,7 +185552,6 @@ define('Scene/TileTerrain',[ return TileTerrain; }); -/*global define*/ define('Scene/GlobeSurfaceTile',[ '../Core/BoundingSphere', '../Core/Cartesian3', @@ -165152,10 +185790,39 @@ define('Scene/GlobeSurfaceTile',[ } }; + function createTileBoundingRegion(tile) { + var minimumHeight; + var maximumHeight; + if (defined(tile.parent) && defined(tile.parent.data)) { + minimumHeight = tile.parent.data.minimumHeight; + maximumHeight = tile.parent.data.maximumHeight; + } + return new TileBoundingRegion({ + rectangle : tile.rectangle, + ellipsoid : tile.tilingScheme.ellipsoid, + minimumHeight : minimumHeight, + maximumHeight : maximumHeight + }); + } + + function createPriorityFunction(surfaceTile, frameState) { + return function() { + return surfaceTile.tileBoundingRegion.distanceToCamera(frameState); + }; + } + GlobeSurfaceTile.processStateMachine = function(tile, frameState, terrainProvider, imageryLayerCollection, vertexArraysToDestroy) { var surfaceTile = tile.data; if (!defined(surfaceTile)) { surfaceTile = tile.data = new GlobeSurfaceTile(); + // Create the TileBoundingRegion now in order to estimate the distance, which is used to prioritize the request. + // Since the terrain isn't loaded yet, estimate the heights using its parent's values. + surfaceTile.tileBoundingRegion = createTileBoundingRegion(tile); + } + + if (!defined(tile._priorityFunction)) { + // The priority function is used to prioritize requests among all requested tiles + tile._priorityFunction = createPriorityFunction(surfaceTile, frameState); } if (tile.state === QuadtreeTileLoadState.START) { @@ -165179,7 +185846,8 @@ define('Scene/GlobeSurfaceTile',[ // Transition imagery states var tileImageryCollection = surfaceTile.imagery; - for (var i = 0, len = tileImageryCollection.length; i < len; ++i) { + var i, len; + for (i = 0, len = tileImageryCollection.length; i < len; ++i) { var tileImagery = tileImageryCollection[i]; if (!defined(tileImagery.loadingImagery)) { isUpsampledOnly = false; @@ -165221,7 +185889,16 @@ define('Scene/GlobeSurfaceTile',[ } if (isDoneLoading) { + var newCallbacks = []; + tile._loadedCallbacks.forEach(function(cb) { + if (!cb(tile)) { + newCallbacks.push(cb); + } + }); + tile._loadedCallbacks = newCallbacks; + tile.state = QuadtreeTileLoadState.DONE; + tile._priorityFunction = undefined; } } }; @@ -165254,7 +185931,7 @@ define('Scene/GlobeSurfaceTile',[ var suspendUpsampling = false; if (defined(loaded)) { - loaded.processLoadStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level); + loaded.processLoadStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level, tile._priorityFunction); // Publish the terrain data on the tile as soon as it is available. // We'll potentially need it to upsample child tiles. @@ -165588,7 +186265,6 @@ define('Scene/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\ @@ -165602,7 +186278,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/ReprojectWebMercatorVS',[],function() { 'use strict'; return "attribute vec4 position;\n\ @@ -165619,14 +186294,15 @@ void main()\n\ }\n\ "; }); -/*global define*/ define('Scene/Imagery',[ '../Core/defined', '../Core/destroyObject', + '../Core/RequestState', './ImageryState' ], function( defined, destroyObject, + RequestState, ImageryState) { 'use strict'; @@ -165641,6 +186317,7 @@ define('Scene/Imagery',[ this.x = x; this.y = y; this.level = level; + this.request = undefined; if (level !== 0) { var parentX = x / 2 | 0; @@ -165705,10 +186382,10 @@ define('Scene/Imagery',[ return this.referenceCount; }; - Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection) { + Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection, priorityFunction) { if (this.state === ImageryState.UNLOADED) { this.state = ImageryState.TRANSITIONING; - this.imageryLayer._requestImagery(this); + this.imageryLayer._requestImagery(this, priorityFunction); } if (this.state === ImageryState.RECEIVED) { @@ -165730,7 +186407,6 @@ define('Scene/Imagery',[ return Imagery; }); -/*global define*/ define('Scene/ImagerySplitDirection',[ '../Core/freezeObject' ], function( @@ -165773,7 +186449,6 @@ define('Scene/ImagerySplitDirection',[ return freezeObject(ImagerySplitDirection); }); -/*global define*/ define('Scene/TileImagery',[ '../Core/defined', './ImageryState' @@ -165825,7 +186500,7 @@ define('Scene/TileImagery',[ var loadingImagery = this.loadingImagery; var imageryLayer = loadingImagery.imageryLayer; - loadingImagery.processStateMachine(frameState, !this.useWebMercatorT); + loadingImagery.processStateMachine(frameState, !this.useWebMercatorT, tile._priorityFunction); if (loadingImagery.state === ImageryState.READY) { if (defined(this.readyImagery)) { @@ -165867,12 +186542,11 @@ define('Scene/TileImagery',[ // 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); + closestAncestorThatNeedsLoading.processStateMachine(frameState, !this.useWebMercatorT, tile._priorityFunction); return false; // not done loading - } else { - // This imagery tile is failed or invalid, and we have the "best available" substitute. - return true; // done loading } + // This imagery tile is failed or invalid, and we have the "best available" substitute. + return true; // done loading } return false; // not done loading @@ -165881,7 +186555,6 @@ define('Scene/TileImagery',[ return TileImagery; }); -/*global define*/ define('Scene/ImageryLayer',[ '../Core/Cartesian2', '../Core/Cartesian4', @@ -165895,6 +186568,9 @@ define('Scene/ImageryLayer',[ '../Core/Math', '../Core/PixelFormat', '../Core/Rectangle', + '../Core/Request', + '../Core/RequestState', + '../Core/RequestType', '../Core/TerrainProvider', '../Core/TileProviderError', '../Core/WebMercatorProjection', @@ -165932,6 +186608,9 @@ define('Scene/ImageryLayer',[ CesiumMath, PixelFormat, Rectangle, + Request, + RequestState, + RequestType, TerrainProvider, TileProviderError, WebMercatorProjection, @@ -166539,8 +187218,9 @@ define('Scene/ImageryLayer',[ * @private * * @param {Imagery} imagery The imagery to request. + * @param {Function} [priorityFunction] The priority function used for sorting the imagery request. */ - ImageryLayer.prototype._requestImagery = function(imagery) { + ImageryLayer.prototype._requestImagery = function(imagery, priorityFunction) { var imageryProvider = this._imageryProvider; var that = this; @@ -166552,14 +187232,23 @@ define('Scene/ImageryLayer',[ imagery.image = image; imagery.state = ImageryState.RECEIVED; + imagery.request = undefined; TileProviderError.handleSuccess(that._requestImageError); } function failure(e) { + if (imagery.request.state === RequestState.CANCELLED) { + // Cancelled due to low priority - try again later. + imagery.state = ImageryState.UNLOADED; + imagery.request = undefined; + return; + } + // Initially assume failure. handleError may retry, in which case the state will // change to TRANSITIONING. imagery.state = ImageryState.FAILED; + imagery.request = undefined; var message = 'Failed to obtain image tile X: ' + imagery.x + ' Y: ' + imagery.y + ' Level: ' + imagery.level + '.'; that._requestImageError = TileProviderError.handleError( @@ -166573,12 +187262,20 @@ define('Scene/ImageryLayer',[ } function doRequest() { + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.IMAGERY, + priorityFunction : priorityFunction + }); + imagery.request = request; imagery.state = ImageryState.TRANSITIONING; - var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level); + var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level, request); if (!defined(imagePromise)) { // Too many parallel requests, so postpone loading tile. imagery.state = ImageryState.UNLOADED; + imagery.request = undefined; return; } @@ -166998,7 +187695,6 @@ define('Scene/ImageryLayer',[ return ImageryLayer; }); -/*global define*/ define('Scene/GlobeSurfaceTileProvider',[ '../Core/BoundingSphere', '../Core/BoxOutlineGeometry', @@ -167020,6 +187716,7 @@ define('Scene/GlobeSurfaceTileProvider',[ '../Core/Math', '../Core/Matrix4', '../Core/OrientedBoundingBox', + '../Core/OrthographicFrustum', '../Core/PrimitiveType', '../Core/Rectangle', '../Core/SphereOutlineGeometry', @@ -167039,7 +187736,6 @@ define('Scene/GlobeSurfaceTileProvider',[ '../Scene/Primitive', './GlobeSurfaceTile', './ImageryLayer', - './OrthographicFrustum', './QuadtreeTileLoadState', './SceneMode', './ShadowMode' @@ -167064,6 +187760,7 @@ define('Scene/GlobeSurfaceTileProvider',[ CesiumMath, Matrix4, OrientedBoundingBox, + OrthographicFrustum, PrimitiveType, Rectangle, SphereOutlineGeometry, @@ -167083,7 +187780,6 @@ define('Scene/GlobeSurfaceTileProvider',[ Primitive, GlobeSurfaceTile, ImageryLayer, - OrthographicFrustum, QuadtreeTileLoadState, SceneMode, ShadowMode) { @@ -167293,16 +187989,14 @@ define('Scene/GlobeSurfaceTileProvider',[ var creditDisplay = frameState.creditDisplay; if (this._terrainProvider.ready && defined(this._terrainProvider.credit)) { - //acevedo - // disable credits - //creditDisplay.addCredit(imageryProvider.credit); + //acevedo + //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)) { //acevedo - // disable credits //creditDisplay.addCredit(imageryProvider.credit); } } @@ -167588,11 +188282,99 @@ define('Scene/GlobeSurfaceTileProvider',[ return destroyObject(this); }; + function getTileReadyCallback(tileImageriesToFree, layer, terrainProvider) { + return function(tile) { + var tileImagery; + var imagery; + var startIndex = -1; + var tileImageryCollection = tile.data.imagery; + var length = tileImageryCollection.length; + var i; + for (i = 0; i < length; ++i) { + tileImagery = tileImageryCollection[i]; + imagery = defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery); + if (imagery.imageryLayer === layer) { + startIndex = i; + break; + } + } + + if (startIndex !== -1) { + var endIndex = startIndex + tileImageriesToFree; + tileImagery = tileImageryCollection[endIndex]; + imagery = defined(tileImagery) ? defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery) : undefined; + if (!defined(imagery) || imagery.imageryLayer !== layer) { + // Return false to keep the callback if we have to wait on the skeletons + // Return true to remove the callback if something went wrong + return !(layer._createTileImagerySkeletons(tile, terrainProvider, endIndex)); + } + + for (i = startIndex; i < endIndex; ++i) { + tileImageryCollection[i].freeResources(); + } + + tileImageryCollection.splice(startIndex, tileImageriesToFree); + } + + return true; // Everything is done, so remove the callback + }; + } + GlobeSurfaceTileProvider.prototype._onLayerAdded = function(layer, index) { if (layer.show) { var terrainProvider = this._terrainProvider; - // create TileImagerys for this layer for all previously loaded tiles + var that = this; + var imageryProvider = layer.imageryProvider; + imageryProvider._reload = function() { + // Clear the layer's cache + layer._imageryCache = {}; + + that._quadtree.forEachLoadedTile(function(tile) { + if (tile.state !== QuadtreeTileLoadState.DONE) { + return; + } + + var i; + + // Figure out how many TileImageries we will need to remove and where to insert new ones + var tileImageryCollection = tile.data.imagery; + var length = tileImageryCollection.length; + var startIndex = -1; + var tileImageriesToFree = 0; + for (i = 0; i < length; ++i) { + var tileImagery = tileImageryCollection[i]; + var imagery = defaultValue(tileImagery.readyImagery, tileImagery.loadingImagery); + if (imagery.imageryLayer === layer) { + if (startIndex === -1) { + startIndex = i; + } + + ++tileImageriesToFree; + } else if (startIndex !== -1) { + // iterated past the section of TileImageries belonging to this layer, no need to continue. + break; + } + } + + if (startIndex === -1) { + return; + } + + // Insert immediately after existing TileImageries + var insertionPoint = startIndex + tileImageriesToFree; + + // Create new TileImageries for all loaded tiles + if (layer._createTileImagerySkeletons(tile, terrainProvider, insertionPoint)) { + // Add callback to remove old TileImageries when the new TileImageries are ready + tile._loadedCallbacks.push(getTileReadyCallback(tileImageriesToFree, layer, terrainProvider)); + + tile.state = QuadtreeTileLoadState.LOADING; + } + }); + }; + + // create TileImageries for this layer for all previously loaded tiles this._quadtree.forEachLoadedTile(function(tile) { if (layer._createTileImagerySkeletons(tile, terrainProvider)) { tile.state = QuadtreeTileLoadState.LOADING; @@ -167633,6 +188415,10 @@ define('Scene/GlobeSurfaceTileProvider',[ tileImageryCollection.splice(startIndex, numDestroyed); } }); + + if (defined(layer.imageryProvider)) { + layer.imageryProvider._reload = undefined; + } }; GlobeSurfaceTileProvider.prototype._onLayerMoved = function(layer, newIndex, oldIndex) { @@ -167829,10 +188615,10 @@ define('Scene/GlobeSurfaceTileProvider',[ (function() { var instanceOBB = new GeometryInstance({ - geometry: BoxOutlineGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0) }) + geometry : BoxOutlineGeometry.fromDimensions({dimensions : new Cartesian3(2.0, 2.0, 2.0)}) }); var instanceSphere = new GeometryInstance({ - geometry: new SphereOutlineGeometry({ radius: 1.0 }) + geometry : new SphereOutlineGeometry({radius : 1.0}) }); var modelMatrix = new Matrix4(); var previousVolume; @@ -168114,8 +188900,7 @@ define('Scene/GlobeSurfaceTileProvider',[ var credits = imagery.credits; for (var creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex) { //acevedo - // disable credits - //creditDisplay.addCredit(imageryProvider.credit); + //creditDisplay.addCredit(credits[creditIndex]); } } @@ -168204,7 +188989,6 @@ define('Scene/GlobeSurfaceTileProvider',[ return GlobeSurfaceTileProvider; }); -/*global define*/ define('Scene/ImageryLayerCollection',[ '../Core/defaultValue', '../Core/defined', @@ -168689,7 +189473,7 @@ define('Scene/ImageryLayerCollection',[ * * @example * layerCollection = layerCollection && layerCollection.destroy(); - * + * * @see ImageryLayerCollection#isDestroyed */ ImageryLayerCollection.prototype.destroy = function() { @@ -168702,7 +189486,8 @@ define('Scene/ImageryLayerCollection',[ var layers = this._layers; var layersShownOrHidden; var layer; - for (var i = 0, len = layers.length; i < len; ++i) { + var i, len; + for (i = 0, len = layers.length; i < len; ++i) { layer = layers[i]; layer._layerIndex = i; @@ -168736,7 +189521,6 @@ define('Scene/ImageryLayerCollection',[ return ImageryLayerCollection; }); -/*global define*/ define('Scene/QuadtreeOccluders',[ '../Core/Cartesian3', '../Core/defineProperties', @@ -168777,7 +189561,6 @@ define('Scene/QuadtreeOccluders',[ return QuadtreeOccluders; }); -/*global define*/ define('Scene/QuadtreeTile',[ '../Core/defined', '../Core/defineProperties', @@ -168828,10 +189611,12 @@ define('Scene/QuadtreeTile',[ // distance - for example, by using the natural ordering of a quadtree. // QuadtreePrimitive gets/sets this private property. this._distance = 0.0; + this._priorityFunction = undefined; this._customData = []; this._frameUpdated = undefined; this._frameRendered = undefined; + this._loadedCallbacks = []; /** * Gets or sets the current state of the tile in the tile load pipeline. @@ -169184,7 +189969,6 @@ define('Scene/QuadtreeTile',[ return QuadtreeTile; }); -/*global define*/ define('Scene/TileReplacementQueue',[ '../Core/defined' ], function( @@ -169309,7 +190093,6 @@ define('Scene/TileReplacementQueue',[ return TileReplacementQueue; }); -/*global define*/ define('Scene/QuadtreePrimitive',[ '../Core/Cartesian3', '../Core/Cartographic', @@ -169320,10 +190103,10 @@ define('Scene/QuadtreePrimitive',[ '../Core/Event', '../Core/getTimestamp', '../Core/Math', + '../Core/OrthographicFrustum', '../Core/Ray', '../Core/Rectangle', '../Core/Visibility', - './OrthographicFrustum', './QuadtreeOccluders', './QuadtreeTile', './QuadtreeTileLoadState', @@ -169339,10 +190122,10 @@ define('Scene/QuadtreePrimitive',[ Event, getTimestamp, CesiumMath, + OrthographicFrustum, Ray, Rectangle, Visibility, - OrthographicFrustum, QuadtreeOccluders, QuadtreeTile, QuadtreeTileLoadState, @@ -169403,7 +190186,6 @@ define('Scene/QuadtreePrimitive',[ 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 = []; @@ -169891,20 +190673,18 @@ define('Scene/QuadtreePrimitive',[ 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 { - 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); - } + // Camera in northeast quadrant + queueChildTileLoad(primitive, northeast); + queueChildTileLoad(primitive, northwest); + queueChildTileLoad(primitive, southeast); + queueChildTileLoad(primitive, southwest); } } @@ -169939,20 +190719,18 @@ define('Scene/QuadtreePrimitive',[ 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 { - 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); - } + // 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); } } @@ -170075,7 +190853,8 @@ define('Scene/QuadtreePrimitive',[ var customDataLength = customData.length; var timeSliceMax = false; - for (var i = primitive._lastTileIndex; i < customDataLength; ++i) { + var i; + for (i = primitive._lastTileIndex; i < customDataLength; ++i) { var data = customData[i]; if (tile.level > data.level) { @@ -170176,7 +190955,6 @@ define('Scene/QuadtreePrimitive',[ return QuadtreePrimitive; }); -/*global define*/ define('Scene/Globe',[ '../Core/BoundingSphere', '../Core/buildModuleUrl', @@ -170759,7 +191537,6 @@ define('Scene/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\ @@ -170772,7 +191549,6 @@ void main() \n\ }\n\ "; }); -/*global define*/ define('Scene/GlobeDepth',[ '../Core/BoundingRectangle', '../Core/Color', @@ -171043,45 +191819,46 @@ define('Scene/GlobeDepth',[ return GlobeDepth; }); -/*global define*/ define('Scene/GoogleEarthEnterpriseImageryProvider',[ - '../Core/Credit', - '../Core/decodeGoogleEarthEnterpriseData', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/DeveloperError', - '../Core/Event', - '../Core/GeographicTilingScheme', - '../Core/GoogleEarthEnterpriseMetadata', - '../Core/loadArrayBuffer', - '../Core/loadImageFromTypedArray', - '../Core/Math', - '../Core/Rectangle', - '../Core/RuntimeError', - '../Core/throttleRequestByServer', - '../Core/TileProviderError', - '../ThirdParty/protobuf-minimal', - '../ThirdParty/when' -], function( - Credit, - decodeGoogleEarthEnterpriseData, - defaultValue, - defined, - defineProperties, - DeveloperError, - Event, - GeographicTilingScheme, - GoogleEarthEnterpriseMetadata, - loadArrayBuffer, - loadImageFromTypedArray, - CesiumMath, - Rectangle, - RuntimeError, - throttleRequestByServer, - TileProviderError, - protobuf, - when) { + '../Core/Credit', + '../Core/decodeGoogleEarthEnterpriseData', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/GeographicTilingScheme', + '../Core/GoogleEarthEnterpriseMetadata', + '../Core/loadArrayBuffer', + '../Core/loadImageFromTypedArray', + '../Core/Math', + '../Core/Rectangle', + '../Core/Request', + '../Core/RequestType', + '../Core/RuntimeError', + '../Core/TileProviderError', + '../ThirdParty/protobuf-minimal', + '../ThirdParty/when' + ], function( + Credit, + decodeGoogleEarthEnterpriseData, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + GoogleEarthEnterpriseMetadata, + loadArrayBuffer, + loadImageFromTypedArray, + CesiumMath, + Rectangle, + Request, + RequestType, + RuntimeError, + TileProviderError, + protobuf, + when) { 'use strict'; function GoogleEarthEnterpriseDiscardPolicy() { @@ -171109,6 +191886,9 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ /** * Provides tiled imagery using the Google Earth Enterprise REST API. * + * Notes: This provider is for use with the 3D Earth API of Google Earth Enterprise, + * {@link GoogleEarthEnterpriseMapsProvider} should be used with 2D Maps API. + * * @alias GoogleEarthEnterpriseImageryProvider * @constructor * @@ -171125,7 +191905,7 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ * * @see GoogleEarthEnterpriseTerrainProvider * @see ArcGisMapServerImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider * @see createTileMapServiceImageryProvider @@ -171427,6 +192207,7 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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 @@ -171434,7 +192215,7 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level) { + GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level, request) { var invalidImage = this._tileDiscardPolicy._image; // Empty image or undefined depending on discard policy var metadata = this._metadata; @@ -171442,11 +192223,16 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ var info = metadata.getTileInformation(x, y, level); if (!defined(info)) { if (metadata.isValid(quadKey)) { - metadata.populateSubtree(x, y, level); + var metadataRequest = new Request({ + throttle : request.throttle, + throttleByServer : request.throttleByServer, + type : request.type, + priorityFunction : request.priorityFunction + }); + metadata.populateSubtree(x, y, level, metadataRequest); return undefined; // No metadata so return undefined so we can be loaded later - } else { - return invalidImage; // Image doesn't exist } + return invalidImage; // Image doesn't exist } if (!info.hasImagery()) { @@ -171455,9 +192241,9 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ } // Load the var url = buildImageUrl(this, info, x, y, level); - var promise = throttleRequestByServer(url, loadArrayBuffer); + var promise = loadArrayBuffer(url, undefined, request); if (!defined(promise)) { - return undefined; //Throttled + return undefined; // Throttled } return promise @@ -171604,8 +192390,7 @@ define('Scene/GoogleEarthEnterpriseImageryProvider',[ return GoogleEarthEnterpriseImageryProvider; }); -/*global define*/ -define('Scene/GoogleEarthImageryProvider',[ +define('Scene/GoogleEarthEnterpriseMapsProvider',[ '../Core/Credit', '../Core/defaultValue', '../Core/defined', @@ -171649,7 +192434,10 @@ define('Scene/GoogleEarthImageryProvider',[ * and add the 'Header set Access-Control-Allow-Origin "*"' option to the '<Directory />' and * '<Directory "/opt/google/gehttpd/htdocs">' directives. * - * @alias GoogleEarthImageryProvider + * This provider is for use with 2D Maps API as part of Google Earth Enterprise. For 3D Earth API uses, it + * is necessary to use {@link GoogleEarthEnterpriseImageryProvider} + * + * @alias GoogleEarthEnterpriseMapsProvider * @constructor * * @param {Object} options Object with the following properties: @@ -171697,14 +192485,14 @@ define('Scene/GoogleEarthImageryProvider',[ * * * @example - * var google = new Cesium.GoogleEarthImageryProvider({ + * var google = new Cesium.GoogleEarthEnterpriseMapsProvider({ * url : 'https://earth.localdomain', * channel : 1008 * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} */ - function GoogleEarthImageryProvider(options) { + function GoogleEarthEnterpriseMapsProvider(options) { options = defaultValue(options, {}); @@ -171714,7 +192502,7 @@ define('Scene/GoogleEarthImageryProvider',[ 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'); + this._credit = new Credit('Google Imagery', GoogleEarthEnterpriseMapsProvider._logoData, 'http://www.google.com/enterprise/mapsearth/products/earthenterprise.html'); /** * The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider. @@ -171823,10 +192611,10 @@ define('Scene/GoogleEarthImageryProvider',[ requestMetadata(); } - defineProperties(GoogleEarthImageryProvider.prototype, { + defineProperties(GoogleEarthEnterpriseMapsProvider.prototype, { /** * Gets the URL of the Google Earth MapServer. - * @memberof GoogleEarthImageryProvider.prototype + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {String} * @readonly */ @@ -171838,7 +192626,7 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets the url path of the data on the Google Earth server. - * @memberof GoogleEarthImageryProvider.prototype + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {String} * @readonly */ @@ -171850,7 +192638,7 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets the proxy used by this provider. - * @memberof GoogleEarthImageryProvider.prototype + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Proxy} * @readonly */ @@ -171862,7 +192650,7 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets the imagery channel (id) currently being used. - * @memberof GoogleEarthImageryProvider.prototype + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Number} * @readonly */ @@ -171874,8 +192662,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets the width of each tile, in pixels. This function should - * not be called before {@link GoogleEarthImageryProvider#ready} returns true. - * @memberof GoogleEarthImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Number} * @readonly */ @@ -171888,8 +192676,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets the height of each tile, in pixels. This function should - * not be called before {@link GoogleEarthImageryProvider#ready} returns true. - * @memberof GoogleEarthImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Number} * @readonly */ @@ -171902,8 +192690,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * 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 + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Number} * @readonly */ @@ -171916,8 +192704,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * 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 + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Number} * @readonly */ @@ -171930,8 +192718,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets the tiling scheme used by this provider. This function should - * not be called before {@link GoogleEarthImageryProvider#ready} returns true. - * @memberof GoogleEarthImageryProvider.prototype + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {TilingScheme} * @readonly */ @@ -171944,8 +192732,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * 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 + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Number} * @readonly */ @@ -171958,8 +192746,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * 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 + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {String} * @readonly */ @@ -171971,8 +192759,8 @@ define('Scene/GoogleEarthImageryProvider',[ }, /** * 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 + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Rectangle} * @readonly */ @@ -171987,8 +192775,8 @@ define('Scene/GoogleEarthImageryProvider',[ * 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 + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {TileDiscardPolicy} * @readonly */ @@ -172003,7 +192791,7 @@ define('Scene/GoogleEarthImageryProvider',[ * 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 + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Event} * @readonly */ @@ -172015,7 +192803,7 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets a value indicating whether or not the provider is ready for use. - * @memberof GoogleEarthImageryProvider.prototype + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Boolean} * @readonly */ @@ -172027,7 +192815,7 @@ define('Scene/GoogleEarthImageryProvider',[ /** * Gets a promise that resolves to true when the provider is ready for use. - * @memberof GoogleEarthImageryProvider.prototype + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Promise.} * @readonly */ @@ -172039,8 +192827,8 @@ define('Scene/GoogleEarthImageryProvider',[ /** * 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 + * the source of the imagery. This function should not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Credit} * @readonly */ @@ -172056,7 +192844,7 @@ define('Scene/GoogleEarthImageryProvider',[ * 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 + * @memberof GoogleEarthEnterpriseMapsProvider.prototype * @type {Boolean} * @readonly */ @@ -172077,17 +192865,18 @@ define('Scene/GoogleEarthImageryProvider',[ * * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. */ - GoogleEarthImageryProvider.prototype.getTileCredits = function(x, y, level) { + GoogleEarthEnterpriseMapsProvider.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. + * not be called before {@link GoogleEarthEnterpriseMapsProvider#ready} returns true. * * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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 @@ -172095,10 +192884,10 @@ define('Scene/GoogleEarthImageryProvider',[ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - GoogleEarthImageryProvider.prototype.requestImage = function(x, y, level) { + GoogleEarthEnterpriseMapsProvider.prototype.requestImage = function(x, y, level, request) { var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, request); }; /** @@ -172115,11 +192904,11 @@ define('Scene/GoogleEarthImageryProvider',[ * 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(x, y, level, longitude, latitude) { + GoogleEarthEnterpriseMapsProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { 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='; + GoogleEarthEnterpriseMapsProvider._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; @@ -172137,11 +192926,10 @@ define('Scene/GoogleEarthImageryProvider',[ return imageUrl; } - return GoogleEarthImageryProvider; + return GoogleEarthEnterpriseMapsProvider; }); -/*global define*/ define('Scene/GridImageryProvider',[ '../Core/Color', '../Core/defaultValue', @@ -172464,12 +193252,13 @@ define('Scene/GridImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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) { + GridImageryProvider.prototype.requestImage = function(x, y, level, request) { return this._canvas; }; @@ -172494,7 +193283,6 @@ define('Scene/GridImageryProvider',[ return GridImageryProvider; }); -/*global define*/ define('Scene/JobScheduler',[ '../Core/defined', '../Core/defineProperties', @@ -172640,7 +193428,8 @@ define('Scene/JobScheduler',[ if ((budget.usedThisFrame + budget.stolenFromMeThisFrame >= budget.total)) { // No budget remaining for jobs of this type. Try to steal from other job types. var length = budgets.length; - for (var i = 0; i < length; ++i) { + var i; + for (i = 0; i < length; ++i) { stolenBudget = budgets[i]; // Steal from this budget if it has time left and it wasn't starved last fame @@ -172650,7 +193439,7 @@ define('Scene/JobScheduler',[ } } - if ((i === length) && progressThisFrame) { + if (i === length && progressThisFrame) { // No other job types can give up their budget this frame, and // this job type already progressed this frame return false; @@ -172684,7 +193473,6 @@ define('Scene/JobScheduler',[ return JobScheduler; }); -/*global define*/ define('Scene/MapboxImageryProvider',[ '../Core/Credit', '../Core/defaultValue', @@ -172998,6 +193786,7 @@ define('Scene/MapboxImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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 @@ -173005,8 +193794,8 @@ define('Scene/MapboxImageryProvider',[ * * @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); + MapboxImageryProvider.prototype.requestImage = function(x, y, level, request) { + return this._imageryProvider.requestImage(x, y, level, request); }; /** @@ -173034,7 +193823,6 @@ define('Scene/MapboxImageryProvider',[ return MapboxImageryProvider; }); -/*global define*/ define('Scene/Moon',[ '../Core/buildModuleUrl', '../Core/Cartesian3', @@ -173081,7 +193869,7 @@ define('Scene/Moon',[ * * @example * scene.moon = new Cesium.Moon(); - * + * * @see Scene#moon */ function Moon(options) { @@ -173213,7 +194001,7 @@ define('Scene/Moon',[ * * @example * moon = moon && moon.destroy(); - * + * * @see Moon#isDestroyed */ Moon.prototype.destroy = function() { @@ -173224,7 +194012,6 @@ define('Scene/Moon',[ return Moon; }); -/*global define*/ define('Scene/NeverTileDiscardPolicy',[], function() { 'use strict'; @@ -173261,7 +194048,6 @@ define('Scene/NeverTileDiscardPolicy',[], function() { }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/AdjustTranslucentFS',[],function() { 'use strict'; return "#ifdef MRT\n\ @@ -173291,7 +194077,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/CompositeOITFS',[],function() { 'use strict'; return "/**\n\ @@ -173327,7 +194112,6 @@ void main()\n\ }\n\ "; }); -/*global define*/ define('Scene/OIT',[ '../Core/BoundingRectangle', '../Core/Color', @@ -173982,7 +194766,1057 @@ define('Scene/OIT',[ return OIT; }); -/*global define*/ +define('Scene/Particle',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties' + ], function( + Cartesian2, + Cartesian3, + Color, + defaultValue, + defined, + defineProperties) { + 'use strict'; + + var defaultSize = new Cartesian2(1.0, 1.0); + + /** + * A particle emitted by a {@link ParticleSystem}. + * + * @alias Particle + * @constructor + * + * @param {Object} options An object with the following properties: + * @param {Number} [options.mass=1.0] The mass of particles in kilograms. + * @param {Cartesian3} [options.position=Cartesian3.ZERO] The initial position of the particle in world coordinates. + * @param {Cartesian3} [options.velocity=Cartesian3.ZERO] The velocity vector of the particle in world coordinates. + * @param {Number} [options.life=Number.MAX_VALUE] The life of particles in seconds. + * @param {Object} [options.image] The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard. + * @param {Color} [options.startColor=Color.WHITE] The color of a particle when it is born. + * @param {Color} [options.endColor=Color.WHITE] The color of a particle when it dies. + * @param {Number} [options.startScale=1.0] The scale of the particle when it is born. + * @param {Number} [options.endScale=1.0] The scale of the particle when it dies. + * @param {Cartesian2} [options.size=new Cartesian2(1.0, 1.0)] The dimensions of particles in pixels. + */ + function Particle(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The mass of the particle in kilograms. + * @type {Number} + * @default 1.0 + */ + this.mass = defaultValue(options.mass, 1.0); + /** + * The positon of the particle in world coordinates. + * @type {Cartesian3} + * @default Cartesian3.ZERO + */ + this.position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO)); + /** + * The velocity of the particle in world coordinates. + * @type {Cartesian3} + * @default Cartesian3.ZERO + */ + this.velocity = Cartesian3.clone(defaultValue(options.velocity, Cartesian3.ZERO)); + /** + * The life of the particle in seconds. + * @type {Number} + * @default Number.MAX_VALUE + */ + this.life = defaultValue(options.life, Number.MAX_VALUE); + /** + * The image to use for the particle. + * @type {Object} + * @default undefined + */ + this.image = options.image; + /** + * The color of the particle when it is born. + * @type {Color} + * @default Color.WHITE + */ + this.startColor = Color.clone(defaultValue(options.startColor, Color.WHITE)); + /** + * The color of the particle when it dies. + * @type {Color} + * @default Color.WHITE + */ + this.endColor = Color.clone(defaultValue(options.endColor, Color.WHITE)); + /** + * the scale of the particle when it is born. + * @type {Number} + * @default 1.0 + */ + this.startScale = defaultValue(options.startScale, 1.0); + /** + * The scale of the particle when it dies. + * @type {Number} + * @default 1.0 + */ + this.endScale = defaultValue(options.endScale, 1.0); + /** + * The dimensions of the particle in pixels. + * @type {Cartesian2} + * @default new Cartesian(1.0, 1.0) + */ + this.size = Cartesian2.clone(defaultValue(options.size, defaultSize)); + + this._age = 0.0; + this._normalizedAge = 0.0; + + // used by ParticleSystem + this._billboard = undefined; + } + + defineProperties(Particle.prototype, { + /** + * Gets the age of the particle in seconds. + * @memberof Particle.prototype + * @type {Number} + */ + age : { + get : function() { + return this._age; + } + }, + /** + * Gets the age normalized to a value in the range [0.0, 1.0]. + * @memberof Particle.prototype + * @type {Number} + */ + normalizedAge : { + get : function() { + return this._normalizedAge; + } + } + }); + + var deltaScratch = new Cartesian3(); + + /** + * @private + */ + Particle.prototype.update = function(dt, forces) { + // Apply the velocity + Cartesian3.multiplyByScalar(this.velocity, dt, deltaScratch); + Cartesian3.add(this.position, deltaScratch, this.position); + + // Update any forces. + if (defined(forces)) { + var length = forces.length; + for (var i = 0; i < length; ++i) { + var force = forces[i]; + if (typeof force === 'function') { + // Force is just a simple callback function. + force(this, dt); + } + } + } + + // Age the particle + this._age += dt; + + // Compute the normalized age. + if (this.life === Number.MAX_VALUE) { + this._normalizedAge = 0.0; + } else { + this._normalizedAge = this._age / this.life; + } + + // If this particle is older than it's lifespan then die. + return this._age <= this.life; + }; + + return Particle; +}); + +define('Scene/ParticleBurst',[ + '../Core/defaultValue', + '../Core/defineProperties' + ], function( + defaultValue, + defineProperties) { + 'use strict'; + + /** + * Represents a burst of {@link Particle}s from a {@link ParticleSystem} at a given time in the systems lifetime. + * + * @alias ParticleBurst + * @constructor + * + * @param {Object} [options] An object with the following properties: + * @param {Number} [options.time=0.0] The time in seconds after the beginning of the particle system's lifetime that the burst will occur. + * @param {Number} [options.minimum=0.0] The minimum number of particles emmitted in the burst. + * @param {Number} [options.maximum=50.0] The maximum number of particles emitted in the burst. + */ + function ParticleBurst(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The time in seconds after the eginning of the particle system's lifetime that the burst will occur. + * @type {Number} + * @default 0.0 + */ + this.time = defaultValue(options.time, 0.0); + /** + * The minimum number of particles emitted. + * @type {Number} + * @default 0.0 + */ + this.minimum = defaultValue(options.minimum, 0.0); + /** + * The maximum number of particles emitted. + * @type {Number} + * @default 50.0 + */ + this.maximum = defaultValue(options.maximum, 50.0); + + this._complete = false; + } + + defineProperties(ParticleBurst.prototype, { + /** + * true if the burst has been completed; false otherwise. + * @memberof ParticleBurst.prototype + * @type {Boolean} + */ + complete : { + get : function() { + return this._complete; + } + } + }); + + return ParticleBurst; +}); + +define('Scene/ParticleEmitter',[ + '../Core/defineProperties', + '../Core/DeveloperError' + ], function( + defineProperties, + DeveloperError) { + 'use strict'; + + /** + *

    + * An object that initializes a {@link Particle} from a {@link ParticleSystem}. + *

    + *

    + * This type describes an interface and is not intended to be instantiated directly. + *

    + * + * @alias ParticleEmitter + * @constructor + * + * @see BoxEmitter + * @see CircleEmitter + * @see ConeEmitter + * @see SphereEmitter + */ + function ParticleEmitter(options) { + } + + /** + * Initializes the given {Particle} by setting it's position and velocity. + * + * @private + * @param {Particle} The particle to initialize + */ + ParticleEmitter.prototype.emit = function(particle) { + DeveloperError.throwInstantiationError(); + }; + + return ParticleEmitter; +}); + +define('Scene/ParticleSystem',[ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Check', + '../Core/Color', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/Event', + '../Core/JulianDate', + '../Core/Math', + '../Core/Matrix4', + './BillboardCollection', + './CircleEmitter', + './Particle' + ], function( + Cartesian2, + Cartesian3, + Check, + Color, + defaultValue, + defined, + defineProperties, + destroyObject, + Event, + JulianDate, + CesiumMath, + Matrix4, + BillboardCollection, + CircleEmitter, + Particle) { + 'use strict'; + + /** + * A ParticleSystem manages the updating and display of a collection of particles. + * + * @alias ParticleSystem + * @constructor + * + * @param {Object} [options] Object with the following properties: + * @param {Boolean} [options.show=true] Whether to display the particle system. + * @param {ParticleSystem~applyForce[]} [options.forces] An array of force callbacks. + * @param {ParticleEmitter} [options.emitter=new CircleEmitter(0.5)] The particle emitter for this system. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the particle system from model to world coordinates. + * @param {Matrix4} [options.emitterModelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the particle system emitter within the particle systems local coordinate system. + * @param {Color} [options.startColor=Color.WHITE] The color of a particle when it is born. + * @param {Color} [options.endColor=Color.WHITE] The color of a particle when it dies. + * @param {Number} [options.startScale=1.0] The scale of the particle when it is born. + * @param {Number} [options.endScale=1.0] The scale of the particle when it dies. + * @param {Number} [options.rate=5] The number of particles to emit per second. + * @param {ParticleBurst[]} [options.bursts] An array of {@link ParticleBurst}, emitting bursts of particles at periodic times. + * @param {Boolean} [options.loop=true] Whether the particle system should loop it's bursts when it is complete. + * @param {Number} [options.speed] Sets the minimum and maximum speed in meters per second + * @param {Number} [options.minimumSpeed=1.0] Sets the minimum speed in meters per second. + * @param {Number} [options.maximumSpeed=1.0] Sets the maximum speed in meters per second. + * @param {Number} [options.life] Sets the minimum and maximum life of particles in seconds. + * @param {Number} [options.minimumLife=5.0] Sets the minimum life of particles in seconds. + * @param {Number} [options.maximumLife=5.0] Sets the maximum life of particles in seconds. + * @param {Number} [options.mass] Sets the minimum and maximum mass of particles in kilograms. + * @param {Number} [options.minimumMass=1.0] Sets the minimum mass of particles in kilograms. + * @param {Number} [options.maximumMass=1.0] Sets the maximum mass of particles in kilograms. + * @param {Object} [options.image] The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard. + * @param {Number} [options.width] Sets the minimum and maximum width of particles in pixels. + * @param {Number} [options.minimumWidth=1.0] Sets the minimum width of particles in pixels. + * @param {Number} [options.maximumWidth=1.0] Sets the maximum width of particles in pixels. + * @param {Number} [options.height] Sets the minimum and maximum height of particles in pixels. + * @param {Number} [options.minimumHeight=1.0] Sets the minimum height of particles in pixels. + * @param {Number} [options.maximumHeight=1.0] Sets the maximum height of particles in pixels. + * @param {Number} [options.lifeTime=Number.MAX_VALUE] How long the particle system will emit particles, in seconds. + * + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=ParticleSystem.html|Particle Systems Demo} + */ + function ParticleSystem(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * Whether to display the particle system. + * @type {Boolean} + * @default true + */ + this.show = defaultValue(options.show, true); + + /** + * An array of force callbacks. The callback is passed a {@link Particle} and the difference from the last time + * @type {ParticleSystem~applyForce[]} + * @default undefined + */ + this.forces = options.forces; + + /** + * Whether the particle system should loop it's bursts when it is complete. + * @type {Boolean} + * @default true + */ + this.loop = defaultValue(options.loop, true); + + /** + * The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard. + * @type {Object} + * @default undefined + */ + this.image = defaultValue(options.image, undefined); + + var emitter = options.emitter; + if (!defined(emitter)) { + emitter = new CircleEmitter(0.5); + } + this._emitter = emitter; + + this._bursts = options.bursts; + + this._modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + this._emitterModelMatrix = Matrix4.clone(defaultValue(options.emitterModelMatrix, Matrix4.IDENTITY)); + this._matrixDirty = true; + this._combinedMatrix = new Matrix4(); + + this._startColor = Color.clone(defaultValue(options.startColor, Color.WHITE)); + this._endColor = Color.clone(defaultValue(options.endColor, Color.WHITE)); + + this._startScale = defaultValue(options.startScale, 1.0); + this._endScale = defaultValue(options.endScale, 1.0); + + this._rate = defaultValue(options.rate, 5); + + this._minimumSpeed = defaultValue(options.speed, defaultValue(options.minimumSpeed, 1.0)); + this._maximumSpeed = defaultValue(options.speed, defaultValue(options.maximumSpeed, 1.0)); + + this._minimumLife = defaultValue(options.life, defaultValue(options.minimumLife, 5.0)); + this._maximumLife = defaultValue(options.life, defaultValue(options.maximumLife, 5.0)); + + this._minimumMass = defaultValue(options.mass, defaultValue(options.minimumMass, 1.0)); + this._maximumMass = defaultValue(options.mass, defaultValue(options.maximumMass, 1.0)); + + this._minimumWidth = defaultValue(options.width, defaultValue(options.minimumWidth, 1.0)); + this._maximumWidth = defaultValue(options.width, defaultValue(options.maximumWidth, 1.0)); + + this._minimumHeight = defaultValue(options.height, defaultValue(options.minimumHeight, 1.0)); + this._maximumHeight = defaultValue(options.height, defaultValue(options.maximumHeight, 1.0)); + + this._lifeTime = defaultValue(options.lifeTime, Number.MAX_VALUE); + + this._billboardCollection = undefined; + this._particles = []; + + // An array of available particles that we can reuse instead of allocating new. + this._particlePool = []; + + this._previousTime = undefined; + this._currentTime = 0.0; + this._carryOver = 0.0; + + this._complete = new Event(); + this._isComplete = false; + + this._updateParticlePool = true; + this._particleEstimate = 0; + } + + defineProperties(ParticleSystem.prototype, { + /** + * The particle emitter for this + * @memberof ParticleSystem.prototype + * @type {ParticleEmitter} + * @default CricleEmitter + */ + emitter : { + get : function() { + return this._emitter; + }, + set : function(value) { + this._emitter = value; + } + }, + /** + * An array of {@link ParticleBurst}, emitting bursts of particles at periodic times. + * @type {ParticleBurst[]} + * @default undefined + */ + bursts : { + get : function() { + return this._bursts; + }, + set : function(value) { + this._bursts = value; + this._updateParticlePool = true; + } + }, + /** + * The 4x4 transformation matrix that transforms the particle system from model to world coordinates. + * @memberof ParticleSystem.prototype + * @type {Matrix4} + * @default Matrix4.IDENTITY + */ + modelMatrix : { + get : function() { + return this._modelMatrix; + }, + set : function(value) { + this._matrixDirty = this._matrixDirty || !Matrix4.equals(this._modelMatrix, value); + Matrix4.clone(value, this._modelMatrix); + } + }, + /** + * The 4x4 transformation matrix that transforms the particle system emitter within the particle systems local coordinate system. + * @memberof ParticleSystem.prototype + * @type {Matrix4} + * @default Matrix4.IDENTITY + */ + emitterModelMatrix : { + get : function() { + return this._emitterModelMatrix; + }, + set : function(value) { + this._matrixDirty = this._matrixDirty || !Matrix4.equals(this._emitterModelMatrix, value); + Matrix4.clone(value, this._emitterModelMatrix); + } + }, + /** + * The color of a particle when it is born. + * @memberof ParticleSystem.prototype + * @type {Color} + * @default Color.WHITE + */ + startColor : { + get : function() { + return this._startColor; + }, + set : function(value) { + Color.clone(value, this._startColor); + } + }, + /** + * The color of a particle when it dies. + * @memberof ParticleSystem.prototype + * @type {Color} + * @default Color.WHITE + */ + endColor : { + get : function() { + return this._endColor; + }, + set : function(value) { + Color.clone(value, this._endColor); + } + }, + /** + * The scale of the particle when it is born. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + startScale : { + get : function() { + return this._startScale; + }, + set : function(value) { + this._startScale = value; + } + }, + /** + * The scale of the particle when it dies. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + endScale : { + get : function() { + return this._endScale; + }, + set : function(value) { + this._endScale = value; + } + }, + /** + * The number of particles to emit per second. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 5 + */ + rate : { + get : function() { + return this._rate; + }, + set : function(value) { + this._rate = value; + this._updateParticlePool = true; + } + }, + /** + * Sets the minimum speed in meters per second. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + minimumSpeed : { + get : function() { + return this._minimumSpeed; + }, + set : function(value) { + this._minimumSpeed = value; + } + }, + /** + * Sets the maximum speed in meters per second. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + maximumSpeed : { + get : function() { + return this._maximumSpeed; + }, + set : function(value) { + this._maximumSpeed = value; + } + }, + /** + * Sets the minimum life of particles in seconds. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 5.0 + */ + minimumLife : { + get : function() { + return this._minimumLife; + }, + set : function(value) { + this._minimumLife = value; + } + }, + /** + * Sets the maximum life of particles in seconds. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 5.0 + */ + maximumLife : { + get : function() { + return this._maximumLife; + }, + set : function(value) { + this._maximumLife = value; + this._updateParticlePool = true; + } + }, + /** + * Sets the minimum mass of particles in kilograms. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + minimumMass : { + get : function() { + return this._minimumMass; + }, + set : function(value) { + this._minimumMass = value; + } + }, + /** + * Sets the maximum mass of particles in kilograms. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + maximumMass : { + get : function() { + return this._maximumMass; + }, + set : function(value) { + this._maximumMass = value; + } + }, + /** + * Sets the minimum width of particles in pixels. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + minimumWidth : { + get : function() { + return this._minimumWidth; + }, + set : function(value) { + this._minimumWidth = value; + } + }, + /** + * Sets the maximum width of particles in pixels. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + maximumWidth : { + get : function() { + return this._maximumWidth; + }, + set : function(value) { + this._maximumWidth = value; + } + }, + /** + * Sets the minimum height of particles in pixels. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + minimumHeight : { + get : function() { + return this._minimumHeight; + }, + set : function(value) { + this._minimumHeight = value; + } + }, + /** + * Sets the maximum height of particles in pixels. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default 1.0 + */ + maximumHeight : { + get : function() { + return this._maximumHeight; + }, + set : function(value) { + this._maximumHeight = value; + } + }, + /** + * How long the particle system will emit particles, in seconds. + * @memberof ParticleSystem.prototype + * @type {Number} + * @default Number.MAX_VALUE + */ + lifeTime : { + get : function() { + return this._lifeTime; + }, + set : function(value) { + this._lifeTime = value; + } + }, + /** + * Fires an event when the particle system has reached the end of its lifetime. + * @memberof ParticleSystem.prototype + * @type {Event} + */ + complete : { + get : function() { + return this._complete; + } + }, + /** + * When true, the particle system has reached the end of its lifetime; false otherwise. + * @memberof ParticleSystem.prototype + * @type {Boolean} + */ + isComplete : { + get : function() { + return this._isComplete; + } + } + }); + + function updateParticlePool(system) { + var rate = system._rate; + var life = system._maximumLife; + + var burstAmount = 0; + var bursts = system._bursts; + if (defined(bursts)) { + var length = bursts.length; + for (var i = 0; i < length; ++i) { + burstAmount += bursts[i].maximum; + } + } + + var billboardCollection = system._billboardCollection; + var image = system.image; + + var particleEstimate = Math.ceil(rate * life + burstAmount); + var particles = system._particles; + var particlePool = system._particlePool; + var numToAdd = Math.max(particleEstimate - particles.length - particlePool.length, 0); + + for (var j = 0; j < numToAdd; ++j) { + var particle = new Particle(); + particle._billboard = billboardCollection.add({ + image : image + }); + particlePool.push(particle); + } + + system._particleEstimate = particleEstimate; + } + + function getOrCreateParticle(system) { + // Try to reuse an existing particle from the pool. + var particle = system._particlePool.pop(); + if (!defined(particle)) { + // Create a new one + particle = new Particle(); + } + return particle; + } + + function addParticleToPool(system, particle) { + system._particlePool.push(particle); + } + + function freeParticlePool(system) { + var particles = system._particles; + var particlePool = system._particlePool; + var billboardCollection = system._billboardCollection; + + var numParticles = particles.length; + var numInPool = particlePool.length; + var estimate = system._particleEstimate; + + var start = numInPool - Math.max(estimate - numParticles - numInPool, 0); + for (var i = start; i < numInPool; ++i) { + var p = particlePool[i]; + billboardCollection.remove(p._billboard); + } + particlePool.length = start; + } + + function removeBillboard(particle) { + if (defined(particle._billboard)) { + particle._billboard.show = false; + } + } + + function updateBillboard(system, particle) { + var billboard = particle._billboard; + if (!defined(billboard)) { + billboard = particle._billboard = system._billboardCollection.add({ + image : particle.image + }); + } + billboard.width = particle.size.x; + billboard.height = particle.size.y; + billboard.position = particle.position; + billboard.show = true; + + // Update the color + var r = CesiumMath.lerp(particle.startColor.red, particle.endColor.red, particle.normalizedAge); + var g = CesiumMath.lerp(particle.startColor.green, particle.endColor.green, particle.normalizedAge); + var b = CesiumMath.lerp(particle.startColor.blue, particle.endColor.blue, particle.normalizedAge); + var a = CesiumMath.lerp(particle.startColor.alpha, particle.endColor.alpha, particle.normalizedAge); + billboard.color = new Color(r,g,b,a); + + // Update the scale + var scale = CesiumMath.lerp(particle.startScale, particle.endScale, particle.normalizedAge); + billboard.scale = scale; + } + + function addParticle(system, particle) { + particle.startColor = Color.clone(system._startColor, particle.startColor); + particle.endColor = Color.clone(system._endColor, particle.endColor); + particle.startScale = system._startScale; + particle.endScale = system._endScale; + particle.image = system.image; + particle.life = CesiumMath.randomBetween(system._minimumLife, system._maximumLife); + particle.mass = CesiumMath.randomBetween(system._minimumMass, system._maximumMass); + + var width = CesiumMath.randomBetween(system._minimumWidth, system._maximumWidth); + var height = CesiumMath.randomBetween(system._minimumHeight, system._maximumHeight); + particle.size = Cartesian2.fromElements(width, height, particle.size); + + // Reset the normalizedAge and age in case the particle was reused. + particle._normalizedAge = 0.0; + particle._age = 0.0; + + var speed = CesiumMath.randomBetween(system._minimumSpeed, system._maximumSpeed); + Cartesian3.multiplyByScalar(particle.velocity, speed, particle.velocity); + + system._particles.push(particle); + } + + function calculateNumberToEmit(system, dt) { + // This emitter is finished if it exceeds it's lifetime. + if (system._isComplete) { + return 0; + } + + dt = CesiumMath.mod(dt, system._lifeTime); + + // Compute the number of particles to emit based on the rate. + var v = dt * system._rate; + var numToEmit = Math.floor(v); + system._carryOver += (v - numToEmit); + if (system._carryOver > 1.0) + { + numToEmit++; + system._carryOver -= 1.0; + } + + // Apply any bursts + if (defined(system.bursts)) { + var length = system.bursts.length; + for (var i = 0; i < length; i++) { + var burst = system.bursts[i]; + var currentTime = system._currentTime; + if (defined(burst) && !burst._complete && currentTime > burst.time) { + numToEmit += CesiumMath.randomBetween(burst.minimum, burst.maximum); + burst._complete = true; + } + } + } + + return numToEmit; + } + + var rotatedVelocityScratch = new Cartesian3(); + + /** + * @private + */ + ParticleSystem.prototype.update = function(frameState) { + if (!this.show) { + return; + } + + if (!defined(this._billboardCollection)) { + this._billboardCollection = new BillboardCollection(); + } + + if (this._updateParticlePool) { + updateParticlePool(this); + this._updateParticlePool = false; + } + + // Compute the frame time + var dt = 0.0; + if (this._previousTime) { + dt = JulianDate.secondsDifference(frameState.time, this._previousTime); + } + + if (dt < 0.0) { + dt = 0.0; + } + + var particles = this._particles; + var emitter = this._emitter; + var forces = this.forces; + + var i; + var particle; + + // update particles and remove dead particles + var length = particles.length; + for (i = 0; i < length; ++i) { + particle = particles[i]; + if (!particle.update(dt, forces)) { + removeBillboard(particle); + // Add the particle back to the pool so it can be reused. + addParticleToPool(this, particle); + particles[i] = particles[length - 1]; + --i; + --length; + } else { + updateBillboard(this, particle); + } + } + particles.length = length; + + var numToEmit = calculateNumberToEmit(this, dt); + + if (numToEmit > 0 && defined(emitter)) { + // Compute the final model matrix by combining the particle systems model matrix and the emitter matrix. + if (this._matrixDirty) { + this._combinedMatrix = Matrix4.multiply(this.modelMatrix, this.emitterModelMatrix, this._combinedMatrix); + this._matrixDirty = false; + } + + var combinedMatrix = this._combinedMatrix; + + for (i = 0; i < numToEmit; i++) { + // Create a new particle. + particle = getOrCreateParticle(this); + + // Let the emitter initialize the particle. + this._emitter.emit(particle); + + //For the velocity we need to add it to the original position and then multiply by point. + Cartesian3.add(particle.position, particle.velocity, rotatedVelocityScratch); + Matrix4.multiplyByPoint(combinedMatrix, rotatedVelocityScratch, rotatedVelocityScratch); + + // Change the position to be in world coordinates + particle.position = Matrix4.multiplyByPoint(combinedMatrix, particle.position, particle.position); + + // Orient the velocity in world space as well. + Cartesian3.subtract(rotatedVelocityScratch, particle.position, particle.velocity); + Cartesian3.normalize(particle.velocity, particle.velocity); + + // Add the particle to the system. + addParticle(this, particle); + updateBillboard(this, particle); + } + } + + this._billboardCollection.update(frameState); + this._previousTime = JulianDate.clone(frameState.time, this._previousTime); + this._currentTime += dt; + + if (this._lifeTime !== Number.MAX_VALUE && this._currentTime > this._lifeTime) { + if (this.loop) { + this._currentTime = CesiumMath.mod(this._currentTime, this._lifeTime); + if (this.bursts) { + var burstLength = this.bursts.length; + // Reset any bursts + for (i = 0; i < burstLength; i++) { + this.bursts[i]._complete = false; + } + } + } else { + this._isComplete = true; + this._complete.raiseEvent(this); + } + } + + // free particles in the pool and release billboard GPU memory + if (frameState.frameNumber % 120 === 0) { + freeParticlePool(this); + } + }; + + /** + * 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 ParticleSystem#destroy + */ + ParticleSystem.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 ParticleSystem#isDestroyed + */ + ParticleSystem.prototype.destroy = function() { + this._billboardCollection = this._billboardCollection && this._billboardCollection.destroy(); + return destroyObject(this); + }; + + /** + * A function used to apply a force to the particle on each time step. + * @callback ParticleSystem~applyForce + * + * @param {Particle} particle The particle to apply the force to. + * @param {Number} dt The time since the last update. + * + * @example + * function applyGravity(particle, dt) { + * var position = particle.position; + * var gravityVector = Cesium.Cartesian3.normalize(position, new Cesium.Cartesian3()); + * Cesium.Cartesian3.multiplyByScalar(gravityVector, GRAVITATIONAL_CONSTANT * dt, gravityVector); + * particle.velocity = Cesium.Cartesian3.add(particle.velocity, gravityVector, particle.velocity); + * } + */ + + return ParticleSystem; +}); + define('Widgets/getElement',[ '../Core/DeveloperError' ], function( @@ -174009,7 +195843,6 @@ define('Widgets/getElement',[ return getElement; }); -/*global define*/ define('Scene/PerformanceDisplay',[ '../Core/defaultValue', '../Core/defined', @@ -174040,11 +195873,11 @@ define('Scene/PerformanceDisplay',[ display.className = 'cesium-performanceDisplay'; var fpsElement = document.createElement('div'); fpsElement.className = 'cesium-performanceDisplay-fps'; - this._fpsText = document.createTextNode(""); + this._fpsText = document.createTextNode(''); fpsElement.appendChild(this._fpsText); var msElement = document.createElement('div'); msElement.className = 'cesium-performanceDisplay-ms'; - this._msText = document.createTextNode(""); + this._msText = document.createTextNode(''); msElement.appendChild(this._msText); display.appendChild(msElement); display.appendChild(fpsElement); @@ -174091,7 +195924,6 @@ define('Scene/PerformanceDisplay',[ return PerformanceDisplay; }); -/*global define*/ define('Scene/PickDepth',[ '../Core/defined', '../Core/destroyObject', @@ -174246,7 +196078,6 @@ define('Scene/PickDepth',[ return PickDepth; }); -/*global define*/ define('Scene/PrimitiveCollection',[ '../Core/createGuid', '../Core/defaultValue', @@ -174378,7 +196209,7 @@ define('Scene/PrimitiveCollection',[ * @example * var billboards = scene.primitives.add(new Cesium.BillboardCollection()); * scene.primitives.remove(p); // Returns true - * + * * @see PrimitiveCollection#destroyPrimitives */ PrimitiveCollection.prototype.remove = function(primitive) { @@ -174575,7 +196406,7 @@ define('Scene/PrimitiveCollection',[ * var p = primitives.get(i); * p.show = !p.show; * } - * + * * @see PrimitiveCollection#length */ PrimitiveCollection.prototype.get = function(index) { @@ -174633,7 +196464,7 @@ define('Scene/PrimitiveCollection',[ * * @example * primitives = primitives && primitives.destroy(); - * + * * @see PrimitiveCollection#isDestroyed */ PrimitiveCollection.prototype.destroy = function() { @@ -174644,7 +196475,6 @@ define('Scene/PrimitiveCollection',[ return PrimitiveCollection; }); -/*global define*/ define('Scene/QuadtreeTileProvider',[ '../Core/defineProperties', '../Core/DeveloperError' @@ -174751,7 +196581,7 @@ define('Scene/QuadtreeTileProvider',[ * 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} + * @see QuadtreeTileProvider#computeDefaultLevelZeroMaximumGeometricError * * @memberof QuadtreeTileProvider * @function @@ -174851,7 +196681,7 @@ define('Scene/QuadtreeTileProvider',[ * * @example * provider = provider && provider(); - * + * * @see QuadtreeTileProvider#isDestroyed */ QuadtreeTileProvider.prototype.destroy = DeveloperError.throwInstantiationError; @@ -174859,42 +196689,43 @@ define('Scene/QuadtreeTileProvider',[ return QuadtreeTileProvider; }); -/*global define*/ define('Scene/SceneTransitioner',[ '../Core/Cartesian3', '../Core/Cartographic', + '../Core/Check', '../Core/defined', '../Core/destroyObject', '../Core/DeveloperError', '../Core/EasingFunction', '../Core/Math', '../Core/Matrix4', + '../Core/OrthographicFrustum', + '../Core/OrthographicOffCenterFrustum', + '../Core/PerspectiveFrustum', '../Core/Ray', '../Core/ScreenSpaceEventHandler', '../Core/ScreenSpaceEventType', '../Core/Transforms', './Camera', - './OrthographicFrustum', - './OrthographicOffCenterFrustum', - './PerspectiveFrustum', './SceneMode' ], function( Cartesian3, Cartographic, + Check, defined, destroyObject, DeveloperError, EasingFunction, CesiumMath, Matrix4, + OrthographicFrustum, + OrthographicOffCenterFrustum, + PerspectiveFrustum, Ray, ScreenSpaceEventHandler, ScreenSpaceEventType, Transforms, Camera, - OrthographicFrustum, - OrthographicOffCenterFrustum, - PerspectiveFrustum, SceneMode) { 'use strict'; @@ -175051,6 +196882,14 @@ define('Scene/SceneTransitioner',[ } }; + var scratchCVTo3DCamera = { + position : new Cartesian3(), + direction : new Cartesian3(), + up : new Cartesian3(), + frustum : undefined + }; + var scratch2DTo3DFrustumPersp = new PerspectiveFrustum(); + SceneTransitioner.prototype.morphTo3D = function(duration, ellipsoid) { if (defined(this._completeMorph)) { this._completeMorph(); @@ -175158,12 +196997,6 @@ define('Scene/SceneTransitioner',[ 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; @@ -175232,8 +197065,13 @@ define('Scene/SceneTransitioner',[ transitioner._currentTweens.push(tween); } - var scratch2DTo3DFrustumPersp = new PerspectiveFrustum(); var scratch2DTo3DFrustumOrtho = new OrthographicFrustum(); + 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 morphFrom2DTo3D(transitioner, duration, ellipsoid) { duration /= 3.0; @@ -175643,13 +197481,6 @@ define('Scene/SceneTransitioner',[ } } - 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; @@ -175790,7 +197621,6 @@ define('Scene/SceneTransitioner',[ return SceneTransitioner; }); -/*global define*/ define('Scene/TweenCollection',[ '../Core/clone', '../Core/defaultValue', @@ -176284,13 +198114,11 @@ define('Scene/TweenCollection',[ if (tween.needsStart) { tween.needsStart = false; tweenjs.start(time); + } else if (tweenjs.update(time)) { + i++; } else { - if (tweenjs.update(time)) { - i++; - } else { - tweenjs.stop(); - tweens.splice(i, 1); - } + tweenjs.stop(); + tweens.splice(i, 1); } } }; @@ -176313,7 +198141,6 @@ define('Scene/TweenCollection',[ return TweenCollection; }); -/*global define*/ define('Scene/ScreenSpaceCameraController',[ '../Core/Cartesian2', '../Core/Cartesian3', @@ -176324,12 +198151,14 @@ define('Scene/ScreenSpaceCameraController',[ '../Core/destroyObject', '../Core/DeveloperError', '../Core/Ellipsoid', + '../Core/HeadingPitchRoll', '../Core/IntersectionTests', '../Core/isArray', '../Core/KeyboardEventModifier', '../Core/Math', '../Core/Matrix3', '../Core/Matrix4', + '../Core/OrthographicFrustum', '../Core/Plane', '../Core/Quaternion', '../Core/Ray', @@ -176337,7 +198166,6 @@ define('Scene/ScreenSpaceCameraController',[ './CameraEventAggregator', './CameraEventType', './MapMode2D', - './OrthographicFrustum', './SceneMode', './SceneTransforms', './TweenCollection' @@ -176351,12 +198179,14 @@ define('Scene/ScreenSpaceCameraController',[ destroyObject, DeveloperError, Ellipsoid, + HeadingPitchRoll, IntersectionTests, isArray, KeyboardEventModifier, CesiumMath, Matrix3, Matrix4, + OrthographicFrustum, Plane, Quaternion, Ray, @@ -176364,7 +198194,6 @@ define('Scene/ScreenSpaceCameraController',[ CameraEventAggregator, CameraEventType, MapMode2D, - OrthographicFrustum, SceneMode, SceneTransforms, TweenCollection) { @@ -176757,6 +198586,9 @@ define('Scene/ScreenSpaceCameraController',[ var scratchCartesian = new Cartesian3(); var scratchCartesianTwo = new Cartesian3(); var scratchCartesianThree = new Cartesian3(); + var scratchZoomViewOptions = { + orientation: new HeadingPitchRoll() + }; function handleZoom(object, startPosition, movement, zoomFactor, distanceMeasure, unitPositionDotDirection) { var percentage = 1.0; @@ -176796,6 +198628,11 @@ define('Scene/ScreenSpaceCameraController',[ var camera = scene.camera; var mode = scene.mode; + var orientation = scratchZoomViewOptions.orientation; + orientation.heading = camera.heading; + orientation.pitch = camera.pitch; + orientation.roll = camera.roll; + if (camera.frustum instanceof OrthographicFrustum) { if (Math.abs(distance) > 0.0) { camera.zoomIn(distance); @@ -176957,6 +198794,7 @@ define('Scene/ScreenSpaceCameraController',[ Cartesian3.cross(camera.direction, camera.up, camera.right); Cartesian3.cross(camera.right, camera.direction, camera.up); + camera.setView(scratchZoomViewOptions); return; } @@ -177002,6 +198840,8 @@ define('Scene/ScreenSpaceCameraController',[ } else { camera.zoomIn(distance); } + + camera.setView(scratchZoomViewOptions); } var translate2DStart = new Ray(); @@ -177236,6 +199076,7 @@ define('Scene/ScreenSpaceCameraController',[ var rotateCVOldTransform = new Matrix4(); var rotateCVQuaternion = new Quaternion(); var rotateCVMatrix = new Matrix3(); + var tilt3DCartesian3 = new Cartesian3(); function rotateCV(controller, startPosition, movement) { if (defined(movement.angleAndHeight)) { @@ -177555,6 +199396,7 @@ define('Scene/ScreenSpaceCameraController',[ var scratchStrafePlane = new Plane(Cartesian3.UNIT_X, 0.0); var scratchStrafeIntersection = new Cartesian3(); var scratchStrafeDirection = new Cartesian3(); + var scratchMousePos = new Cartesian3(); function strafe(controller, startPosition, movement) { var scene = controller._scene; @@ -177589,7 +199431,6 @@ define('Scene/ScreenSpaceCameraController',[ var spin3DPick = new Cartesian3(); var scratchCartographic = new Cartographic(); - var scratchMousePos = new Cartesian3(); var scratchRadii = new Cartesian3(); var scratchEllipsoid = new Ellipsoid(); var scratchLookUp = new Cartesian3(); @@ -177644,11 +199485,10 @@ define('Scene/ScreenSpaceCameraController',[ pan3D(controller, startPosition, movement, ellipsoid); } return; - } else { - controller._looking = false; - controller._rotating = false; - controller._strafing = false; } + controller._looking = false; + controller._rotating = false; + controller._strafing = false; if (defined(globe) && height < controller._minimumPickingTerrainHeight) { if (defined(mousePos)) { @@ -177868,7 +199708,6 @@ define('Scene/ScreenSpaceCameraController',[ 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(); @@ -178313,7 +200152,6 @@ define('Scene/ScreenSpaceCameraController',[ return ScreenSpaceCameraController; }); -/*global define*/ define('Scene/ShadowMapShader',[ '../Core/defined', '../Renderer/ShaderSource' @@ -178673,7 +200511,6 @@ define('Scene/ShadowMapShader',[ return ShadowMapShader; }); -/*global define*/ define('Scene/ShadowMap',[ '../Core/BoundingRectangle', '../Core/BoundingSphere', @@ -178686,6 +200523,7 @@ define('Scene/ShadowMap',[ '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', + '../Core/CullingVolume', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -178696,6 +200534,8 @@ define('Scene/ShadowMap',[ '../Core/Intersect', '../Core/Math', '../Core/Matrix4', + '../Core/OrthographicOffCenterFrustum', + '../Core/PerspectiveFrustum', '../Core/PixelFormat', '../Core/Quaternion', '../Core/SphereOutlineGeometry', @@ -178718,11 +200558,8 @@ define('Scene/ShadowMap',[ '../Renderer/TextureWrap', './Camera', './CullFace', - './CullingVolume', './DebugCameraPrimitive', - './OrthographicOffCenterFrustum', './PerInstanceColorAppearance', - './PerspectiveFrustum', './Primitive', './ShadowMapShader' ], function( @@ -178737,6 +200574,7 @@ define('Scene/ShadowMap',[ Color, ColorGeometryInstanceAttribute, combine, + CullingVolume, defaultValue, defined, defineProperties, @@ -178747,6 +200585,8 @@ define('Scene/ShadowMap',[ Intersect, CesiumMath, Matrix4, + OrthographicOffCenterFrustum, + PerspectiveFrustum, PixelFormat, Quaternion, SphereOutlineGeometry, @@ -178769,11 +200609,8 @@ define('Scene/ShadowMap',[ TextureWrap, Camera, CullFace, - CullingVolume, DebugCameraPrimitive, - OrthographicOffCenterFrustum, PerInstanceColorAppearance, - PerspectiveFrustum, Primitive, ShadowMapShader) { 'use strict'; @@ -179667,6 +201504,8 @@ define('Scene/ShadowMap',[ var scratchSplits = new Array(5); var scratchFrustum = new PerspectiveFrustum(); var scratchCascadeDistances = new Array(4); + var scratchMin = new Cartesian3(); + var scratchMax = new Cartesian3(); function computeCascades(shadowMap, frameState) { var shadowMapCamera = shadowMap._shadowMapCamera; @@ -179791,8 +201630,6 @@ define('Scene/ShadowMap',[ 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) { @@ -180297,7 +202134,6 @@ define('Scene/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\ @@ -180320,7 +202156,6 @@ void main()\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\ @@ -180356,7 +202191,6 @@ void main()\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\ @@ -180398,7 +202232,6 @@ void main()\n\ }\n\ "; }); -/*global define*/ define('Scene/SunPostProcess',[ '../Core/BoundingRectangle', '../Core/Cartesian2', @@ -180763,7 +202596,7 @@ define('Scene/SunPostProcess',[ scissorRectangle.height = Math.min(size.y, height); this._uCenter = Cartesian2.clone(sunPositionWC, this._uCenter); - this._uRadius = Math.max(size.x, size.y) * 0.5; + this._uRadius = Math.max(size.x, size.y) * 0.15; // create down sampled render state viewportTransformation = Matrix4.computeViewportTransformation(downSampleViewport, 0.0, 1.0, postProcessMatrix4Scratch); @@ -180804,11 +202637,11 @@ define('Scene/SunPostProcess',[ return SunPostProcess; }); -/*global define*/ define('Scene/Scene',[ '../Core/BoundingRectangle', '../Core/BoundingSphere', '../Core/BoxGeometry', + '../Core/buildModuleUrl', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', @@ -180816,6 +202649,7 @@ define('Scene/Scene',[ '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/createGuid', + '../Core/CullingVolume', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -180834,7 +202668,12 @@ define('Scene/Scene',[ '../Core/Matrix4', '../Core/mergeSort', '../Core/Occluder', + '../Core/OrthographicFrustum', + '../Core/OrthographicOffCenterFrustum', + '../Core/PerspectiveFrustum', + '../Core/PerspectiveOffCenterFrustum', '../Core/PixelFormat', + '../Core/RequestScheduler', '../Core/ShowGeometryInstanceAttribute', '../Core/Transforms', '../Renderer/ClearCommand', @@ -180843,16 +202682,18 @@ define('Scene/Scene',[ '../Renderer/ContextLimits', '../Renderer/DrawCommand', '../Renderer/Framebuffer', + '../Renderer/loadCubeMap', '../Renderer/Pass', '../Renderer/PassState', '../Renderer/PixelDatatype', '../Renderer/RenderState', + '../Renderer/Sampler', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', '../Renderer/Texture', + './BrdfLutGenerator', './Camera', './CreditDisplay', - './CullingVolume', './DebugCameraPrimitive', './DepthPlane', './DeviceOrientationCameraController', @@ -180864,12 +202705,8 @@ define('Scene/Scene',[ './JobScheduler', './MapMode2D', './OIT', - './OrthographicFrustum', - './OrthographicOffCenterFrustum', './PerformanceDisplay', './PerInstanceColorAppearance', - './PerspectiveFrustum', - './PerspectiveOffCenterFrustum', './PickDepth', './Primitive', './PrimitiveCollection', @@ -180884,6 +202721,7 @@ define('Scene/Scene',[ BoundingRectangle, BoundingSphere, BoxGeometry, + buildModuleUrl, Cartesian2, Cartesian3, Cartesian4, @@ -180891,6 +202729,7 @@ define('Scene/Scene',[ Color, ColorGeometryInstanceAttribute, createGuid, + CullingVolume, defaultValue, defined, defineProperties, @@ -180909,7 +202748,12 @@ define('Scene/Scene',[ Matrix4, mergeSort, Occluder, + OrthographicFrustum, + OrthographicOffCenterFrustum, + PerspectiveFrustum, + PerspectiveOffCenterFrustum, PixelFormat, + RequestScheduler, ShowGeometryInstanceAttribute, Transforms, ClearCommand, @@ -180918,16 +202762,18 @@ define('Scene/Scene',[ ContextLimits, DrawCommand, Framebuffer, + loadCubeMap, Pass, PassState, PixelDatatype, RenderState, + Sampler, ShaderProgram, ShaderSource, Texture, + BrdfLutGenerator, Camera, CreditDisplay, - CullingVolume, DebugCameraPrimitive, DepthPlane, DeviceOrientationCameraController, @@ -180939,12 +202785,8 @@ define('Scene/Scene',[ JobScheduler, MapMode2D, OIT, - OrthographicFrustum, - OrthographicOffCenterFrustum, PerformanceDisplay, PerInstanceColorAppearance, - PerspectiveFrustum, - PerspectiveOffCenterFrustum, PickDepth, Primitive, PrimitiveCollection, @@ -181423,6 +203265,8 @@ define('Scene/Scene',[ enabled : defaultValue(options.shadows, false) }); + this._brdfLutGenerator = new BrdfLutGenerator(); + this._terrainExaggeration = defaultValue(options.terrainExaggeration, 1.0); this._performanceDisplay = undefined; @@ -181688,6 +203532,10 @@ define('Scene/Scene',[ */ imageryLayers : { get : function() { + if (!defined(this.globe)) { + return undefined; + } + return this.globe.imageryLayers; } }, @@ -181700,10 +203548,16 @@ define('Scene/Scene',[ */ terrainProvider : { get : function() { + if (!defined(this.globe)) { + return undefined; + } + return this.globe.terrainProvider; }, set : function(terrainProvider) { - this.globe.terrainProvider = terrainProvider; + if (defined(this.globe)) { + this.globe.terrainProvider = terrainProvider; + } } }, @@ -181716,6 +203570,10 @@ define('Scene/Scene',[ */ terrainProviderChanged : { get : function() { + if (!defined(this.globe)) { + return undefined; + } + return this.globe.terrainProviderChanged; } }, @@ -182060,6 +203918,8 @@ define('Scene/Scene',[ var frameState = scene._frameState; frameState.commandList.length = 0; frameState.shadowMaps.length = 0; + frameState.brdfLutGenerator = scene._brdfLutGenerator; + frameState.environmentMap = scene.skyBox && scene.skyBox._cubeMap; frameState.mode = scene._mode; frameState.morphTime = scene.morphTime; frameState.mapProjection = scene.mapProjection; @@ -182411,6 +204271,11 @@ define('Scene/Scene',[ return; } + if (command instanceof ClearCommand) { + command.execute(context, passState); + return; + } + var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled; var lightShadowsEnabled = shadowsEnabled && (scene.frameState.shadowHints.lightShadowMaps.length > 0); @@ -182686,28 +204551,42 @@ define('Scene/Scene',[ passState.framebuffer = fb; } - us.updatePass(Pass.GROUND); - commands = frustumCommands.commands[Pass.GROUND]; - length = frustumCommands.indices[Pass.GROUND]; + us.updatePass(Pass.TERRAIN_CLASSIFICATION); + commands = frustumCommands.commands[Pass.TERRAIN_CLASSIFICATION]; + length = frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION]; + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } + + if (clearGlobeDepth) { + clearDepth.execute(context, passState); + } + + us.updatePass(Pass.CESIUM_3D_TILE); + commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; + length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; 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); - } + us.updatePass(Pass.CESIUM_3D_TILE_CLASSIFICATION); + commands = frustumCommands.commands[Pass.CESIUM_3D_TILE_CLASSIFICATION]; + length = frustumCommands.indices[Pass.CESIUM_3D_TILE_CLASSIFICATION]; + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } + + if (clearGlobeDepth && 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 startPass = Pass.CESIUM_3D_TILE_CLASSIFICATION + 1; var endPass = Pass.TRANSLUCENT; for (var pass = startPass; pass < endPass; ++pass) { us.updatePass(pass); @@ -182778,7 +204657,7 @@ define('Scene/Scene',[ var command = commandList[i]; updateDerivedCommands(scene, command); - if (command.castShadows && (command.pass === Pass.GLOBE || command.pass === Pass.OPAQUE || command.pass === Pass.TRANSLUCENT)) { + if (command.castShadows && (command.pass === Pass.GLOBE || command.pass === Pass.CESIUM_3D_TILE || command.pass === Pass.OPAQUE || command.pass === Pass.TRANSLUCENT)) { if (isVisible(command, shadowVolume)) { if (isPointLight) { for (var k = 0; k < numberOfPasses; ++k) { @@ -182852,6 +204731,8 @@ define('Scene/Scene',[ } } + var scratchEyeTranslation = new Cartesian3(); + function updateAndExecuteCommands(scene, passState, backgroundColor) { var context = scene._context; @@ -182912,13 +204793,11 @@ define('Scene/Scene',[ executeCommands(scene, passState); Camera.clone(savedCamera, camera); + } else if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) { + executeCommandsInViewport(true, scene, passState, backgroundColor); } else { updateAndClearFramebuffers(scene, passState, backgroundColor); - if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) { - executeCommandsInViewport(true, scene, passState); - } else { - execute2DViewportCommands(scene, passState); - } + execute2DViewportCommands(scene, passState); } } @@ -183044,7 +204923,7 @@ define('Scene/Scene',[ passState.viewport = originalViewport; } - function executeCommandsInViewport(firstViewport, scene, passState) { + function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor) { var depthOnly = scene.frameState.passes.depth; if (!firstViewport && !depthOnly) { @@ -183057,9 +204936,14 @@ define('Scene/Scene',[ createPotentiallyVisibleSet(scene); - if (firstViewport && !depthOnly) { - executeComputeCommands(scene); - executeShadowMapCastCommands(scene); + if (firstViewport) { + if (defined(backgroundColor)) { + updateAndClearFramebuffers(scene, passState, backgroundColor); + } + if (!depthOnly) { + executeComputeCommands(scene); + executeShadowMapCastCommands(scene); + } } executeCommands(scene, passState); @@ -183331,8 +205215,6 @@ define('Scene/Scene',[ this._camera._updateCameraChanged(); }; - var scratchEyeTranslation = new Cartesian3(); - function render(scene, time) { scene._pickPositionCacheDirty = true; @@ -183421,6 +205303,7 @@ define('Scene/Scene',[ } context.endFrame(); + RequestScheduler.update(); callAfterRenderFunctions(frameState); scene._postRender.raiseEvent(scene, time); @@ -183547,14 +205430,31 @@ define('Scene/Scene',[ * 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. + *

    + * When a feature of a 3D Tiles tileset is picked, pick returns a {@link Cesium3DTileFeature} object. + *

    + * + * @example + * // On mouse over, color the feature yellow. + * handler.setInputAction(function(movement) { + * var feature = scene.pick(movement.endPosition); + * if (feature instanceof Cesium.Cesium3DTileFeature) { + * feature.color = Cesium.Color.YELLOW; + * } + * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); * * @param {Cartesian2} windowPosition Window coordinates to perform picking on. + * @param {Number} [width=3] Width of the pick rectangle. + * @param {Number} [height=3] Height of the pick rectangle. * @returns {Object} Object containing the picked primitive. * * @exception {DeveloperError} windowPosition is undefined. */ - Scene.prototype.pick = function(windowPosition) { + Scene.prototype.pick = function(windowPosition, width, height) { + rectangleWidth = defaultValue(width, 3.0); + rectangleHeight = defaultValue(height, rectangleWidth); + var context = this._context; var us = context.uniformState; var frameState = this._frameState; @@ -183576,9 +205476,11 @@ define('Scene/Scene',[ scratchRectangle.x = drawingBufferPosition.x - ((rectangleWidth - 1.0) * 0.5); scratchRectangle.y = (this.drawingBufferHeight - drawingBufferPosition.y) - ((rectangleHeight - 1.0) * 0.5); - + scratchRectangle.width = rectangleWidth; + scratchRectangle.height = rectangleHeight; var passState = this._pickFramebuffer.begin(scratchRectangle); + updateEnvironment(this, passState); updateAndExecuteCommands(this, passState, scratchColorZero); resolveFramebuffers(this, passState); @@ -183725,6 +205627,7 @@ define('Scene/Scene',[ passState.scissorTest.rectangle.width = 1; passState.scissorTest.rectangle.height = 1; + updateEnvironment(scene, passState); updateAndExecuteCommands(scene, passState, scratchColorZero); resolveFramebuffers(scene, passState); @@ -184050,6 +205953,7 @@ define('Scene/Scene',[ this._depthPlane = this._depthPlane && this._depthPlane.destroy(); this._transitioner.destroy(); this._debugFrustumPlanes = this._debugFrustumPlanes && this._debugFrustumPlanes.destroy(); + this._brdfLutGenerator = this._brdfLutGenerator && this._brdfLutGenerator.destroy(); if (defined(this._globeDepth)) { this._globeDepth.destroy(); @@ -184095,7 +205999,6 @@ define('Scene/Scene',[ return Scene; }); -/*global define*/ define('Scene/SingleTileImageryProvider',[ '../Core/Credit', '../Core/defaultValue', @@ -184140,7 +206043,7 @@ define('Scene/SingleTileImageryProvider',[ * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see createOpenStreetMapImageryProvider * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider @@ -184432,6 +206335,7 @@ define('Scene/SingleTileImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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 @@ -184439,7 +206343,7 @@ define('Scene/SingleTileImageryProvider',[ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - SingleTileImageryProvider.prototype.requestImage = function(x, y, level) { + SingleTileImageryProvider.prototype.requestImage = function(x, y, level, request) { return this._image; }; @@ -184497,7 +206401,6 @@ define('Scene/SingleTileImageryProvider',[ * 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 "/**\n\ @@ -184539,6 +206442,8 @@ define('Shaders/SkyAtmosphereFS',[],function() { uniform vec3 u_hsbShift; // Hue, saturation, brightness\n\ #endif\n\ \n\ +uniform vec4 u_cameraAndRadiiAndDynamicAtmosphereColor; // Camera height, outer radius, inner radius, dynamic atmosphere color flag\n\ +\n\ const float g = -0.95;\n\ const float g2 = g * g;\n\ \n\ @@ -184575,7 +206480,14 @@ void main (void)\n\ l = min(l, czm_luminance(rgb));\n\ #endif\n\ \n\ - gl_FragColor = vec4(rgb, min(smoothstep(0.0, 0.1, l), 1.0) * smoothstep(0.0, 1.0, czm_morphTime));\n\ + // Alter alpha based on how close the viewer is to the ground (1.0 = on ground, 0.0 = at edge of atmosphere)\n\ + float atmosphereAlpha = clamp((u_cameraAndRadiiAndDynamicAtmosphereColor.y - u_cameraAndRadiiAndDynamicAtmosphereColor.x) / (u_cameraAndRadiiAndDynamicAtmosphereColor.y - u_cameraAndRadiiAndDynamicAtmosphereColor.z), 0.0, 1.0);\n\ +\n\ + // Alter alpha based on time of day (0.0 = night , 1.0 = day)\n\ + float nightAlpha = (u_cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? clamp(dot(normalize(czm_viewerPositionWC), normalize(czm_sunPositionWC)), 0.0, 1.0) : 1.0;\n\ + atmosphereAlpha *= pow(nightAlpha, 0.5);\n\ +\n\ + gl_FragColor = vec4(rgb, mix(rgb.b, 1.0, atmosphereAlpha) * smoothstep(0.0, 1.0, czm_morphTime));\n\ }\n\ "; }); @@ -184583,11 +206495,11 @@ void main (void)\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, @@ -184596,7 +206508,7 @@ void main (void)\n\ * * 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 @@ -184611,18 +206523,17 @@ void main (void)\n\ * 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 "/**\n\ * @license\n\ * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)\n\ * All rights reserved.\n\ - * \n\ + *\n\ * Redistribution and use in source and binary forms, with or without\n\ * modification, are permitted provided that the following conditions\n\ * are met:\n\ - * \n\ + *\n\ * * Redistributions of source code must retain the above copyright notice,\n\ * this list of conditions and the following disclaimer.\n\ * * Redistributions in binary form must reproduce the above copyright notice,\n\ @@ -184631,7 +206542,7 @@ define('Shaders/SkyAtmosphereVS',[],function() { * * Neither the name of the project nor the names of its contributors may be\n\ * used to endorse or promote products derived from this software without\n\ * specific prior written permission.\n\ - * \n\ + *\n\ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n\ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n\ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n\ @@ -184645,10 +206556,10 @@ define('Shaders/SkyAtmosphereVS',[],function() { *\n\ * Modifications made by Analytical Graphics, Inc.\n\ */\n\ - \n\ +\n\ // Code: http://sponeil.net/\n\ // GPU Gems 2 Article: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html\n\ - \n\ +\n\ attribute vec4 position;\n\ \n\ uniform vec4 u_cameraAndRadiiAndDynamicAtmosphereColor; // Camera height, outer radius, inner radius, dynamic atmosphere color flag\n\ @@ -184665,7 +206576,7 @@ const vec3 InvWavelength = vec3(\n\ 9.473284437923038, // Green = 1.0 / Math.pow(0.570, 4.0)\n\ 19.643802610477206); // Blue = 1.0 / Math.pow(0.475, 4.0)\n\ const float rayleighScaleDepth = 0.25;\n\ - \n\ +\n\ const int nSamples = 2;\n\ const float fSamples = 2.0;\n\ \n\ @@ -184746,7 +206657,6 @@ void main(void)\n\ }\n\ "; }); -/*global define*/ define('Scene/SkyAtmosphere',[ '../Core/Cartesian3', '../Core/Cartesian4', @@ -185071,7 +206981,6 @@ define('Scene/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\ @@ -185086,7 +206995,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/SkyBoxVS',[],function() { 'use strict'; return "attribute vec3 position;\n\ @@ -185101,7 +207009,6 @@ void main()\n\ }\n\ "; }); -/*global define*/ define('Scene/SkyBox',[ '../Core/BoxGeometry', '../Core/Cartesian3', @@ -185172,7 +207079,7 @@ define('Scene/SkyBox',[ * negativeZ : 'skybox_nz.png' * } * }); - * + * * @see Scene#skyBox * @see Transforms.computeTemeToPseudoFixedMatrix */ @@ -185216,6 +207123,8 @@ define('Scene/SkyBox',[ * @exception {DeveloperError} this.sources properties must all be the same type. */ SkyBox.prototype.update = function(frameState) { + var that = this; + if (!this.show) { return undefined; } @@ -185255,8 +207164,6 @@ define('Scene/SkyBox',[ var command = this._command; if (!defined(command.vertexArray)) { - var that = this; - command.uniformMap = { u_cubeMap: function() { return that._cubeMap; @@ -185324,7 +207231,7 @@ define('Scene/SkyBox',[ * * @example * skyBox = skyBox && skyBox.destroy(); - * + * * @see SkyBox#isDestroyed */ SkyBox.prototype.destroy = function() { @@ -185338,8 +207245,155 @@ define('Scene/SkyBox',[ return SkyBox; }); +define('Scene/SphereEmitter',[ + '../Core/Cartesian3', + '../Core/Check', + '../Core/defaultValue', + '../Core/defineProperties', + '../Core/Math' + ], function( + Cartesian3, + Check, + defaultValue, + defineProperties, + CesiumMath) { + 'use strict'; + + /** + * A ParticleEmitter that emits particles within a sphere. + * Particles will be positioned randomly within the sphere and have initial velocities emanating from the center of the sphere. + * + * @alias SphereEmitter + * @constructor + * + * @param {Number} [radius=1.0] The radius of the sphere in meters. + */ + function SphereEmitter(radius) { + radius = defaultValue(radius, 1.0); + + + this._radius = defaultValue(radius, 1.0); + } + + defineProperties(SphereEmitter.prototype, { + /** + * The radius of the sphere in meters. + * @memberof SphereEmitter.prototype + * @type {Number} + * @default 1.0 + */ + radius : { + get : function() { + return this._radius; + }, + set : function(value) { + this._radius = value; + } + } + }); + + /** + * Initializes the given {Particle} by setting it's position and velocity. + * + * @private + * @param {Particle} particle The particle to initialize + */ + SphereEmitter.prototype.emit = function(particle) { + var theta = CesiumMath.randomBetween(0.0, CesiumMath.TWO_PI); + var phi = CesiumMath.randomBetween(0.0, CesiumMath.PI); + var rad = CesiumMath.randomBetween(0.0, this._radius); + + var x = rad * Math.cos(theta) * Math.sin(phi); + var y = rad * Math.sin(theta) * Math.sin(phi); + var z = rad * Math.cos(phi); + + particle.position = Cartesian3.fromElements(x, y, z, particle.position); + particle.velocity = Cartesian3.normalize(particle.position, particle.velocity); + }; + + return SphereEmitter; +}); + +define('Scene/StyleExpression',[ + '../Core/DeveloperError' + ], function( + DeveloperError) { + 'use strict'; + + /** + * An expression for a style applied to a {@link Cesium3DTileset}. + *

    + * Derived classes of this interface evaluate expressions in the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. + *

    + *

    + * This type describes an interface and is not intended to be instantiated directly. + *

    + * + * @alias StyleExpression + * @constructor + * + * @see Expression + * @see ConditionsExpression + */ + function StyleExpression() { + } + + /** + * Evaluates the result of an expression, optionally using the provided feature's properties. If the result of + * the expression in the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + * is of type Boolean, Number, or String, the corresponding JavaScript + * primitive type will be returned. If the result is a RegExp, a Javascript RegExp + * object will be returned. If the result is a Cartesian2, Cartesian3, or Cartesian4, + * a {@link Cartesian2}, {@link Cartesian3}, or {@link Cartesian4} object will be returned. If the result argument is + * a {@link Color}, the {@link Cartesian4} value is converted to a {@link Color} and then returned. + * + * @param {FrameState} frameState The frame state. + * @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression. + * @param {Object} [result] The object onto which to store the result. + * @returns {Boolean|Number|String|RegExp|Cartesian2|Cartesian3|Cartesian4|Color} The result of evaluating the expression. + */ + StyleExpression.prototype.evaluate = function(frameState, feature, result) { + DeveloperError.throwInstantiationError(); + }; + + /** + * Evaluates the result of a Color expression, optionally using the provided feature's properties. + *

    + * This is equivalent to {@link StyleExpression#evaluate} but always returns a {@link Color} object. + *

    + * + * @param {FrameState} frameState The frame state. + * @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression. + * @param {Color} [result] The object in which to store the result. + * @returns {Color} The modified result parameter or a new Color instance if one was not provided. + */ + StyleExpression.prototype.evaluateColor = function(frameState, feature, result) { + DeveloperError.throwInstantiationError(); + }; + + /** + * Gets the shader function for this expression. + * Returns undefined if the shader function can't be generated from this expression. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * @param {String} returnType The return type of the generated function. + * + * @returns {String} The shader function. + * + * @private + */ + StyleExpression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { + DeveloperError.throwInstantiationError(); + }; + + return StyleExpression; +}); + //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/SunFS',[],function() { 'use strict'; return "uniform sampler2D u_texture;\n\ @@ -185353,7 +207407,6 @@ void main()\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\ @@ -185415,7 +207468,6 @@ void main()\n\ "; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/SunVS',[],function() { 'use strict'; return "attribute vec2 direction;\n\ @@ -185448,7 +207500,6 @@ void main() \n\ }\n\ "; }); -/*global define*/ define('Scene/Sun',[ '../Core/BoundingSphere', '../Core/Cartesian2', @@ -185803,170 +207854,6 @@ define('Scene/Sun',[ return Sun; }); -/*global define*/ -define('Scene/TileBoundingSphere',[ - '../Core/BoundingSphere', - '../Core/Cartesian3', - '../Core/Check', - '../Core/ColorGeometryInstanceAttribute', - '../Core/defined', - '../Core/defineProperties', - '../Core/GeometryInstance', - '../Core/Matrix4', - '../Core/SphereOutlineGeometry', - './PerInstanceColorAppearance', - './Primitive' - ], function( - BoundingSphere, - Cartesian3, - Check, - ColorGeometryInstanceAttribute, - defined, - defineProperties, - GeometryInstance, - Matrix4, - SphereOutlineGeometry, - PerInstanceColorAppearance, - Primitive) { - 'use strict'; - - /** - * A tile bounding volume specified as a sphere. - * @alias TileBoundingSphere - * @constructor - * - * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the bounding sphere. - * @param {Number} [radius=0.0] The radius of the bounding sphere. - * - * @private - */ - function TileBoundingSphere(center, radius) { - this._boundingSphere = new BoundingSphere(center, radius); - } - - defineProperties(TileBoundingSphere.prototype, { - /** - * The center of the bounding sphere - * - * @memberof TileBoundingSphere.prototype - * - * @type {Cartesian3} - * @readonly - */ - center : { - get : function() { - return this._boundingSphere.center; - } - }, - - /** - * The radius of the bounding sphere - * - * @memberof TileBoundingSphere.prototype - * - * @type {Number} - * @readonly - */ - radius : { - get : function() { - return this._boundingSphere.radius; - } - }, - - /** - * The underlying bounding volume - * - * @memberof TileBoundingSphere.prototype - * - * @type {Object} - * @readonly - */ - boundingVolume : { - get : function() { - return this._boundingSphere; - } - }, - /** - * The underlying bounding sphere - * - * @memberof TileBoundingSphere.prototype - * - * @type {BoundingSphere} - * @readonly - */ - boundingSphere : { - get : function() { - return this._boundingSphere; - } - } - }); - - /** - * Computes the distance between this bounding sphere and the camera attached to frameState. - * - * @param {FrameState} frameState The frameState to which the camera is attached. - * @returns {Number} The distance between the camera and the bounding sphere in meters. Returns 0 if the camera is inside the bounding volume. - * - */ - TileBoundingSphere.prototype.distanceToCamera = function(frameState) { - var bs = this._boundingSphere; - return Math.max(0.0, Cartesian3.distance(bs.center, frameState.camera.positionWC) - bs.radius); - }; - - /** - * Determines which side of a plane this 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. - */ - TileBoundingSphere.prototype.intersectPlane = function(plane) { - return BoundingSphere.intersectPlane(this._boundingSphere, plane); - }; - - /** - * Update the bounding sphere after the tile is transformed. - */ - TileBoundingSphere.prototype.update = function(center, radius) { - Cartesian3.clone(center, this._boundingSphere.center); - this._boundingSphere.radius = radius; - }; - - /** - * Creates a debug primitive that shows the outline of the sphere. - * - * @param {Color} color The desired color of the primitive's mesh - * @return {Primitive} - */ - TileBoundingSphere.prototype.createDebugVolume = function(color) { - var geometry = new SphereOutlineGeometry({ - radius: this.radius - }); - var modelMatrix = Matrix4.fromTranslation(this.center, new Matrix4.clone(Matrix4.IDENTITY)); - var instance = new GeometryInstance({ - geometry : geometry, - modelMatrix : modelMatrix, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(color) - } - }); - - return new Primitive({ - geometryInstances : instance, - appearance : new PerInstanceColorAppearance({ - translucent : false, - flat : true - }), - asynchronous : false - }); - }; - - return TileBoundingSphere; -}); - -/*global define*/ define('Scene/TileBoundingVolume',[ '../Core/DeveloperError' ], function( @@ -185987,7 +207874,7 @@ define('Scene/TileBoundingVolume',[ } /** - * The underlying bounding volume + * The underlying bounding volume. * * @memberof TileBoundingVolume.prototype * @@ -185997,7 +207884,7 @@ define('Scene/TileBoundingVolume',[ TileBoundingVolume.prototype.boundingVolume = undefined; /** - * The underlying bounding sphere + * The underlying bounding sphere. * * @memberof TileBoundingVolume.prototype * @@ -186006,17 +207893,6 @@ define('Scene/TileBoundingVolume',[ */ TileBoundingVolume.prototype.boundingSphere = undefined; - /** - * Creates a debug primitive that shows the outline of the tile bounding - * volume. - * - * @param {Color} color The desired color of the primitive's mesh - * @return {Primitive} - */ - TileBoundingVolume.prototype.createDebugVolume = function(color) { - DeveloperError.throwInstantiationError(); - }; - /** * Calculates the distance between the tile and the camera. * @@ -186041,10 +207917,20 @@ define('Scene/TileBoundingVolume',[ DeveloperError.throwInstantiationError(); }; + /** + * Creates a debug primitive that shows the outline of the tile bounding + * volume. + * + * @param {Color} color The desired color of the primitive's mesh + * @return {Primitive} + */ + TileBoundingVolume.prototype.createDebugVolume = function(color) { + DeveloperError.throwInstantiationError(); + }; + return TileBoundingVolume; }); -/*global define*/ define('Scene/TileCoordinatesImageryProvider',[ '../Core/Color', '../Core/defaultValue', @@ -186286,12 +208172,13 @@ define('Scene/TileCoordinatesImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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) { + TileCoordinatesImageryProvider.prototype.requestImage = function(x, y, level, request) { var canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256; @@ -186335,7 +208222,6 @@ define('Scene/TileCoordinatesImageryProvider',[ return TileCoordinatesImageryProvider; }); -/*global define*/ define('Scene/TileDiscardPolicy',[ '../Core/DeveloperError' ], function( @@ -186376,174 +208262,315 @@ define('Scene/TileDiscardPolicy',[ return TileDiscardPolicy; }); -/*global define*/ -define('Scene/TileOrientedBoundingBox',[ - '../Core/BoundingSphere', - '../Core/BoxOutlineGeometry', - '../Core/Cartesian3', +define('Scene/TileState',[ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * @private + */ + var TileState = { + START : 0, + LOADING : 1, + READY : 2, + UPSAMPLED_ONLY : 3 + }; + + return freezeObject(TileState); +}); + +define('Scene/TimeDynamicImagery',[ '../Core/Check', - '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', - '../Core/GeometryInstance', - '../Core/Matrix3', - '../Core/Matrix4', - '../Core/OrientedBoundingBox', - './PerInstanceColorAppearance', - './Primitive' + '../Core/DeveloperError', + '../Core/JulianDate', + '../Core/Request', + '../Core/RequestType' ], function( - BoundingSphere, - BoxOutlineGeometry, - Cartesian3, Check, - ColorGeometryInstanceAttribute, defaultValue, defined, defineProperties, - GeometryInstance, - Matrix3, - Matrix4, - OrientedBoundingBox, - PerInstanceColorAppearance, - Primitive) { + DeveloperError, + JulianDate, + Request, + RequestType) { 'use strict'; /** - * A tile bounding volume specified as an oriented bounding box. - * @alias TileOrientedBoundingBox - * @constructor + * Provides functionality for ImageryProviders that have time dynamic imagery * - * @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. + * @alias TimeDynamicImagery + * @constructor * - * @private + * @param {Object} options Object with the following properties: + * @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. Required when options.times is specified. + * @param {TimeIntervalCollection} options.times TimeIntervalCollection with its data property being an object containing time dynamic dimension and their values. + * @param {Function} options.requestImageFunction A function that will request imagery tiles. + * @param {Function} options.reloadFunction A function that will be called when all imagery tiles need to be reloaded. */ - function TileOrientedBoundingBox(center, halfAxes) { - this._orientedBoundingBox = new OrientedBoundingBox(center, halfAxes); - this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox); + function TimeDynamicImagery(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + + this._tileCache = {}; + this._tilesRequestedForInterval = []; + + var clock = this._clock = options.clock; + this._times = options.times; + this._requestImageFunction = options.requestImageFunction; + this._reloadFunction = options.reloadFunction; + this._currentIntervalIndex = -1; + + clock.onTick.addEventListener(this._clockOnTick, this); + this._clockOnTick(clock); } - defineProperties(TileOrientedBoundingBox.prototype, { + defineProperties(TimeDynamicImagery.prototype, { /** - * The underlying bounding volume - * - * @memberof TileOrientedBoundingBox.prototype - * - * @type {Object} - * @readonly + * Gets or sets a clock that is used to get keep the time used for time dynamic parameters. + * @memberof TimeDynamicImagery.prototype + * @type {Clock} */ - boundingVolume : { + clock : { get : function() { - return this._orientedBoundingBox; + return this._clock; + }, + set : function(value) { + + if (this._clock !== value) { + this._clock = value; + this._clockOnTick(value); + this._reloadFunction(); + } } }, /** - * The underlying bounding sphere - * - * @memberof TileOrientedBoundingBox.prototype - * - * @type {BoundingSphere} - * @readonly + * Gets or sets a time interval collection. + * @memberof TimeDynamicImagery.prototype + * @type {TimeIntervalCollection} */ - boundingSphere : { + times : { get : function() { - return this._boundingSphere; + return this._times; + }, + set : function(value) { + + if (this._times !== value) { + this._times = value; + this._clockOnTick(this._clock); + this._reloadFunction(); + } + } + }, + /** + * Gets the current interval. + * @memberof TimeDynamicImagery.prototype + * @type {TimeInterval} + */ + currentInterval : { + get : function() { + return this._times.get(this._currentIntervalIndex); } } }); /** - * Computes the distance between this bounding box and the camera attached to frameState. + * Gets the tile from the cache if its available. * - * @param {FrameState} frameState The frameState to which the camera is attached. - * @returns {Number} The distance between the camera and the bounding box in meters. Returns 0 if the camera is inside the bounding volume. + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. + * + * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or + * undefined if the tile is not in the cache. */ - TileOrientedBoundingBox.prototype.distanceToCamera = function(frameState) { - return Math.sqrt(this._orientedBoundingBox.distanceSquaredTo(frameState.camera.positionWC)); + TimeDynamicImagery.prototype.getFromCache = function(x, y, level, request) { + var key = getKey(x, y, level); + var result; + var cache = this._tileCache[this._currentIntervalIndex]; + if (defined(cache) && defined(cache[key])) { + var item = cache[key]; + result = item.promise + .otherwise(function(e) { + // Set the correct state in case it was cancelled + request.state = item.request.state; + throw e; + }); + delete cache[key]; + } + + return result; }; /** - * Determines which side of a plane this box is located. + * Checks if the next interval is approaching and will start preload the tile if necessary. Otherwise it will + * just add the tile to a list to preload when we approach the next interval. * - * @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. - */ - TileOrientedBoundingBox.prototype.intersectPlane = function(plane) { - return this._orientedBoundingBox.intersectPlane(plane); - }; + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. + */ + TimeDynamicImagery.prototype.checkApproachingInterval = function(x, y, level, request) { + var key = getKey(x, y, level); + var tilesRequestedForInterval = this._tilesRequestedForInterval; + + // If we are approaching an interval, preload this tile in the next interval + var approachingInterval = getApproachingInterval(this); + var tile = { + key : key, + // Determines priority based on camera distance to the tile. + // Since the imagery regardless of time will be attached to the same tile we can just steal it. + priorityFunction : request.priorityFunction + }; + if (!defined(approachingInterval) || !addToCache(this, tile, approachingInterval)) { + // Add to recent request list if we aren't approaching and interval or the request was throttled + tilesRequestedForInterval.push(tile); + } - /** - * Update the bounding box after the tile is transformed. - */ - TileOrientedBoundingBox.prototype.update = function(center, halfAxes) { - Cartesian3.clone(center, this._orientedBoundingBox.center); - Matrix3.clone(halfAxes, this._orientedBoundingBox.halfAxes); - BoundingSphere.fromOrientedBoundingBox(this._orientedBoundingBox, this._boundingSphere); + // Don't let the tile list get out of hand + if (tilesRequestedForInterval.length >= 512) { + tilesRequestedForInterval.splice(0, 256); + } }; - /** - * Creates a debug primitive that shows the outline of the box. - * - * @param {Color} color The desired color of the primitive's mesh - * @return {Primitive} - */ - TileOrientedBoundingBox.prototype.createDebugVolume = function(color) { - - var geometry = new BoxOutlineGeometry({ - // Make a 2x2x2 cube - minimum: new Cartesian3(-1.0, -1.0, -1.0), - maximum: new Cartesian3(1.0, 1.0, 1.0) - }); - var modelMatrix = Matrix4.fromRotationTranslation(this.boundingVolume.halfAxes, this.boundingVolume.center); - var instance = new GeometryInstance({ - geometry : geometry, - modelMatrix : modelMatrix, - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(color) + TimeDynamicImagery.prototype._clockOnTick = function(clock) { + var time = clock.currentTime; + var times = this._times; + var index = times.indexOf(time); + var currentIntervalIndex = this._currentIntervalIndex; + + if (index !== currentIntervalIndex) { + // Cancel all outstanding requests and clear out caches not from current time interval + var currentCache = this._tileCache[currentIntervalIndex]; + for (var t in currentCache) { + if (currentCache.hasOwnProperty(t)) { + currentCache[t].request.cancel(); + } } - }); + delete this._tileCache[currentIntervalIndex]; + this._tilesRequestedForInterval = []; - return new Primitive({ - geometryInstances : instance, - appearance : new PerInstanceColorAppearance({ - translucent : false, - flat : true - }), - asynchronous : false - }); - }; + this._currentIntervalIndex = index; + this._reloadFunction(); - return TileOrientedBoundingBox; -}); + return; + } -/*global define*/ -define('Scene/TileState',[ - '../Core/freezeObject' - ], function( - freezeObject) { - 'use strict'; + var approachingInterval = getApproachingInterval(this); + if (defined(approachingInterval)) { + // Start loading recent tiles from end of this._tilesRequestedForInterval + // We keep preloading until we hit a throttling limit. + var tilesRequested = this._tilesRequestedForInterval; + var success = true; + while (success) { + if (tilesRequested.length === 0) { + break; + } - /** - * @private - */ - var TileState = { - START : 0, - LOADING : 1, - READY : 2, - UPSAMPLED_ONLY : 3 + var tile = tilesRequested.pop(); + success = addToCache(this, tile, approachingInterval); + if (!success) { + tilesRequested.push(tile); + } + } + } }; - return freezeObject(TileState); + function getKey(x, y, level) { + return x + '-' + y + '-' + level; + } + + function getKeyElements(key) { + var s = key.split('-'); + if (s.length !== 3) { + return undefined; + } + + return { + x : Number(s[0]), + y : Number(s[1]), + level : Number(s[2]) + }; + } + + function getApproachingInterval(that) { + var times = that._times; + if (!defined(times)) { + return undefined; + } + var clock = that._clock; + var time = clock.currentTime; + var isAnimating = clock.canAnimate && clock.shouldAnimate; + var multiplier = clock.multiplier; + + if (!isAnimating && multiplier !== 0) { + return undefined; + } + + var seconds; + var index = times.indexOf(time); + if (index === -1) { + return undefined; + } + + var interval = times.get(index); + if (multiplier > 0) { // animating forward + seconds = JulianDate.secondsDifference(interval.stop, time); + ++index; + } else { //backwards + seconds = JulianDate.secondsDifference(interval.start, time); // Will be negative + --index; + } + seconds /= multiplier; // Will always be positive + + // Less than 5 wall time seconds + return (index >= 0 && seconds <= 5.0) ? times.get(index) : undefined; + } + + function addToCache(that, tile, interval) { + var index = that._times.indexOf(interval.start); + var tileCache = that._tileCache; + var intervalTileCache = tileCache[index]; + if (!defined(intervalTileCache)) { + intervalTileCache = tileCache[index] = {}; + } + + var key = tile.key; + if (defined(intervalTileCache[key])) { + return true; // Already in the cache + } + + var keyElements = getKeyElements(key); + var request = new Request({ + throttle : true, + throttleByServer : true, + type : RequestType.IMAGERY, + priorityFunction : tile.priorityFunction + }); + var promise = that._requestImageFunction(keyElements.x, keyElements.y, keyElements.level, request, interval); + if (!defined(promise)) { + return false; + } + + intervalTileCache[key] = { + promise : promise, + request : request + }; + + return true; + } + + return TimeDynamicImagery; }); //This file is automatically rebuilt by the Cesium build process. -/*global define*/ define('Shaders/ViewportQuadFS',[],function() { 'use strict'; return "\n\ @@ -186564,7 +208591,6 @@ void main()\n\ }\n\ "; }); -/*global define*/ define('Scene/ViewportQuad',[ '../Core/BoundingRectangle', '../Core/Color', @@ -186755,7 +208781,6 @@ define('Scene/ViewportQuad',[ return ViewportQuad; }); -/*global define*/ define('Scene/WebMapServiceImageryProvider',[ '../Core/combine', '../Core/defaultValue', @@ -186828,7 +208853,7 @@ define('Scene/WebMapServiceImageryProvider',[ * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider * @see createTileMapServiceImageryProvider @@ -187182,6 +209207,7 @@ define('Scene/WebMapServiceImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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 @@ -187189,8 +209215,8 @@ define('Scene/WebMapServiceImageryProvider',[ * * @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); + WebMapServiceImageryProvider.prototype.requestImage = function(x, y, level, request) { + return this._tileProvider.requestImage(x, y, level, request); }; /** @@ -187263,7 +209289,6 @@ define('Scene/WebMapServiceImageryProvider',[ return WebMapServiceImageryProvider; }); -/*global define*/ define('Scene/WebMapTileServiceImageryProvider',[ '../Core/combine', '../Core/Credit', @@ -187277,10 +209302,13 @@ define('Scene/WebMapTileServiceImageryProvider',[ '../Core/objectToQuery', '../Core/queryToObject', '../Core/Rectangle', + '../Core/Request', + '../Core/RequestType', '../Core/WebMercatorTilingScheme', '../ThirdParty/Uri', '../ThirdParty/when', - './ImageryProvider' + './ImageryProvider', + './TimeDynamicImagery' ], function( combine, Credit, @@ -187294,10 +209322,13 @@ define('Scene/WebMapTileServiceImageryProvider',[ objectToQuery, queryToObject, Rectangle, + Request, + RequestType, WebMercatorTilingScheme, Uri, when, - ImageryProvider) { + ImageryProvider, + TimeDynamicImagery) { 'use strict'; /** @@ -187314,6 +209345,9 @@ define('Scene/WebMapTileServiceImageryProvider',[ * @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 {Clock} [options.clock] A Clock instance that is used when determining the value for the time dimension. Required when options.times is specified. + * @param {TimeIntervalCollection} [options.times] TimeIntervalCollection with its data property being an object containing time dynamic dimension and their values. + * @param {Object} [options.dimensions] A object containing static dimensions and their values. * @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. @@ -187327,6 +209361,7 @@ define('Scene/WebMapTileServiceImageryProvider',[ * 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. * + * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Web%20Map%20Tile%20Service%20with%20Time.html|Cesium Sandcastle Web Map Tile Service with Time Demo} * * @example * // Example 1. USGS shaded relief tiles (KVP) @@ -187355,9 +209390,32 @@ define('Scene/WebMapTileServiceImageryProvider',[ * }); * viewer.imageryLayers.addImageryProvider(shadedRelief2); * + * @example + * // Example 3. NASA time dynamic weather data (RESTful) + * var times = Cesium.TimeIntervalCollection.fromIso8601({ + * iso8601: '2015-07-30/2017-06-16/P1D', + * dataCallback: function dataCallback(interval, index) { + * return { + * Time: Cesium.JulianDate.toIso8601(interval.start) + * }; + * } + * }); + * var weather = new Cesium.WebMapTileServiceImageryProvider({ + * url : 'https://gibs.earthdata.nasa.gov/wmts/epsg4326/best/AMSR2_Snow_Water_Equivalent/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png', + * layer : 'AMSR2_Snow_Water_Equivalent', + * style : 'default', + * tileMatrixSetID : '2km', + * maximumLevel : 5, + * format : 'image/png', + * clock: clock, + * times: times, + * credit : new Cesium.Credit('NASA Global Imagery Browse Services for EOSDIS') + * }); + * viewer.imageryLayers.addImageryProvider(weather); + * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider - * @see GoogleEarthImageryProvider + * @see GoogleEarthEnterpriseMapsProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider * @see createTileMapServiceImageryProvider @@ -187377,7 +209435,7 @@ define('Scene/WebMapTileServiceImageryProvider',[ this._proxy = options.proxy; this._tileDiscardPolicy = options.tileDiscardPolicy; - this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid }); + this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ellipsoid : options.ellipsoid}); this._tileWidth = defaultValue(options.tileWidth, 256); this._tileHeight = defaultValue(options.tileHeight, 256); @@ -187385,6 +209443,24 @@ define('Scene/WebMapTileServiceImageryProvider',[ this._maximumLevel = options.maximumLevel; this._rectangle = defaultValue(options.rectangle, this._tilingScheme.rectangle); + this._dimensions = options.dimensions; + + var that = this; + this._reload = undefined; + if (defined(options.times)) { + this._timeDynamicImagery = new TimeDynamicImagery({ + clock : options.clock, + times : options.times, + requestImageFunction : function(x, y, level, request, interval) { + return requestImage(that, x, y, level, request, interval); + }, + reloadFunction : function() { + if (defined(that._reload)) { + that._reload(); + } + } + }); + } this._readyPromise = when.resolve(true); @@ -187416,11 +209492,14 @@ define('Scene/WebMapTileServiceImageryProvider',[ request : 'GetTile' }); - function buildImageUrl(imageryProvider, col, row, level) { + function requestImage(imageryProvider, col, row, level, request, interval) { var labels = imageryProvider._tileMatrixLabels; var tileMatrix = defined(labels) ? labels[level] : level.toString(); var subdomains = imageryProvider._subdomains; var url; + var key; + var staticDimensions = imageryProvider._dimensions; + var dynamicIntervalData = defined(interval) ? interval.data : undefined; if (imageryProvider._url.indexOf('{') >= 0) { // resolve tile-URL template @@ -187432,6 +209511,22 @@ define('Scene/WebMapTileServiceImageryProvider',[ .replace('{TileRow}', row.toString()) .replace('{TileCol}', col.toString()) .replace('{s}', subdomains[(col + row + level) % subdomains.length]); + + if (defined(staticDimensions)) { + for (key in staticDimensions) { + if (staticDimensions.hasOwnProperty(key)) { + url = url.replace('{' + key + '}', staticDimensions[key]); + } + } + } + + if (defined(dynamicIntervalData)) { + for (key in dynamicIntervalData) { + if (dynamicIntervalData.hasOwnProperty(key)) { + url = url.replace('{' + key + '}', dynamicIntervalData[key]); + } + } + } } else { // build KVP request @@ -187448,6 +209543,22 @@ define('Scene/WebMapTileServiceImageryProvider',[ queryOptions.tilematrixset = imageryProvider._tileMatrixSetID; queryOptions.format = imageryProvider._format; + if (defined(staticDimensions)) { + for (key in staticDimensions) { + if (staticDimensions.hasOwnProperty(key)) { + queryOptions[key] = staticDimensions[key]; + } + } + } + + if (defined(dynamicIntervalData)) { + for (key in dynamicIntervalData) { + if (dynamicIntervalData.hasOwnProperty(key)) { + queryOptions[key] = dynamicIntervalData[key]; + } + } + } + uri.query = objectToQuery(queryOptions); url = uri.toString(); @@ -187458,7 +209569,7 @@ define('Scene/WebMapTileServiceImageryProvider',[ url = proxy.getURL(url); } - return url; + return ImageryProvider.loadImage(imageryProvider, url, request); } defineProperties(WebMapTileServiceImageryProvider.prototype, { @@ -187612,7 +209723,7 @@ define('Scene/WebMapTileServiceImageryProvider',[ * @readonly */ ready : { - value: true + value : true }, /** @@ -187654,6 +209765,52 @@ define('Scene/WebMapTileServiceImageryProvider',[ get : function() { return true; } + }, + /** + * Gets or sets a clock that is used to get keep the time used for time dynamic parameters. + * @memberof WebMapTileServiceImageryProvider.prototype + * @type {Clock} + */ + clock : { + get : function() { + return this._timeDynamicImagery.clock; + }, + set : function(value) { + this._timeDynamicImagery.clock = value; + } + }, + /** + * Gets or sets a time interval collection that is used to get time dynamic parameters. The data of each + * TimeInterval is an object containing the keys and values of the properties that are used during + * tile requests. + * @memberof WebMapTileServiceImageryProvider.prototype + * @type {TimeIntervalCollection} + */ + times : { + get : function() { + return this._timeDynamicImagery.times; + }, + set : function(value) { + this._timeDynamicImagery.times = value; + } + }, + /** + * Gets or sets an object that contains static dimensions and their values. + * @memberof WebMapTileServiceImageryProvider.prototype + * @type {Object} + */ + dimensions : { + get : function() { + return this._dimensions; + }, + set : function(value) { + if (this._dimensions !== value) { + this._dimensions = value; + if (defined(this._reload)) { + this._reload(); + } + } + } } }); @@ -187678,6 +209835,7 @@ define('Scene/WebMapTileServiceImageryProvider',[ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. * @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 @@ -187685,9 +209843,28 @@ define('Scene/WebMapTileServiceImageryProvider',[ * * @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); + WebMapTileServiceImageryProvider.prototype.requestImage = function(x, y, level, request) { + var result; + var timeDynamicImagery = this._timeDynamicImagery; + var currentInterval; + + // Try and load from cache + if (defined(timeDynamicImagery)) { + currentInterval = timeDynamicImagery.currentInterval; + result = timeDynamicImagery.getFromCache(x, y, level, request); + } + + // Couldn't load from cache + if (!defined(result)) { + result = requestImage(this, x, y, level, request, currentInterval); + } + + // If we are approaching an interval, preload this tile in the next interval + if (defined(result) && defined(timeDynamicImagery)) { + timeDynamicImagery.checkApproachingInterval(x, y, level, request); + } + + return result; }; /** @@ -187750,7 +209927,6 @@ define('Scene/WebMapTileServiceImageryProvider',[ // For instructions, see: // https://github.com/AnalyticalGraphicsInc/crunch -/*global define*/ define('ThirdParty/crunch',[], function() { var Module;if(!Module)Module=(typeof Module!=="undefined"?Module:null)||{};var moduleOverrides={};for(var key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){if(!Module["print"])Module["print"]=function print(x){process["stdout"].write(x+"\n")};if(!Module["printErr"])Module["printErr"]=function printErr(x){process["stderr"].write(x+"\n")};var nodeFS=require("fs");var nodePath=require("path");Module["read"]=function read(filename,binary){filename=nodePath["normalize"](filename);var ret=nodeFS["readFileSync"](filename);if(!ret&&filename!=nodePath["resolve"](filename)){filename=path.join(__dirname,"..","src",filename);ret=nodeFS["readFileSync"](filename)}if(ret&&!binary)ret=ret.toString();return ret};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};Module["load"]=function load(f){globalEval(read(f))};if(!Module["thisProgram"]){if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}else{Module["thisProgram"]="unknown-program"}}Module["arguments"]=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",(function(ex){if(!(ex instanceof ExitStatus)){throw ex}}));Module["inspect"]=(function(){return"[Emscripten Module object]"})}else if(ENVIRONMENT_IS_SHELL){if(!Module["print"])Module["print"]=print;if(typeof printErr!="undefined")Module["printErr"]=printErr;if(typeof read!="undefined"){Module["read"]=read}else{Module["read"]=function read(){throw"no read() available (jsc?)"}}Module["readBinary"]=function readBinary(f){if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}var data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof console!=="undefined"){if(!Module["print"])Module["print"]=function print(x){console.log(x)};if(!Module["printErr"])Module["printErr"]=function printErr(x){console.log(x)}}else{var TRY_USE_DUMP=false;if(!Module["print"])Module["print"]=TRY_USE_DUMP&&typeof dump!=="undefined"?(function(x){dump(x)}):(function(x){})}if(ENVIRONMENT_IS_WORKER){Module["load"]=importScripts}if(typeof Module["setWindowTitle"]==="undefined"){Module["setWindowTitle"]=(function(title){document.title=title})}}else{throw"Unknown runtime environment. Where are we?"}function globalEval(x){eval.call(null,x)}if(!Module["load"]&&Module["read"]){Module["load"]=function load(f){globalEval(Module["read"](f))}}if(!Module["print"]){Module["print"]=(function(){})}if(!Module["printErr"]){Module["printErr"]=Module["print"]}if(!Module["arguments"]){Module["arguments"]=[]}if(!Module["thisProgram"]){Module["thisProgram"]="./this.program"}Module.print=Module["print"];Module.printErr=Module["printErr"];Module["preRun"]=[];Module["postRun"]=[];for(var key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}var Runtime={setTempRet0:(function(value){tempRet0=value}),getTempRet0:(function(){return tempRet0}),stackSave:(function(){return STACKTOP}),stackRestore:(function(stackTop){STACKTOP=stackTop}),getNativeTypeSize:(function(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return Runtime.QUANTUM_SIZE}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0);return bits/8}else{return 0}}}}),getNativeFieldSize:(function(type){return Math.max(Runtime.getNativeTypeSize(type),Runtime.QUANTUM_SIZE)}),STACK_ALIGN:16,prepVararg:(function(ptr,type){if(type==="double"||type==="i64"){if(ptr&7){assert((ptr&7)===4);ptr+=4}}else{assert((ptr&3)===0)}return ptr}),getAlignSize:(function(type,size,vararg){if(!vararg&&(type=="i64"||type=="double"))return 8;if(!type)return Math.min(size,8);return Math.min(size||(type?Runtime.getNativeFieldSize(type):0),Runtime.QUANTUM_SIZE)}),dynCall:(function(sig,ptr,args){if(args&&args.length){if(!args.splice)args=Array.prototype.slice.call(args);args.splice(0,0,ptr);return Module["dynCall_"+sig].apply(null,args)}else{return Module["dynCall_"+sig].call(null,ptr)}}),functionPointers:[],addFunction:(function(func){for(var i=0;i=TOTAL_MEMORY){var success=enlargeMemory();if(!success){DYNAMICTOP=ret;return 0}}return ret}),alignMemory:(function(size,quantum){var ret=size=Math.ceil(size/(quantum?quantum:16))*(quantum?quantum:16);return ret}),makeBigInt:(function(low,high,unsigned){var ret=unsigned?+(low>>>0)+ +(high>>>0)*+4294967296:+(low>>>0)+ +(high|0)*+4294967296;return ret}),GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module["Runtime"]=Runtime;var __THREW__=0;var ABORT=false;var EXITSTATUS=0;var undef=0;var tempValue,tempInt,tempBigInt,tempInt2,tempBigInt2,tempPair,tempBigIntI,tempBigIntR,tempBigIntS,tempBigIntP,tempBigIntD,tempDouble,tempFloat;var tempI64,tempI64b;var tempRet0,tempRet1,tempRet2,tempRet3,tempRet4,tempRet5,tempRet6,tempRet7,tempRet8,tempRet9;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var globalScope=this;function getCFunc(ident){var func=Module["_"+ident];if(!func){try{func=eval("_"+ident)}catch(e){}}assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)");return func}var cwrap,ccall;((function(){var JSfuncs={"stackSave":(function(){Runtime.stackSave()}),"stackRestore":(function(){Runtime.stackRestore()}),"arrayToC":(function(arr){var ret=Runtime.stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}),"stringToC":(function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=Runtime.stackAlloc((str.length<<2)+1);writeStringToMemory(str,ret)}return ret})};var toC={"string":JSfuncs["stringToC"],"array":JSfuncs["arrayToC"]};ccall=function ccallFunc(ident,returnType,argTypes,args,opts){var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math_abs(tempDouble)>=+1?tempDouble>+0?(Math_min(+Math_floor(tempDouble/+4294967296),+4294967295)|0)>>>0:~~+Math_ceil((tempDouble- +(~~tempDouble>>>0))/+4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}Module["setValue"]=setValue;function getValue(ptr,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];default:abort("invalid type for setValue: "+type)}return null}Module["getValue"]=getValue;var ALLOC_NORMAL=0;var ALLOC_STACK=1;var ALLOC_STATIC=2;var ALLOC_DYNAMIC=3;var ALLOC_NONE=4;Module["ALLOC_NORMAL"]=ALLOC_NORMAL;Module["ALLOC_STACK"]=ALLOC_STACK;Module["ALLOC_STATIC"]=ALLOC_STATIC;Module["ALLOC_DYNAMIC"]=ALLOC_DYNAMIC;Module["ALLOC_NONE"]=ALLOC_NONE;function allocate(slab,types,allocator,ptr){var zeroinit,size;if(typeof slab==="number"){zeroinit=true;size=slab}else{zeroinit=false;size=slab.length}var singleType=typeof types==="string"?types:null;var ret;if(allocator==ALLOC_NONE){ret=ptr}else{ret=[_malloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][allocator===undefined?ALLOC_STATIC:allocator](Math.max(size,singleType?1:types.length))}if(zeroinit){var ptr=ret,stop;assert((ret&3)==0);stop=ret+(size&~3);for(;ptr>2]=0}stop=ret+size;while(ptr>0]=0}return ret}if(singleType==="i8"){if(slab.subarray||slab.slice){HEAPU8.set(slab,ret)}else{HEAPU8.set(new Uint8Array(slab),ret)}return ret}var i=0,type,typeSize,previousType;while(i>0];hasUtf|=t;if(t==0&&!length)break;i++;if(length&&i==length)break}if(!length)length=i;var ret="";if(hasUtf<128){var MAX_CHUNK=1024;var curr;while(length>0){curr=String.fromCharCode.apply(String,HEAPU8.subarray(ptr,ptr+Math.min(length,MAX_CHUNK)));ret=ret?ret+curr:curr;ptr+=MAX_CHUNK;length-=MAX_CHUNK}return ret}return Module["UTF8ToString"](ptr)}Module["Pointer_stringify"]=Pointer_stringify;function AsciiToString(ptr){var str="";while(1){var ch=HEAP8[ptr++>>0];if(!ch)return str;str+=String.fromCharCode(ch)}}Module["AsciiToString"]=AsciiToString;function stringToAscii(str,outPtr){return writeAsciiToMemory(str,outPtr,false)}Module["stringToAscii"]=stringToAscii;function UTF8ArrayToString(u8Array,idx){var u0,u1,u2,u3,u4,u5;var str="";while(1){u0=u8Array[idx++];if(!u0)return str;if(!(u0&128)){str+=String.fromCharCode(u0);continue}u1=u8Array[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}u2=u8Array[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u3=u8Array[idx++]&63;if((u0&248)==240){u0=(u0&7)<<18|u1<<12|u2<<6|u3}else{u4=u8Array[idx++]&63;if((u0&252)==248){u0=(u0&3)<<24|u1<<18|u2<<12|u3<<6|u4}else{u5=u8Array[idx++]&63;u0=(u0&1)<<30|u1<<24|u2<<18|u3<<12|u4<<6|u5}}}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}}Module["UTF8ArrayToString"]=UTF8ArrayToString;function UTF8ToString(ptr){return UTF8ArrayToString(HEAPU8,ptr)}Module["UTF8ToString"]=UTF8ToString;function stringToUTF8Array(str,outU8Array,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127){if(outIdx>=endIdx)break;outU8Array[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;outU8Array[outIdx++]=192|u>>6;outU8Array[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;outU8Array[outIdx++]=224|u>>12;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else if(u<=2097151){if(outIdx+3>=endIdx)break;outU8Array[outIdx++]=240|u>>18;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else if(u<=67108863){if(outIdx+4>=endIdx)break;outU8Array[outIdx++]=248|u>>24;outU8Array[outIdx++]=128|u>>18&63;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else{if(outIdx+5>=endIdx)break;outU8Array[outIdx++]=252|u>>30;outU8Array[outIdx++]=128|u>>24&63;outU8Array[outIdx++]=128|u>>18&63;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}}outU8Array[outIdx]=0;return outIdx-startIdx}Module["stringToUTF8Array"]=stringToUTF8Array;function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}Module["stringToUTF8"]=stringToUTF8;function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127){++len}else if(u<=2047){len+=2}else if(u<=65535){len+=3}else if(u<=2097151){len+=4}else if(u<=67108863){len+=5}else{len+=6}}return len}Module["lengthBytesUTF8"]=lengthBytesUTF8;function UTF16ToString(ptr){var i=0;var str="";while(1){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)return str;++i;str+=String.fromCharCode(codeUnit)}}Module["UTF16ToString"]=UTF16ToString;function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}Module["stringToUTF16"]=stringToUTF16;function lengthBytesUTF16(str){return str.length*2}Module["lengthBytesUTF16"]=lengthBytesUTF16;function UTF32ToString(ptr){var i=0;var str="";while(1){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)return str;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}}Module["UTF32ToString"]=UTF32ToString;function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}Module["stringToUTF32"]=stringToUTF32;function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}Module["lengthBytesUTF32"]=lengthBytesUTF32;function demangle(func){var hasLibcxxabi=!!Module["___cxa_demangle"];if(hasLibcxxabi){try{var buf=_malloc(func.length);writeStringToMemory(func.substr(1),buf);var status=_malloc(4);var ret=Module["___cxa_demangle"](buf,0,0,status);if(getValue(status,"i32")===0&&ret){return Pointer_stringify(ret)}}catch(e){}finally{if(buf)_free(buf);if(status)_free(status);if(ret)_free(ret)}}var i=3;var basicTypes={"v":"void","b":"bool","c":"char","s":"short","i":"int","l":"long","f":"float","d":"double","w":"wchar_t","a":"signed char","h":"unsigned char","t":"unsigned short","j":"unsigned int","m":"unsigned long","x":"long long","y":"unsigned long long","z":"..."};var subs=[];var first=true;function dump(x){if(x)Module.print(x);Module.print(func);var pre="";for(var a=0;a"}else{ret=name}paramLoop:while(i0){var c=func[i++];if(c in basicTypes){list.push(basicTypes[c])}else{switch(c){case"P":list.push(parse(true,1,true)[0]+"*");break;case"R":list.push(parse(true,1,true)[0]+"&");break;case"L":{i++;var end=func.indexOf("E",i);var size=end-i;list.push(func.substr(i,size));i+=size+2;break};case"A":{var size=parseInt(func.substr(i));i+=size.toString().length;if(func[i]!=="_")throw"?";i++;list.push(parse(true,1,true)[0]+" ["+size+"]");break};case"E":break paramLoop;default:ret+="?"+c;break paramLoop}}}if(!allowVoid&&list.length===1&&list[0]==="void")list=[];if(rawList){if(ret){list.push(ret+"?")}return list}else{return ret+flushList()}}var parsed=func;try{if(func=="Object._main"||func=="_main"){return"main()"}if(typeof func==="number")func=Pointer_stringify(func);if(func[0]!=="_")return func;if(func[1]!=="_")return func;if(func[2]!=="Z")return func;switch(func[3]){case"n":return"operator new()";case"d":return"operator delete()"}parsed=parse()}catch(e){parsed+="?"}if(parsed.indexOf("?")>=0&&!hasLibcxxabi){Runtime.warnOnce("warning: a problem occurred in builtin C++ name demangling; build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling")}return parsed}function demangleAll(text){return text.replace(/__Z[\w\d_]+/g,(function(x){var y=demangle(x);return x===y?x:x+" ["+y+"]"}))}function jsStackTrace(){var err=new Error;if(!err.stack){try{throw new Error(0)}catch(e){err=e}if(!err.stack){return"(no stack trace available)"}}return err.stack.toString()}function stackTrace(){return demangleAll(jsStackTrace())}Module["stackTrace"]=stackTrace;var PAGE_SIZE=4096;function alignMemoryPage(x){if(x%4096>0){x+=4096-x%4096}return x}var HEAP;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var STATIC_BASE=0,STATICTOP=0,staticSealed=false;var STACK_BASE=0,STACKTOP=0,STACK_MAX=0;var DYNAMIC_BASE=0,DYNAMICTOP=0;function enlargeMemory(){var OLD_TOTAL_MEMORY=TOTAL_MEMORY;var LIMIT=Math.pow(2,31);if(DYNAMICTOP>=LIMIT)return false;while(TOTAL_MEMORY<=DYNAMICTOP){if(TOTAL_MEMORY=LIMIT)return false;try{if(ArrayBuffer.transfer){buffer=ArrayBuffer.transfer(buffer,TOTAL_MEMORY)}else{var oldHEAP8=HEAP8;buffer=new ArrayBuffer(TOTAL_MEMORY)}}catch(e){return false}var success=_emscripten_replace_memory(buffer);if(!success)return false;Module["buffer"]=buffer;Module["HEAP8"]=HEAP8=new Int8Array(buffer);Module["HEAP16"]=HEAP16=new Int16Array(buffer);Module["HEAP32"]=HEAP32=new Int32Array(buffer);Module["HEAPU8"]=HEAPU8=new Uint8Array(buffer);Module["HEAPU16"]=HEAPU16=new Uint16Array(buffer);Module["HEAPU32"]=HEAPU32=new Uint32Array(buffer);Module["HEAPF32"]=HEAPF32=new Float32Array(buffer);Module["HEAPF64"]=HEAPF64=new Float64Array(buffer);if(!ArrayBuffer.transfer){HEAP8.set(oldHEAP8)}return true}var byteLength;try{byteLength=Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype,"byteLength").get);byteLength(new ArrayBuffer(4))}catch(e){byteLength=(function(buffer){return buffer.byteLength})}var TOTAL_STACK=Module["TOTAL_STACK"]||5242880;var TOTAL_MEMORY=Module["TOTAL_MEMORY"]||16777216;var totalMemory=64*1024;while(totalMemory0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Runtime.dynCall("v",func)}else{Runtime.dynCall("vi",func,[callback.arg])}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){if(runtimeInitialized)return;runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__);runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}Module["addOnPreRun"]=addOnPreRun;function addOnInit(cb){__ATINIT__.unshift(cb)}Module["addOnInit"]=addOnInit;function addOnPreMain(cb){__ATMAIN__.unshift(cb)}Module["addOnPreMain"]=addOnPreMain;function addOnExit(cb){__ATEXIT__.unshift(cb)}Module["addOnExit"]=addOnExit;function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}Module["addOnPostRun"]=addOnPostRun;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}Module["intArrayFromString"]=intArrayFromString;function intArrayToString(array){var ret=[];for(var i=0;i255){chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}Module["intArrayToString"]=intArrayToString;function writeStringToMemory(string,buffer,dontAddNull){var array=intArrayFromString(string,dontAddNull);var i=0;while(i>0]=chr;i=i+1}}Module["writeStringToMemory"]=writeStringToMemory;function writeArrayToMemory(array,buffer){for(var i=0;i>0]=array[i]}}Module["writeArrayToMemory"]=writeArrayToMemory;function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}Module["writeAsciiToMemory"]=writeAsciiToMemory;function unSign(value,bits,ignore){if(value>=0){return value}return bits<=32?2*Math.abs(1<=half&&(bits<=32||value>half)){value=-2*half+value}return value}if(!Math["imul"]||Math["imul"](4294967295,5)!==-5)Math["imul"]=function imul(a,b){var ah=a>>>16;var al=a&65535;var bh=b>>>16;var bl=b&65535;return al*bl+(ah*bl+al*bh<<16)|0};Math.imul=Math["imul"];if(!Math["clz32"])Math["clz32"]=(function(x){x=x>>>0;for(var i=0;i<32;i++){if(x&1<<31-i)return i}return 32});Math.clz32=Math["clz32"];var Math_abs=Math.abs;var Math_cos=Math.cos;var Math_sin=Math.sin;var Math_tan=Math.tan;var Math_acos=Math.acos;var Math_asin=Math.asin;var Math_atan=Math.atan;var Math_atan2=Math.atan2;var Math_exp=Math.exp;var Math_log=Math.log;var Math_sqrt=Math.sqrt;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_pow=Math.pow;var Math_imul=Math.imul;var Math_fround=Math.fround;var Math_min=Math.min;var Math_clz32=Math.clz32;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}Module["addRunDependency"]=addRunDependency;function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["removeRunDependency"]=removeRunDependency;Module["preloadedImages"]={};Module["preloadedAudios"]={};var memoryInitializer=null;var ASM_CONSTS=[];STATIC_BASE=8;STATICTOP=STATIC_BASE+5888;__ATINIT__.push();allocate([116,0,0,0,86,7,0,0,116,0,0,0,99,7,0,0,156,0,0,0,112,7,0,0,16,0,0,0,0,0,0,0,156,0,0,0,145,7,0,0,24,0,0,0,0,0,0,0,156,0,0,0,215,7,0,0,24,0,0,0,0,0,0,0,156,0,0,0,179,7,0,0,56,0,0,0,0,0,0,0,156,0,0,0,249,7,0,0,40,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,40,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,4,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,88,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,4,0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,2,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,108,1,0,0,220,1,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,0,0,0,0,0,0,0,0,0,0,2,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,0,0,0,0,0,0,255,255,255,255,255,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,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,4,0,0,0,227,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,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,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,4,0,0,0,219,16,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,115,40,37,117,41,58,32,65,115,115,101,114,116,105,111,110,32,102,97,105,108,117,114,101,58,32,34,37,115,34,10,0,109,95,115,105,122,101,32,60,61,32,109,95,99,97,112,97,99,105,116,121,0,46,47,105,110,99,92,99,114,110,95,100,101,99,111,109,112,46,104,0,109,105,110,95,110,101,119,95,99,97,112,97,99,105,116,121,32,60,32,40,48,120,55,70,70,70,48,48,48,48,85,32,47,32,101,108,101,109,101,110,116,95,115,105,122,101,41,0,110,101,119,95,99,97,112,97,99,105,116,121,32,38,38,32,40,110,101,119,95,99,97,112,97,99,105,116,121,32,62,32,109,95,99,97,112,97,99,105,116,121,41,0,110,117,109,95,99,111,100,101,115,91,99,93,0,115,111,114,116,101,100,95,112,111,115,32,60,32,116,111,116,97,108,95,117,115,101,100,95,115,121,109,115,0,112,67,111,100,101,115,105,122,101,115,91,115,121,109,95,105,110,100,101,120,93,32,61,61,32,99,111,100,101,115,105,122,101,0,116,32,60,32,40,49,85,32,60,60,32,116,97,98,108,101,95,98,105,116,115,41,0,109,95,108,111,111,107,117,112,91,116,93,32,61,61,32,99,85,73,78,84,51,50,95,77,65,88,0,99,114,110,100,95,109,97,108,108,111,99,58,32,115,105,122,101,32,116,111,111,32,98,105,103,0,99,114,110,100,95,109,97,108,108,111,99,58,32,111,117,116,32,111,102,32,109,101,109,111,114,121,0,40,40,117,105,110,116,51,50,41,112,95,110,101,119,32,38,32,40,67,82,78,68,95,77,73,78,95,65,76,76,79,67,95,65,76,73,71,78,77,69,78,84,32,45,32,49,41,41,32,61,61,32,48,0,99,114,110,100,95,114,101,97,108,108,111,99,58,32,98,97,100,32,112,116,114,0,99,114,110,100,95,102,114,101,101,58,32,98,97,100,32,112,116,114,0,102,97,108,115,101,0,40,116,111,116,97,108,95,115,121,109,115,32,62,61,32,49,41,32,38,38,32,40,116,111,116,97,108,95,115,121,109,115,32,60,61,32,112,114,101,102,105,120,95,99,111,100,105,110,103,58,58,99,77,97,120,83,117,112,112,111,114,116,101,100,83,121,109,115,41,0,17,18,19,20,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15,16,48,0,110,117,109,95,98,105,116,115,32,60,61,32,51,50,85,0,109,95,98,105,116,95,99,111,117,110,116,32,60,61,32,99,66,105,116,66,117,102,83,105,122,101,0,116,32,33,61,32,99,85,73,78,84,51,50,95,77,65,88,0,109,111,100,101,108,46,109,95,99,111,100,101,95,115,105,122,101,115,91,115,121,109,93,32,61,61,32,108,101,110,0,0,2,3,1,0,2,3,4,5,6,7,1,40,108,101,110,32,62,61,32,49,41,32,38,38,32,40,108,101,110,32,60,61,32,99,77,97,120,69,120,112,101,99,116,101,100,67,111,100,101,83,105,122,101,41,0,105,32,60,32,109,95,115,105,122,101,0,110,101,120,116,95,108,101,118,101,108,95,111,102,115,32,62,32,99,117,114,95,108,101,118,101,108,95,111,102,115,0,1,2,2,3,3,3,3,4,0,0,0,0,0,0,1,1,0,1,0,1,0,0,1,2,1,2,0,0,0,1,0,2,1,0,2,0,0,1,2,3,110,117,109,32,38,38,32,40,110,117,109,32,61,61,32,126,110,117,109,95,99,104,101,99,107,41,0,83,116,57,101,120,99,101,112,116,105,111,110,0,83,116,57,116,121,112,101,95,105,110,102,111,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,54,95,95,115,104,105,109,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,55,95,95,99,108,97,115,115,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,57,95,95,112,111,105,110,116,101,114,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,55,95,95,112,98,97,115,101,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,50,48,95,95,115,105,95,99,108,97,115,115,95,116,121,112,101,95,105,110,102,111,69,0,112,116,104,114,101,97,100,95,111,110,99,101,32,102,97,105,108,117,114,101,32,105,110,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,95,102,97,115,116,40,41,0,116,101,114,109,105,110,97,116,101,95,104,97,110,100,108,101,114,32,117,110,101,120,112,101,99,116,101,100,108,121,32,114,101,116,117,114,110,101,100,0,99,97,110,110,111,116,32,99,114,101,97,116,101,32,112,116,104,114,101,97,100,32,107,101,121,32,102,111,114,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,40,41,0,99,97,110,110,111,116,32,122,101,114,111,32,111,117,116,32,116,104,114,101,97,100,32,118,97,108,117,101,32,102,111,114,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,40,41,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,101,120,99,101,112,116,105,111,110,32,111,102,32,116,121,112,101,32,37,115,58,32,37,115,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,101,120,99,101,112,116,105,111,110,32,111,102,32,116,121,112,101,32,37,115,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,102,111,114,101,105,103,110,32,101,120,99,101,112,116,105,111,110,0,116,101,114,109,105,110,97,116,105,110,103,0,117,110,99,97,117,103,104,116,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,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,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,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,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,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,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,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,18,0,0,0,18,18,18,0,0,0,0,0,0,9,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,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,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,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,46,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=Runtime.alignMemory(allocate(12,"i8",ALLOC_STATIC),8);assert(tempDoublePtr%8==0);function copyTempFloat(ptr){HEAP8[tempDoublePtr]=HEAP8[ptr];HEAP8[tempDoublePtr+1]=HEAP8[ptr+1];HEAP8[tempDoublePtr+2]=HEAP8[ptr+2];HEAP8[tempDoublePtr+3]=HEAP8[ptr+3]}function copyTempDouble(ptr){HEAP8[tempDoublePtr]=HEAP8[ptr];HEAP8[tempDoublePtr+1]=HEAP8[ptr+1];HEAP8[tempDoublePtr+2]=HEAP8[ptr+2];HEAP8[tempDoublePtr+3]=HEAP8[ptr+3];HEAP8[tempDoublePtr+4]=HEAP8[ptr+4];HEAP8[tempDoublePtr+5]=HEAP8[ptr+5];HEAP8[tempDoublePtr+6]=HEAP8[ptr+6];HEAP8[tempDoublePtr+7]=HEAP8[ptr+7]}var _BDtoIHigh=true;Module["_i64Subtract"]=_i64Subtract;Module["_i64Add"]=_i64Add;function _pthread_cleanup_push(routine,arg){__ATEXIT__.push((function(){Runtime.dynCall("vi",routine,[arg])}));_pthread_cleanup_push.level=__ATEXIT__.length}Module["_memset"]=_memset;var _BDtoILow=true;Module["_bitshift64Lshr"]=_bitshift64Lshr;Module["_bitshift64Shl"]=_bitshift64Shl;function _pthread_cleanup_pop(){assert(_pthread_cleanup_push.level==__ATEXIT__.length,"cannot pop if something else added meanwhile!");__ATEXIT__.pop();_pthread_cleanup_push.level=__ATEXIT__.length}function _abort(){Module["abort"]()}function __ZSt18uncaught_exceptionv(){return!!__ZSt18uncaught_exceptionv.uncaught_exception}var EXCEPTIONS={last:0,caught:[],infos:{},deAdjust:(function(adjusted){if(!adjusted||EXCEPTIONS.infos[adjusted])return adjusted;for(var ptr in EXCEPTIONS.infos){var info=EXCEPTIONS.infos[ptr];if(info.adjusted===adjusted){return ptr}}return adjusted}),addRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];info.refcount++}),decRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];assert(info.refcount>0);info.refcount--;if(info.refcount===0){if(info.destructor){Runtime.dynCall("vi",info.destructor,[ptr])}delete EXCEPTIONS.infos[ptr];___cxa_free_exception(ptr)}}),clearRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];info.refcount=0})};function ___cxa_begin_catch(ptr){__ZSt18uncaught_exceptionv.uncaught_exception--;EXCEPTIONS.caught.push(ptr);EXCEPTIONS.addRef(EXCEPTIONS.deAdjust(ptr));return ptr}function _pthread_once(ptr,func){if(!_pthread_once.seen)_pthread_once.seen={};if(ptr in _pthread_once.seen)return;Runtime.dynCall("v",func);_pthread_once.seen[ptr]=1}function _emscripten_memcpy_big(dest,src,num){HEAPU8.set(HEAPU8.subarray(src,src+num),dest);return dest}Module["_memcpy"]=_memcpy;var SYSCALLS={varargs:0,get:(function(varargs){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret}),getStr:(function(){var ret=Pointer_stringify(SYSCALLS.get());return ret}),get64:(function(){var low=SYSCALLS.get(),high=SYSCALLS.get();if(low>=0)assert(high===0);else assert(high===-1);return low}),getZero:(function(){assert(SYSCALLS.get()===0)})};function ___syscall6(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD();FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var PTHREAD_SPECIFIC={};function _pthread_getspecific(key){return PTHREAD_SPECIFIC[key]||0}function ___setErrNo(value){if(Module["___errno_location"])HEAP32[Module["___errno_location"]()>>2]=value;return value}var ERRNO_CODES={EPERM:1,ENOENT:2,ESRCH:3,EINTR:4,EIO:5,ENXIO:6,E2BIG:7,ENOEXEC:8,EBADF:9,ECHILD:10,EAGAIN:11,EWOULDBLOCK:11,ENOMEM:12,EACCES:13,EFAULT:14,ENOTBLK:15,EBUSY:16,EEXIST:17,EXDEV:18,ENODEV:19,ENOTDIR:20,EISDIR:21,EINVAL:22,ENFILE:23,EMFILE:24,ENOTTY:25,ETXTBSY:26,EFBIG:27,ENOSPC:28,ESPIPE:29,EROFS:30,EMLINK:31,EPIPE:32,EDOM:33,ERANGE:34,ENOMSG:42,EIDRM:43,ECHRNG:44,EL2NSYNC:45,EL3HLT:46,EL3RST:47,ELNRNG:48,EUNATCH:49,ENOCSI:50,EL2HLT:51,EDEADLK:35,ENOLCK:37,EBADE:52,EBADR:53,EXFULL:54,ENOANO:55,EBADRQC:56,EBADSLT:57,EDEADLOCK:35,EBFONT:59,ENOSTR:60,ENODATA:61,ETIME:62,ENOSR:63,ENONET:64,ENOPKG:65,EREMOTE:66,ENOLINK:67,EADV:68,ESRMNT:69,ECOMM:70,EPROTO:71,EMULTIHOP:72,EDOTDOT:73,EBADMSG:74,ENOTUNIQ:76,EBADFD:77,EREMCHG:78,ELIBACC:79,ELIBBAD:80,ELIBSCN:81,ELIBMAX:82,ELIBEXEC:83,ENOSYS:38,ENOTEMPTY:39,ENAMETOOLONG:36,ELOOP:40,EOPNOTSUPP:95,EPFNOSUPPORT:96,ECONNRESET:104,ENOBUFS:105,EAFNOSUPPORT:97,EPROTOTYPE:91,ENOTSOCK:88,ENOPROTOOPT:92,ESHUTDOWN:108,ECONNREFUSED:111,EADDRINUSE:98,ECONNABORTED:103,ENETUNREACH:101,ENETDOWN:100,ETIMEDOUT:110,EHOSTDOWN:112,EHOSTUNREACH:113,EINPROGRESS:115,EALREADY:114,EDESTADDRREQ:89,EMSGSIZE:90,EPROTONOSUPPORT:93,ESOCKTNOSUPPORT:94,EADDRNOTAVAIL:99,ENETRESET:102,EISCONN:106,ENOTCONN:107,ETOOMANYREFS:109,EUSERS:87,EDQUOT:122,ESTALE:116,ENOTSUP:95,ENOMEDIUM:123,EILSEQ:84,EOVERFLOW:75,ECANCELED:125,ENOTRECOVERABLE:131,EOWNERDEAD:130,ESTRPIPE:86};function _sysconf(name){switch(name){case 30:return PAGE_SIZE;case 85:return totalMemory/PAGE_SIZE;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1e3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:{if(typeof navigator==="object")return navigator["hardwareConcurrency"]||1;return 1}}___setErrNo(ERRNO_CODES.EINVAL);return-1}function _sbrk(bytes){var self=_sbrk;if(!self.called){DYNAMICTOP=alignMemoryPage(DYNAMICTOP);self.called=true;assert(Runtime.dynamicAlloc);self.alloc=Runtime.dynamicAlloc;Runtime.dynamicAlloc=(function(){abort("cannot dynamically allocate, sbrk now has control")})}var ret=DYNAMICTOP;if(bytes!=0){var success=self.alloc(bytes);if(!success)return-1>>>0}return ret}var PTHREAD_SPECIFIC_NEXT_KEY=1;function _pthread_key_create(key,destructor){if(key==0){return ERRNO_CODES.EINVAL}HEAP32[key>>2]=PTHREAD_SPECIFIC_NEXT_KEY;PTHREAD_SPECIFIC[PTHREAD_SPECIFIC_NEXT_KEY]=0;PTHREAD_SPECIFIC_NEXT_KEY++;return 0}var _BItoD=true;var PATH=undefined;function _emscripten_set_main_loop_timing(mode,value){Browser.mainLoop.timingMode=mode;Browser.mainLoop.timingValue=value;if(!Browser.mainLoop.func){return 1}if(mode==0){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setTimeout(){setTimeout(Browser.mainLoop.runner,value)};Browser.mainLoop.method="timeout"}else if(mode==1){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_rAF(){Browser.requestAnimationFrame(Browser.mainLoop.runner)};Browser.mainLoop.method="rAF"}else if(mode==2){if(!window["setImmediate"]){var setImmediates=[];var emscriptenMainLoopMessageId="__emcc";function Browser_setImmediate_messageHandler(event){if(event.source===window&&event.data===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}}window.addEventListener("message",Browser_setImmediate_messageHandler,true);window["setImmediate"]=function Browser_emulated_setImmediate(func){setImmediates.push(func);window.postMessage(emscriptenMainLoopMessageId,"*")}}Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setImmediate(){window["setImmediate"](Browser.mainLoop.runner)};Browser.mainLoop.method="immediate"}return 0}function _emscripten_set_main_loop(func,fps,simulateInfiniteLoop,arg,noSetTiming){Module["noExitRuntime"]=true;assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func=func;Browser.mainLoop.arg=arg;var thisMainLoopId=Browser.mainLoop.currentlyRunningMainloop;Browser.mainLoop.runner=function Browser_mainLoop_runner(){if(ABORT)return;if(Browser.mainLoop.queue.length>0){var start=Date.now();var blocker=Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if(Browser.mainLoop.remainingBlockers){var remaining=Browser.mainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){Browser.mainLoop.remainingBlockers=next}else{next=next+.5;Browser.mainLoop.remainingBlockers=(8*remaining+next)/9}}console.log('main loop blocker "'+blocker.name+'" took '+(Date.now()-start)+" ms");Browser.mainLoop.updateStatus();setTimeout(Browser.mainLoop.runner,0);return}if(thisMainLoopId1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}if(Browser.mainLoop.method==="timeout"&&Module.ctx){Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!");Browser.mainLoop.method=""}Browser.mainLoop.runIter((function(){if(typeof arg!=="undefined"){Runtime.dynCall("vi",func,[arg])}else{Runtime.dynCall("v",func)}}));if(thisMainLoopId0)_emscripten_set_main_loop_timing(0,1e3/fps);else _emscripten_set_main_loop_timing(1,1);Browser.mainLoop.scheduler()}if(simulateInfiniteLoop){throw"SimulateInfiniteLoop"}}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:(function(){Browser.mainLoop.scheduler=null;Browser.mainLoop.currentlyRunningMainloop++}),resume:(function(){Browser.mainLoop.currentlyRunningMainloop++;var timingMode=Browser.mainLoop.timingMode;var timingValue=Browser.mainLoop.timingValue;var func=Browser.mainLoop.func;Browser.mainLoop.func=null;_emscripten_set_main_loop(func,0,false,Browser.mainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);Browser.mainLoop.scheduler()}),updateStatus:(function(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=Browser.mainLoop.remainingBlockers;var expected=Browser.mainLoop.expectedBlockers;if(remaining){if(remaining=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.substr(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;Browser.safeSetTimeout((function(){finish(audio)}),1e4)}else{return fail()}};Module["preloadPlugins"].push(audioPlugin);var canvas=Module["canvas"];function pointerLockChange(){Browser.pointerLock=document["pointerLockElement"]===canvas||document["mozPointerLockElement"]===canvas||document["webkitPointerLockElement"]===canvas||document["msPointerLockElement"]===canvas}if(canvas){canvas.requestPointerLock=canvas["requestPointerLock"]||canvas["mozRequestPointerLock"]||canvas["webkitRequestPointerLock"]||canvas["msRequestPointerLock"]||(function(){});canvas.exitPointerLock=document["exitPointerLock"]||document["mozExitPointerLock"]||document["webkitExitPointerLock"]||document["msExitPointerLock"]||(function(){});canvas.exitPointerLock=canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange",pointerLockChange,false);document.addEventListener("mozpointerlockchange",pointerLockChange,false);document.addEventListener("webkitpointerlockchange",pointerLockChange,false);document.addEventListener("mspointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",(function(ev){if(!Browser.pointerLock&&canvas.requestPointerLock){canvas.requestPointerLock();ev.preventDefault()}}),false)}}}),createContext:(function(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module.ctx&&canvas==Module.canvas)return Module.ctx;var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}canvas.style.backgroundColor="black"}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){if(!useWebGL)assert(typeof GLctx==="undefined","cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Module.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach((function(callback){callback()}));Browser.init()}return ctx}),destroyContext:(function(canvas,useWebGL,setInModule){}),fullScreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullScreen:(function(lockPointer,resizeCanvas,vrDevice){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;Browser.vrDevice=vrDevice;if(typeof Browser.lockPointer==="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas==="undefined")Browser.resizeCanvas=false;if(typeof Browser.vrDevice==="undefined")Browser.vrDevice=null;var canvas=Module["canvas"];function fullScreenChange(){Browser.isFullScreen=false;var canvasContainer=canvas.parentNode;if((document["webkitFullScreenElement"]||document["webkitFullscreenElement"]||document["mozFullScreenElement"]||document["mozFullscreenElement"]||document["fullScreenElement"]||document["fullscreenElement"]||document["msFullScreenElement"]||document["msFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvasContainer){canvas.cancelFullScreen=document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["webkitCancelFullScreen"]||document["msExitFullscreen"]||document["exitFullscreen"]||(function(){});canvas.cancelFullScreen=canvas.cancelFullScreen.bind(document);if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullScreen=true;if(Browser.resizeCanvas)Browser.setFullScreenCanvasSize()}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas)Browser.setWindowedCanvasSize()}if(Module["onFullScreen"])Module["onFullScreen"](Browser.isFullScreen);Browser.updateCanvasDimensions(canvas)}if(!Browser.fullScreenHandlersInstalled){Browser.fullScreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullScreenChange,false);document.addEventListener("mozfullscreenchange",fullScreenChange,false);document.addEventListener("webkitfullscreenchange",fullScreenChange,false);document.addEventListener("MSFullscreenChange",fullScreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullScreen=canvasContainer["requestFullScreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullScreen"]?(function(){canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"])}):null);if(vrDevice){canvasContainer.requestFullScreen({vrDisplay:vrDevice})}else{canvasContainer.requestFullScreen()}}),nextRAF:0,fakeRequestAnimationFrame:(function(func){var now=Date.now();if(Browser.nextRAF===0){Browser.nextRAF=now+1e3/60}else{while(now+2>=Browser.nextRAF){Browser.nextRAF+=1e3/60}}var delay=Math.max(Browser.nextRAF-now,0);setTimeout(func,delay)}),requestAnimationFrame:function requestAnimationFrame(func){if(typeof window==="undefined"){Browser.fakeRequestAnimationFrame(func)}else{if(!window.requestAnimationFrame){window.requestAnimationFrame=window["requestAnimationFrame"]||window["mozRequestAnimationFrame"]||window["webkitRequestAnimationFrame"]||window["msRequestAnimationFrame"]||window["oRequestAnimationFrame"]||Browser.fakeRequestAnimationFrame}window.requestAnimationFrame(func)}},safeCallback:(function(func){return(function(){if(!ABORT)return func.apply(null,arguments)})}),allowAsyncCallbacks:true,queuedAsyncCallbacks:[],pauseAsyncCallbacks:(function(){Browser.allowAsyncCallbacks=false}),resumeAsyncCallbacks:(function(){Browser.allowAsyncCallbacks=true;if(Browser.queuedAsyncCallbacks.length>0){var callbacks=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[];callbacks.forEach((function(func){func()}))}}),safeRequestAnimationFrame:(function(func){return Browser.requestAnimationFrame((function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}else{Browser.queuedAsyncCallbacks.push(func)}}))}),safeSetTimeout:(function(func,timeout){Module["noExitRuntime"]=true;return setTimeout((function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}else{Browser.queuedAsyncCallbacks.push(func)}}),timeout)}),safeSetInterval:(function(func,timeout){Module["noExitRuntime"]=true;return setInterval((function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}}),timeout)}),getMimetype:(function(name){return{"jpg":"image/jpeg","jpeg":"image/jpeg","png":"image/png","bmp":"image/bmp","ogg":"audio/ogg","wav":"audio/wav","mp3":"audio/mpeg"}[name.substr(name.lastIndexOf(".")+1)]}),getUserMedia:(function(func){if(!window.getUserMedia){window.getUserMedia=navigator["getUserMedia"]||navigator["mozGetUserMedia"]}window.getUserMedia(func)}),getMovementX:(function(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0}),getMovementY:(function(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0}),getMouseWheelDelta:(function(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail;break;case"mousewheel":delta=event.wheelDelta;break;case"wheel":delta=event["deltaY"];break;default:throw"unrecognized mouse wheel event: "+event.type}return delta}),mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:(function(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}if(typeof SDL!="undefined"){Browser.mouseX=SDL.mouseX+Browser.mouseMovementX;Browser.mouseY=SDL.mouseY+Browser.mouseMovementY}else{Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}}else{var rect=Module["canvas"].getBoundingClientRect();var cw=Module["canvas"].width;var ch=Module["canvas"].height;var scrollX=typeof window.scrollX!=="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!=="undefined"?window.scrollY:window.pageYOffset;if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var adjustedX=touch.pageX-(scrollX+rect.left);var adjustedY=touch.pageY-(scrollY+rect.top);adjustedX=adjustedX*(cw/rect.width);adjustedY=adjustedY*(ch/rect.height);var coords={x:adjustedX,y:adjustedY};if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];if(!last)last=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}var x=event.pageX-(scrollX+rect.left);var y=event.pageY-(scrollY+rect.top);x=x*(cw/rect.width);y=y*(ch/rect.height);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y}}),xhrLoad:(function(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response)}else{onerror()}};xhr.onerror=onerror;xhr.send(null)}),asyncLoad:(function(url,onload,onerror,noRunDep){Browser.xhrLoad(url,(function(arrayBuffer){assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(!noRunDep)removeRunDependency("al "+url)}),(function(event){if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}}));if(!noRunDep)addRunDependency("al "+url)}),resizeListeners:[],updateResizeListeners:(function(){var canvas=Module["canvas"];Browser.resizeListeners.forEach((function(listener){listener(canvas.width,canvas.height)}))}),setCanvasSize:(function(width,height,noUpdates){var canvas=Module["canvas"];Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()}),windowedWidth:0,windowedHeight:0,setFullScreenCanvasSize:(function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];flags=flags|8388608;HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=flags}Browser.updateResizeListeners()}),setWindowedCanvasSize:(function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];flags=flags&~8388608;HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=flags}Browser.updateResizeListeners()}),updateCanvasDimensions:(function(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]&&Module["forcedAspectRatio"]>0){if(w/h>2]=ret}return ret}function _pthread_self(){return 0}function ___syscall140(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),offset_high=SYSCALLS.get(),offset_low=SYSCALLS.get(),result=SYSCALLS.get(),whence=SYSCALLS.get();var offset=offset_low;assert(offset_high===0);FS.llseek(stream,offset,whence);HEAP32[result>>2]=stream.position;if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall146(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.get(),iov=SYSCALLS.get(),iovcnt=SYSCALLS.get();var ret=0;if(!___syscall146.buffer)___syscall146.buffer=[];var buffer=___syscall146.buffer;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;ja.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("