diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index 3fe63914b5f8..733b9d6edc37 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -130,7 +130,7 @@ }); addStyle('Min and Max', { - color : "rgb(min(${POSITION}[0], 0.75) * 255, max(${POSITION}[2], 0.25) * 255, 255)", + color : "rgb(min(${POSITION}.x, 0.75) * 255, max(${POSITION}.z, 0.25) * 255, 255)", pointSize : "5" }); @@ -141,12 +141,11 @@ addStyle('Secondary Color', { color : { - expression : "[${secondaryColor}[0], ${secondaryColor}[1], ${secondaryColor}[2], 1.0]", conditions : [ - ["${id} < 250", "${expression}"], - ["${id} < 500", "${expression} * ${expression}"], - ["${id} < 750", "${expression} / 5.0"], - ["${id} < 1000", "rgb(0, 0, Number(${expression}[0] < 0.5) * 255)"] + ["${id} < 250", "vec4(${secondaryColor}, 1.0)"], + ["${id} < 500", "vec4(${secondaryColor} * ${secondaryColor}, 1.0)"], + ["${id} < 750", "vec4(${secondaryColor} / 5.0, 1.0)"], + ["${id} < 1000", "rgb(0, 0, Number(${secondaryColor}.x < 0.5) * 255)"] ] } }); @@ -159,8 +158,9 @@ show : "${POSITION}[0] > 0.5 || ${POSITION}[1] > 0.5 || ${POSITION}[2] > 0.5" }); +// POSITION contains 0 as its last component, so add 1.0 to make the point cloud opaque addStyle('Color based on position', { - color : "rgb(${POSITION}[0] * 255, ${POSITION}[1] * 255, ${POSITION}[2] * 255)" + color : "vec4(${POSITION}, 1.0)" }); addStyle('Style point size', { diff --git a/Apps/Sandcastle/gallery/Custom Geocoder.html b/Apps/Sandcastle/gallery/Custom Geocoder.html new file mode 100644 index 000000000000..b1f388c92db4 --- /dev/null +++ b/Apps/Sandcastle/gallery/Custom Geocoder.html @@ -0,0 +1,90 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+ + + \ No newline at end of file diff --git a/Apps/Sandcastle/gallery/Offline.html b/Apps/Sandcastle/gallery/Offline.html new file mode 100644 index 000000000000..36d02c454644 --- /dev/null +++ b/Apps/Sandcastle/gallery/Offline.html @@ -0,0 +1,52 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Offline.jpg b/Apps/Sandcastle/gallery/Offline.jpg new file mode 100644 index 000000000000..7ab0c0b70bc2 Binary files /dev/null and b/Apps/Sandcastle/gallery/Offline.jpg differ diff --git a/CHANGES.md b/CHANGES.md index ebe0a1152442..07c15fcd4b01 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ Change Log ========== +### 1.30 - 2017-02-01 +* Deprecated + * The properties `url` and `key` will be removed from `GeocoderViewModel` in 1.31. These properties will be available on geocoder services that support them, like `BingMapsGeocoderService`. +* Added support for custom geocoder services and autocomplete [#4723](https://github.com/AnalyticalGraphicsInc/cesium/pull/4723). + * Added [Custom Geocoder Sandcastle example](http://localhost:8080/Apps/Sandcastle/index.html?src=Custom%20Geocoder.html) +* Added `GeocoderService`, an interface for geocoders. +* Added `BingMapsGeocoderService` implementing the `GeocoderService` interface. +* Added `CartographicGeocoderService` implementing the `GeocoderService` interface. +* Updated the morph so the default view in Columbus View is now angled. [#3878](https://github.com/AnalyticalGraphicsInc/cesium/issues/3878) +* Fixed sky atmosphere from causing incorrect picking and hanging drill picking. [#4783](https://github.com/AnalyticalGraphicsInc/cesium/issues/4783) and [#4784](https://github.com/AnalyticalGraphicsInc/cesium/issues/4784) + ### TODO * Added compressed texture support. @@ -15,23 +26,27 @@ Change Log * `Cesium3DTileFeature` ### 1.29 - 2017-01-02 - * Improved 3D Models * Added the ability to blend a `Model` with a color/translucency. Added `color`, `colorBlendMode`, and `colorBlendAmount` properties to `Model`, `ModelGraphics`, and CZML. Also added `ColorBlendMode` enum. [#4547](https://github.com/AnalyticalGraphicsInc/cesium/pull/4547) * Added the ability to render a `Model` with a silhouette. Added `silhouetteColor` and `silhouetteSize` properties to `Model`, `ModelGraphics`, and CZML. [#4314](https://github.com/AnalyticalGraphicsInc/cesium/pull/4314) * Improved Labels * Added new `Label` properties `showBackground`, `backgroundColor`, and `backgroundPadding` to the primitive, Entity, and CZML layers. + * Added support for newlines (`\n`) in Cesium `Label`s and CZML. [#2402] * Added new enum `VerticalOrigin.BASELINE`. Previously, `VerticalOrigin.BOTTOM` would sometimes align to the baseline depending on the contents of a label. - * Added support for newlines (`\n`) in Cesium `Label`s and CZML. [#2402](https://github.com/AnalyticalGraphicsInc/cesium/issues/2402) -* Fixed texture rotation for `RectangleGeometry` [#2737](https://github.com/AnalyticalGraphicsInc/cesium/issues/2737) -* Fixed translucency in Firefox 50. https://github.com/AnalyticalGraphicsInc/cesium/pull/4762 +(https://github.com/AnalyticalGraphicsInc/cesium/issues/2402) +* Fixed translucency in Firefox 50. [#4762](https://github.com/AnalyticalGraphicsInc/cesium/pull/4762) +* Fixed texture rotation for `RectangleGeometry`. [#2737](https://github.com/AnalyticalGraphicsInc/cesium/issues/2737) +* Fixed issue where billboards on terrain had an incorrect offset. [#4598](https://github.com/AnalyticalGraphicsInc/cesium/issues/4598) +* Fixed issue where `globe.getHeight` incorrectly returned `undefined`. [#3411](https://github.com/AnalyticalGraphicsInc/cesium/issues/3411) * Fixed a bug that caused `GroundPrimitive` to render incorrectly on systems without the `WEBGL_depth_texture` extension. [#4747](https://github.com/AnalyticalGraphicsInc/cesium/pull/4747) * Fixed default Mapbox token and added a watermark to notify users that they need to sign up for their own token. +* Fixed glTF models with skinning that used `bindShapeMatrix`. [#4722](https://github.com/AnalyticalGraphicsInc/cesium/issues/4722) * Fixed a bug that could cause a "readyImagery is not actually ready" exception with some configurations of imagery layers. -* Fixed `Rectangle.union` to correctly account for rectangles that cross the IDL [#4732](https://github.com/AnalyticalGraphicsInc/cesium/pull/4732). +* Fixed `Rectangle.union` to correctly account for rectangles that cross the IDL. [#4732](https://github.com/AnalyticalGraphicsInc/cesium/pull/4732) +* Fixed tooltips for gallery thumbnails in Sandcastle [#4702].(https://github.com/AnalyticalGraphicsInc/cesium/pull/4702) +* DataSourceClock.getValue now preserves the provided `result` properties when its properties are `undefined`. [#4029](https://github.com/AnalyticalGraphicsInc/cesium/issues/4029) * Added `divideComponents` function to `Cartesian2`, `Cartesian3`, and `Cartesian4`. [#4750](https://github.com/AnalyticalGraphicsInc/cesium/pull/4750) * Added `WebGLConstants` enum. Previously, this was part of the private Renderer API. [#4731](https://github.com/AnalyticalGraphicsInc/cesium/pull/4731) -* Fixed tooltips for gallery thumbnails in Sandcastle [#4702](https://github.com/AnalyticalGraphicsInc/cesium/pull/4702) ### 1.28 - 2016-12-01 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 234e8502ce02..2354198c1187 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -119,3 +119,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [David Friedman](https://github.com/duvifn) * [Abhishek Potnis](https://github.com/abhishekvp) * [Brad Hover](https://github.com/tekhaus) +* [Hüseyin Ateş](https://github.com/ateshuseyin) diff --git a/Documentation/Contributors/CodingGuide/README.md b/Documentation/Contributors/CodingGuide/README.md index 8dbbb8e2fa81..55d1c80318d5 100644 --- a/Documentation/Contributors/CodingGuide/README.md +++ b/Documentation/Contributors/CodingGuide/README.md @@ -383,26 +383,40 @@ Some common sensible defaults are ### Throwing Exceptions -* Throw Cesium's `DeveloperError` when the user has a coding error. The most common errors are missing parameters and out-of-range parameters. For example: +Use the functions of Cesium's [Check](https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Core/Check.js) class to throw a `DeveloperError` when the user has a coding error. The most common errors are parameters that are missing, have the wrong type or are out of rangers of the wrong type or are out of range. + +* For example, to check that a parameter is defined and is an object: ```javascript Cartesian3.maximumComponent = function(cartesian) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required.'); - } + Check.typeOf.object(cartesian, 'cartesian'); //>>includeEnd('debug'); return Math.max(cartesian.x, cartesian.y, cartesian.z); }; ``` + +* For more complicated parameter checks, manually check the parameter and then throw a `DeveloperError`. Example: +```javascript +Cartesian3.unpackArray = function(array, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined(array, 'array'); + Check.numeric.minimum(array.length, 3); + if (array.length % 3 !== 0) { + throw new DeveloperError('array length must be a multiple of 3.'); + } + //>>includeEnd('debug'); + + // ... +}; +``` + * To check for `DeveloperError`, surround code in `includeStart`/`includeEnd` comments, as shown above, so developer error checks can be optimized out of release builds. Do not include required side effects inside `includeStart`/`includeEnd`, e.g., ```javascript Cartesian3.maximumComponent = function(cartesian) { //>>includeStart('debug', pragmas.debug); var c = cartesian; - if (!defined(c)) { - throw new DeveloperError('cartesian is required.'); - } + Check.typeOf.object(cartesian, 'cartesian'); //>>includeEnd('debug'); // Works in debug. Fails in release since c is optimized out! diff --git a/Source/Core/BingMapsGeocoderService.js b/Source/Core/BingMapsGeocoderService.js new file mode 100644 index 000000000000..52f73196cf6e --- /dev/null +++ b/Source/Core/BingMapsGeocoderService.js @@ -0,0 +1,107 @@ +/*global define*/ +define([ + './BingMapsApi', + './defaultValue', + './defined', + './defineProperties', + './DeveloperError', + './loadJsonp', + './Rectangle' +], function( + BingMapsApi, + defaultValue, + defined, + defineProperties, + DeveloperError, + loadJsonp, + Rectangle) { + 'use strict'; + + var url = 'https://dev.virtualearth.net/REST/v1/Locations'; + + /** + * Provides geocoding through Bing Maps. + * @alias BingMapsGeocoderService + * @constructor + * + * @param {Object} options Object with the following properties: + * @param {String} [key] A key to use with the Bing Maps geocoding service + * @param {Boolean} autoComplete Indicates whether this service shall be used to fetch auto-complete suggestions + */ + function BingMapsGeocoderService(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this._url = 'https://dev.virtualearth.net/REST/v1/Locations'; + this._key = BingMapsApi.getKey(options.key); + } + + defineProperties(BingMapsGeocoderService.prototype, { + /** + * The URL endpoint for the Bing geocoder service + * @type {String} + * @memberof {BingMapsGeocoderService.prototype} + * @readonly + */ + url : { + get : function () { + return this._url; + } + }, + + /** + * The key for the Bing geocoder service + * @type {String} + * @memberof {BingMapsGeocoderService.prototype} + * @readonly + */ + key : { + get : function () { + return this._key; + } + } + }); + + /** + * @function + * + * @param {String} query The query to be sent to the geocoder service + * @returns {Promise} + */ + BingMapsGeocoderService.prototype.geocode = function(query) { + //>>includeStart('debug', pragmas.debug); + if (!defined(query)) { + throw new DeveloperError('query must be defined'); + } + //>>includeEnd('debug'); + + var key = this.key; + var promise = loadJsonp(url, { + parameters : { + query : query, + key : key + }, + callbackParameterName : 'jsonp' + }); + + return promise.then(function(result) { + if (result.resourceSets.length === 0) { + return []; + } + + var results = result.resourceSets[0].resources; + + return results.map(function (resource) { + var bbox = resource.bbox; + var south = bbox[0]; + var west = bbox[1]; + var north = bbox[2]; + var east = bbox[3]; + return { + displayName: resource.name, + destination: Rectangle.fromDegrees(west, south, east, north) + }; + }); + }); + }; + + return BingMapsGeocoderService; +}); diff --git a/Source/Core/Cartesian3.js b/Source/Core/Cartesian3.js index 0c8325d43cab..e350dcdd3192 100644 --- a/Source/Core/Cartesian3.js +++ b/Source/Core/Cartesian3.js @@ -1,11 +1,13 @@ /*global define*/ define([ - './defaultValue', - './defined', - './DeveloperError', - './freezeObject', - './Math' + './Check', + './defaultValue', + './defined', + './DeveloperError', + './freezeObject', + './Math' ], function( + Check, defaultValue, defined, DeveloperError, @@ -58,9 +60,7 @@ define([ */ Cartesian3.fromSpherical = function(spherical, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(spherical)) { - throw new DeveloperError('spherical is required'); - } + Check.typeOf.object(spherical, 'spherical'); //>>includeEnd('debug'); if (!defined(result)) { @@ -146,13 +146,8 @@ define([ */ Cartesian3.pack = function(value, array, startingIndex) { //>>includeStart('debug', pragmas.debug); - if (!defined(value)) { - throw new DeveloperError('value is required'); - } - - if (!defined(array)) { - throw new DeveloperError('array is required'); - } + Check.typeOf.object(value, 'value'); + Check.defined(array, 'array'); //>>includeEnd('debug'); startingIndex = defaultValue(startingIndex, 0); @@ -174,9 +169,7 @@ define([ */ Cartesian3.unpack = function(array, startingIndex, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(array)) { - throw new DeveloperError('array is required'); - } + Check.defined(array, 'array'); //>>includeEnd('debug'); startingIndex = defaultValue(startingIndex, 0); @@ -199,9 +192,7 @@ define([ */ Cartesian3.packArray = function(array, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(array)) { - throw new DeveloperError('array is required'); - } + Check.defined(array, 'array'); //>>includeEnd('debug'); var length = array.length; @@ -226,12 +217,8 @@ define([ */ Cartesian3.unpackArray = function(array, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(array)) { - throw new DeveloperError('array is required'); - } - if (array.length < 3) { - throw new DeveloperError('array length cannot be less than 3.'); - } + Check.defined(array, 'array'); + Check.numeric.minimum(array.length, 3); if (array.length % 3 !== 0) { throw new DeveloperError('array length must be a multiple of 3.'); } @@ -279,9 +266,7 @@ define([ */ Cartesian3.maximumComponent = function(cartesian) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); //>>includeEnd('debug'); return Math.max(cartesian.x, cartesian.y, cartesian.z); @@ -295,9 +280,7 @@ define([ */ Cartesian3.minimumComponent = function(cartesian) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); //>>includeEnd('debug'); return Math.min(cartesian.x, cartesian.y, cartesian.z); @@ -313,15 +296,9 @@ define([ */ Cartesian3.minimumByComponent = function(first, second, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(first)) { - throw new DeveloperError('first is required.'); - } - if (!defined(second)) { - throw new DeveloperError('second is required.'); - } - if (!defined(result)) { - throw new DeveloperError('result is required.'); - } + Check.typeOf.object(first, 'first'); + Check.typeOf.object(second, 'second'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = Math.min(first.x, second.x); @@ -341,15 +318,9 @@ define([ */ Cartesian3.maximumByComponent = function(first, second, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(first)) { - throw new DeveloperError('first is required.'); - } - if (!defined(second)) { - throw new DeveloperError('second is required.'); - } - if (!defined(result)) { - throw new DeveloperError('result is required.'); - } + Check.typeOf.object(first, 'first'); + Check.typeOf.object(second, 'second'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = Math.max(first.x, second.x); @@ -366,9 +337,7 @@ define([ */ Cartesian3.magnitudeSquared = function(cartesian) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); //>>includeEnd('debug'); return cartesian.x * cartesian.x + cartesian.y * cartesian.y + cartesian.z * cartesian.z; @@ -399,9 +368,8 @@ define([ */ Cartesian3.distance = function(left, right) { //>>includeStart('debug', pragmas.debug); - if (!defined(left) || !defined(right)) { - throw new DeveloperError('left and right are required.'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); //>>includeEnd('debug'); Cartesian3.subtract(left, right, distanceScratch); @@ -422,9 +390,8 @@ define([ */ Cartesian3.distanceSquared = function(left, right) { //>>includeStart('debug', pragmas.debug); - if (!defined(left) || !defined(right)) { - throw new DeveloperError('left and right are required.'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); //>>includeEnd('debug'); Cartesian3.subtract(left, right, distanceScratch); @@ -440,12 +407,8 @@ define([ */ Cartesian3.normalize = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); var magnitude = Cartesian3.magnitude(cartesian); @@ -472,12 +435,8 @@ define([ */ Cartesian3.dot = function(left, right) { //>>includeStart('debug', pragmas.debug); - if (!defined(left)) { - throw new DeveloperError('left is required'); - } - if (!defined(right)) { - throw new DeveloperError('right is required'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); //>>includeEnd('debug'); return left.x * right.x + left.y * right.y + left.z * right.z; @@ -493,15 +452,9 @@ define([ */ Cartesian3.multiplyComponents = function(left, right, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(left)) { - throw new DeveloperError('left is required'); - } - if (!defined(right)) { - throw new DeveloperError('right is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = left.x * right.x; @@ -547,15 +500,9 @@ define([ */ Cartesian3.add = function(left, right, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(left)) { - throw new DeveloperError('left is required'); - } - if (!defined(right)) { - throw new DeveloperError('right is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = left.x + right.x; @@ -574,15 +521,9 @@ define([ */ Cartesian3.subtract = function(left, right, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(left)) { - throw new DeveloperError('left is required'); - } - if (!defined(right)) { - throw new DeveloperError('right is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = left.x - right.x; @@ -601,15 +542,9 @@ define([ */ Cartesian3.multiplyByScalar = function(cartesian, scalar, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } - if (typeof scalar !== 'number') { - throw new DeveloperError('scalar is required and must be a number.'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); + Check.typeOf.number(scalar, 'scalar'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = cartesian.x * scalar; @@ -628,15 +563,9 @@ define([ */ Cartesian3.divideByScalar = function(cartesian, scalar, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } - if (typeof scalar !== 'number') { - throw new DeveloperError('scalar is required and must be a number.'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); + Check.typeOf.number(scalar, 'scalar'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = cartesian.x / scalar; @@ -654,12 +583,8 @@ define([ */ Cartesian3.negate = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = -cartesian.x; @@ -677,12 +602,8 @@ define([ */ Cartesian3.abs = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(cartesian, 'cartesian'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); result.x = Math.abs(cartesian.x); @@ -703,18 +624,10 @@ define([ */ Cartesian3.lerp = function(start, end, t, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(start)) { - throw new DeveloperError('start is required.'); - } - if (!defined(end)) { - throw new DeveloperError('end is required.'); - } - if (typeof t !== 'number') { - throw new DeveloperError('t is required and must be a number.'); - } - if (!defined(result)) { - throw new DeveloperError('result is required.'); - } + Check.typeOf.object(start, 'start'); + Check.typeOf.object(end, 'end'); + Check.typeOf.number(t, 't'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); Cartesian3.multiplyByScalar(end, t, lerpScratch); @@ -733,12 +646,8 @@ define([ */ Cartesian3.angleBetween = function(left, right) { //>>includeStart('debug', pragmas.debug); - if (!defined(left)) { - throw new DeveloperError('left is required'); - } - if (!defined(right)) { - throw new DeveloperError('right is required'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); //>>includeEnd('debug'); Cartesian3.normalize(left, angleBetweenScratch); @@ -758,12 +667,8 @@ define([ */ Cartesian3.mostOrthogonalAxis = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(cartesian)) { - throw new DeveloperError('cartesian is required.'); - } - if (!defined(result)) { - throw new DeveloperError('result is required.'); - } + Check.typeOf.object(cartesian, 'cartesian'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); var f = Cartesian3.normalize(cartesian, mostOrthogonalAxisScratch); @@ -842,15 +747,9 @@ define([ */ Cartesian3.cross = function(left, right, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(left)) { - throw new DeveloperError('left is required'); - } - if (!defined(right)) { - throw new DeveloperError('right is required'); - } - if (!defined(result)) { - throw new DeveloperError('result is required'); - } + Check.typeOf.object(left, 'left'); + Check.typeOf.object(right, 'right'); + Check.typeOf.object(result, 'result'); //>>includeEnd('debug'); var leftX = left.x; @@ -885,12 +784,8 @@ define([ */ Cartesian3.fromDegrees = function(longitude, latitude, height, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(longitude)) { - throw new DeveloperError('longitude is required'); - } - if (!defined(latitude)) { - throw new DeveloperError('latitude is required'); - } + Check.typeOf.number(longitude, 'longitude'); + Check.typeOf.number(latitude, 'latitude'); //>>includeEnd('debug'); longitude = CesiumMath.toRadians(longitude); @@ -917,12 +812,8 @@ define([ */ Cartesian3.fromRadians = function(longitude, latitude, height, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(longitude)) { - throw new DeveloperError('longitude is required'); - } - if (!defined(latitude)) { - throw new DeveloperError('latitude is required'); - } + Check.typeOf.number(longitude, 'longitude'); + Check.typeOf.number(latitude, 'latitude'); //>>includeEnd('debug'); height = defaultValue(height, 0.0); @@ -958,14 +849,9 @@ define([ */ Cartesian3.fromDegreesArray = function(coordinates, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(coordinates)) { - throw new DeveloperError('coordinates is required.'); - } - if (coordinates.length < 2) { - throw new DeveloperError('coordinates length cannot be less than 2.'); - } - if (coordinates.length % 2 !== 0) { - throw new DeveloperError('coordinates length must be a multiple of 2.'); + Check.defined(coordinates, 'coordinates'); + if (coordinates.length < 2 || coordinates.length % 2 !== 0) { + throw new DeveloperError('the number of coordinates must be a multiple of 2 and at least 2'); } //>>includeEnd('debug'); @@ -999,14 +885,9 @@ define([ */ Cartesian3.fromRadiansArray = function(coordinates, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(coordinates)) { - throw new DeveloperError('coordinates is required.'); - } - if (coordinates.length < 2) { - throw new DeveloperError('coordinates length cannot be less than 2.'); - } - if (coordinates.length % 2 !== 0) { - throw new DeveloperError('coordinates length must be a multiple of 2.'); + Check.defined(coordinates, 'coordinates'); + if (coordinates.length < 2 || coordinates.length % 2 !== 0) { + throw new DeveloperError('the number of coordinates must be a multiple of 2 and at least 2'); } //>>includeEnd('debug'); @@ -1040,14 +921,9 @@ define([ */ Cartesian3.fromDegreesArrayHeights = function(coordinates, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(coordinates)) { - throw new DeveloperError('coordinates is required.'); - } - if (coordinates.length < 3) { - throw new DeveloperError('coordinates length cannot be less than 3.'); - } - if (coordinates.length % 3 !== 0) { - throw new DeveloperError('coordinates length must be a multiple of 3.'); + Check.defined(coordinates, 'coordinates'); + if (coordinates.length < 3 || coordinates.length % 3 !== 0) { + throw new DeveloperError('the number of coordinates must be a multiple of 3 and at least 3'); } //>>includeEnd('debug'); @@ -1082,14 +958,9 @@ define([ */ Cartesian3.fromRadiansArrayHeights = function(coordinates, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); - if (!defined(coordinates)) { - throw new DeveloperError('coordinates is required.'); - } - if (coordinates.length < 3) { - throw new DeveloperError('coordinates length cannot be less than 3.'); - } - if (coordinates.length % 3 !== 0) { - throw new DeveloperError('coordinates length must be a multiple of 3.'); + Check.defined(coordinates, 'coordinates'); + if (coordinates.length < 3 || coordinates.length % 3 !== 0) { + throw new DeveloperError('the number of coordinates must be a multiple of 3 and at least 3'); } //>>includeEnd('debug'); diff --git a/Source/Core/CartographicGeocoderService.js b/Source/Core/CartographicGeocoderService.js new file mode 100644 index 000000000000..e7475fcdcca1 --- /dev/null +++ b/Source/Core/CartographicGeocoderService.js @@ -0,0 +1,59 @@ +/*global define*/ +define([ + './Cartesian3', + './defaultValue', + './defineProperties', + './defined', + './DeveloperError', + '../ThirdParty/when' +], function( + Cartesian3, + defaultValue, + defineProperties, + defined, + DeveloperError, + when) { + 'use strict'; + + /** + * Geocodes queries containing longitude and latitude coordinates and an optional height. + * Query format: `longitude latitude (height)` with longitude/latitude in degrees and height in meters. + * + * @alias CartographicGeocoderService + * @constructor + */ + function CartographicGeocoderService() { + } + + /** + * @function + * + * @param {String} query The query to be sent to the geocoder service + * @returns {Promise} + */ + CartographicGeocoderService.prototype.geocode = function(query) { + //>>includeStart('debug', pragmas.debug); + if (!defined(query)) { + throw new DeveloperError('query must be defined'); + } + //>>includeEnd('debug'); + + var splitQuery = query.match(/[^\s,\n]+/g); + if ((splitQuery.length === 2) || (splitQuery.length === 3)) { + var longitude = +splitQuery[0]; + var latitude = +splitQuery[1]; + var height = (splitQuery.length === 3) ? +splitQuery[2] : 300.0; + + if (!isNaN(longitude) && !isNaN(latitude) && !isNaN(height)) { + var result = { + displayName: query, + destination: Cartesian3.fromDegrees(longitude, latitude, height) + }; + return when.resolve([result]); + } + } + return when.resolve([]); + }; + + return CartographicGeocoderService; +}); diff --git a/Source/Core/Check.js b/Source/Core/Check.js new file mode 100644 index 000000000000..5bf150dcab79 --- /dev/null +++ b/Source/Core/Check.js @@ -0,0 +1,150 @@ +/*global define*/ +define([ + './defaultValue', + './defined', + './DeveloperError', + './isArray' + ], function( + defaultValue, + defined, + DeveloperError, + isArray) { + 'use strict'; + + /** + * Contains functions for checking that supplied arguments are of a specified type + * or meet specified conditions + * @private + */ + var Check = {}; + + /** + * Contains type checking functions, all using the typeof operator + */ + Check.typeOf = {}; + + /** + * Contains functions for checking numeric conditions such as minimum and maximum values + */ + Check.numeric = {}; + + function getUndefinedErrorMessage(name) { + return name + ' was required but undefined.'; + } + + function getFailedTypeErrorMessage(actual, expected, name) { + return 'Expected ' + name + ' to be typeof ' + expected + ', got ' + actual; + } + + /** + * Throws if test is not defined + * + * @param {*} test The value that is to be checked + * @param {String} name The name of the variable being tested + * @exception {DeveloperError} test must be defined + */ + Check.defined = function (test, name) { + if (!defined(test)) { + throw new DeveloperError(getUndefinedErrorMessage(name)); + } + }; + + /** + * Throws if test is greater than maximum + * + * @param {Number} test The value to test + * @param {Number} maximum The maximum allowed value + * @exception {DeveloperError} test must not be greater than maximum + * @exception {DeveloperError} Both test and maximum must be typeof 'number' + */ + Check.numeric.maximum = function (test, maximum) { + Check.typeOf.number(test); + Check.typeOf.number(maximum); + if (test > maximum) { + throw new DeveloperError('Expected ' + test + ' to be at most ' + maximum); + } + }; + + /** + * Throws if test is less than minimum + * + * @param {Number} test The value to test + * @param {Number} minimum The minimum allowed value + * @exception {DeveloperError} test must not be less than mininum + * @exception {DeveloperError} Both test and maximum must be typeof 'number' + */ + Check.numeric.minimum = function (test, minimum) { + Check.typeOf.number(test); + Check.typeOf.number(minimum); + if (test < minimum) { + throw new DeveloperError('Expected ' + test + ' to be at least ' + minimum); + } + }; + + /** + * Throws if test is not typeof 'function' + * + * @param {*} test The value to test + * @param {String} name The name of the variable being tested + * @exception {DeveloperError} test must be typeof 'function' + */ + Check.typeOf.func = function (test, name) { + if (typeof test !== 'function') { + throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'function', name)); + } + }; + + /** + * Throws if test is not typeof 'string' + * + * @param {*} test The value to test + * @param {String} name The name of the variable being tested + * @exception {DeveloperError} test must be typeof 'string' + */ + Check.typeOf.string = function (test, name) { + if (typeof test !== 'string') { + throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'string', name)); + } + }; + + /** + * Throws if test is not typeof 'number' + * + * @param {*} test The value to test + * @param {String} name The name of the variable being tested + * @exception {DeveloperError} test must be typeof 'number' + */ + Check.typeOf.number = function (test, name) { + if (typeof test !== 'number') { + throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'number', name)); + } + }; + + /** + * Throws if test is not typeof 'object' + * + * @param {*} test The value to test + * @param {String} name The name of the variable being tested + * @exception {DeveloperError} test must be typeof 'object' + */ + Check.typeOf.object = function (test, name) { + if (typeof test !== 'object') { + throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'object', name)); + } + }; + + /** + * Throws if test is not typeof 'boolean' + * + * @param {*} test The value to test + * @param {String} name The name of the variable being tested + * @exception {DeveloperError} test must be typeof 'boolean' + */ + Check.typeOf.boolean = function (test, name) { + if (typeof test !== 'boolean') { + throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'boolean', name)); + } + }; + + return Check; +}); diff --git a/Source/Core/Ellipsoid.js b/Source/Core/Ellipsoid.js index 5428122a9ef0..deca2629e1ae 100644 --- a/Source/Core/Ellipsoid.js +++ b/Source/Core/Ellipsoid.js @@ -55,6 +55,10 @@ define([ ellipsoid._maximumRadius = Math.max(x, y, z); ellipsoid._centerToleranceSquared = CesiumMath.EPSILON1; + + if (ellipsoid._radiiSquared.z !== 0) { + ellipsoid._sqauredXOverSquaredZ = ellipsoid._radiiSquared.x / ellipsoid._radiiSquared.z; + } } /** @@ -86,6 +90,7 @@ define([ this._minimumRadius = undefined; this._maximumRadius = undefined; this._centerToleranceSquared = undefined; + this._sqauredXOverSquaredZ = undefined; initialize(this, x, y, z); } @@ -608,5 +613,53 @@ define([ return this._radii.toString(); }; + /** + * Computes a point which is the intersection of the surface normal with the z-axis. + * + * @param {Cartesian3} position the position. must be on the surface of the ellipsoid. + * @param {Number} [buffer = 0.0] A buffer to subtract from the ellipsoid size when checking if the point is inside the ellipsoid. + * In earth case, with common earth datums, there is no need for this buffer since the intersection point is always (relatively) very close to the center. + * In WGS84 datum, intersection point is at max z = +-42841.31151331382 (0.673% of z-axis). + * Intersection point could be outside the ellipsoid if the ratio of MajorAxis / AxisOfRotation is bigger than the square root of 2 + * @param {Cartesian} [result] The cartesian to which to copy the result, or undefined to create and + * return a new instance. + * @returns {Cartesian | undefined} the intersection point if it's inside the ellipsoid, undefined otherwise + * + * @exception {DeveloperError} position is required. + * @exception {DeveloperError} Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y). + * @exception {DeveloperError} Ellipsoid.radii.z must be greater than 0. + */ + Ellipsoid.prototype.getSurfaceNormalIntersectionWithZAxis = function(position, buffer, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(position)) { + throw new DeveloperError('position is required.'); + } + if (!CesiumMath.equalsEpsilon(this._radii.x, this._radii.y, CesiumMath.EPSILON15)) { + throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)'); + } + if (this._radii.z === 0) { + throw new DeveloperError('Ellipsoid.radii.z must be greater than 0'); + } + //>>includeEnd('debug'); + + buffer = defaultValue(buffer, 0.0); + + var sqauredXOverSquaredZ = this._sqauredXOverSquaredZ; + + if (!defined(result)) { + result = new Cartesian3(); + } + + result.x = 0.0; + result.y = 0.0; + result.z = position.z * (1 - sqauredXOverSquaredZ); + + if (Math.abs(result.z) >= this._radii.z - buffer) { + return undefined; + } + + return result; + }; + return Ellipsoid; }); diff --git a/Source/Core/GeocoderService.js b/Source/Core/GeocoderService.js new file mode 100644 index 000000000000..c15982213a0f --- /dev/null +++ b/Source/Core/GeocoderService.js @@ -0,0 +1,36 @@ +/*global define*/ +define([ + './defineProperties', + './DeveloperError' + ], function( + defineProperties, + 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; +}); diff --git a/Source/Core/IntersectionTests.js b/Source/Core/IntersectionTests.js index 3657b9b48ee5..e468763c9ff0 100644 --- a/Source/Core/IntersectionTests.js +++ b/Source/Core/IntersectionTests.js @@ -80,6 +80,10 @@ define([ /** * Computes the intersection of a ray and a triangle as a parametric distance along the input ray. + * + * Implements {@link https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf| + * Fast Minimum Storage Ray/Triangle Intersection} by Tomas Moller and Ben Trumbore. + * * @memberof IntersectionTests * * @param {Ray} ray The ray. @@ -170,6 +174,10 @@ define([ /** * Computes the intersection of a ray and a triangle as a Cartesian3 coordinate. + * + * Implements {@link https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf| + * Fast Minimum Storage Ray/Triangle Intersection} by Tomas Moller and Ben Trumbore. + * * @memberof IntersectionTests * * @param {Ray} ray The ray. diff --git a/Source/Core/WebGLConstants.js b/Source/Core/WebGLConstants.js index 62a0da8c26d2..73bcff7bae74 100644 --- a/Source/Core/WebGLConstants.js +++ b/Source/Core/WebGLConstants.js @@ -1,6 +1,6 @@ /*global define*/ define([ - '../Core/freezeObject' + './freezeObject' ], function( freezeObject) { 'use strict'; diff --git a/Source/DataSources/DataSourceClock.js b/Source/DataSources/DataSourceClock.js index 454d8fe7b050..70f8b2871322 100644 --- a/Source/DataSources/DataSourceClock.js +++ b/Source/DataSources/DataSourceClock.js @@ -165,12 +165,12 @@ define([ if (!defined(result)) { result = new Clock(); } - result.startTime = this.startTime; - result.stopTime = this.stopTime; - result.currentTime = this.currentTime; - result.clockRange = this.clockRange; - result.multiplier = this.multiplier; - result.clockStep = this.clockStep; + 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; }; diff --git a/Source/Renderer/ComputeCommand.js b/Source/Renderer/ComputeCommand.js index 2e7678276f76..8d0231c673bc 100644 --- a/Source/Renderer/ComputeCommand.js +++ b/Source/Renderer/ComputeCommand.js @@ -1,7 +1,7 @@ /*global define*/ define([ '../Core/defaultValue', - '../Scene/Pass' + './Pass' ], function( defaultValue, Pass) { diff --git a/Source/Scene/Pass.js b/Source/Renderer/Pass.js similarity index 100% rename from Source/Scene/Pass.js rename to Source/Renderer/Pass.js diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 678e4050b48d..d51112ba0ed6 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -72,6 +72,9 @@ define([ this._features = undefined; } + // This can be overridden for testing purposes + Batched3DModel3DTileContent._deprecationWarning = deprecationWarning; + defineProperties(Batched3DModel3DTileContent.prototype, { /** * Part of the {@link Cesium3DTileContent} interface. @@ -183,7 +186,7 @@ define([ if (!defined(batchIdAttributeName)) { batchIdAttributeName = getAttributeOrUniformBySemantic(gltf, 'BATCHID'); if (defined(batchIdAttributeName)) { - deprecationWarning('b3dm-legacy-batchid', 'The glTF in this b3dm uses the semantic `BATCHID`. Application-specific semantics should be prefixed with an underscore: `_BATCHID`.'); + 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; diff --git a/Source/Scene/BillboardCollection.js b/Source/Scene/BillboardCollection.js index 0e29e5d9af74..f20fb3afc4af 100644 --- a/Source/Scene/BillboardCollection.js +++ b/Source/Scene/BillboardCollection.js @@ -19,6 +19,7 @@ define([ '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -29,7 +30,6 @@ define([ './BlendingState', './HeightReference', './HorizontalOrigin', - './Pass', './SceneMode', './TextureAtlas', './VerticalOrigin' @@ -53,6 +53,7 @@ define([ Buffer, BufferUsage, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -63,7 +64,6 @@ define([ BlendingState, HeightReference, HorizontalOrigin, - Pass, SceneMode, TextureAtlas, VerticalOrigin) { diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js index 6fc8b90cf00e..efe89fd96694 100644 --- a/Source/Scene/Cesium3DTileBatchTable.js +++ b/Source/Scene/Cesium3DTileBatchTable.js @@ -15,6 +15,7 @@ define([ '../Core/PixelFormat', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/PixelDatatype', '../Renderer/RenderState', '../Renderer/Sampler', @@ -25,8 +26,7 @@ define([ './BlendingState', './Cesium3DTileColorBlendMode', './CullFace', - './getBinaryAccessor', - './Pass' + './getBinaryAccessor' ], function( arrayFill, Cartesian2, @@ -43,6 +43,7 @@ define([ PixelFormat, ContextLimits, DrawCommand, + Pass, PixelDatatype, RenderState, Sampler, @@ -53,8 +54,7 @@ define([ BlendingState, Cesium3DTileColorBlendMode, CullFace, - getBinaryAccessor, - Pass) { + getBinaryAccessor) { 'use strict'; /** diff --git a/Source/Scene/DepthPlane.js b/Source/Scene/DepthPlane.js index 71f81e70d14f..78e5808e05b1 100644 --- a/Source/Scene/DepthPlane.js +++ b/Source/Scene/DepthPlane.js @@ -10,13 +10,13 @@ define([ '../Core/PrimitiveType', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/VertexArray', '../Shaders/DepthPlaneFS', '../Shaders/DepthPlaneVS', './DepthFunction', - './Pass', './SceneMode' ], function( BoundingSphere, @@ -29,13 +29,13 @@ define([ PrimitiveType, BufferUsage, DrawCommand, + Pass, RenderState, ShaderProgram, VertexArray, DepthPlaneFS, DepthPlaneVS, DepthFunction, - Pass, SceneMode) { 'use strict'; diff --git a/Source/Scene/EllipsoidPrimitive.js b/Source/Scene/EllipsoidPrimitive.js index 732d41f74d33..2bdab578c846 100644 --- a/Source/Scene/EllipsoidPrimitive.js +++ b/Source/Scene/EllipsoidPrimitive.js @@ -12,6 +12,7 @@ define([ '../Core/VertexFormat', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -21,7 +22,6 @@ define([ './BlendingState', './CullFace', './Material', - './Pass', './SceneMode' ], function( BoundingSphere, @@ -36,6 +36,7 @@ define([ VertexFormat, BufferUsage, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -45,7 +46,6 @@ define([ BlendingState, CullFace, Material, - Pass, SceneMode) { 'use strict'; diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index d3eaaa8b4f55..17ae36798635 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1,5 +1,8 @@ /*global define*/ define([ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', '../Core/Color', '../Core/defined', '../Core/defineProperties', @@ -9,6 +12,9 @@ define([ '../ThirdParty/jsep', './ExpressionNodeType' ], function( + Cartesian2, + Cartesian3, + Cartesian4, Color, defined, defineProperties, @@ -31,17 +37,53 @@ define([ var ScratchStorage = { scratchColorIndex : 0, - scratchColors : [new Color()], + scratchColorArray : [new Color()], + scratchArrayIndex : 0, + scratchArrayArray : [[]], + scratchCartesian2Index : 0, + scratchCartesian3Index : 0, + scratchCartesian4Index : 0, + scratchCartesian2Array : [new Cartesian2()], + scratchCartesian3Array : [new Cartesian3()], + scratchCartesian4Array : [new Cartesian4()], reset : function() { this.scratchColorIndex = 0; + this.scratchArrayIndex = 0; + this.scratchCartesian2Index = 0; + this.scratchCartesian3Index = 0; + this.scratchCartesian4Index = 0; }, getColor : function() { - if (this.scratchColorIndex >= this.scratchColors.length) { - this.scratchColors.push(new Color()); + if (this.scratchColorIndex >= this.scratchColorArray.length) { + this.scratchColorArray.push(new Color()); } - var scratchColor = this.scratchColors[this.scratchColorIndex]; - ++this.scratchColorIndex; - return scratchColor; + return this.scratchColorArray[this.scratchColorIndex++]; + }, + getArray : function() { + if (this.scratchArrayIndex >= this.scratchArrayArray.length) { + this.scratchArrayArray.push([]); + } + var scratchArray = this.scratchArrayArray[this.scratchArrayIndex++]; + scratchArray.length = 0; + return scratchArray; + }, + getCartesian2 : function() { + if (this.scratchCartesian2Index >= this.scratchCartesian2Array.length) { + this.scratchCartesian2Array.push(new Cartesian2()); + } + return this.scratchCartesian2Array[this.scratchCartesian2Index++]; + }, + getCartesian3 : function() { + if (this.scratchCartesian3Index >= this.scratchCartesian3Array.length) { + this.scratchCartesian3Array.push(new Cartesian3()); + } + return this.scratchCartesian3Array[this.scratchCartesian3Index++]; + }, + getCartesian4 : function() { + if (this.scratchCartesian4Index >= this.scratchCartesian4Array.length) { + this.scratchCartesian4Array.push(new Cartesian4()); + } + return this.scratchCartesian4Array[this.scratchCartesian4Index++]; } }; @@ -143,16 +185,18 @@ define([ * 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 Color, a {@link Color} 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. * * @param {FrameState} frameState The frame state. * @param {Cesium3DTileFeature} feature The feature who's properties may be used as variables in the expression. - * @returns {Boolean|Number|String|Color|RegExp} The result of evaluating the expression. + * @returns {Boolean|Number|String|Color|Cartesian2|Cartesian3|Cartesian4|RegExp} The result of evaluating the expression. */ Expression.prototype.evaluate = function(frameState, feature) { ScratchStorage.reset(); var result = this._runtimeAst.evaluate(frameState, feature); - if (result instanceof Color) { - return Color.clone(result); + if ((result instanceof Color) || (result instanceof Cartesian2) || (result instanceof Cartesian3) || (result instanceof Cartesian4)) { + return result.clone(); } return result; }; @@ -286,6 +330,7 @@ define([ function parseCall(expression, ast) { var args = ast.arguments; + var argsLength = args.length; var call; var val, left, right; @@ -300,7 +345,7 @@ define([ throw new DeveloperError('Error: ' + call + ' is not a function.'); } //>>includeEnd('debug'); - if (args.length === 0) { + if (argsLength === 0) { if (call === 'test') { return new Node(ExpressionNodeType.LITERAL_BOOLEAN, false); } else { @@ -323,7 +368,7 @@ define([ // Non-member function calls call = ast.callee.name; if (call === 'color') { - if (args.length === 0) { + if (argsLength === 0) { return new Node(ExpressionNodeType.LITERAL_COLOR, call); } val = createRuntimeAst(expression, args[0]); @@ -334,7 +379,7 @@ define([ return new Node(ExpressionNodeType.LITERAL_COLOR, call, [val]); } else if (call === 'rgb' || call === 'hsl') { //>>includeStart('debug', pragmas.debug); - if (args.length < 3) { + if (argsLength < 3) { throw new DeveloperError('Error: ' + call + ' requires three arguments.'); } //>>includeEnd('debug'); @@ -346,7 +391,7 @@ define([ return new Node(ExpressionNodeType.LITERAL_COLOR, call, val); } else if (call === 'rgba' || call === 'hsla') { //>>includeStart('debug', pragmas.debug); - if (args.length < 4) { + if (argsLength < 4) { throw new DeveloperError('Error: ' + call + ' requires four arguments.'); } //>>includeEnd('debug'); @@ -357,8 +402,15 @@ define([ 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 (args.length === 0) { + if (argsLength === 0) { if (call === 'isNaN') { return new Node(ExpressionNodeType.LITERAL_BOOLEAN, true); } else { @@ -369,7 +421,7 @@ define([ return new Node(ExpressionNodeType.UNARY, call, val); } else if (call === 'isExactClass' || call === 'isClass') { //>>includeStart('debug', pragmas.debug); - if (args.length < 1 || args.length > 1) { + if (argsLength < 1 || argsLength > 1) { throw new DeveloperError('Error: ' + call + ' requires exactly one argument.'); } //>>includeEnd('debug'); @@ -377,14 +429,14 @@ define([ return new Node(ExpressionNodeType.UNARY, call, val); } else if (call === 'getExactClassName') { //>>includeStart('debug', pragmas.debug); - if (args.length > 0) { + if (argsLength > 0) { throw new DeveloperError('Error: ' + call + ' does not take any argument.'); } //>>includeEnd('debug'); return new Node(ExpressionNodeType.UNARY, call); } else if (defined(unaryFunctions[call])) { //>>includeStart('debug', pragmas.debug); - if (args.length !== 1) { + if (argsLength !== 1) { throw new DeveloperError('Error: ' + call + ' requires exactly one argument.'); } //>>includeEnd('debug'); @@ -392,7 +444,7 @@ define([ return new Node(ExpressionNodeType.UNARY, call, val); } else if (defined(binaryFunctions[call])) { //>>includeStart('debug', pragmas.debug); - if (args.length !== 2) { + if (argsLength !== 2) { throw new DeveloperError('Error: ' + call + ' requires exactly two arguments.'); } //>>includeEnd('debug'); @@ -401,7 +453,7 @@ define([ return new Node(ExpressionNodeType.BINARY, call, left, right); } else if (defined(ternaryFunctions[call])) { //>>includeStart('debug', pragmas.debug); - if (args.length !== 3) { + if (argsLength !== 3) { throw new DeveloperError('Error: ' + call + ' requires exactly three arguments.'); } //>>includeEnd('debug'); @@ -410,19 +462,19 @@ define([ var test = createRuntimeAst(expression, args[2]); return new Node(ExpressionNodeType.TERNARY, call, left, right, test); } else if (call === 'Boolean') { - if (args.length === 0) { + 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 (args.length === 0) { + 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 (args.length === 0) { + if (argsLength === 0) { return new Node(ExpressionNodeType.LITERAL_STRING, ''); } val = createRuntimeAst(expression, args[0]); @@ -499,12 +551,14 @@ define([ } function parseMemberExpression(expression, ast) { + var val; var obj = createRuntimeAst(expression, ast.object); if (ast.computed) { - var val = createRuntimeAst(expression, ast.property); + val = createRuntimeAst(expression, ast.property); return new Node(ExpressionNodeType.MEMBER, 'brackets', obj, val); } else { - return new Node(ExpressionNodeType.MEMBER, 'dot', obj, ast.property.name); + val = new Node(ExpressionNodeType.LITERAL_STRING, ast.property.name); + return new Node(ExpressionNodeType.MEMBER, 'dot', obj, val); } } @@ -677,6 +731,8 @@ define([ 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) { @@ -726,42 +782,101 @@ define([ if (!defined(args)) { return Color.fromBytes(255, 255, 255, 255, result); } else if (args.length > 1) { - Color.fromCssColorString(args[0].evaluate(frameState, feature, result), result); - result.alpha = args[1].evaluate(frameState, feature, result); + Color.fromCssColorString(args[0].evaluate(frameState, feature), result); + result.alpha = args[1].evaluate(frameState, feature); } else { - Color.fromCssColorString(args[0].evaluate(frameState, feature, result), result); + Color.fromCssColorString(args[0].evaluate(frameState, feature), result); } } else if (this._value === 'rgb') { Color.fromBytes( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), 255, result); } 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, result) * 255; + var a = args[3].evaluate(frameState, feature) * 255; Color.fromBytes( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), a, result); } else if (this._value === 'hsl') { Color.fromHsl( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), 1.0, result); } else if (this._value === 'hsla') { Color.fromHsl( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), - args[3].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), + args[3].evaluate(frameState, feature), result); } return result; }; + 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 DeveloperError 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 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); + } + } + + var componentsLength = components.length; + var call = this._value; + var vectorLength = parseInt(call.charAt(3)); + + //>>includeStart('debug', pragmas.debug); + if (componentsLength === 0) { + throw new DeveloperError('Error: Invalid ' + call + ' constructor. No valid arguments.'); + } else if ((componentsLength < vectorLength) && (componentsLength > 1)) { + throw new DeveloperError('Error: Invalid ' + call + ' constructor. Not enough arguments.'); + } else if ((componentsLength > vectorLength) && (argsLength > 1)) { + throw new DeveloperError('Error: Invalid ' + call + ' constructor. Too many arguments.'); + } + //>>includeEnd('debug'); + + if (componentsLength === 1) { + // Add the same component 3 more times + var component = components[0]; + components.push(component, component, component); + } + + 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()); + } + }; + Node.prototype._evaluateLiteralString = function(frameState, feature) { return this._value; }; @@ -793,25 +908,66 @@ define([ // 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); + if (checkFeature(this._left)) { + return feature.getProperty(this._right.evaluate(frameState, feature)); } var property = this._left.evaluate(frameState, feature); if (!defined(property)) { return undefined; } - return property[this._right]; + + var member = this._right.evaluate(frameState, feature); + if (property instanceof Color) { + // Color components may be accessed with .x, .y, .z, .w and implicitly with .red, .green, .blue, .alpha + if (member === 'x') { + return property.red; + } else if (member === 'y') { + return property.green; + } else if (member === 'z') { + return property.blue; + } else if (member === 'w') { + return property.alpha; + } + } + + return property[member]; }; Node.prototype._evaluateMemberBrackets = function(frameState, feature) { - if(checkFeature(this._left)) { + if (checkFeature(this._left)) { return feature.getProperty(this._right.evaluate(frameState, feature)); } var property = this._left.evaluate(frameState, feature); if (!defined(property)) { return undefined; } - return property[this._right.evaluate(frameState, feature)]; + + var member = this._right.evaluate(frameState, feature); + if (property instanceof Color) { + // Color components may be accessed with [0][1][2][3], ['x']['y']['z']['w'], and implicitly with ['red']['green']['blue']['alpha'] + if (member === 0 || member === 'x') { + return property.red; + } else if (member === 1 || member === 'y') { + return property.green; + } else if (member === 2 || member === 'z') { + return property.blue; + } else if (member === 3 || member === 'w') { + return property.alpha; + } + } else if ((property instanceof Cartesian2) || (property instanceof Cartesian3) || (property instanceof Cartesian4)) { + // Vector components may be accessed with [0][1][2][3] and implicitly with ['x']['y']['z']['w'] + // For Cartesian2 and Cartesian3 out-of-range components will just return undefined + if (member === 0) { + return property.x; + } else if (member === 1) { + return property.y; + } else if (member === 2) { + return property.z; + } else if (member === 3) { + return property.w; + } + } + return property[member]; }; Node.prototype._evaluateArray = function(frameState, feature) { @@ -830,11 +986,23 @@ define([ }; Node.prototype._evaluateNegative = function(frameState, feature) { - return -(this._left.evaluate(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()); + } + return -left; }; Node.prototype._evaluatePositive = function(frameState, feature) { - return +(this._left.evaluate(frameState, feature)); + var left = this._left.evaluate(frameState, feature); + if ((left instanceof Color) || (left instanceof Cartesian2) || (left instanceof Cartesian3) || (left instanceof Cartesian4)) { + return left; + } + return +left; }; Node.prototype._evaluateLessThan = function(frameState, feature) { @@ -910,6 +1078,12 @@ define([ var right = this._right.evaluate(frameState, feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.add(left, right, ScratchStorage.getColor()); + } else 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()); } return left + right; }; @@ -919,6 +1093,12 @@ define([ var right = this._right.evaluate(frameState, feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.subtract(left, right, ScratchStorage.getColor()); + } else 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()); } return left - right; }; @@ -932,6 +1112,24 @@ define([ return Color.multiplyByScalar(right, left, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { return Color.multiplyByScalar(left, right, ScratchStorage.getColor()); + } else 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()); } return left * right; }; @@ -943,6 +1141,18 @@ define([ return Color.divide(left, right, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { return Color.divideByScalar(left, right, ScratchStorage.getColor()); + } else 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()); } return left / right; }; @@ -952,6 +1162,12 @@ define([ var right = this._right.evaluate(frameState, feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.mod(left, right, ScratchStorage.getColor()); + } else 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()); } return left % right; }; @@ -959,8 +1175,11 @@ define([ Node.prototype._evaluateEqualsStrict = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (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; }; @@ -968,8 +1187,11 @@ define([ Node.prototype._evaluateEquals = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return left.equals(right); } // Specifically want to do an abstract equality comparison (==) instead of a strict equality comparison (===) @@ -980,8 +1202,11 @@ define([ Node.prototype._evaluateNotEqualsStrict = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return !Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (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; }; @@ -989,8 +1214,11 @@ define([ Node.prototype._evaluateNotEquals = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return !Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return !left.equals(right); } // Specifically want to do an abstract inequality comparison (!=) instead of a strict inequality comparison (!==) // so that cases like "5 != '5'" return false. Tell jsHint to ignore this line. @@ -1093,7 +1321,7 @@ define([ Node.prototype._evaluateToString = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); - if ((left instanceof RegExp) || (left instanceof Color)) { + if ((left instanceof RegExp) || (left instanceof Color) || (left instanceof Cartesian2) || (left instanceof Cartesian3) || (left instanceof Cartesian4)) { return String(left); } //>>includeStart('debug', pragmas.debug); @@ -1162,11 +1390,11 @@ define([ return 'vec4(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; } - function getExpressionArray(array, attributePrefix, shaderState) { + function getExpressionArray(array, attributePrefix, shaderState, parent) { var length = array.length; var expressions = new Array(length); for (var i = 0; i < length; ++i) { - var shader = array[i].getShaderExpression(attributePrefix, shaderState); + var shader = array[i].getShaderExpression(attributePrefix, shaderState, parent); if (!defined(shader)) { // If any of the expressions are not valid, the array is not valid return undefined; @@ -1176,7 +1404,7 @@ define([ return expressions; } - Node.prototype.getShaderExpression = function(attributePrefix, shaderState) { + Node.prototype.getShaderExpression = function(attributePrefix, shaderState, parent) { var color; var left; var right; @@ -1185,21 +1413,12 @@ define([ var type = this._type; var value = this._value; - // Right may be a string if it's a member variable: e.g. "${property.name}" - if (typeof(this._right) === 'string') { - //>>includeStart('debug', pragmas.debug); - throw new DeveloperError('Error generating style shader: string members are not supported.'); - //>>includeEnd('debug'); - // Return undefined when not in debug. Tell jsHint to ignore this line. - return; // jshint ignore:line - } - if (defined(this._left)) { if (isArray(this._left)) { - // Left can be an array if the type is LITERAL_COLOR - left = getExpressionArray(this._left, attributePrefix, shaderState); + // Left can be an array if the type is LITERAL_COLOR or LITERAL_VECTOR + left = getExpressionArray(this._left, attributePrefix, shaderState, this); } else { - left = this._left.getShaderExpression(attributePrefix, shaderState); + left = this._left.getShaderExpression(attributePrefix, shaderState, this); } if (!defined(left)) { // If the left side is not valid shader code, then the expression is not valid @@ -1208,7 +1427,7 @@ define([ } if (defined(this._right)) { - right = this._right.getShaderExpression(attributePrefix, shaderState); + right = this._right.getShaderExpression(attributePrefix, shaderState, this); if (!defined(right)) { // If the right side is not valid shader code, then the expression is not valid return undefined; @@ -1216,7 +1435,7 @@ define([ } if (defined(this._test)) { - test = this._test.getShaderExpression(attributePrefix, shaderState); + test = this._test.getShaderExpression(attributePrefix, shaderState, this); if (!defined(test)) { // If the test is not valid shader code, then the expression is not valid return undefined; @@ -1225,7 +1444,7 @@ define([ if (isArray(this._value)) { // For ARRAY type - value = getExpressionArray(this._value, attributePrefix, shaderState); + value = getExpressionArray(this._value, attributePrefix, shaderState, this); if (!defined(value)) { // If the values are not valid shader code, then the expression is not valid return undefined; @@ -1247,14 +1466,15 @@ define([ return 'cos(' + left + ')'; } else if (value === 'sqrt') { return 'sqrt(' + left + ')'; + } else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) { + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); + //>>includeEnd('debug'); + // Return undefined when not in debug. Tell jsHint to ignore this line. + return undefined; // jshint ignore:line } else if (defined(unaryFunctions[value])) { return value + '(' + left + ')'; } - //>>includeStart('debug', pragmas.debug); - else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) { - throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); - } - //>>includeEnd('debug'); return value + left; case ExpressionNodeType.BINARY: // Supported types: ||, &&, ===, ==, !==, !=, <, >, <=, >=, +, -, *, /, % @@ -1278,7 +1498,18 @@ define([ case ExpressionNodeType.CONDITIONAL: return '(' + test + ' ? ' + left + ' : ' + right + ')'; case ExpressionNodeType.MEMBER: - // This is intended for accessing the components of vec2, vec3, and vec4 properties. String members aren't supported. + // 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. + // In this shader there is not much distinction between colors and vectors so allow .red to access the 0th component for both. + if (right === 'red' || right === 'x' || right === '0.0') { + return left + '[0]'; + } else if (right === 'green' || right === 'y' || right === '1.0') { + return left + '[1]'; + } else if (right === 'blue' || right === 'z' || right === '2.0') { + return left + '[2]'; + } else if (right === 'alpha' || right === 'w' || right === '3.0') { + return left + '[3]'; + } return left + '[int(' + right + ')]'; case ExpressionNodeType.FUNCTION_CALL: //>>includeStart('debug', pragmas.debug); @@ -1315,7 +1546,15 @@ define([ case ExpressionNodeType.LITERAL_NUMBER: return numberToString(value); case ExpressionNodeType.LITERAL_STRING: - // The only supported strings are css color strings + // Check if parent is of type MEMBER. Otherwise it is not possible to know whether 'red', 'green', and 'blue' + // refer to CSS strings or component accessors. + if (defined(parent) && (parent._type === ExpressionNodeType.MEMBER)) { + if (value === 'red' || value === 'green' || value === 'blue' || value === 'alpha' || + 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); @@ -1377,6 +1616,17 @@ define([ } } 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: //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Error generating style shader: Regular expressions are not supported.'); diff --git a/Source/Scene/ExpressionNodeType.js b/Source/Scene/ExpressionNodeType.js index bf8ccd9e9094..f7d9a85fa1ca 100644 --- a/Source/Scene/ExpressionNodeType.js +++ b/Source/Scene/ExpressionNodeType.js @@ -24,9 +24,10 @@ define([ LITERAL_NUMBER : 12, LITERAL_STRING : 13, LITERAL_COLOR : 14, - LITERAL_REGEX : 15, - LITERAL_UNDEFINED : 16, - LITERAL_GLOBAL : 17 + LITERAL_VECTOR : 15, + LITERAL_REGEX : 16, + LITERAL_UNDEFINED : 17, + LITERAL_GLOBAL : 18 }; return freezeObject(ExpressionNodeType); diff --git a/Source/Scene/FrustumCommands.js b/Source/Scene/FrustumCommands.js index 2d33d09d338a..fe5828910791 100644 --- a/Source/Scene/FrustumCommands.js +++ b/Source/Scene/FrustumCommands.js @@ -1,7 +1,7 @@ /*global define*/ define([ '../Core/defaultValue', - './Pass' + '../Renderer/Pass' ], function( defaultValue, Pass) { diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index ee15de74b801..bb92954ec673 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -14,6 +14,7 @@ define([ '../Core/Event', '../Core/IntersectionTests', '../Core/loadImage', + '../Core/Math', '../Core/Ray', '../Core/Rectangle', '../Renderer/ShaderSource', @@ -43,6 +44,7 @@ define([ Event, IntersectionTests, loadImage, + CesiumMath, Ray, Rectangle, ShaderSource, @@ -424,10 +426,27 @@ define([ } var ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid; - var cartesian = ellipsoid.cartographicToCartesian(cartographic, scratchGetHeightCartesian); + + //cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal` + var cartesian = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0, ellipsoid, scratchGetHeightCartesian); var ray = scratchGetHeightRay; - Cartesian3.normalize(cartesian, ray.direction); + var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesian, ray.direction); + + // Try to find the intersection point between the surface normal and z-axis. + // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider + var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian, 11500.0, ray.origin); + + // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid + if (!defined(rayOrigin)) { + // intersection point is outside the ellipsoid, try other value + // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider + var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0); + + // multiply by the *positive* value of the magnitude + var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchGetHeightIntersection); + Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin); + } var intersection = tile.data.pick(ray, undefined, undefined, false, scratchGetHeightIntersection); if (!defined(intersection)) { diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index eea28a81bcdb..7383e795dd83 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -30,11 +30,11 @@ define([ '../Renderer/BufferUsage', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/VertexArray', '../Scene/BlendingState', '../Scene/DepthFunction', - '../Scene/Pass', '../Scene/PerInstanceColorAppearance', '../Scene/Primitive', './GlobeSurfaceTile', @@ -73,11 +73,11 @@ define([ BufferUsage, ContextLimits, DrawCommand, + Pass, RenderState, VertexArray, BlendingState, DepthFunction, - Pass, PerInstanceColorAppearance, Primitive, GlobeSurfaceTile, diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index cfcea5ec94e3..9939662eea12 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -20,6 +20,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/Rectangle', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -28,7 +29,6 @@ define([ '../ThirdParty/when', './BlendingState', './DepthFunction', - './Pass', './PerInstanceColorAppearance', './Primitive', './SceneMode', @@ -55,6 +55,7 @@ define([ OrientedBoundingBox, Rectangle, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -63,7 +64,6 @@ define([ when, BlendingState, DepthFunction, - Pass, PerInstanceColorAppearance, Primitive, SceneMode, diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index cd329db67f94..8c10bdded593 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -42,6 +42,7 @@ define([ '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/Sampler', '../Renderer/ShaderProgram', @@ -65,7 +66,6 @@ define([ './modelMaterialsCommon', './ModelMesh', './ModelNode', - './Pass', './SceneMode', './ShadowMode' ], function( @@ -111,6 +111,7 @@ define([ Buffer, BufferUsage, DrawCommand, + Pass, RenderState, Sampler, ShaderProgram, @@ -134,7 +135,6 @@ define([ modelMaterialsCommon, ModelMesh, ModelNode, - Pass, SceneMode, ShadowMode) { 'use strict'; diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 6b4e3f954d7c..e00cea225aec 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -23,6 +23,7 @@ define([ '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -33,8 +34,7 @@ define([ './Cesium3DTileColorBlendMode', './Cesium3DTileContentState', './Cesium3DTileFeature', - './Cesium3DTileFeatureTable', - './Pass' + './Cesium3DTileFeatureTable' ], function( Cartesian3, Color, @@ -59,6 +59,7 @@ define([ Buffer, BufferUsage, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -69,8 +70,7 @@ define([ Cesium3DTileColorBlendMode, Cesium3DTileContentState, Cesium3DTileFeature, - Cesium3DTileFeatureTable, - Pass) { + Cesium3DTileFeatureTable) { 'use strict'; /** @@ -908,12 +908,7 @@ define([ attributeLocations.a_batchId = batchIdLocation; } - var vs = 'attribute vec3 a_position; \n' + - 'varying vec4 v_color; \n' + - 'uniform float u_pointSize; \n' + - 'uniform vec4 u_constantColor; \n' + - 'uniform vec4 u_highlightColor; \n' + - 'uniform float u_tilesetTime; \n'; + var attributeDeclarations = ''; var length = styleableProperties.length; for (i = 0; i < length; ++i) { @@ -934,10 +929,19 @@ define([ attributeType = 'vec' + componentCount; } - vs += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + attributeDeclarations += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; attributeLocations[attributeName] = attribute.location; } + var vs = 'attribute vec3 a_position; \n' + + 'varying vec4 v_color; \n' + + 'uniform float u_pointSize; \n' + + 'uniform vec4 u_constantColor; \n' + + 'uniform vec4 u_highlightColor; \n' + + 'uniform float u_tilesetTime; \n'; + + vs += attributeDeclarations; + if (usesColors) { if (isTranslucent) { vs += 'attribute vec4 a_color; \n'; diff --git a/Source/Scene/PointPrimitiveCollection.js b/Source/Scene/PointPrimitiveCollection.js index 78820c715716..52f0b5f42a80 100644 --- a/Source/Scene/PointPrimitiveCollection.js +++ b/Source/Scene/PointPrimitiveCollection.js @@ -16,6 +16,7 @@ define([ '../Renderer/BufferUsage', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -23,7 +24,6 @@ define([ '../Shaders/PointPrimitiveCollectionFS', '../Shaders/PointPrimitiveCollectionVS', './BlendingState', - './Pass', './PointPrimitive', './SceneMode' ], function( @@ -43,6 +43,7 @@ define([ BufferUsage, ContextLimits, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -50,7 +51,6 @@ define([ PointPrimitiveCollectionFS, PointPrimitiveCollectionVS, BlendingState, - Pass, PointPrimitive, SceneMode) { 'use strict'; diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index 3e24d43528c3..4d491863be01 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -23,6 +23,7 @@ define([ '../Renderer/BufferUsage', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -33,7 +34,6 @@ define([ './BatchTable', './BlendingState', './Material', - './Pass', './Polyline', './SceneMode' ], function( @@ -60,6 +60,7 @@ define([ BufferUsage, ContextLimits, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -70,7 +71,6 @@ define([ BatchTable, BlendingState, Material, - Pass, Polyline, SceneMode) { 'use strict'; diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index dd05d4749feb..23b812e41b38 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -27,6 +27,7 @@ define([ '../Renderer/BufferUsage', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -34,7 +35,6 @@ define([ '../ThirdParty/when', './BatchTable', './CullFace', - './Pass', './PrimitivePipeline', './PrimitiveState', './SceneMode', @@ -67,6 +67,7 @@ define([ BufferUsage, ContextLimits, DrawCommand, + Pass, RenderState, ShaderProgram, ShaderSource, @@ -74,7 +75,6 @@ define([ when, BatchTable, CullFace, - Pass, PrimitivePipeline, PrimitiveState, SceneMode, diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js index 782230e37ea2..9149b60359e4 100644 --- a/Source/Scene/QuadtreePrimitive.js +++ b/Source/Scene/QuadtreePrimitive.js @@ -240,7 +240,7 @@ define([ QuadtreePrimitive.prototype.updateHeight = function(cartographic, callback) { var primitive = this; var object = { - position : undefined, + positionOnEllipsoidSurface : undefined, positionCartographic : cartographic, level : -1, callback : callback @@ -455,7 +455,7 @@ define([ customDataRemoved.length = 0; } - // Our goal with load ordering is to first load all of the tiles we need to + // Our goal with load ordering is to first load all of the tiles we need to // render the current scene at full detail. Loading any other tiles is just // a form of prefetching, and we need not do it at all (other concerns aside). This // simple and obvious statement gets more complicated when we realize that, because @@ -761,13 +761,30 @@ define([ var data = customData[i]; if (tile.level > data.level) { - if (!defined(data.position)) { - data.position = ellipsoid.cartographicToCartesian(data.positionCartographic); + if (!defined(data.positionOnEllipsoidSurface)) { + // cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal` + data.positionOnEllipsoidSurface = Cartesian3.fromRadians(data.positionCartographic.longitude, data.positionCartographic.latitude, 0.0, ellipsoid); } if (mode === SceneMode.SCENE3D) { - Cartesian3.clone(Cartesian3.ZERO, scratchRay.origin); - Cartesian3.normalize(data.position, scratchRay.direction); + var surfaceNormal = ellipsoid.geodeticSurfaceNormal(data.positionOnEllipsoidSurface, scratchRay.direction); + + // compute origin point + + // Try to find the intersection point between the surface normal and z-axis. + // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider + var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(data.positionOnEllipsoidSurface, 11500.0, scratchRay.origin); + + // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid + if (!defined(rayOrigin)) { + // intersection point is outside the ellipsoid, try other value + // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider + var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0); + + // multiply by the *positive* value of the magnitude + var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchPosition); + Cartesian3.subtract(data.positionOnEllipsoidSurface, vectorToMinimumPoint, scratchRay.origin); + } } else { Cartographic.clone(data.positionCartographic, scratchCartographic); diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index cb4baa2efd11..88f9d7a77612 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -36,6 +36,7 @@ define([ '../Renderer/Context', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/PassState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', @@ -53,7 +54,6 @@ define([ './MapMode2D', './OIT', './OrthographicFrustum', - './Pass', './PerformanceDisplay', './PerInstanceColorAppearance', './PerspectiveFrustum', @@ -105,6 +105,7 @@ define([ Context, ContextLimits, DrawCommand, + Pass, PassState, ShaderProgram, ShaderSource, @@ -122,7 +123,6 @@ define([ MapMode2D, OIT, OrthographicFrustum, - Pass, PerformanceDisplay, PerInstanceColorAppearance, PerspectiveFrustum, @@ -1628,7 +1628,7 @@ define([ var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum(); var scratchOrthographicFrustum = new OrthographicFrustum(); - function executeCommands(scene, passState) { + function executeCommands(scene, passState, picking) { var camera = scene._camera; var context = scene.context; var us = context.uniformState; @@ -1652,37 +1652,40 @@ define([ us.updateFrustum(frustum); us.updatePass(Pass.ENVIRONMENT); + var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D; var environmentState = scene._environmentState; - var skyBoxCommand = environmentState.skyBoxCommand; - if (defined(skyBoxCommand)) { - executeCommand(skyBoxCommand, scene, context, passState); - } - if (environmentState.isSkyAtmosphereVisible) { - executeCommand(environmentState.skyAtmosphereCommand, scene, context, passState); - } + // Do not render environment primitives during a pick pass since they do not generate picking commands. + if (!picking) { + var skyBoxCommand = environmentState.skyBoxCommand; + if (defined(skyBoxCommand)) { + executeCommand(skyBoxCommand, scene, context, passState); + } - var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D; + if (environmentState.isSkyAtmosphereVisible) { + executeCommand(environmentState.skyAtmosphereCommand, scene, context, passState); + } - if (environmentState.isSunVisible) { - environmentState.sunDrawCommand.execute(context, passState); - if (scene.sunBloom && !useWebVR) { - var framebuffer; - if (environmentState.useGlobeDepthFramebuffer) { - framebuffer = scene._globeDepth.framebuffer; - } else if (environmentState.useFXAA) { - framebuffer = scene._fxaa.getColorFramebuffer(); - } else { - framebuffer = environmentState.originalFramebuffer; + if (environmentState.isSunVisible) { + environmentState.sunDrawCommand.execute(context, passState); + if (scene.sunBloom && !useWebVR) { + var framebuffer; + if (environmentState.useGlobeDepthFramebuffer) { + framebuffer = scene._globeDepth.framebuffer; + } else if (environmentState.useFXAA) { + framebuffer = scene._fxaa.getColorFramebuffer(); + } else { + framebuffer = environmentState.originalFramebuffer; + } + scene._sunPostProcess.execute(context, framebuffer); + passState.framebuffer = framebuffer; } - scene._sunPostProcess.execute(context, framebuffer); - passState.framebuffer = framebuffer; } - } - // Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere. - if (environmentState.isMoonVisible) { - environmentState.moonCommand.execute(context, passState); + // Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere. + if (environmentState.isMoonVisible) { + environmentState.moonCommand.execute(context, passState); + } } // Determine how translucent surfaces will be handled. @@ -1958,14 +1961,14 @@ define([ Cartesian3.add(savedCamera.position, eyeTranslation, camera.position); camera.frustum.xOffset = offset; - executeCommands(scene, passState); + executeCommands(scene, passState, picking); viewport.x = passState.viewport.width; Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position); camera.frustum.xOffset = -offset; - executeCommands(scene, passState); + executeCommands(scene, passState, picking); Camera.clone(savedCamera, camera); } else { @@ -2116,7 +2119,7 @@ define([ executeShadowMapCastCommands(scene); } - executeCommands(scene, passState); + executeCommands(scene, passState, picking); } function updateEnvironment(scene) { @@ -2345,7 +2348,7 @@ define([ if (defined(this._deviceOrientationCameraController)) { this._deviceOrientationCameraController.update(); } - + this._camera.update(this._mode); this._camera._updateCameraChanged(); }; diff --git a/Source/Scene/SceneTransitioner.js b/Source/Scene/SceneTransitioner.js index 59ccefbd81bb..76c0989a6786 100644 --- a/Source/Scene/SceneTransitioner.js +++ b/Source/Scene/SceneTransitioner.js @@ -126,11 +126,12 @@ define([ if (duration > 0.0) { position.x = 0.0; - position.y = 0.0; - position.z = 5.0 * ellipsoid.maximumRadius; + position.y = -1.0; + position.z = 1.0; + position = Cartesian3.multiplyByScalar(Cartesian3.normalize(position, position), 5.0 * ellipsoid.maximumRadius, position); - Cartesian3.negate(Cartesian3.UNIT_Z, direction); - Cartesian3.clone(Cartesian3.UNIT_Y, up); + Cartesian3.negate(Cartesian3.normalize(position, direction), direction); + Cartesian3.cross(Cartesian3.UNIT_X, direction, up); } else { var camera = scene.camera; if (this._previousMode === SceneMode.SCENE2D) { @@ -694,48 +695,42 @@ define([ var scene = transitioner._scene; var camera = scene.camera; - var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos); - var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir); - var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp); - var endPos = Cartesian3.clone(cameraCV.position, scratch3DToCVEndPos); var endDir = Cartesian3.clone(cameraCV.direction, scratch3DToCVEndDir); var endUp = Cartesian3.clone(cameraCV.up, scratch3DToCVEndUp); - var startRight = camera.frustum.right; - var endRight = endPos.z * 0.5; - - function update(value) { - columbusViewMorph(startPos, endPos, value.time, camera.position); - columbusViewMorph(startDir, endDir, value.time, camera.direction); - columbusViewMorph(startUp, endUp, value.time, camera.up); - Cartesian3.cross(camera.direction, camera.up, camera.right); - Cartesian3.normalize(camera.right, camera.right); - - var frustum = camera.frustum; - frustum.right = CesiumMath.lerp(startRight, endRight, value.time); - frustum.left = -frustum.right; - frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); - frustum.bottom = -frustum.top; - - camera.position.z = 2.0 * scene.mapProjection.ellipsoid.maximumRadius; - } - var tween = scene.tweens.add({ - duration : duration, - easingFunction : EasingFunction.QUARTIC_OUT, - startObject : { - time : 0.0 - }, - stopObject : { - time : 1.0 - }, - update : update, - complete : function() { - scene._mode = SceneMode.MORPHING; - morphOrthographicToPerspective(transitioner, duration, cameraCV, complete); + scene._mode = SceneMode.MORPHING; + morphOrthographicToPerspective(transitioner, 0.0, cameraCV, function() { + camera.frustum = cameraCV.frustum.clone(); + + var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos); + var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir); + var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp); + startPos.z = endPos.z; + + function update(value) { + columbusViewMorph(startPos, endPos, value.time, camera.position); + columbusViewMorph(startDir, endDir, value.time, camera.direction); + columbusViewMorph(startUp, endUp, value.time, camera.up); + Cartesian3.cross(camera.direction, camera.up, camera.right); + Cartesian3.normalize(camera.right, camera.right); } + var tween = scene.tweens.add({ + duration : duration, + easingFunction : EasingFunction.QUARTIC_OUT, + startObject : { + time : 0.0 + }, + stopObject : { + time : 1.0 + }, + update : update, + complete : function() { + complete(transitioner); + } + }); + transitioner._currentTweens.push(tween); }); - transitioner._currentTweens.push(tween); } var scratch3DToCVStartPos = new Cartesian3(); diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index 8a52c42dd7df..7f0652571b0a 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -153,9 +153,9 @@ define([ */ this.bounceAnimationTime = 3.0; /** - * The minimum magnitude, in meters, of the camera position when zooming. Defaults to 20.0. + * The minimum magnitude, in meters, of the camera position when zooming. Defaults to 1.0. * @type {Number} - * @default 20.0 + * @default 1.0 */ this.minimumZoomDistance = 1.0; /** diff --git a/Source/Scene/ShadowMap.js b/Source/Scene/ShadowMap.js index 594bc3346381..cce66085ebdb 100644 --- a/Source/Scene/ShadowMap.js +++ b/Source/Scene/ShadowMap.js @@ -30,6 +30,7 @@ define([ '../Renderer/CubeMap', '../Renderer/DrawCommand', '../Renderer/Framebuffer', + '../Renderer/Pass', '../Renderer/PassState', '../Renderer/PixelDatatype', '../Renderer/Renderbuffer', @@ -46,7 +47,6 @@ define([ './CullingVolume', './DebugCameraPrimitive', './OrthographicFrustum', - './Pass', './PerInstanceColorAppearance', './PerspectiveFrustum', './Primitive', @@ -82,6 +82,7 @@ define([ CubeMap, DrawCommand, Framebuffer, + Pass, PassState, PixelDatatype, Renderbuffer, @@ -98,7 +99,6 @@ define([ CullingVolume, DebugCameraPrimitive, OrthographicFrustum, - Pass, PerInstanceColorAppearance, PerspectiveFrustum, Primitive, diff --git a/Source/Scene/ViewportQuad.js b/Source/Scene/ViewportQuad.js index 5d26694eb94b..b8dbeb2269f9 100644 --- a/Source/Scene/ViewportQuad.js +++ b/Source/Scene/ViewportQuad.js @@ -5,24 +5,24 @@ define([ '../Core/defined', '../Core/destroyObject', '../Core/DeveloperError', + '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderSource', '../Shaders/ViewportQuadFS', './BlendingState', - './Material', - './Pass' + './Material' ], function( BoundingRectangle, Color, defined, destroyObject, DeveloperError, + Pass, RenderState, ShaderSource, ViewportQuadFS, BlendingState, - Material, - Pass) { + Material) { 'use strict'; /** diff --git a/Source/Scene/modelMaterialsCommon.js b/Source/Scene/modelMaterialsCommon.js index 9d1ca8abe7d6..69f3a6182337 100644 --- a/Source/Scene/modelMaterialsCommon.js +++ b/Source/Scene/modelMaterialsCommon.js @@ -353,7 +353,7 @@ define([ if (options.addBatchIdToGeneratedShaders) { techniqueAttributes.a_batchId = 'batchId'; techniqueParameters.batchId = { - semantic: 'BATCHID', + semantic: '_BATCHID', type: WebGLConstants.FLOAT }; vertexShader += 'attribute float a_batchId;\n'; diff --git a/Source/ThirdParty/gltfDefaults.js b/Source/ThirdParty/gltfDefaults.js index 14cb12615d82..d9174d665b2f 100644 --- a/Source/ThirdParty/gltfDefaults.js +++ b/Source/ThirdParty/gltfDefaults.js @@ -401,7 +401,7 @@ define([ for (var name in skins) { if (skins.hasOwnProperty(name)) { var skin = skins[name]; - if (defined(skin.bindShapeMatrix)) { + if (!defined(skin.bindShapeMatrix)) { skin.bindShapeMatrix = [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/Source/Widgets/Geocoder/Geocoder.css b/Source/Widgets/Geocoder/Geocoder.css index b8da4c244277..244e41de2c8d 100644 --- a/Source/Widgets/Geocoder/Geocoder.css +++ b/Source/Widgets/Geocoder/Geocoder.css @@ -37,6 +37,33 @@ width: 250px; } +.cesium-viewer-geocoderContainer .search-results { + position: absolute; + background-color: #000; + color: #eee; + overflow-y: auto; + opacity: 0.8; + width: 100%; +} + +.cesium-viewer-geocoderContainer .search-results ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +.cesium-viewer-geocoderContainer .search-results ul li { + font-size: 14px; + padding: 3px 10px; +} +.cesium-viewer-geocoderContainer .search-results ul li:hover { + cursor: pointer; +} + +.cesium-viewer-geocoderContainer .search-results ul li.active { + background: #48b; +} + .cesium-geocoder-searchButton { background-color: #303336; display: inline-block; diff --git a/Source/Widgets/Geocoder/Geocoder.js b/Source/Widgets/Geocoder/Geocoder.js index 6ed87298029e..2b42e86a2ccb 100644 --- a/Source/Widgets/Geocoder/Geocoder.js +++ b/Source/Widgets/Geocoder/Geocoder.js @@ -32,6 +32,8 @@ define([ * @param {Object} options Object with the following properties: * @param {Element|String} options.container The DOM element or ID that will contain the widget. * @param {Scene} options.scene The Scene instance to use. + * @param {GeocoderService[]} [options.geocoderServices] The geocoder services to be used + * @param {Boolean} [options.autoComplete = true] True if the geocoder should query as the user types to autocomplete * @param {String} [options.url='https://dev.virtualearth.net'] The base URL of the Bing Maps API. * @param {String} [options.key] The Bing Maps key for your application, which can be * created at {@link https://www.bingmapsportal.com}. @@ -66,10 +68,11 @@ define([ textBox.className = 'cesium-geocoder-input'; textBox.setAttribute('placeholder', 'Enter an address or landmark...'); textBox.setAttribute('data-bind', '\ -value: searchText,\ -valueUpdate: "afterkeydown",\ +textInput: searchText,\ disable: isSearchInProgress,\ -css: { "cesium-geocoder-input-wide" : keepExpanded || searchText.length > 0 }'); +event: { keyup: handleKeyUp, keydown: handleKeyDown, mouseover: deselectSuggestion },\ +css: { "cesium-geocoder-input-wide" : keepExpanded || searchText.length > 0 },\ +hasFocus: _focusTextbox'); this._onTextBoxFocus = function() { // as of 2016-10-19, setTimeout is required to ensure that the @@ -92,21 +95,41 @@ cesiumSvgPath: { path: isSearchInProgress ? _stopSearchPath : _startSearchPath, container.appendChild(form); + var searchSuggestionsContainer = document.createElement('div'); + searchSuggestionsContainer.className = 'search-results'; + searchSuggestionsContainer.setAttribute('data-bind', 'visible: _suggestionsVisible'); + + var suggestionsList = document.createElement('ul'); + suggestionsList.setAttribute('data-bind', 'foreach: _suggestions'); + var suggestions = document.createElement('li'); + suggestionsList.appendChild(suggestions); + suggestions.setAttribute('data-bind', 'text: $data.displayName, \ +click: $parent.activateSuggestion, \ +event: { mouseover: $parent.handleMouseover}, \ +css: { active: $data === $parent._selectedSuggestion }'); + + searchSuggestionsContainer.appendChild(suggestionsList); + container.appendChild(searchSuggestionsContainer); + knockout.applyBindings(viewModel, form); + knockout.applyBindings(viewModel, searchSuggestionsContainer); this._container = container; + this._searchSuggestionsContainer = searchSuggestionsContainer; this._viewModel = viewModel; this._form = form; this._onInputBegin = function(e) { if (!container.contains(e.target)) { - textBox.blur(); + viewModel._focusTextbox = false; + viewModel.hideSuggestions(); } }; this._onInputEnd = function(e) { if (container.contains(e.target)) { - textBox.focus(); + viewModel._focusTextbox = true; + viewModel.showSuggestions(); } }; @@ -140,6 +163,18 @@ cesiumSvgPath: { path: isSearchInProgress ? _stopSearchPath : _startSearchPath, } }, + /** + * Gets the parent container. + * @memberof Geocoder.prototype + * + * @type {Element} + */ + searchSuggestionsContainer : { + get : function() { + return this._searchSuggestionsContainer; + } + }, + /** * Gets the view model. * @memberof Geocoder.prototype @@ -174,9 +209,11 @@ cesiumSvgPath: { path: isSearchInProgress ? _stopSearchPath : _startSearchPath, document.removeEventListener('touchstart', this._onInputBegin, true); document.removeEventListener('touchend', this._onInputEnd, true); } - + this._viewModel.destroy(); knockout.cleanNode(this._form); + knockout.cleanNode(this._searchSuggestionsContainer); this._container.removeChild(this._form); + this._container.removeChild(this._searchSuggestionsContainer); this._textBox.removeEventListener('focus', this._onTextBoxFocus, false); return destroyObject(this); diff --git a/Source/Widgets/Geocoder/GeocoderViewModel.js b/Source/Widgets/Geocoder/GeocoderViewModel.js index 4f30f7912303..6f51f93db92b 100644 --- a/Source/Widgets/Geocoder/GeocoderViewModel.js +++ b/Source/Widgets/Geocoder/GeocoderViewModel.js @@ -1,34 +1,40 @@ /*global define*/ define([ '../../Core/BingMapsApi', + '../../Core/BingMapsGeocoderService', '../../Core/Cartesian3', '../../Core/defaultValue', '../../Core/defined', '../../Core/defineProperties', + '../../Core/deprecationWarning', '../../Core/DeveloperError', '../../Core/Event', - '../../Core/loadJsonp', + '../../Core/CartographicGeocoderService', '../../Core/Matrix4', '../../Core/Rectangle', '../../Core/RequestScheduler', '../../ThirdParty/knockout', '../../ThirdParty/when', - '../createCommand' + '../createCommand', + '../getElement' ], function( BingMapsApi, + BingMapsGeocoderService, Cartesian3, defaultValue, defined, defineProperties, + deprecationWarning, DeveloperError, Event, - loadJsonp, + CartographicGeocoderService, Matrix4, Rectangle, RequestScheduler, knockout, when, - createCommand) { + createCommand, + getElement) { 'use strict'; /** @@ -38,6 +44,9 @@ define([ * * @param {Object} options Object with the following properties: * @param {Scene} options.scene The Scene instance to use. + * @param {GeocoderService[]} [options.geocoderServices] Geocoder services to use for geocoding queries. + * If more than one are supplied, suggestions will be gathered for the geocoders that support it, + * and if no suggestion is selected the result from the first geocoder service wil be used. * @param {String} [options.url='https://dev.virtualearth.net'] The base URL of the Bing Maps API. * @param {String} [options.key] The Bing Maps key for your application, which can be * created at {@link https://www.bingmapsportal.com}. @@ -55,33 +64,123 @@ define([ } //>>includeEnd('debug'); + if (defined(options.geocoderServices)) { + this._geocoderServices = options.geocoderServices; + } else { + this._geocoderServices = [ + new CartographicGeocoderService(), + new BingMapsGeocoderService() + ]; + } + + var errorCredit; this._url = defaultValue(options.url, 'https://dev.virtualearth.net/'); if (this._url.length > 0 && this._url[this._url.length - 1] !== '/') { this._url += '/'; } this._key = BingMapsApi.getKey(options.key); - var errorCredit = BingMapsApi.getErrorCredit(options.key); + this._defaultGeocoderOptions = { + url: this._url, + key: this._key + }; + + if (defined(options.key)) { + errorCredit = BingMapsApi.getErrorCredit(options.key); + } if (defined(errorCredit)) { options.scene._frameState.creditDisplay.addDefaultCredit(errorCredit); } + this._viewContainer = options.container; this._scene = options.scene; this._flightDuration = options.flightDuration; this._searchText = ''; this._isSearchInProgress = false; - this._geocodeInProgress = undefined; + this._geocodePromise = undefined; this._complete = new Event(); + this._suggestions = []; + this._selectedSuggestion = undefined; + this._showSuggestions = true; + this._updateCamera = updateCamera; + this._adjustSuggestionsScroll = adjustSuggestionsScroll; + this._updateSearchSuggestions = updateSearchSuggestions; + this._handleArrowDown = handleArrowDown; + this._handleArrowUp = handleArrowUp; var that = this; + + this._suggestionsVisible = knockout.pureComputed(function () { + var suggestions = knockout.getObservable(that, '_suggestions'); + var suggestionsNotEmpty = suggestions().length > 0; + var showSuggestions = knockout.getObservable(that, '_showSuggestions')(); + return suggestionsNotEmpty && showSuggestions; + }); + this._searchCommand = createCommand(function() { + that.hideSuggestions(); + if (defined(that._selectedSuggestion)) { + that.activateSuggestion(that._selectedSuggestion); + return false; + } if (that.isSearchInProgress) { cancelGeocode(that); } else { - geocode(that); + geocode(that, that._geocoderServices); } }); + this.deselectSuggestion = function () { + that._selectedSuggestion = undefined; + }; + + this.handleKeyDown = function(data, event) { + var downKey = event.key === 'ArrowDown' || event.key === 'Down' || event.keyCode === 40; + var upKey = event.key === 'ArrowUp' || event.key === 'Up' || event.keyCode === 38; + if (downKey || upKey) { + event.preventDefault(); + } + + return true; + }; + + this.handleKeyUp = function (data, event) { + var downKey = event.key === 'ArrowDown' || event.key === 'Down' || event.keyCode === 40; + var upKey = event.key === 'ArrowUp' || event.key === 'Up' || event.keyCode === 38; + var enterKey = event.key === 'Enter' || event.keyCode === 13; + if (upKey) { + handleArrowUp(that); + } else if (downKey) { + handleArrowDown(that); + } else if (enterKey) { + that._searchCommand(); + } + return true; + }; + + this.activateSuggestion = function (data) { + that.hideSuggestions(); + that._searchText = data.displayName; + var destination = data.destination; + clearSuggestions(that); + updateCamera(that, destination); + }; + + this.hideSuggestions = function () { + that._showSuggestions = false; + that._selectedSuggestion = undefined; + }; + + this.showSuggestions = function () { + that._showSuggestions = true; + }; + + this.handleMouseover = function (data, event) { + if (data !== that._selectedSuggestion) { + that._selectedSuggestion = data; + } + }; + /** * Gets or sets a value indicating if this instance should always show its text input field. * @@ -90,8 +189,22 @@ define([ */ this.keepExpanded = false; - knockout.track(this, ['_searchText', '_isSearchInProgress', 'keepExpanded']); + /** + * True if the geocoder should query as the user types to autocomplete + * @type {Booelan} + * @default true + */ + this.autoComplete = defaultValue(options.autocomplete, true); + + this._focusTextbox = false; + knockout.track(this, ['_searchText', '_isSearchInProgress', 'keepExpanded', '_suggestions', '_selectedSuggestion', '_showSuggestions', '_focusTextbox']); + + var searchTextObservable = knockout.getObservable(this, '_searchText'); + searchTextObservable.extend({ rateLimit: { timeout: 500 } }); + this._suggestionSubscription = searchTextObservable.subscribe(function() { + updateSearchSuggestions(that); + }); /** * Gets a value indicating whether a search is currently in progress. This property is observable. * @@ -116,6 +229,7 @@ define([ if (this.isSearchInProgress) { return 'Searching...'; } + return this._searchText; }, set : function(value) { @@ -124,7 +238,6 @@ define([ throw new DeveloperError('value must be a valid string.'); } //>>includeEnd('debug'); - this._searchText = value; } }); @@ -157,24 +270,28 @@ define([ defineProperties(GeocoderViewModel.prototype, { /** * Gets the Bing maps url. + * @deprecated * @memberof GeocoderViewModel.prototype * * @type {String} */ url : { get : function() { + deprecationWarning('url is deprecated', 'The url property was deprecated in Cesium 1.30 and will be removed in version 1.31.'); return this._url; } }, /** * Gets the Bing maps key. + * @deprecated * @memberof GeocoderViewModel.prototype * * @type {String} */ key : { get : function() { + deprecationWarning('key is deprecated', 'The key property was deprecated in Cesium 1.30 and will be removed in version 1.31.'); return this._key; } }, @@ -213,9 +330,68 @@ define([ get : function() { return this._searchCommand; } + }, + + /** + * Gets the currently selected geocoder search suggestion + * @memberof GeocoderViewModel.prototype + * + * @type {Object} + */ + selectedSuggestion : { + get : function() { + return this._selectedSuggestion; + } + }, + + /** + * Gets the list of geocoder search suggestions + * @memberof GeocoderViewModel.prototype + * + * @type {Object[]} + */ + suggestions : { + get : function() { + return this._suggestions; + } } }); + /** + * Destroys the widget. Should be called if permanently + * removing the widget from layout. + */ + GeocoderViewModel.prototype.destroy = function() { + this._suggestionSubscription.dispose(); + }; + + function handleArrowUp(viewModel) { + if (viewModel._suggestions.length === 0) { + return; + } + var next; + var currentIndex = viewModel._suggestions.indexOf(viewModel._selectedSuggestion); + if (currentIndex === -1 || currentIndex === 0) { + viewModel._selectedSuggestion = undefined; + return; + } + next = currentIndex - 1; + viewModel._selectedSuggestion = viewModel._suggestions[next]; + adjustSuggestionsScroll(viewModel, next); + } + + function handleArrowDown(viewModel) { + if (viewModel._suggestions.length === 0) { + return; + } + var numberOfSuggestions = viewModel._suggestions.length; + var currentIndex = viewModel._suggestions.indexOf(viewModel._selectedSuggestion); + var next = (currentIndex + 1) % numberOfSuggestions; + viewModel._selectedSuggestion = viewModel._suggestions[next]; + + adjustSuggestionsScroll(viewModel, next); + } + function updateCamera(viewModel, destination) { viewModel._scene.camera.flyTo({ destination : destination, @@ -227,81 +403,124 @@ define([ }); } - function geocode(viewModel) { - var query = viewModel.searchText; + function chainPromise(promise, geocoderService, query) { + return promise + .then(function(result) { + if (defined(result) && result.state === 'fulfilled' && result.value.length > 0){ + return result; + } + var nextPromise = geocoderService.geocode(query) + .then(function (result) { + return {state: 'fulfilled', value: result}; + }) + .otherwise(function (err) { + return {state: 'rejected', reason: err}; + }); + + return nextPromise; + }); + } - if (/^\s*$/.test(query)) { - //whitespace string + function geocode(viewModel, geocoderServices) { + var query = viewModel._searchText; + + if (hasOnlyWhitespace(query)) { + viewModel.showSuggestions(); return; } - // If the user entered (longitude, latitude, [height]) in degrees/meters, - // fly without calling the geocoder. - var splitQuery = query.match(/[^\s,\n]+/g); - if ((splitQuery.length === 2) || (splitQuery.length === 3)) { - var longitude = +splitQuery[0]; - var latitude = +splitQuery[1]; - var height = (splitQuery.length === 3) ? +splitQuery[2] : 300.0; - - if (!isNaN(longitude) && !isNaN(latitude) && !isNaN(height)) { - updateCamera(viewModel, Cartesian3.fromDegrees(longitude, latitude, height)); - return; - } - } viewModel._isSearchInProgress = true; - var promise = RequestScheduler.request(viewModel._url + 'REST/v1/Locations', loadJsonp, { - parameters : { - query : query, - key : viewModel._key + var promise = when.resolve(); + for (var i = 0; i < geocoderServices.length; i++) { + promise = chainPromise(promise, geocoderServices[i], query); + } - }, - callbackParameterName : 'jsonp' - }); + viewModel._geocodePromise = promise; + promise + .then(function (result) { + if (promise.cancel) { + return; + } + viewModel._isSearchInProgress = false; - var geocodeInProgress = viewModel._geocodeInProgress = when(promise, function(result) { - if (geocodeInProgress.cancel) { - return; - } - viewModel._isSearchInProgress = false; + var geocoderResults = result.value; + if (result.state === 'fulfilled' && defined(geocoderResults) && geocoderResults.length > 0) { + viewModel._searchText = geocoderResults[0].displayName; + updateCamera(viewModel, geocoderResults[0].destination); + return; + } + viewModel._searchText = query + ' (not found)'; + }); + } - if (result.resourceSets.length === 0) { - viewModel.searchText = viewModel._searchText + ' (not found)'; - return; - } + function adjustSuggestionsScroll(viewModel, focusedItemIndex) { + var container = getElement(viewModel._viewContainer); + var searchResults = container.getElementsByClassName('search-results')[0]; + var listItems = container.getElementsByTagName('li'); + var element = listItems[focusedItemIndex]; - var resourceSet = result.resourceSets[0]; - if (resourceSet.resources.length === 0) { - viewModel.searchText = viewModel._searchText + ' (not found)'; - return; - } + if (focusedItemIndex === 0) { + searchResults.scrollTop = 0; + return; + } - var resource = resourceSet.resources[0]; + var offsetTop = element.offsetTop; + if (offsetTop + element.clientHeight > searchResults.clientHeight) { + searchResults.scrollTop = offsetTop + element.clientHeight; + } else if (offsetTop < searchResults.scrollTop) { + searchResults.scrollTop = offsetTop; + } + } - viewModel._searchText = resource.name; - var bbox = resource.bbox; - var south = bbox[0]; - var west = bbox[1]; - var north = bbox[2]; - var east = bbox[3]; + function cancelGeocode(viewModel) { + viewModel._isSearchInProgress = false; + if (defined(viewModel._geocodePromise)) { + viewModel._geocodePromise.cancel = true; + viewModel._geocodePromise = undefined; + } + } - updateCamera(viewModel, Rectangle.fromDegrees(west, south, east, north)); - }, function() { - if (geocodeInProgress.cancel) { - return; - } + function hasOnlyWhitespace(string) { + return /^\s*$/.test(string); + } - viewModel._isSearchInProgress = false; - viewModel.searchText = viewModel._searchText + ' (error)'; - }); + function clearSuggestions(viewModel) { + knockout.getObservable(viewModel, '_suggestions').removeAll(); } - function cancelGeocode(viewModel) { - viewModel._isSearchInProgress = false; - if (defined(viewModel._geocodeInProgress)) { - viewModel._geocodeInProgress.cancel = true; - viewModel._geocodeInProgress = undefined; + function updateSearchSuggestions(viewModel) { + if (!viewModel.autoComplete) { + return; + } + + var query = viewModel._searchText; + + clearSuggestions(viewModel); + if (hasOnlyWhitespace(query)) { + return; } + + var promise = when.resolve([]); + viewModel._geocoderServices.forEach(function (service) { + promise = promise.then(function(results) { + if (results.length >= 5) { + return results; + } + return service.geocode(query) + .then(function(newResults) { + results = results.concat(newResults); + return results; + }); + }); + }); + promise + .then(function (results) { + var suggestions = viewModel._suggestions; + for (var i = 0; i < results.length; i++) { + suggestions.push(results[i]); + } + }); } return GeocoderViewModel; diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index 0688f4ad8ad5..63a858b38c9b 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -469,6 +469,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to toolbar.appendChild(geocoderContainer); geocoder = new Geocoder({ container : geocoderContainer, + geocoderServices: defined(options.geocoder) ? (isArray(options.geocoder) ? options.geocoder : [options.geocoder]) : undefined, scene : cesiumWidget.scene }); // Subscribe to search so that we can clear the trackedEntity when it is clicked. @@ -1266,6 +1267,11 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to baseLayerPickerDropDown.style.maxHeight = panelMaxHeight + 'px'; } + if (defined(this._geocoder)) { + var geocoderSuggestions = this._geocoder.searchSuggestionsContainer; + geocoderSuggestions.style.maxHeight = panelMaxHeight + 'px'; + } + if (defined(this._infoBox)) { this._infoBox.viewModel.maxHeight = panelMaxHeight; } diff --git a/Specs/Core/BingMapsGeocoderServiceSpec.js b/Specs/Core/BingMapsGeocoderServiceSpec.js new file mode 100644 index 000000000000..5c68a7804d1c --- /dev/null +++ b/Specs/Core/BingMapsGeocoderServiceSpec.js @@ -0,0 +1,57 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/BingMapsGeocoderService', + 'Core/Cartesian3', + 'Core/loadJsonp', + 'Core/Rectangle' +], function( + BingMapsGeocoderService, + Cartesian3, + loadJsonp, + Rectangle) { + 'use strict'; + + var service = new BingMapsGeocoderService(); + + it('returns geocoder results', function (done) { + var query = 'some query'; + jasmine.createSpy('testSpy', loadJsonp).and.returnValue({ + resourceSets: [{ + resources : [{ + name : 'a', + bbox : [32.0, 3.0, 3.0, 4.0] + }] + }] + }); + service.geocode(query, function(err, results) { + expect(results.length).toEqual(1); + expect(results[0].displayName).toEqual('a'); + expect(results[0].destination).toBeInstanceOf(Rectangle); + done(); + }); + }); + + it('returns no geocoder results if Bing has no results', function (done) { + var query = 'some query'; + jasmine.createSpy('testSpy', loadJsonp).and.returnValue({ + resourceSets: [] + }); + service.geocode(query, function(err, results) { + expect(results.length).toEqual(0); + done(); + }); + }); + + it('returns no geocoder results if Bing has results but no resources', function (done) { + var query = 'some query'; + jasmine.createSpy('testSpy', loadJsonp).and.returnValue({ + resourceSets: [{ + resources: [] + }] + }); + service.geocode(query, function(err, results) { + expect(results.length).toEqual(0); + done(); + }); + }); +}); diff --git a/Specs/Core/CartographicGeocoderServiceSpec.js b/Specs/Core/CartographicGeocoderServiceSpec.js new file mode 100644 index 000000000000..5ecf68dbca37 --- /dev/null +++ b/Specs/Core/CartographicGeocoderServiceSpec.js @@ -0,0 +1,47 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/CartographicGeocoderService', + 'Core/Cartesian3' +], function( + CartographicGeocoderService, + Cartesian3) { + 'use strict'; + + var service = new CartographicGeocoderService(); + + it('returns cartesian with matching coordinates for long/lat/height input', function (done) { + var query = ' 1.0, 2.0, 3.0 '; + service.geocode(query, function(err, results) { + expect(results.length).toEqual(1); + expect(results[0]).toEqual(Cartesian3.fromDegrees(1.0, 2.0, 3.0)); + done(); + }); + }); + + it('returns cartesian with matching coordinates for long/lat input', function (done) { + var query = ' 1.0, 2.0 '; + var defaultHeight = 300.0; + service.geocode(query, function(err, results) { + expect(results.length).toEqual(1); + expect(results[0]).toEqual(Cartesian3.fromDegrees(1.0, 2.0, defaultHeight)); + done(); + }); + }); + + it('returns empty array for input with only one number', function (done) { + var query = ' 2.0 '; + service.geocode(query, function(err, results) { + expect(results.length).toEqual(0); + done(); + }); + }); + + it('returns empty array for with string', function (done) { + var query = ' aoeu '; + service.geocode(query, function(err, results) { + expect(results.length).toEqual(0); + done(); + }); + }); + +}); diff --git a/Specs/Core/CheckSpec.js b/Specs/Core/CheckSpec.js new file mode 100644 index 000000000000..54bcc1cd304b --- /dev/null +++ b/Specs/Core/CheckSpec.js @@ -0,0 +1,177 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/Check' + ], function( + Check) { + 'use strict'; + + describe('type checks', function () { + it('Check.typeOf.boolean does not throw when passed a boolean', function () { + expect(function () { + Check.typeOf.boolean(true); + }).not.toThrowDeveloperError(); + }); + + it('Check.typeOf.boolean throws when passed a non-boolean', function () { + expect(function () { + Check.typeOf.boolean({}, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.boolean([], 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.boolean(1, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.boolean('snth', 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.boolean(function () {return true;}, 'mockName'); + }).toThrowDeveloperError(); + }); + + it('Check.typeOf.func does not throw when passed a function', function () { + expect(function () { + Check.typeOf.func(function () {return true;}, 'mockName'); + }).not.toThrowDeveloperError(); + }); + + it('Check.typeOf.func throws when passed a non-function', function () { + expect(function () { + Check.typeOf.func({}, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.func([], 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.func(1, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.func('snth', 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.func(true, 'mockName'); + }).toThrowDeveloperError(); + }); + + it('Check.typeOf.object does not throw when passed object', function() { + expect(function () { + Check.typeOf.object({}, 'mockName'); + }).not.toThrowDeveloperError(); + }); + + it('Check.typeOf.object throws when passed non-object', function() { + expect(function () { + Check.typeOf.object('snth', 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.object(true, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.object(1, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.object(function () {return true;}, 'mockName'); + }).toThrowDeveloperError(); + }); + + it('Check.typeOf.number does not throw when passed number', function() { + expect(function () { + Check.typeOf.number(2, 'mockName'); + }).not.toThrowDeveloperError(); + }); + + it('Check.typeOf.number throws when passed non-number', function() { + expect(function () { + Check.typeOf.number('snth', 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.number(true, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.number({}, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.number([2], 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.number(function () {return true;}, 'mockName'); + }).toThrowDeveloperError(); + }); + + it('Check.typeOf.string does not throw when passed a string', function () { + expect(function () { + Check.typeOf.string('s', 'mockName'); + }).not.toThrowDeveloperError(); + }); + + it('Check.typeOf.string throws on non-string', function () { + expect(function () { + Check.typeOf.string({}, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.string(true, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.string(1, 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.string([2], 'mockName'); + }).toThrowDeveloperError(); + expect(function () { + Check.typeOf.string(function () {return true;}, 'mockName'); + }).toThrowDeveloperError(); + }); + }); + + describe('Check.numeric', function () { + it('minimum throws on value less than minimum', function () { + expect(function () { + Check.numeric.minimum(4, 5); + }).toThrowDeveloperError(); + }); + it('minimum does not throw on value at least as big as minimum', function () { + expect(function () { + Check.numeric.minimum(4, 4); + Check.numeric.minimum(4, 3); + }).not.toThrowDeveloperError(); + }); + + it('maximum throws on value greater than maximum', function () { + expect(function () { + Check.numeric.maximum(6, 5); + }).toThrowDeveloperError(); + }); + it('maximum does not throw on value at most as big as maximum', function () { + expect(function () { + Check.numeric.maximum(5, 5); + Check.numeric.maximum(4, 5); + }).not.toThrowDeveloperError(); + }); + }); + + it('Check.defined does not throw unless passed value that is undefined or null', function () { + expect(function () { + Check.defined({}, 'mockName'); + }).not.toThrowDeveloperError(); + expect(function () { + Check.defined([], 'mockName'); + }).not.toThrowDeveloperError(); + expect(function () { + Check.defined(2, 'mockName'); + }).not.toThrowDeveloperError(); + expect(function () { + Check.defined(function() {return true;}, 'mockName'); + }).not.toThrowDeveloperError(); + expect(function () { + Check.defined('snt', 'mockName'); + }).not.toThrowDeveloperError(); + }); + + it('Check.defined throws when passed undefined', function () { + expect(function () { + Check.defined(undefined, 'mockName'); + }).toThrowDeveloperError(); + }); + +}); diff --git a/Specs/Core/EllipsoidSpec.js b/Specs/Core/EllipsoidSpec.js index d4dfa16d1092..944c388ebb21 100644 --- a/Specs/Core/EllipsoidSpec.js +++ b/Specs/Core/EllipsoidSpec.js @@ -434,5 +434,114 @@ defineSuite([ expect(cloned).toEqual(myEllipsoid); }); + it('getSurfaceNormalIntersectionWithZAxis throws with no position', function() { + expect(function() { + Ellipsoid.WGS84.getSurfaceNormalIntersectionWithZAxis(undefined); + }).toThrowDeveloperError(); + }); + + it('getSurfaceNormalIntersectionWithZAxis throws if the ellipsoid is not an ellipsoid of revolution', function() { + expect(function() { + var ellipsoid = new Ellipsoid(1,2,3); + var cartesian = new Cartesian3(); + ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian); + }).toThrowDeveloperError(); + }); + + it('getSurfaceNormalIntersectionWithZAxis throws if the ellipsoid has radii.z === 0', function() { + expect(function() { + var ellipsoid = new Ellipsoid(1,2,0); + var cartesian = new Cartesian3(); + ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian); + }).toThrowDeveloperError(); + }); + + it('getSurfaceNormalIntersectionWithZAxis works without a result parameter', function() { + var ellipsoid = Ellipsoid.WGS84; + var cartographic = Cartographic.fromDegrees(35.23,33.23); + var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic); + var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface); + expect(returnedResult instanceof Cartesian3).toBe(true); + }); + + it('getSurfaceNormalIntersectionWithZAxis works with a result parameter', function() { + var ellipsoid = Ellipsoid.WGS84; + var cartographic = Cartographic.fromDegrees(35.23,33.23); + var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic); + var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined , cartesianOnTheSurface); + expect(returnedResult).toBe(cartesianOnTheSurface); + }); + + it('getSurfaceNormalIntersectionWithZAxis returns undefined if the result is outside the ellipsoid with buffer parameter', function() { + var ellipsoid = Ellipsoid.WGS84; + var cartographic = Cartographic.fromDegrees(35.23,33.23); + var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic); + var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, ellipsoid.radii.z); + expect(returnedResult).toBe(undefined); + }); + + it('getSurfaceNormalIntersectionWithZAxis returns undefined if the result is outside the ellipsoid without buffer parameter', function() { + var majorAxis = 10; + var minorAxis = 1; + var ellipsoid = new Ellipsoid(majorAxis,majorAxis,minorAxis); + var cartographic = Cartographic.fromDegrees(45.0,90.0); + var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic); + var returnedResult = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined); + expect(returnedResult).toBe(undefined); + }); + + it('getSurfaceNormalIntersectionWithZAxis returns a result that is equal to a value that computed in a different way', function() { + var ellipsoid = Ellipsoid.WGS84; + var cartographic = Cartographic.fromDegrees(35.23,33.23); + var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic); + var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesianOnTheSurface); + var magnitude = cartesianOnTheSurface.x / surfaceNormal.x; + + var expected = new Cartesian3(); + expected.z = cartesianOnTheSurface.z - surfaceNormal.z * magnitude; + var result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined); + expect(result).toEqualEpsilon(expected, CesiumMath.EPSILON8); + + // at the equator + cartesianOnTheSurface = new Cartesian3(ellipsoid.radii.x, 0 , 0); + result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined); + expect(result).toEqualEpsilon(Cartesian3.ZERO, CesiumMath.EPSILON8); + + }); + + it('getSurfaceNormalIntersectionWithZAxis returns a result that when it\'s used as an origin for a vector with the surface normal direction it produces an accurate cartographic', function() { + var ellipsoid = Ellipsoid.WGS84; + var cartographic = Cartographic.fromDegrees(35.23,33.23); + var cartesianOnTheSurface = ellipsoid.cartographicToCartesian(cartographic); + var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesianOnTheSurface); + + var result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined); + + var surfaceNormalWithLength = Cartesian3.multiplyByScalar(surfaceNormal, ellipsoid.maximumRadius, new Cartesian3()); + var position = Cartesian3.add(result,surfaceNormalWithLength,new Cartesian3()); + var resultCartographic = ellipsoid.cartesianToCartographic(position); + resultCartographic.height = 0.0; + expect(resultCartographic).toEqualEpsilon(cartographic, CesiumMath.EPSILON8); + + // at the north pole + cartographic = Cartographic.fromDegrees(0,90); + cartesianOnTheSurface = new Cartesian3(0, 0 ,ellipsoid.radii.z); + surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesianOnTheSurface); + surfaceNormalWithLength = Cartesian3.multiplyByScalar(surfaceNormal, ellipsoid.maximumRadius, new Cartesian3()); + result = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesianOnTheSurface, undefined); + position = Cartesian3.add(result,surfaceNormalWithLength,new Cartesian3()); + resultCartographic = ellipsoid.cartesianToCartographic(position); + resultCartographic.height = 0.0; + expect(resultCartographic).toEqualEpsilon(cartographic, CesiumMath.EPSILON8); + + }); + + it('ellipsoid is initialized with _sqauredXOverSquaredZ property', function() { + var ellipsoid = new Ellipsoid(4 , 4 , 3); + + var sqauredXOverSquaredZ = ellipsoid.radiiSquared.x / ellipsoid.radiiSquared.z; + expect(ellipsoid._sqauredXOverSquaredZ).toEqual(sqauredXOverSquaredZ); + }); + createPackableSpecs(Ellipsoid, Ellipsoid.WGS84, [Ellipsoid.WGS84.radii.x, Ellipsoid.WGS84.radii.y, Ellipsoid.WGS84.radii.z]); }); diff --git a/Specs/DataSources/DataSourceClockSpec.js b/Specs/DataSources/DataSourceClockSpec.js index e957725cb1f1..3a40542bb5d9 100644 --- a/Specs/DataSources/DataSourceClockSpec.js +++ b/Specs/DataSources/DataSourceClockSpec.js @@ -89,4 +89,34 @@ defineSuite([ target.merge(undefined); }).toThrowDeveloperError(); }); + + it('gets value as a clock instance',function () { + var source = new DataSourceClock(); + source.startTime = JulianDate.now(); + source.stopTime = JulianDate.now(); + source.currentTime = JulianDate.now(); + source.clockRange = ClockRange.CLAMPED; + source.clockStep = ClockStep.TICK_DEPENDENT; + source.multiplier = 2; + + var clock = source.getValue(); + expect(clock.startTime).toEqual(source.startTime); + expect(clock.stopTime).toEqual(source.stopTime); + expect(clock.currentTime).toEqual(source.currentTime); + expect(clock.clockRange).toEqual(source.clockRange); + expect(clock.clockStep).toEqual(source.clockStep); + expect(clock.multiplier).toEqual(source.multiplier); + + source.multiplier = undefined; + source.clockStep = undefined; + source.clockRange = undefined; + + clock = source.getValue(); + expect(clock.startTime).toEqual(source.startTime); + expect(clock.stopTime).toEqual(source.stopTime); + expect(clock.currentTime).toEqual(source.currentTime); + expect(clock.clockRange).toEqual(ClockRange.UNBOUNDED); + expect(clock.clockStep).toEqual(ClockStep.SYSTEM_CLOCK_MULTIPLIER); + expect(clock.multiplier).toEqual(1.0); + }); }); diff --git a/Specs/Renderer/AutomaticUniformSpec.js b/Specs/Renderer/AutomaticUniformSpec.js index b00f04ae2779..8e4c5010ee30 100644 --- a/Specs/Renderer/AutomaticUniformSpec.js +++ b/Specs/Renderer/AutomaticUniformSpec.js @@ -4,9 +4,9 @@ defineSuite([ 'Core/Cartesian3', 'Core/defaultValue', 'Core/Matrix4', + 'Renderer/Pass', 'Renderer/Texture', 'Scene/OrthographicFrustum', - 'Scene/Pass', 'Scene/SceneMode', 'Specs/createCamera', 'Specs/createContext', @@ -16,9 +16,9 @@ defineSuite([ Cartesian3, defaultValue, Matrix4, + Pass, Texture, OrthographicFrustum, - Pass, SceneMode, createCamera, createContext, diff --git a/Specs/Renderer/DrawCommandSpec.js b/Specs/Renderer/DrawCommandSpec.js index b15ff6c9b772..a066f2cf8e06 100644 --- a/Specs/Renderer/DrawCommandSpec.js +++ b/Specs/Renderer/DrawCommandSpec.js @@ -1,11 +1,11 @@ /*global defineSuite*/ defineSuite([ - 'Renderer/DrawCommand', 'Core/PrimitiveType', - 'Scene/Pass' + 'Renderer/DrawCommand', + 'Renderer/Pass' ], function( - DrawCommand, PrimitiveType, + DrawCommand, Pass) { 'use strict'; diff --git a/Specs/Scene/Batched3DModel3DTileContentSpec.js b/Specs/Scene/Batched3DModel3DTileContentSpec.js index 182e05286be5..255fd33acbb9 100644 --- a/Specs/Scene/Batched3DModel3DTileContentSpec.js +++ b/Specs/Scene/Batched3DModel3DTileContentSpec.js @@ -103,11 +103,12 @@ defineSuite([ }); it('logs deprecation warning for use of BATCHID without prefixed underscore', function() { - var deprecationWarningSpy = jasmine.createSpy(deprecationWarning); - return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl).then(function(tileset) { - expect(deprecationWarningSpy).toHaveBeenCalled(); - Cesium3DTilesTester.expectRenderTileset(scene, tileset); - }); + spyOn(Batched3DModel3DTileContent, '_deprecationWarning'); + return Cesium3DTilesTester.loadTileset(scene, withBatchTableUrl) + .then(function(tileset) { + expect(Batched3DModel3DTileContent._deprecationWarning).toHaveBeenCalled(); + Cesium3DTilesTester.expectRenderTileset(scene, tileset); + }); }); it('throws with empty gltf', function() { diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index c8b8ced4c52d..078761b40a2e 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -1,11 +1,17 @@ /*global defineSuite*/ defineSuite([ 'Scene/Expression', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartesian4', 'Core/Color', 'Core/Math', 'Scene/ExpressionNodeType' ], function( Expression, + Cartesian2, + Cartesian3, + Cartesian4, Color, CesiumMath, ExpressionNodeType) { @@ -485,7 +491,7 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('evaluates color properties', function() { + it('evaluates color properties (reg, green, blue, alpha)', function() { var expression = new Expression('color(\'#ffffff\').red'); expect(expression.evaluate(frameState, undefined)).toEqual(1); @@ -499,6 +505,254 @@ defineSuite([ expect(expression.evaluate(frameState, undefined)).toEqual(0.5); }); + it('evaluates color properties (x, y, z, w)', function() { + var expression = new Expression('color(\'#ffffff\').x'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0).y'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan").z'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5).w'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates color properties ([0], [1], [2]. [3])', function() { + var expression = new Expression('color(\'#ffffff\')[0]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0)[1]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan")[2]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5)[3]'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates color properties (["red"], ["green"], ["blue"], ["alpha"])', function() { + var expression = new Expression('color(\'#ffffff\')["red"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0)["green"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan")["blue"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5)["alpha"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates color properties (["x"], ["y"], ["z"], ["w"])', function() { + var expression = new Expression('color(\'#ffffff\')["x"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0)["y"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan")["z"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5)["w"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates vec2', function() { + var expression = new Expression('vec2(2.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(2.0, 2.0)); + + expression = new Expression('vec2(3.0, 4.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + + expression = new Expression('vec2(vec2(3.0, 4.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + + expression = new Expression('vec2(vec3(3.0, 4.0, 5.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + + expression = new Expression('vec2(vec4(3.0, 4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + }); + + it('throws if vec2 has invalid number of arguments', function() { + var expression = new Expression('vec2()'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec2(3.0, 4.0, 5.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec2(vec2(3.0, 4.0), 5.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + }); + + it('evaluates vec3', function() { + var expression = new Expression('vec3(2.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(2.0, 2.0, 2.0)); + + expression = new Expression('vec3(3.0, 4.0, 5.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(vec2(3.0, 4.0), 5.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(3.0, vec2(4.0, 5.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(vec3(3.0, 4.0, 5.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(vec4(3.0, 4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + }); + + it ('throws if vec3 has invalid number of arguments', function() { + var expression = new Expression('vec3()'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(3.0, 4.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(3.0, 4.0, 5.0, 6.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(vec2(3.0, 4.0), vec2(5.0, 6.0))'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(vec4(3.0, 4.0, 5.0, 6.0), 1.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + }); + + it('evaluates vec4', function() { + var expression = new Expression('vec4(2.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(2.0, 2.0, 2.0, 2.0)); + + expression = new Expression('vec4(3.0, 4.0, 5.0, 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(vec2(3.0, 4.0), 5.0, 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(3.0, vec2(4.0, 5.0), 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(3.0, 4.0, vec2(5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(vec3(3.0, 4.0, 5.0), 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(3.0, vec3(4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(vec4(3.0, 4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + }); + + it ('throws if vec4 has invalid number of arguments', function() { + var expression = new Expression('vec4()'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(3.0, 4.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(3.0, 4.0, 5.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(3.0, 4.0, 5.0, 6.0, 7.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(vec3(3.0, 4.0, 5.0))'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + }); + + it('evaluates vector with expressions as arguments', function() { + var feature = new MockFeature(); + feature.addProperty('height', 2); + feature.addProperty('width', 4); + feature.addProperty('depth', 3); + feature.addProperty('scale', 1); + + var expression = new Expression('vec4(${height}, ${width}, ${depth}, ${scale})'); + expect(expression.evaluate(frameState, feature)).toEqual(new Cartesian4(2.0, 4.0, 3.0, 1.0)); + }); + + it('evaluates expression with multiple nested vectors', function() { + var expression = new Expression('vec4(vec2(1, 2)[vec3(6, 1, 5).y], 2, vec4(1.0).w, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(2.0, 2.0, 1.0, 5.0)); + }); + + it('evaluates vector properties (x, y, z, w)', function() { + var expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).x'); + expect(expression.evaluate(frameState, undefined)).toEqual(1.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).y'); + expect(expression.evaluate(frameState, undefined)).toEqual(2.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).z'); + expect(expression.evaluate(frameState, undefined)).toEqual(3.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).w'); + expect(expression.evaluate(frameState, undefined)).toEqual(4.0); + }); + + it('evaluates vector properties ([0], [1], [2], [3])', function() { + var expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[0]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[1]'); + expect(expression.evaluate(frameState, undefined)).toEqual(2.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[2]'); + expect(expression.evaluate(frameState, undefined)).toEqual(3.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[3]'); + expect(expression.evaluate(frameState, undefined)).toEqual(4.0); + }); + + it('evaluates vector properties (["x"], ["y"], ["z"]. ["w"])', function() { + var expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["x"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["y"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(2.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["z"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(3.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["w"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(4.0); + }); + it('evaluates unary not', function() { var expression = new Expression('!true'); expect(expression.evaluate(frameState, undefined)).toEqual(false); @@ -748,7 +1002,10 @@ defineSuite([ }); it('evaluates color operations', function() { - var expression = new Expression('rgba(255, 0, 0, 0.5) + rgba(0, 0, 255, 0.5)'); + var expression = new Expression('+rgba(255, 0, 0, 1.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(Color.RED); + + expression = new Expression('rgba(255, 0, 0, 0.5) + rgba(0, 0, 255, 0.5)'); expect(expression.evaluate(frameState, undefined)).toEqual(Color.MAGENTA); expression = new Expression('rgba(0, 255, 255, 1.0) - rgba(0, 255, 0, 0)'); @@ -783,6 +1040,140 @@ defineSuite([ expression = new Expression('color(\'green\') != color(\'green\')'); expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('color(\'green\') !== color(\'green\')'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + }); + + it('evaluates vector operations', function() { + var expression = new Expression('+vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(1, 2)); + + expression = new Expression('+vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(1, 2, 3)); + + expression = new Expression('+vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(1, 2, 3, 4)); + + expression = new Expression('-vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(-1, -2)); + + expression = new Expression('-vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(-1, -2, -3)); + + expression = new Expression('-vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(-1, -2, -3, -4)); + + expression = new Expression('vec2(1, 2) + vec2(3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(4, 6)); + + expression = new Expression('vec3(1, 2, 3) + vec3(3, 4, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(4, 6, 8)); + + expression = new Expression('vec4(1, 2, 3, 4) + vec4(3, 4, 5, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(4, 6, 8, 10)); + + expression = new Expression('vec2(1, 2) - vec2(3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(-2, -2)); + + expression = new Expression('vec3(1, 2, 3) - vec3(3, 4, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(-2, -2, -2)); + + expression = new Expression('vec4(1, 2, 3, 4) - vec4(3, 4, 5, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(-2, -2, -2, -2)); + + expression = new Expression('vec2(1, 2) * vec2(3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3, 8)); + + expression = new Expression('vec2(1, 2) * 3.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3, 6)); + + expression = new Expression('3.0 * vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3, 6)); + + expression = new Expression('vec3(1, 2, 3) * vec3(3, 4, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3, 8, 15)); + + expression = new Expression('vec3(1, 2, 3) * 3.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3, 6, 9)); + + expression = new Expression('3.0 * vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3, 6, 9)); + + expression = new Expression('vec4(1, 2, 3, 4) * vec4(3, 4, 5, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3, 8, 15, 24)); + + expression = new Expression('vec4(1, 2, 3, 4) * 3.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3, 6, 9, 12)); + + expression = new Expression('3.0 * vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3, 6, 9, 12)); + + expression = new Expression('vec2(1, 2) / vec2(2, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(0.5, 0.4)); + + expression = new Expression('vec2(1, 2) / 2.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(0.5, 1.0)); + + expression = new Expression('vec3(1, 2, 3) / vec3(2, 5, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(0.5, 0.4, 1.0)); + + expression = new Expression('vec3(1, 2, 3) / 2.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(0.5, 1.0, 1.5)); + + expression = new Expression('vec4(1, 2, 3, 4) / vec4(2, 5, 3, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(0.5, 0.4, 1.0, 2.0)); + + expression = new Expression('vec4(1, 2, 3, 4) / 2.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(0.5, 1.0, 1.5, 2.0)); + + expression = new Expression('vec2(2, 3) % vec2(3, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(2, 0)); + + expression = new Expression('vec3(2, 3, 4) % vec3(3, 3, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(2, 0, 1)); + + expression = new Expression('vec4(2, 3, 4, 5) % vec4(3, 3, 3, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(2, 0, 1, 1)); + + expression = new Expression('vec2(1, 3) == vec2(1, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec3(1, 3, 4) == vec3(1, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec4(1, 3, 4, 6) == vec4(1, 3, 4, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec2(1, 2) === vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec3(1, 2, 3) === vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec4(1, 2, 3, 4) === vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('!!vec4(1.0) == true'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec2(1, 2) != vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec3(1, 2, 3) != vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec4(1, 2, 3, 4) != vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec2(1, 2) !== vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec3(1, 2, 3) !== vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec4(1, 2, 3, 4) !== vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); }); it('evaluates color toString function', function() { @@ -799,6 +1190,23 @@ defineSuite([ expect(expression.evaluate(frameState, feature)).toEqual('(0, 0, 1, 1)'); }); + it('evaluates vector toString function', function() { + var feature = new MockFeature(); + feature.addProperty('property', new Cartesian4(1, 2, 3, 4)); + + var expression = new Expression('vec2(1, 2).toString()'); + expect(expression.evaluate(frameState, undefined)).toEqual('(1, 2)'); + + expression = new Expression('vec3(1, 2, 3).toString()'); + expect(expression.evaluate(frameState, undefined)).toEqual('(1, 2, 3)'); + + expression = new Expression('vec4(1, 2, 3, 4).toString()'); + expect(expression.evaluate(frameState, undefined)).toEqual('(1, 2, 3, 4)'); + + expression = new Expression('${property}.toString()'); + expect(expression.evaluate(frameState, feature)).toEqual('(1, 2, 3, 4)'); + }); + it('evaluates isNaN function', function() { var expression = new Expression('isNaN()'); expect(expression.evaluate(frameState, undefined)).toEqual(true); @@ -1524,7 +1932,7 @@ defineSuite([ expect(expression.evaluate(frameState, feature)).toEqual(false); }); - it('throws if test is not call with a RegExp', function() { + it('throws if test is not called with a RegExp', function() { expect(function() { return new Expression('color("blue").test()'); }).toThrowDeveloperError(); @@ -1780,12 +2188,12 @@ defineSuite([ it('gets shader expression for array indexing', function() { var expression = new Expression('${property[0]}'); var shaderExpression = expression.getShaderExpression('', {}); - var expected = 'property[int(0.0)]'; + var expected = 'property[0]'; expect(shaderExpression).toEqual(expected); - expression = new Expression('rgb(0,0,0)[1]'); + expression = new Expression('${property[4 / 2]}'); shaderExpression = expression.getShaderExpression('', {}); - expected = 'vec4(0.0, 0.0, 0.0, 1.0)[int(1.0)]'; + expected = 'property[int((4.0 / 2.0))]'; expect(shaderExpression).toEqual(expected); }); @@ -1944,6 +2352,55 @@ defineSuite([ expect(shaderState.translucent).toBe(true); }); + it('gets shader expression for color components', function() { + // .red, .green, .blue, .alpha + var expression = new Expression('color().red + color().green + color().blue + color().alpha'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(((vec4(1.0)[0] + vec4(1.0)[1]) + vec4(1.0)[2]) + vec4(1.0)[3])'; + expect(shaderExpression).toEqual(expected); + + // .x, .y, .z, .w + expression = new Expression('color().x + color().y + color().z + color().w'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual(expected); + + // [0], [1], [2], [3] + expression = new Expression('color()[0] + color()[1] + color()[2] + color()[3]'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for vector', function() { + var expression = new Expression('vec4(1, 2, 3, 4)'); + var shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('vec4(1.0, 2.0, 3.0, 4.0)'); + + expression = new Expression('vec4(1) + vec4(2)'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('(vec4(1.0) + vec4(2.0))'); + + expression = new Expression('vec4(1, ${property}, vec2(1, 2).x, 0)'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('vec4(1.0, property, vec2(1.0, 2.0)[0], 0.0)'); + + expression = new Expression('vec4(vec3(2), 1.0)'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('vec4(vec3(2.0), 1.0)'); + }); + + it('gets shader expression for vector components', function() { + // .x, .y, .z, .w + var expression = new Expression('vec4(1).x + vec4(1).y + vec4(1).z + vec4(1).w'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(((vec4(1.0)[0] + vec4(1.0)[1]) + vec4(1.0)[2]) + vec4(1.0)[3])'; + expect(shaderExpression).toEqual(expected); + + // [0], [1], [2], [3] + expression = new Expression('vec4(1)[0] + vec4(1)[1] + vec4(1)[2] + vec4(1)[3]'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual(expected); + }); + it('gets shader expression for TILES3D_TILESET_TIME', function() { var expression = new Expression('TILES3D_TILESET_TIME'); var shaderExpression = expression.getShaderExpression('', {}); diff --git a/Specs/Scene/FrustumCommandsSpec.js b/Specs/Scene/FrustumCommandsSpec.js index 57bc636a5a0b..e3f5a7eb9afe 100644 --- a/Specs/Scene/FrustumCommandsSpec.js +++ b/Specs/Scene/FrustumCommandsSpec.js @@ -1,10 +1,10 @@ /*global defineSuite*/ defineSuite([ - 'Scene/FrustumCommands', - 'Scene/Pass' + 'Renderer/Pass', + 'Scene/FrustumCommands' ], function( - FrustumCommands, - Pass) { + Pass, + FrustumCommands) { 'use strict'; it('constructs without arguments', function() { diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index a64916d714dc..4b46d3a7a17a 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -1,6 +1,5 @@ /*global defineSuite*/ defineSuite([ - 'Scene/GroundPrimitive', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', 'Core/destroyObject', @@ -13,13 +12,13 @@ defineSuite([ 'Core/Rectangle', 'Core/RectangleGeometry', 'Core/ShowGeometryInstanceAttribute', - 'Scene/Pass', + 'Renderer/Pass', + 'Scene/GroundPrimitive', 'Scene/PerInstanceColorAppearance', 'Scene/Primitive', 'Specs/createScene', 'Specs/pollToPromise' ], function( - GroundPrimitive, Color, ColorGeometryInstanceAttribute, destroyObject, @@ -33,6 +32,7 @@ defineSuite([ RectangleGeometry, ShowGeometryInstanceAttribute, Pass, + GroundPrimitive, PerInstanceColorAppearance, Primitive, createScene, diff --git a/Specs/Scene/ModelSpec.js b/Specs/Scene/ModelSpec.js index a3c9496858ac..4593dc89358a 100644 --- a/Specs/Scene/ModelSpec.js +++ b/Specs/Scene/ModelSpec.js @@ -24,11 +24,11 @@ defineSuite([ 'Core/PrimitiveType', 'Core/Transforms', 'Core/WebGLConstants', + 'Renderer/Pass', 'Renderer/RenderState', 'Renderer/ShaderSource', 'Scene/ColorBlendMode', 'Scene/HeightReference', - 'Scene/Pass', 'Scene/ModelAnimationLoop', 'Specs/createScene', 'Specs/pollToPromise', @@ -58,11 +58,11 @@ defineSuite([ PrimitiveType, Transforms, WebGLConstants, + Pass, RenderState, ShaderSource, ColorBlendMode, HeightReference, - Pass, ModelAnimationLoop, createScene, pollToPromise, diff --git a/Specs/Scene/MultifrustumSpec.js b/Specs/Scene/MultifrustumSpec.js index 7b0ff8a8f5c9..dfa07c5481fc 100644 --- a/Specs/Scene/MultifrustumSpec.js +++ b/Specs/Scene/MultifrustumSpec.js @@ -14,6 +14,7 @@ defineSuite([ 'Core/Matrix4', 'Renderer/BufferUsage', 'Renderer/DrawCommand', + 'Renderer/Pass', 'Renderer/RenderState', 'Renderer/Sampler', 'Renderer/ShaderProgram', @@ -22,7 +23,6 @@ defineSuite([ 'Renderer/VertexArray', 'Scene/BillboardCollection', 'Scene/BlendingState', - 'Scene/Pass', 'Scene/TextureAtlas', 'Specs/createScene', 'ThirdParty/when' @@ -41,6 +41,7 @@ defineSuite([ Matrix4, BufferUsage, DrawCommand, + Pass, RenderState, Sampler, ShaderProgram, @@ -49,7 +50,6 @@ defineSuite([ VertexArray, BillboardCollection, BlendingState, - Pass, TextureAtlas, createScene, when) { diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index a4729f3cb711..b18de90eb279 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -16,6 +16,7 @@ defineSuite([ 'Core/WebMercatorProjection', 'Renderer/DrawCommand', 'Renderer/Framebuffer', + 'Renderer/Pass', 'Renderer/PixelDatatype', 'Renderer/ShaderProgram', 'Renderer/Texture', @@ -23,7 +24,6 @@ defineSuite([ 'Scene/EllipsoidSurfaceAppearance', 'Scene/FrameState', 'Scene/Globe', - 'Scene/Pass', 'Scene/Primitive', 'Scene/PrimitiveCollection', 'Scene/Scene', @@ -51,6 +51,7 @@ defineSuite([ WebMercatorProjection, DrawCommand, Framebuffer, + Pass, PixelDatatype, ShaderProgram, Texture, @@ -58,7 +59,6 @@ defineSuite([ EllipsoidSurfaceAppearance, FrameState, Globe, - Pass, Primitive, PrimitiveCollection, Scene, diff --git a/Specs/Widgets/Geocoder/GeocoderSpec.js b/Specs/Widgets/Geocoder/GeocoderSpec.js index 484dfb350750..0b2bde8b24ad 100644 --- a/Specs/Widgets/Geocoder/GeocoderSpec.js +++ b/Specs/Widgets/Geocoder/GeocoderSpec.js @@ -1,13 +1,36 @@ /*global defineSuite*/ defineSuite([ 'Widgets/Geocoder/Geocoder', - 'Specs/createScene' + 'Core/Cartesian3', + 'Specs/createScene', + 'ThirdParty/when' ], function( Geocoder, - createScene) { + Cartesian3, + createScene, + when) { 'use strict'; var scene; + + var mockDestination = new Cartesian3(1.0, 2.0, 3.0); + var geocoderResults = [{ + displayName: 'a', + destination: mockDestination + }, { + displayName: 'b', + destination: mockDestination + }, { + displayName: 'c', + destination: mockDestination + }]; + + var customGeocoderOptions = { + autoComplete : true, + geocode : function (input) { + return when.resolve(geocoderResults); + } + }; beforeEach(function() { scene = createScene(); }); @@ -80,4 +103,33 @@ defineSuite([ }); }).toThrowDeveloperError(); }); + + it('automatic suggestions can be navigated by arrow up/down keys', function() { + var container = document.createElement('div'); + container.id = 'testContainer'; + document.body.appendChild(container); + var geocoder = new Geocoder({ + container : 'testContainer', + scene : scene, + geocoderServices : [customGeocoderOptions] + }); + var viewModel = geocoder._viewModel; + viewModel._searchText = 'some_text'; + viewModel._updateSearchSuggestions(viewModel); + + expect(viewModel._selectedSuggestion).toEqual(undefined); + viewModel._handleArrowDown(viewModel); + expect(viewModel._selectedSuggestion.displayName).toEqual('a'); + viewModel._handleArrowDown(viewModel); + viewModel._handleArrowDown(viewModel); + expect(viewModel._selectedSuggestion.displayName).toEqual('c'); + viewModel._handleArrowDown(viewModel); + expect(viewModel._selectedSuggestion.displayName).toEqual('a'); + viewModel._handleArrowDown(viewModel); + viewModel._handleArrowUp(viewModel); + expect(viewModel._selectedSuggestion.displayName).toEqual('a'); + viewModel._handleArrowUp(viewModel); + expect(viewModel._selectedSuggestion).toBeUndefined(); + }); + }, 'WebGL'); diff --git a/Specs/Widgets/Geocoder/GeocoderViewModelSpec.js b/Specs/Widgets/Geocoder/GeocoderViewModelSpec.js index 3df6917aad7d..442e7dd9ded2 100644 --- a/Specs/Widgets/Geocoder/GeocoderViewModelSpec.js +++ b/Specs/Widgets/Geocoder/GeocoderViewModelSpec.js @@ -4,16 +4,55 @@ defineSuite([ 'Core/Cartesian3', 'Scene/Camera', 'Specs/createScene', - 'Specs/pollToPromise' + 'Specs/pollToPromise', + 'ThirdParty/when' ], function( GeocoderViewModel, Cartesian3, Camera, createScene, - pollToPromise) { + pollToPromise, + when) { 'use strict'; var scene; + var mockDestination = new Cartesian3(1.0, 2.0, 3.0); + + var geocoderResults1 = [{ + displayName: 'a', + destination: mockDestination + }, { + displayName: 'b', + destination: mockDestination + }]; + var customGeocoderOptions = { + autoComplete: true, + geocode: function (input) { + return when.resolve(geocoderResults1); + } + }; + + var geocoderResults2 = [{ + displayName: '1', + destination: mockDestination + }, { + displayName: '2', + destination: mockDestination + }]; + var customGeocoderOptions2 = { + autoComplete: true, + geocode: function (input) { + return when.resolve(geocoderResults2); + } + }; + + var noResultsGeocoder = { + autoComplete: true, + geocode: function (input) { + return when.resolve([]); + } + }; + beforeAll(function() { scene = createScene(); }); @@ -55,7 +94,8 @@ defineSuite([ it('throws is searchText is not a string', function() { var viewModel = new GeocoderViewModel({ - scene : scene + scene : scene, + geocoderServices : [customGeocoderOptions] }); expect(function() { viewModel.searchText = undefined; @@ -64,7 +104,8 @@ defineSuite([ it('moves camera when search command invoked', function() { var viewModel = new GeocoderViewModel({ - scene : scene + scene : scene, + geocoderServices : [customGeocoderOptions] }); var cameraPosition = Cartesian3.clone(scene.camera.position); @@ -78,27 +119,6 @@ defineSuite([ }); }); - it('Zooms to longitude, latitude, height', function() { - var viewModel = new GeocoderViewModel({ - scene : scene - }); - - spyOn(Camera.prototype, 'flyTo'); - - viewModel.searchText = ' 1.0, 2.0, 3.0 '; - viewModel.search(); - expect(Camera.prototype.flyTo).toHaveBeenCalled(); - expect(Camera.prototype.flyTo.calls.mostRecent().args[0].destination).toEqual(Cartesian3.fromDegrees(1.0, 2.0, 3.0)); - - viewModel.searchText = '1.0 2.0 3.0'; - viewModel.search(); - expect(Camera.prototype.flyTo.calls.mostRecent().args[0].destination).toEqual(Cartesian3.fromDegrees(1.0, 2.0, 3.0)); - - viewModel.searchText = '-1.0, -2.0'; - viewModel.search(); - expect(Camera.prototype.flyTo.calls.mostRecent().args[0].destination).toEqual(Cartesian3.fromDegrees(-1.0, -2.0, 300.0)); - }); - it('constructor throws without scene', function() { expect(function() { return new GeocoderViewModel(); @@ -108,7 +128,8 @@ defineSuite([ it('raises the complete event camera finished', function() { var viewModel = new GeocoderViewModel({ scene : scene, - flightDuration : 0 + flightDuration : 0, + geocoderServices : [customGeocoderOptions] }); var spyListener = jasmine.createSpy('listener'); @@ -120,7 +141,7 @@ defineSuite([ expect(spyListener.calls.count()).toBe(1); viewModel.flightDuration = 1.5; - viewModel.serachText = '2.0, 2.0'; + viewModel.searchText = '2.0, 2.0'; viewModel.search(); return pollToPromise(function() { @@ -128,4 +149,73 @@ defineSuite([ return spyListener.calls.count() === 2; }); }); + + it('can be created with a custom geocoder', function() { + expect(function() { + return new GeocoderViewModel({ + scene : scene, + geocoderServices : [customGeocoderOptions] + }); + }).not.toThrowDeveloperError(); + }); + + it('automatic suggestions can be retrieved', function() { + var geocoder = new GeocoderViewModel({ + scene : scene, + geocoderServices : [customGeocoderOptions] + }); + geocoder._searchText = 'some_text'; + geocoder._updateSearchSuggestions(geocoder); + expect(geocoder._suggestions.length).toEqual(2); + }); + + it('update search suggestions results in empty list if the query is empty', function() { + var geocoder = new GeocoderViewModel({ + scene : scene, + geocoderServices : [customGeocoderOptions] + }); + geocoder._searchText = ''; + spyOn(geocoder, '_adjustSuggestionsScroll'); + geocoder._updateSearchSuggestions(geocoder); + expect(geocoder._suggestions.length).toEqual(0); + }); + + it('can activate selected search suggestion', function () { + var geocoder = new GeocoderViewModel({ + scene : scene, + geocoderServices : [customGeocoderOptions] + }); + spyOn(geocoder, '_updateCamera'); + spyOn(geocoder, '_adjustSuggestionsScroll'); + + var suggestion = {displayName: 'a', destination: {west: 0.0, east: 0.1, north: 0.1, south: -0.1}}; + geocoder._selectedSuggestion = suggestion; + geocoder.activateSuggestion(suggestion); + expect(geocoder._searchText).toEqual('a'); + }); + + it('if more than one geocoder service is provided, use first result from first geocode in array order', function () { + var geocoder = new GeocoderViewModel({ + scene : scene, + geocoderServices : [noResultsGeocoder, customGeocoderOptions2] + }); + geocoder._searchText = 'sthsnth'; // an empty query will prevent geocoding + spyOn(geocoder, '_updateCamera'); + spyOn(geocoder, '_adjustSuggestionsScroll'); + geocoder.search(); + expect(geocoder._searchText).toEqual(geocoderResults2[0].displayName); + }); + + it('can update autoComplete suggestions list using multiple geocoders', function () { + var geocoder = new GeocoderViewModel({ + scene : scene, + geocoderServices : [customGeocoderOptions, customGeocoderOptions2] + }); + geocoder._searchText = 'sthsnth'; // an empty query will prevent geocoding + spyOn(geocoder, '_updateCamera'); + spyOn(geocoder, '_adjustSuggestionsScroll'); + geocoder._updateSearchSuggestions(geocoder); + expect(geocoder._suggestions.length).toEqual(geocoderResults1.length + geocoderResults2.length); + }); + }, 'WebGL'); diff --git a/Specs/pick.js b/Specs/pick.js index 409a28f8549f..711798051a7d 100644 --- a/Specs/pick.js +++ b/Specs/pick.js @@ -4,19 +4,19 @@ define([ 'Core/Color', 'Core/defined', 'Renderer/ClearCommand', + 'Renderer/Pass', 'Scene/CreditDisplay', 'Scene/FrameState', - 'Scene/JobScheduler', - 'Scene/Pass' + 'Scene/JobScheduler' ], function( BoundingRectangle, Color, defined, ClearCommand, + Pass, CreditDisplay, FrameState, - JobScheduler, - Pass) { + JobScheduler) { 'use strict'; function executeCommands(context, passState, commands) { diff --git a/Specs/render.js b/Specs/render.js index ed2caf3d0346..d239ed626176 100644 --- a/Specs/render.js +++ b/Specs/render.js @@ -2,7 +2,7 @@ define([ 'Core/defined', 'Core/Intersect', - 'Scene/Pass', + 'Renderer/Pass', 'Scene/SceneMode' ], function( defined, diff --git a/package.json b/package.json index 1bce15dbaa92..c5df42eebcee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cesium", - "version": "1.28.0", + "version": "1.29.0", "description": "Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "homepage": "http://cesiumjs.org", "license": "Apache-2.0",