diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html new file mode 100644 index 000000000000..0daead9e19d1 --- /dev/null +++ b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html @@ -0,0 +1,68 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.jpg b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.jpg new file mode 100644 index 000000000000..0cae73fff0af Binary files /dev/null and b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.jpg differ diff --git a/Apps/Sandcastle/gallery/Picking.html b/Apps/Sandcastle/gallery/Picking.html index a976f27363ac..e4de902d2e0e 100644 --- a/Apps/Sandcastle/gallery/Picking.html +++ b/Apps/Sandcastle/gallery/Picking.html @@ -51,7 +51,7 @@ handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); handler.setInputAction(function(movement) { var cartesian = viewer.camera.pickEllipsoid(movement.endPosition, scene.globe.ellipsoid); - if (cartesian) { + if (Cesium.defined(cartesian)) { var cartographic = Cesium.Cartographic.fromCartesian(cartesian); var longitudeString = Cesium.Math.toDegrees(cartographic.longitude).toFixed(2); var latitudeString = Cesium.Math.toDegrees(cartographic.latitude).toFixed(2); diff --git a/CHANGES.md b/CHANGES.md index ada7ebfa448b..0f1df98d3ec4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -43,6 +43,7 @@ Change Log * Added new `Ion`, `IonResource`, and `IonImageryProvider` objects for loading data hosted on [Cesium ion](https://cesium.com/blog/2018/03/01/hello-cesium-ion/). * Added `createWorldTerrain` helper function for easily constructing the new Cesium World Terrain. * Added support for a promise to a resource for `CesiumTerrainProvider`, `createTileMapServiceImageryProvider` and `Cesium3DTileset` [#6204](https://github.com/AnalyticalGraphicsInc/cesium/pull/6204) +* Added `MinimumTerrainHeightProperty`, which can be used for extruding geometry down to terrain [#6214](https://github.com/AnalyticalGraphicsInc/cesium/pull/6214) * Added `Cesium.Math.cbrt`. [#6222](https://github.com/AnalyticalGraphicsInc/cesium/pull/6222) * Added `PolylineVisualizer` for displaying polyline entities [#6239](https://github.com/AnalyticalGraphicsInc/cesium/pull/6239) * `Resource` class [#6205](https://github.com/AnalyticalGraphicsInc/cesium/issues/6205) diff --git a/Source/Core/ApproximateTerrainHeights.js b/Source/Core/ApproximateTerrainHeights.js new file mode 100644 index 000000000000..c083814469b0 --- /dev/null +++ b/Source/Core/ApproximateTerrainHeights.js @@ -0,0 +1,203 @@ +define([ + './buildModuleUrl', + './defaultValue', + './defined', + './BoundingSphere', + './Cartesian2', + './Cartesian3', + './Cartographic', + './Check', + './DeveloperError', + './Ellipsoid', + './GeographicTilingScheme', + './Rectangle', + './Resource' +], function( + buildModuleUrl, + defaultValue, + defined, + BoundingSphere, + Cartesian2, + Cartesian3, + Cartographic, + Check, + DeveloperError, + Ellipsoid, + GeographicTilingScheme, + Rectangle, + Resource) { + 'use strict'; + + var scratchDiagonalCartesianNE = new Cartesian3(); + var scratchDiagonalCartesianSW = new Cartesian3(); + var scratchDiagonalCartographic = new Cartographic(); + var scratchCenterCartesian = new Cartesian3(); + var scratchSurfaceCartesian = new Cartesian3(); + + var scratchBoundingSphere = new BoundingSphere(); + var tilingScheme = new GeographicTilingScheme(); + var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; + var scratchTileXY = new Cartesian2(); + + /** + * A collection of functions for approximating terrain height + * @private + */ + var ApproximateTerrainHeights = {}; + + /** + * Initializes the minimum and maximum terrain heights + * @return {Promise} + */ + ApproximateTerrainHeights.initialize = function() { + var initPromise = ApproximateTerrainHeights._initPromise; + if (defined(initPromise)) { + return initPromise; + } + + ApproximateTerrainHeights._initPromise = Resource.fetchJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) { + ApproximateTerrainHeights._terrainHeights = json; + }); + + return ApproximateTerrainHeights._initPromise; + }; + + /** + * Computes the minimum and maximum terrain heights for a given rectangle + * @param {Rectangle} rectangle THe bounding rectangle + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid + * @return {{minimumTerrainHeight: Number, maximumTerrainHeight: Number}} + */ + ApproximateTerrainHeights.getApproximateTerrainHeights = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.defined('rectangle', rectangle); + if (!defined(ApproximateTerrainHeights._terrainHeights)) { + throw new DeveloperError('You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function'); + } + //>>includeEnd('debug'); + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + + var xyLevel = getTileXYLevel(rectangle); + + // Get the terrain min/max for that tile + var minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; + var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + if (defined(xyLevel)) { + var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; + var heights = ApproximateTerrainHeights._terrainHeights[key]; + if (defined(heights)) { + minTerrainHeight = heights[0]; + maxTerrainHeight = heights[1]; + } + + // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface + ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle, scratchDiagonalCartographic), + scratchDiagonalCartesianNE); + ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle, scratchDiagonalCartographic), + scratchDiagonalCartesianSW); + + Cartesian3.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian); + Cartesian3.add(scratchDiagonalCartesianNE, + Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian); + var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian); + if (defined(surfacePosition)) { + var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition); + minTerrainHeight = Math.min(minTerrainHeight, -distance); + } else { + minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; + } + } + + minTerrainHeight = Math.max(ApproximateTerrainHeights._defaultMinTerrainHeight, minTerrainHeight); + + return { + minimumTerrainHeight: minTerrainHeight, + maximumTerrainHeight: maxTerrainHeight + }; + }; + + /** + * Computes the bounding sphere based on the tile heights in the rectangle + * @param {Rectangle} rectangle The bounding rectangle + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid + * @return {BoundingSphere} The result bounding sphere + */ + ApproximateTerrainHeights.getInstanceBoundingSphere = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.defined('rectangle', rectangle); + if (!defined(ApproximateTerrainHeights._terrainHeights)) { + throw new DeveloperError('You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function'); + } + //>>includeEnd('debug'); + ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); + + var xyLevel = getTileXYLevel(rectangle); + + // Get the terrain max for that tile + var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + if (defined(xyLevel)) { + var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; + var heights = ApproximateTerrainHeights._terrainHeights[key]; + if (defined(heights)) { + maxTerrainHeight = heights[1]; + } + } + + var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0); + BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere); + + return BoundingSphere.union(result, scratchBoundingSphere, result); + }; + + function getTileXYLevel(rectangle) { + Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]); + Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]); + Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]); + Cartographic.fromRadians(rectangle.west, rectangle.south, 0.0, scratchCorners[3]); + + // Determine which tile the bounding rectangle is in + var lastLevelX = 0, lastLevelY = 0; + var currentX = 0, currentY = 0; + var maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel; + var i; + for(i = 0; i <= maxLevel; ++i) { + var failed = false; + for(var j = 0; j < 4; ++j) { + var corner = scratchCorners[j]; + tilingScheme.positionToTileXY(corner, i, scratchTileXY); + if (j === 0) { + currentX = scratchTileXY.x; + currentY = scratchTileXY.y; + } else if(currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) { + failed = true; + break; + } + } + + if (failed) { + break; + } + + lastLevelX = currentX; + lastLevelY = currentY; + } + + if (i === 0) { + return undefined; + } + + return { + x : lastLevelX, + y : lastLevelY, + level : (i > maxLevel) ? maxLevel : (i - 1) + }; + } + + ApproximateTerrainHeights._terrainHeightsMaxLevel = 6; + ApproximateTerrainHeights._defaultMaxTerrainHeight = 9000.0; + ApproximateTerrainHeights._defaultMinTerrainHeight = -100000.0; + ApproximateTerrainHeights._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + + return ApproximateTerrainHeights; +}); diff --git a/Source/DataSources/CentroidPositionProperty.js b/Source/DataSources/CentroidPositionProperty.js new file mode 100644 index 000000000000..6eac6f063818 --- /dev/null +++ b/Source/DataSources/CentroidPositionProperty.js @@ -0,0 +1,151 @@ +define([ + '../Core/Cartesian3', + '../Core/Check', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/ReferenceFrame', + './createPropertyDescriptor', + './PositionProperty', + './Property' +], function( + Cartesian3, + Check, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + ReferenceFrame, + createPropertyDescriptor, + PositionProperty, + Property) { + 'use strict'; + + function computeCentroid(positions, result) { + var centroid = Cartesian3.clone(Cartesian3.ZERO, result); + var length = positions.length; + for (var i = 0; i < length; i++) { + centroid = Cartesian3.add(positions[i], centroid, centroid); + } + return Cartesian3.multiplyByScalar(centroid, 1 / length, centroid); + } + + /** + * A {@link PositionProperty} whose value is the centroid of the given list of positions + * + * @alias CentroidPositionProperty + * @constructor + * + * @param {Property} [positions] The property value that resolves to an array of Cartesian3 positions. + */ + function CentroidPositionProperty(positions) { + this._definitionChanged = new Event(); + this._positions = undefined; + this._positionsSubscription = undefined; + this._referenceFrame = ReferenceFrame.FIXED; + + this.positions = positions; + } + + defineProperties(CentroidPositionProperty.prototype, { + /** + * Gets a value indicating if this property is constant. A property is considered + * constant if getValue always returns the same result for the current definition. + * @memberof CentroidPositionProperty.prototype + * + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return Property.isConstant(this._positions); + } + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * The definition is considered to have changed if a call to getValue would return + * a different result for the same time. + * @memberof CentroidPositionProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets the reference frame in which the position is defined. + * @memberof CentroidPositionProperty.prototype + * @type {ReferenceFrame} + * @default ReferenceFrame.FIXED; + */ + referenceFrame : { + get : function() { + return this._referenceFrame; + } + }, + /** + * Gets or sets the positions property used to compute the value. + * @memberof CentroidPositionProperty.prototype + * + * @type {Property} + */ + positions : createPropertyDescriptor('positions') + }); + + /** + * Gets the value of the property at the provided time in the fixed frame. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied. + */ + CentroidPositionProperty.prototype.getValue = function(time, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('time', time); + //>>includeEnd('debug'); + + var positions = Property.getValueOrUndefined(this._positions, time); + if (!defined(positions)) { + return; + } + return computeCentroid(positions, result); + }; + + /** + * Gets the value of the property at the provided time and in the provided reference frame. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result. + * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied. + */ + CentroidPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('time', time); + Check.defined('referenceFrame', referenceFrame); + //>>includeEnd('debug'); + var value = this.getValue(time, result); + return PositionProperty.convertToReferenceFrame(time, value, this._referenceFrame, referenceFrame, result); + }; + + /** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + CentroidPositionProperty.prototype.equals = function(other) { + return this === other || + (other instanceof CentroidPositionProperty && + Cartesian3.equals(this._positions, other._positions)); + }; + + return CentroidPositionProperty; +}); diff --git a/Source/DataSources/MinimumTerrainHeightProperty.js b/Source/DataSources/MinimumTerrainHeightProperty.js new file mode 100644 index 000000000000..bc17d8b61562 --- /dev/null +++ b/Source/DataSources/MinimumTerrainHeightProperty.js @@ -0,0 +1,120 @@ +define([ + '../Core/ApproximateTerrainHeights', + '../Core/defined', + '../Core/defineProperties', + '../Core/isArray', + '../Core/Check', + '../Core/Event', + '../Core/Rectangle', + './createPropertyDescriptor', + './Property' +], function( + ApproximateTerrainHeights, + defined, + defineProperties, + isArray, + Check, + Event, + Rectangle, + createPropertyDescriptor, + Property) { + 'use strict'; + + var scratchRectangle = new Rectangle(); + + /** + * A {@link Property} which evaluates to a Number based on the minimum height of terrain + * within the bounds of the provided positions. + * + * @alias MinimumTerrainHeightProperty + * @constructor + * + * @param {Property} [positions] A Property specifying an array of {@link Cartesian3} positions. + * + * @example + * var polygonPositions = new Cesium.ConstantProperty(polygonPositions); + * var redPolygon = viewer.entities.add({ + * polygon : { + * hierarchy : polygonPositions, + * material : Cesium.Color.RED, + * height : 1800.0, + * extrudedHeight : new Cesium.MinimumTerrainHeightProperty(polygonPositions) + * } + * }); + */ + function MinimumTerrainHeightProperty(positions) { + this._positions = undefined; + this._positionsSubscription = undefined; + this._definitionChanged = new Event(); + + this.positions = positions; + } + + defineProperties(MinimumTerrainHeightProperty.prototype, { + /** + * Gets a value indicating if this property is constant. + * @memberof MinimumTerrainHeightProperty.prototype + * + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return Property.isConstant(this._positions); + } + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * @memberof MinimumTerrainHeightProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets or sets the positions property used to compute the value. + * @memberof MinimumTerrainHeightProperty.prototype + * + * @type {Property} + */ + positions : createPropertyDescriptor('positions') + }); + + /** + * Gets the minimum terrain height based on the positions at the provided time. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @returns {Number} The minimum terrain height + */ + MinimumTerrainHeightProperty.prototype.getValue = function(time) { + //>>includeStart('debug', pragmas.debug); + Check.defined('time', time); + //>>includeEnd('debug'); + + var positions = Property.getValueOrUndefined(this._positions, time); + if (!defined(positions)) { + return; + } + var rectangle = Rectangle.fromCartesianArray(positions, undefined, scratchRectangle); + return ApproximateTerrainHeights.getApproximateTerrainHeights(rectangle).minimumTerrainHeight; + }; + + /** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + MinimumTerrainHeightProperty.prototype.equals = function(other) { + return this === other ||// + (other instanceof MinimumTerrainHeightProperty && + Property.equals(this._positions, other._positions)); + }; + + return MinimumTerrainHeightProperty; +}); diff --git a/Source/DataSources/PositionProperty.js b/Source/DataSources/PositionProperty.js index af5b00665e19..bffeacf488c0 100644 --- a/Source/DataSources/PositionProperty.js +++ b/Source/DataSources/PositionProperty.js @@ -105,7 +105,7 @@ define([ */ PositionProperty.convertToReferenceFrame = function(time, value, inputFrame, outputFrame, result) { if (!defined(value)) { - return value; + return; } if (!defined(result)){ result = new Cartesian3(); diff --git a/Source/DataSources/RelativeToTerrainHeightProperty.js b/Source/DataSources/RelativeToTerrainHeightProperty.js new file mode 100644 index 000000000000..e1b538079602 --- /dev/null +++ b/Source/DataSources/RelativeToTerrainHeightProperty.js @@ -0,0 +1,192 @@ +define([ + '../Core/ApproximateTerrainHeights', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/isArray', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/Check', + '../Core/Event', + '../Core/Iso8601', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/sampleTerrainMostDetailed', + './createPropertyDescriptor', + './Property' +], function( + ApproximateTerrainHeights, + defaultValue, + defined, + defineProperties, + isArray, + Cartesian3, + Cartographic, + Check, + Event, + Iso8601, + Rectangle, + RuntimeError, + sampleTerrainMostDetailed, + createPropertyDescriptor, + Property) { + 'use strict'; + + /** + * A {@link Property} which evaluates to a Number based on the height of terrain + * within the bounds of the provided positions. + * + * @alias RelativeToTerrainHeightProperty + * @constructor + * + * @param {TerrainProvider} terrainProvider The terrain provider used on the globe + * @param {PositionProperty} position A Property specifying the position the height should be relative to. + * @param {Property} [heightRelativeToTerrain] A Property specifying the numeric height value relative to terrain + * + * @example + * var hierarchy = new Cesium.ConstantProperty(polygonPositions); + * var redPolygon = viewer.entities.add({ + * ellipse : { + * hierarchy : hierarchy, + * material : Cesium.Color.RED, + * height : new Cesium.RelativeToTerrainHeightProperty(viewer.terrainProvider, positions, 11.0) + * } + * }); + */ + function RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain) { + //>>includeStart('debug', pragmas.debug); + Check.defined('terrainProvider', terrainProvider); + //>>includeEnd('debug'); + + this._position = undefined; + this._subscription = undefined; + this._heightRelativeToTerrain = undefined; + this._heightRelativeToTerrainSubscription = undefined; + this._definitionChanged = new Event(); + + this._terrainProvider = terrainProvider; + this._cartographicPosition = new Cartographic(); + this._terrainHeight = 0; + this._pending = false; + + this.position = position; + this.heightRelativeToTerrain = heightRelativeToTerrain; + } + + defineProperties(RelativeToTerrainHeightProperty.prototype, { + /** + * Gets a value indicating if this property is constant. + * @memberof RelativeToTerrainHeightProperty.prototype + * + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return Property.isConstant(this._positions); + } + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * @memberof RelativeToTerrainHeightProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets or sets the position property used to compute the value. + * @memberof RelativeToTerrainHeightProperty.prototype + * + * @type {PositionProperty} + */ + position : { + get : function() { + return this._position; + }, + set : function(value) { + var oldValue = this._positions; + if (oldValue !== value) { + if (defined(oldValue)) { + this._subscription(); + } + + this._position = value; + + if (defined(value)) { + this._subscription = value._definitionChanged.addEventListener(function() { + this._fetchTerrainHeight(); + }, this); + } + + this._fetchTerrainHeight(); + } + } + }, + heightRelativeToTerrain : createPropertyDescriptor('heightRelativeToTerrain') + }); + + /** + * @private + */ + RelativeToTerrainHeightProperty.prototype._fetchTerrainHeight = function() { + if (this._pending) { + return; + } + this._terrainHeight = 0; + var property = this._position; + var position = Property.getValueOrUndefined(property, Iso8601.MINIMUM_VALUE); + if (!defined(position)) { + return; + } + + var carto = Cartographic.fromCartesian(position, undefined, this._cartographicPosition); + carto.height = 0.0; + var that = this; + this._pending = true; + RelativeToTerrainHeightProperty._sampleTerrainMostDetailed(this._terrainProvider, [carto]) + .then(function(results) { + if (that._position !== property || !Cartesian3.equals(position, Property.getValueOrUndefined(that._position, Iso8601.MINIMUM_VALUE))) { + return; + } + that._terrainHeight = defaultValue(results[0].height, 0); + that._definitionChanged.raiseEvent(that); + }) + .always(function() { + that._pending = false; + }); + }; + + /** + * Gets the height relative to the terrain based on the positions. + * + * @returns {Number} The height relative to terrain + */ + RelativeToTerrainHeightProperty.prototype.getValue = function(time) { + return this._terrainHeight + Property.getValueOrDefault(this.heightRelativeToTerrain, time, 0); + }; + + /** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + RelativeToTerrainHeightProperty.prototype.equals = function(other) { + return this === other ||// + (other instanceof RelativeToTerrainHeightProperty && + this._terrainProvider === other._terrainProvider && + Property.equals(this._position, other._position) && + Property.equals(this.heightRelativeToTerrain, other.heightRelativeToTerrain)); + }; + + //for specs + RelativeToTerrainHeightProperty._sampleTerrainMostDetailed = sampleTerrainMostDetailed; + + return RelativeToTerrainHeightProperty; +}); diff --git a/Source/DataSources/VelocityOrientationProperty.js b/Source/DataSources/VelocityOrientationProperty.js index 46c11685fc6b..842777d63ff2 100644 --- a/Source/DataSources/VelocityOrientationProperty.js +++ b/Source/DataSources/VelocityOrientationProperty.js @@ -123,7 +123,7 @@ define([ /** * Gets the value of the property at the provided time. * - * @param {JulianDate} [time] The time for which to retrieve the value. + * @param {JulianDate} time The time for which to retrieve the value. * @param {Quaternion} [result] The object to store the value into, if omitted, a new instance is created and returned. * @returns {Quaternion} The modified result parameter or a new instance if the result parameter was not supplied. */ diff --git a/Source/DataSources/VelocityVectorProperty.js b/Source/DataSources/VelocityVectorProperty.js index 96a6e14f9535..c1ef5fb81eaa 100644 --- a/Source/DataSources/VelocityVectorProperty.js +++ b/Source/DataSources/VelocityVectorProperty.js @@ -133,7 +133,7 @@ define([ /** * Gets the value of the property at the provided time. * - * @param {JulianDate} [time] The time for which to retrieve the value. + * @param {JulianDate} time The time for which to retrieve the value. * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned. * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied. */ diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index c8a68f040822..40f94b847938 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -1,4 +1,5 @@ define([ + '../Core/ApproximateTerrainHeights', '../Core/BoundingSphere', '../Core/buildModuleUrl', '../Core/Cartesian2', @@ -22,6 +23,7 @@ define([ './ClassificationType', './SceneMode' ], function( + ApproximateTerrainHeights, BoundingSphere, buildModuleUrl, Cartesian2, @@ -207,8 +209,8 @@ define([ this._maxHeight = undefined; this._minHeight = undefined; - this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; + this._maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + this._minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; this._boundingSpheresKeys = []; this._boundingSpheres = []; @@ -364,12 +366,6 @@ define([ */ GroundPrimitive.isSupported = ClassificationPrimitive.isSupported; - GroundPrimitive._defaultMaxTerrainHeight = 9000.0; - GroundPrimitive._defaultMinTerrainHeight = -100000.0; - - GroundPrimitive._terrainHeights = undefined; - GroundPrimitive._terrainHeightsMaxLevel = 6; - function getComputeMaximumHeightFunction(primitive) { return function(granularity, ellipsoid) { var r = ellipsoid.maximumRadius; @@ -389,9 +385,6 @@ define([ var scratchBVCartesian = new Cartesian3(); var scratchBVCartographic = new Cartographic(); var scratchBVRectangle = new Rectangle(); - var tilingScheme = new GeographicTilingScheme(); - var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; - var scratchTileXY = new Cartesian2(); function getRectangle(frameState, geometry) { var ellipsoid = frameState.mapProjection.ellipsoid; @@ -438,110 +431,11 @@ define([ return rectangle; } - var scratchDiagonalCartesianNE = new Cartesian3(); - var scratchDiagonalCartesianSW = new Cartesian3(); - var scratchDiagonalCartographic = new Cartographic(); - var scratchCenterCartesian = new Cartesian3(); - var scratchSurfaceCartesian = new Cartesian3(); - - function getTileXYLevel(rectangle) { - Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]); - Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]); - Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]); - Cartographic.fromRadians(rectangle.west, rectangle.south, 0.0, scratchCorners[3]); - - // Determine which tile the bounding rectangle is in - var lastLevelX = 0, lastLevelY = 0; - var currentX = 0, currentY = 0; - var maxLevel = GroundPrimitive._terrainHeightsMaxLevel; - var i; - for(i = 0; i <= maxLevel; ++i) { - var failed = false; - for(var j = 0; j < 4; ++j) { - var corner = scratchCorners[j]; - tilingScheme.positionToTileXY(corner, i, scratchTileXY); - if (j === 0) { - currentX = scratchTileXY.x; - currentY = scratchTileXY.y; - } else if(currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) { - failed = true; - break; - } - } - - if (failed) { - break; - } - - lastLevelX = currentX; - lastLevelY = currentY; - } - - if (i === 0) { - return undefined; - } - - return { - x : lastLevelX, - y : lastLevelY, - level : (i > maxLevel) ? maxLevel : (i - 1) - }; - } - function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) { - var xyLevel = getTileXYLevel(rectangle); - - // Get the terrain min/max for that tile - var minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; - var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - if (defined(xyLevel)) { - var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; - var heights = GroundPrimitive._terrainHeights[key]; - if (defined(heights)) { - minTerrainHeight = heights[0]; - maxTerrainHeight = heights[1]; - } + var result = ApproximateTerrainHeights.getApproximateTerrainHeights(rectangle, ellipsoid); - // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface - ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle, scratchDiagonalCartographic), - scratchDiagonalCartesianNE); - ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle, scratchDiagonalCartographic), - scratchDiagonalCartesianSW); - - Cartesian3.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian); - Cartesian3.add(scratchDiagonalCartesianNE, - Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian); - var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian); - if (defined(surfacePosition)) { - var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition); - minTerrainHeight = Math.min(minTerrainHeight, -distance); - } else { - minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; - } - } - - primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight); - primitive._maxTerrainHeight = maxTerrainHeight; - } - - var scratchBoundingSphere = new BoundingSphere(); - function getInstanceBoundingSphere(rectangle, ellipsoid) { - var xyLevel = getTileXYLevel(rectangle); - - // Get the terrain max for that tile - var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - if (defined(xyLevel)) { - var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; - var heights = GroundPrimitive._terrainHeights[key]; - if (defined(heights)) { - maxTerrainHeight = heights[1]; - } - } - - var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0); - BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere); - - return BoundingSphere.union(result, scratchBoundingSphere, result); + primitive._minTerrainHeight = result.minimumTerrainHeight; + primitive._maxTerrainHeight = result.maximumTerrainHeight; } function createBoundingVolume(groundPrimitive, frameState, geometry) { @@ -663,10 +557,10 @@ define([ return initPromise; } - GroundPrimitive._initPromise = Resource.fetchJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) { - GroundPrimitive._initialized = true; - GroundPrimitive._terrainHeights = json; - }); + GroundPrimitive._initPromise = ApproximateTerrainHeights.initialize() + .then(function() { + GroundPrimitive._initialized = true; + }); return GroundPrimitive._initPromise; }; @@ -727,7 +621,7 @@ define([ var id = instance.id; if (defined(id) && defined(instanceRectangle)) { - var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid); + var boundingSphere = ApproximateTerrainHeights.getInstanceBoundingSphere(instanceRectangle, ellipsoid); this._boundingSpheresKeys.push(id); this._boundingSpheres.push(boundingSphere); } diff --git a/Specs/Core/ApproximateTerrainHeightsSpec.js b/Specs/Core/ApproximateTerrainHeightsSpec.js new file mode 100644 index 000000000000..df85d432a348 --- /dev/null +++ b/Specs/Core/ApproximateTerrainHeightsSpec.js @@ -0,0 +1,70 @@ +defineSuite([ + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/Math', + 'Core/Rectangle' +], function( + ApproximateTerrainHeights, + Cartesian3, + CesiumMath, + Rectangle) { + 'use strict'; + + beforeAll(function() { + return ApproximateTerrainHeights.initialize(); + }); + + afterAll(function() { + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + }); + + it('initializes', function() { + return ApproximateTerrainHeights.initialize() + .then(function() { + expect(ApproximateTerrainHeights._terrainHeights).toBeDefined(); + }); + }); + + it('getApproximateTerrainHeights computes minimum and maximum terrain heights', function() { + var result = ApproximateTerrainHeights.getApproximateTerrainHeights(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + expect(result.minimumTerrainHeight).toEqualEpsilon(-476.125711887558, CesiumMath.EPSILON10); + expect(result.maximumTerrainHeight).toEqualEpsilon(-28.53441619873047, CesiumMath.EPSILON10); + }); + + it('getApproximateTerrainHeights throws with no rectangle', function() { + expect(function() { + return ApproximateTerrainHeights.getApproximateTerrainHeights(); + }).toThrowDeveloperError(); + }); + + it('getApproximateTerrainHeights throws if ApproximateTerrainHeights was not initialized first', function() { + var heights = ApproximateTerrainHeights._terrainHeights; + ApproximateTerrainHeights._terrainHeights = undefined; + expect(function() { + return ApproximateTerrainHeights.getApproximateTerrainHeights(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + }); + ApproximateTerrainHeights._terrainHeights = heights; + }); + + it('getInstanceBoundingSphere computes a bounding sphere', function() { + var result = ApproximateTerrainHeights.getInstanceBoundingSphere(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + expect(result.center).toEqualEpsilon(new Cartesian3(-3183013.8480289434, -5403772.557261968, 1154581.5817616477), CesiumMath.EPSILON10); + expect(result.radius).toEqualEpsilon(77884.16539096291, CesiumMath.EPSILON10); + }); + + it('getInstanceBoundingSphere throws with no rectangle', function() { + expect(function() { + return ApproximateTerrainHeights.getInstanceBoundingSphere(); + }).toThrowDeveloperError(); + }); + + it('getInstanceBoundingSphere throws if ApproximateTerrainHeights was not initialized first', function() { + var heights = ApproximateTerrainHeights._terrainHeights; + ApproximateTerrainHeights._terrainHeights = undefined; + expect(function() { + return ApproximateTerrainHeights.getInstanceBoundingSphere(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + }); + ApproximateTerrainHeights._terrainHeights = heights; + }); +}); diff --git a/Specs/DataSources/CentroidPositionPropertySpec.js b/Specs/DataSources/CentroidPositionPropertySpec.js new file mode 100644 index 000000000000..f8623d9bb010 --- /dev/null +++ b/Specs/DataSources/CentroidPositionPropertySpec.js @@ -0,0 +1,99 @@ +defineSuite([ + 'DataSources/CentroidPositionProperty', + 'Core/Cartesian3', + 'Core/JulianDate', + 'Core/ReferenceFrame', + 'DataSources/ConstantProperty', + 'DataSources/PositionProperty' +], function( + CentroidPositionProperty, + Cartesian3, + JulianDate, + ReferenceFrame, + ConstantProperty, + PositionProperty) { + 'use strict'; + + var time = JulianDate.now(); + + it('Constructor sets expected defaults', function() { + var positions = new ConstantProperty(Cartesian3.fromDegreesArray([0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0])); + var property = new CentroidPositionProperty(positions); + expect(property.referenceFrame).toBe(ReferenceFrame.FIXED); + expect(property.positions).toBe(positions); + }); + + it('getValue works without a result parameter', function() { + var positions = new ConstantProperty(new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)); + var property = new CentroidPositionProperty(positions); + + var result = property.getValue(time); + expect(result).toEqual(new Cartesian3(0.5, 0.5, 0.0)); + }); + + it('getValue works with a result parameter', function() { + var positions = new ConstantProperty([new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)]); + var property = new CentroidPositionProperty(positions); + + var expected = new Cartesian3(); + var result = property.getValue(time, expected); + expect(result).toBe(expected); + expect(expected).toEqual(new Cartesian3(0.5, 0.5, 0.0)); + }); + + it('getValueInReferenceFrame works without a result parameter', function() { + var positions = new ConstantProperty([new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)]); + var property = new CentroidPositionProperty(positions); + + var result = property.getValueInReferenceFrame(time, ReferenceFrame.INERTIAL); + expect(result).toEqual(PositionProperty.convertToReferenceFrame(time, new Cartesian3(0.5, 0.5, 0.0), ReferenceFrame.FIXED, ReferenceFrame.INERTIAL)); + }); + + it('getValueInReferenceFrame works with a result parameter', function() { + var positions = new ConstantProperty([new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)]); + var property = new CentroidPositionProperty(positions); + + var expected = new Cartesian3(); + var result = property.getValueInReferenceFrame(time, ReferenceFrame.FIXED, expected); + expect(result).toBe(expected); + expect(expected).toEqual(PositionProperty.convertToReferenceFrame(time, new Cartesian3(0.5, 0.5, 0.0), ReferenceFrame.INERTIAL, ReferenceFrame.FIXED)); + }); + + it('equals works', function() { + var left = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + var right = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + expect(left.equals(right)).toEqual(true); + + right = new CentroidPositionProperty([new Cartesian3(1, 2, 4)]); + expect(left.equals(right)).toEqual(false); + }); + + it('getValue throws without time parameter', function() { + var property = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + expect(function() { + property.getValue(undefined); + }).toThrowDeveloperError(); + }); + + it('getValueInReferenceFrame throws with no referenceFrame parameter', function() { + var property = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + expect(function() { + property.getValueInReferenceFrame(time, undefined); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/DataSources/DataSourceDisplaySpec.js b/Specs/DataSources/DataSourceDisplaySpec.js index 6c243b072063..fe27ce8bcb7f 100644 --- a/Specs/DataSources/DataSourceDisplaySpec.js +++ b/Specs/DataSources/DataSourceDisplaySpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/DataSourceDisplay', + 'Core/ApproximateTerrainHeights', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Iso8601', @@ -11,6 +12,7 @@ defineSuite([ 'Specs/MockDataSource' ], function( DataSourceDisplay, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Iso8601, @@ -38,7 +40,8 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); afterEach(function() { @@ -357,7 +360,8 @@ defineSuite([ it('verify update returns false till terrain heights are initialized', function() { GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; var source1 = new MockDataSource(); var source2 = new MockDataSource(); diff --git a/Specs/DataSources/GeometryVisualizerSpec.js b/Specs/DataSources/GeometryVisualizerSpec.js index 6ed873b66239..00fab66b830c 100644 --- a/Specs/DataSources/GeometryVisualizerSpec.js +++ b/Specs/DataSources/GeometryVisualizerSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/GeometryVisualizer', + 'Core/ApproximateTerrainHeights', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', @@ -31,6 +32,7 @@ defineSuite([ 'Specs/pollToPromise' ], function( GeometryVisualizer, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Color, @@ -77,7 +79,8 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); it('Can create and destroy', function() { diff --git a/Specs/DataSources/MinimumTerrainHeightPropertySpec.js b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js new file mode 100644 index 000000000000..2deb902ef04d --- /dev/null +++ b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js @@ -0,0 +1,122 @@ +defineSuite([ + 'DataSources/MinimumTerrainHeightProperty', + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/Event', + 'Core/ExtrapolationType', + 'Core/JulianDate', + 'Core/Math', + 'DataSources/CallbackProperty', + 'DataSources/ConstantProperty' +], function( + MinimumTerrainHeightProperty, + ApproximateTerrainHeights, + Cartesian3, + Event, + ExtrapolationType, + JulianDate, + CesiumMath, + CallbackProperty, + ConstantProperty) { + 'use strict'; + + var time = JulianDate.now(); + + beforeAll(function() { + return ApproximateTerrainHeights.initialize(); + }); + + afterAll(function() { + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + }); + + it('can default construct', function() { + var property = new MinimumTerrainHeightProperty(); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.positions).toBeUndefined(); + expect(property.getValue(time)).toBeUndefined(); + }); + + it('can construct with arguments', function() { + var positions = new ConstantProperty(); + var property = new MinimumTerrainHeightProperty(positions); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.positions).toBe(positions); + }); + + it('raises definitionChanged event when positions is set', function() { + var property = new MinimumTerrainHeightProperty(); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + var positions = new ConstantProperty(); + property.positions = positions; + expect(listener).toHaveBeenCalledWith(property, 'positions', positions, undefined); + }); + + it('subscribes and unsubscribes to position definitionChanged and propagates up', function() { + var positions = new ConstantProperty(); + var property = new MinimumTerrainHeightProperty(positions); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + //Position changing should raise out property change event + positions.definitionChanged.raiseEvent(positions); + expect(listener).toHaveBeenCalledWith(property, 'positions', positions, positions); + + //Make sure it unsubscribes when value is changed + property.positions = undefined; + + listener.calls.reset(); + positions.definitionChanged.raiseEvent(positions); + expect(listener.calls.count()).toBe(0); + }); + + it('does not raise definitionChanged event when position is set to the same instance', function() { + var positions = new ConstantProperty(); + var property = new MinimumTerrainHeightProperty(positions); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + property.positions = positions; + expect(listener.calls.count()).toBe(0); + }); + + it('produces correct value', function() { + var positions = new ConstantProperty(Cartesian3.fromDegreesArray([-120.0, 40.0, + -119.0, 40.0, + -119.0, 41.0, + -120.0, 41.0])); + var property = new MinimumTerrainHeightProperty(positions); + + expect(property.getValue(time)).toEqualEpsilon(-382.8696126443784, CesiumMath.EPSILON10); + }); + + it('equals works', function() { + var positions = new ConstantProperty(); + + var left = new MinimumTerrainHeightProperty(); + var right = new MinimumTerrainHeightProperty(); + + expect(left.equals(right)).toBe(true); + + left.positions = positions; + expect(left.equals(right)).toBe(false); + + right.positions = positions; + expect(left.equals(right)).toBe(true); + }); + + it('getValue throws without time', function() { + var property = new MinimumTerrainHeightProperty(); + expect(function() { + property.getValue(); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/DataSources/RelativeToTerrainHeightPropertySpec.js b/Specs/DataSources/RelativeToTerrainHeightPropertySpec.js new file mode 100644 index 000000000000..db60a594dd0d --- /dev/null +++ b/Specs/DataSources/RelativeToTerrainHeightPropertySpec.js @@ -0,0 +1,166 @@ +defineSuite([ + 'DataSources/RelativeToTerrainHeightProperty', + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/EllipsoidTerrainProvider', + 'Core/Event', + 'Core/ExtrapolationType', + 'Core/JulianDate', + 'Core/Math', + 'DataSources/CallbackProperty', + 'DataSources/ConstantProperty', + 'ThirdParty/when' +], function( + RelativeToTerrainHeightProperty, + ApproximateTerrainHeights, + Cartesian3, + EllipsoidTerrainProvider, + Event, + ExtrapolationType, + JulianDate, + CesiumMath, + CallbackProperty, + ConstantProperty, + when) { + 'use strict'; + + var time = JulianDate.now(); + var terrainProvider = new EllipsoidTerrainProvider(); + + it('can default construct', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.position).toBeUndefined(); + expect(property.heightRelativeToTerrain).toBeUndefined(); + expect(property.getValue(time)).toBe(0); + }); + + it('can construct with arguments', function() { + var position = new ConstantProperty(); + var heightRelativeToTerrain = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.position).toBe(position); + expect(property.heightRelativeToTerrain).toBe(heightRelativeToTerrain); + }); + + it('raises definitionChanged event when position is set', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + var position = new ConstantProperty(); + property.position = position; + expect(listener).toHaveBeenCalledWith(property, 'position', position, undefined); + }); + + it('raises definitionChanged event when heightRelativeToTerrain is set', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + var heightRelativeToTerrain = new ConstantProperty(); + property.heightRelativeToTerrain = heightRelativeToTerrain; + expect(listener).toHaveBeenCalledWith(property, 'heightRelativeToTerrain', heightRelativeToTerrain, undefined); + }); + + it('subscribes and unsubscribes to position definitionChanged and propagates up', function() { + var position = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + //Position changing should raise out property change event + position.definitionChanged.raiseEvent(position); + expect(listener).toHaveBeenCalledWith(property, 'position', position, position); + + //Make sure it unsubscribes when value is changed + property.position = undefined; + + listener.calls.reset(); + position.definitionChanged.raiseEvent(position); + expect(listener.calls.count()).toBe(0); + }); + + it('subscribes and unsubscribes to heightRelativeToTerrain definitionChanged and propagates up', function() { + var heightRelativeToTerrain = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, undefined, heightRelativeToTerrain); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + //Position changing should raise out property change event + heightRelativeToTerrain.definitionChanged.raiseEvent(heightRelativeToTerrain); + expect(listener).toHaveBeenCalledWith(property, 'heightRelativeToTerrain', heightRelativeToTerrain, heightRelativeToTerrain); + + //Make sure it unsubscribes when value is changed + property.heightRelativeToTerrain = undefined; + + listener.calls.reset(); + heightRelativeToTerrain.definitionChanged.raiseEvent(heightRelativeToTerrain); + expect(listener.calls.count()).toBe(0); + }); + + it('does not raise definitionChanged event when position is set to the same instance', function() { + var position = new ConstantProperty(); + var heightRelativeToTerrain = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + property.position = position; + property.heightRelativeToTerrain = heightRelativeToTerrain; + expect(listener.calls.count()).toBe(0); + }); + + it('produces correct value', function() { + var terrainHeight = 30.0; + spyOn(RelativeToTerrainHeightProperty, '_sampleTerrainMostDetailed').and.returnValue(when.resolve(terrainHeight)); + var position = new ConstantProperty(Cartesian3.fromDegrees(-120.0, 40.0)); + var heightRelativeToTerrain = new ConstantProperty(40.0); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain); + + expect(property.getValue(time)).toEqual(70.0); + }); + + it('equals works', function() { + var position = new ConstantProperty(); + var heightRelativeToTerrain = new ConstantProperty(); + + var left = new RelativeToTerrainHeightProperty(terrainProvider); + var right = new RelativeToTerrainHeightProperty(terrainProvider); + + expect(left.equals(right)).toBe(true); + + left.position = position; + expect(left.equals(right)).toBe(false); + + right.position = position; + expect(left.equals(right)).toBe(true); + + left.heightRelativeToTerrain = heightRelativeToTerrain; + expect(left.equals(right)).toBe(false); + + right.heightRelativeToTerrain = heightRelativeToTerrain; + expect(left.equals(right)).toBe(false); + }); + + it('constructor throws without terrainProvider', function() { + expect(function() { + return new RelativeToTerrainHeightProperty(); + }).toThrowDeveloperError(); + }); + + it('getValue throws without time', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + expect(function() { + property.getValue(); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index a17aab5a121c..1d8c8b4d701e 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Scene/GroundPrimitive', + 'Core/ApproximateTerrainHeights', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', 'Core/destroyObject', @@ -20,6 +21,7 @@ defineSuite([ 'Specs/pollToPromise' ], function( GroundPrimitive, + ApproximateTerrainHeights, Color, ColorGeometryInstanceAttribute, destroyObject, @@ -69,7 +71,8 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); function MockGlobePrimitive(primitive) {