diff --git a/Apps/Sandcastle/gallery/Ground Clamping.html b/Apps/Sandcastle/gallery/Ground Clamping.html index bba58ebb09e9..6c45eb7006ca 100644 --- a/Apps/Sandcastle/gallery/Ground Clamping.html +++ b/Apps/Sandcastle/gallery/Ground Clamping.html @@ -218,6 +218,36 @@ viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); }); } +}, { + text : 'Draw polyline on terrain', + onselect : function() { + + if (!Cesium.Entity.supportsPolylinesOnTerrain(viewer.scene)) { + console.log('Polylines on terrain are not supported on this platform'); + } + + viewer.entities.add({ + polyline : { + positions : Cesium.Cartesian3.fromDegreesArray([ + 86.953793, 27.928257, + 86.953793, 27.988257, + 86.896497, 27.988257 + ]), + clampToGround : true, + width : 5, + material : new Cesium.PolylineOutlineMaterialProperty({ + color : Cesium.Color.ORANGE, + outlineWidth : 2, + outlineColor : Cesium.Color.BLACK + }) + } + }); + + var target = new Cesium.Cartesian3(300770.50872389384, 5634912.131394585, 2978152.2865545116); + var offset = new Cesium.Cartesian3(6344.974098678562, -793.3419798081741, 2499.9508860763162); + viewer.camera.lookAt(target, offset); + viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + } }], 'zoomButtons'); Sandcastle.reset = function () { diff --git a/Apps/Sandcastle/gallery/Polyline.html b/Apps/Sandcastle/gallery/Polyline.html index 0dcfc9a7f72a..203f2afe55c4 100644 --- a/Apps/Sandcastle/gallery/Polyline.html +++ b/Apps/Sandcastle/gallery/Polyline.html @@ -30,12 +30,13 @@ var viewer = new Cesium.Viewer('cesiumContainer'); var redLine = viewer.entities.add({ - name : 'Red line on the surface', + name : 'Red line on terrain', polyline : { positions : Cesium.Cartesian3.fromDegreesArray([-75, 35, -125, 35]), width : 5, - material : Cesium.Color.RED + material : Cesium.Color.RED, + clampToGround : true } }); diff --git a/Apps/Sandcastle/gallery/Z-Indexing Geometry.html b/Apps/Sandcastle/gallery/Z-Indexing Geometry.html index b9a8e1005cd6..6940e7dd4555 100644 --- a/Apps/Sandcastle/gallery/Z-Indexing Geometry.html +++ b/Apps/Sandcastle/gallery/Z-Indexing Geometry.html @@ -32,6 +32,7 @@ var viewer = new Cesium.Viewer('cesiumContainer'); viewer.entities.add({ + id : 'Red rectangle, zIndex 1', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-110.0, 20.0, -100.5, 30.0), material : Cesium.Color.RED, @@ -40,6 +41,7 @@ }); viewer.entities.add({ + id : 'Textured rectangle, zIndex 2', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-112.0, 25.0, -102.5, 35.0), material : '../images/Cesium_Logo_Color.jpg', @@ -48,6 +50,7 @@ }); viewer.entities.add({ + id : 'Blue rectangle, zIndex 3', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-110.0, 31.0, -100.5, 41.0), material : Cesium.Color.BLUE, @@ -56,6 +59,7 @@ }); viewer.entities.add({ + id : 'Textured rectangle, zIndex 3', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-99.5, 20.0, -90.0, 30.0), material : '../images/Cesium_Logo_Color.jpg', @@ -64,6 +68,7 @@ }); viewer.entities.add({ + id : 'Green rectangle, zIndex 2', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-97.5, 25.0, -88.0, 35.0), material : Cesium.Color.GREEN, @@ -72,6 +77,7 @@ }); viewer.entities.add({ + id : 'Blue rectangle, zIndex 1', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-99.5, 31.0, -90.0, 41.0), material : Cesium.Color.BLUE, @@ -79,6 +85,31 @@ } }); +if (!Cesium.Entity.supportsPolylinesOnTerrain(viewer.scene)) { + console.log('Polylines on terrain are not supported on this platform, Z-index will be ignored'); +} + +if (!Cesium.Entity.supportsMaterialsforEntitiesOnTerrain(viewer.scene)) { + console.log('Textured materials on terrain polygons are not supported on this platform, Z-index will be ignored'); +} + +viewer.entities.add({ + id : 'Polyline, zIndex 2', + polyline : { + positions : Cesium.Cartesian3.fromDegreesArray([ + -120.0, 22.0, + -80.0, 22.0 + ]), + width : 8.0, + material : new Cesium.PolylineGlowMaterialProperty({ + glowPower : 0.2, + color : Cesium.Color.BLUE + }), + zIndex: 2, + clampToGround : true + } +}); + viewer.zoomTo(viewer.entities); //Sandcastle_End Sandcastle.finishedLoading(); diff --git a/Apps/Sandcastle/gallery/Z-Indexing Geometry.jpg b/Apps/Sandcastle/gallery/Z-Indexing Geometry.jpg index caae2ada52e6..e9d0bf936912 100644 Binary files a/Apps/Sandcastle/gallery/Z-Indexing Geometry.jpg and b/Apps/Sandcastle/gallery/Z-Indexing Geometry.jpg differ diff --git a/CHANGES.md b/CHANGES.md index 42c30d1723c9..e651fc693cce 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,7 +10,13 @@ Change Log ##### Additions :tada: * `PostProcessStage` has a `selectedFeatures` property which is an array of primitives used for selectively applying a post-process stage. In the fragment shader, use the function `bool czm_selected(vec2 textureCoordinates` to determine whether or not the stage should be applied at that fragment. * The black-and-white and silhouette stages have per-feature support. +* Added support for Polylines on Terrain via the `Entity` API [#6689](https://github.com/AnalyticalGraphicsInc/cesium/pull/6689) + * Use the `clampToGround` option for `PolylineGraphics`. + * Requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), otherwise `clampToGround` will be ignored. + * Added `Entity.supportsPolylinesOnTerrain` for checking if the current platform supports `clampToGround`. * Added `GroundPolylinePrimitive` and `GroundPolylineGeometry` for rendering polylines on terrain via the `Primitive` API. [#6615](https://github.com/AnalyticalGraphicsInc/cesium/pull/6615) + * Requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`). + * Use `GroundPolylinePrimitive.isSupported` to check for support. ##### Fixes :wrench: * Fixed a bug causing crashes with custom vertex attributes on `Geometry` crossing the IDL. Attributes will be barycentrically interpolated. [#6644](https://github.com/AnalyticalGraphicsInc/cesium/pull/6644) diff --git a/Source/DataSources/DataSourceDisplay.js b/Source/DataSources/DataSourceDisplay.js index 896503d7d71f..28a2d74dc071 100644 --- a/Source/DataSources/DataSourceDisplay.js +++ b/Source/DataSources/DataSourceDisplay.js @@ -130,7 +130,7 @@ define([ new ModelVisualizer(scene, entities), new PointVisualizer(entityCluster, entities), new PathVisualizer(scene, entities), - new PolylineVisualizer(scene, entities)]; + new PolylineVisualizer(scene, entities, dataSource._groundPrimitives)]; }; defineProperties(DataSourceDisplay.prototype, { diff --git a/Source/DataSources/Entity.js b/Source/DataSources/Entity.js index b9d400c66935..928ee41aa570 100644 --- a/Source/DataSources/Entity.js +++ b/Source/DataSources/Entity.js @@ -12,6 +12,7 @@ define([ '../Core/Quaternion', '../Core/Transforms', '../Scene/GroundPrimitive', + '../Scene/GroundPolylinePrimitive', './BillboardGraphics', './BoxGraphics', './ConstantPositionProperty', @@ -47,6 +48,7 @@ define([ Quaternion, Transforms, GroundPrimitive, + GroundPolylinePrimitive, BillboardGraphics, BoxGraphics, ConstantPositionProperty, @@ -631,5 +633,17 @@ define([ return GroundPrimitive.supportsMaterials(scene); }; + /** + * Checks if the given Scene supports polylines clamped to the ground.. + * If this feature is not supported, Entities with PolylineGraphics will be rendered with vertices at + * the provided heights and using the `followSurface` parameter instead of clamped to the ground. + * + * @param {Scene} scene The current scene. + * @returns {Boolean} Whether or not the current scene supports Polylines on Terrain. + */ + Entity.supportsPolylinesOnTerrain = function(scene) { + return GroundPolylinePrimitive.isSupported(scene); + }; + return Entity; }); diff --git a/Source/DataSources/PolylineGeometryUpdater.js b/Source/DataSources/PolylineGeometryUpdater.js index df08ce7b0bf0..5cd450d676a3 100644 --- a/Source/DataSources/PolylineGeometryUpdater.js +++ b/Source/DataSources/PolylineGeometryUpdater.js @@ -12,10 +12,13 @@ define([ '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/Event', '../Core/GeometryInstance', + '../Core/GroundPolylineGeometry', '../Core/Iso8601', '../Core/PolylineGeometry', '../Core/PolylinePipeline', '../Core/ShowGeometryInstanceAttribute', + '../DataSources/Entity', + '../Scene/GroundPolylinePrimitive', '../Scene/PolylineCollection', '../Scene/PolylineColorAppearance', '../Scene/PolylineMaterialAppearance', @@ -39,10 +42,13 @@ define([ DistanceDisplayConditionGeometryInstanceAttribute, Event, GeometryInstance, + GroundPolylineGeometry, Iso8601, PolylineGeometry, PolylinePipeline, ShowGeometryInstanceAttribute, + Entity, + GroundPolylinePrimitive, PolylineCollection, PolylineColorAppearance, PolylineMaterialAppearance, @@ -54,6 +60,8 @@ define([ Property) { 'use strict'; + var defaultZIndex = new ConstantProperty(0); + //We use this object to create one polyline collection per-scene. var polylineCollections = {}; @@ -63,8 +71,7 @@ define([ var defaultShadows = new ConstantProperty(ShadowMode.DISABLED); var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); - function GeometryOptions(entity) { - this.id = entity; + function GeometryOptions() { this.vertexFormat = undefined; this.positions = undefined; this.width = undefined; @@ -72,6 +79,11 @@ define([ this.granularity = undefined; } + function GroundGeometryOptions() { + this.positions = undefined; + this.width = undefined; + } + /** * A {@link GeometryUpdater} for polylines. * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}. @@ -102,8 +114,13 @@ define([ this._shadowsProperty = undefined; this._distanceDisplayConditionProperty = undefined; this._depthFailMaterialProperty = undefined; - this._options = new GeometryOptions(entity); + this._geometryOptions = new GeometryOptions(); + this._groundGeometryOptions = new GroundGeometryOptions(); this._id = 'polyline-' + entity.id; + this._clampToGround = false; + this._supportsPolylinesOnTerrain = Entity.supportsPolylinesOnTerrain(scene); + + this._zIndex = 0; this._onEntityPropertyChanged(entity, 'polyline', entity.polyline, undefined); } @@ -272,6 +289,32 @@ define([ get : function() { return this._geometryChanged; } + }, + + /** + * Gets a value indicating if the geometry is clamped to the ground. + * Returns false if polylines on terrain is not supported. + * @memberof PolylineGeometryUpdater.prototype + * + * @type {Boolean} + * @readonly + */ + clampToGround : { + get : function() { + return this._clampToGround && this._supportsPolylinesOnTerrain; + } + }, + + /** + * Gets the zindex + * @type {Number} + * @memberof GroundGeometryUpdater.prototype + * @readonly + */ + zIndex: { + get: function() { + return this._zIndex; + } } }); @@ -337,6 +380,14 @@ define([ attributes.color = ColorGeometryInstanceAttribute.fromColor(currentColor); } + if (this.clampToGround) { + return new GeometryInstance({ + id : entity, + geometry : new GroundPolylineGeometry(this._groundGeometryOptions), + attributes : attributes + }); + } + if (defined(this._depthFailMaterialProperty) && this._depthFailMaterialProperty instanceof ColorMaterialProperty) { if (defined(this._depthFailMaterialProperty.color) && (this._depthFailMaterialProperty.color.isConstant || isAvailable)) { currentColor = this._depthFailMaterialProperty.color.getValue(time, scratchColor); @@ -349,7 +400,7 @@ define([ return new GeometryInstance({ id : entity, - geometry : new PolylineGeometry(this._options), + geometry : new PolylineGeometry(this._geometryOptions), attributes : attributes }); }; @@ -414,6 +465,7 @@ define([ return; } + var zIndex = polyline.zIndex; var material = defaultValue(polyline.material, defaultMaterial); var isColorMaterial = material instanceof ColorMaterialProperty; this._materialProperty = material; @@ -422,20 +474,23 @@ define([ this._shadowsProperty = defaultValue(polyline.shadows, defaultShadows); this._distanceDisplayConditionProperty = defaultValue(polyline.distanceDisplayCondition, defaultDistanceDisplayCondition); this._fillEnabled = true; + this._zIndex = defaultValue(zIndex, defaultZIndex); var width = polyline.width; var followSurface = polyline.followSurface; + var clampToGround = polyline.clampToGround; var granularity = polyline.granularity; if (!positionsProperty.isConstant || !Property.isConstant(width) || - !Property.isConstant(followSurface) || !Property.isConstant(granularity)) { + !Property.isConstant(followSurface) || !Property.isConstant(granularity) || + !Property.isConstant(clampToGround) || !Property.isConstant(zIndex)) { if (!this._dynamic) { this._dynamic = true; this._geometryChanged.raiseEvent(this); } } else { - var options = this._options; - var positions = positionsProperty.getValue(Iso8601.MINIMUM_VALUE, options.positions); + var geometryOptions = this._geometryOptions; + var positions = positionsProperty.getValue(Iso8601.MINIMUM_VALUE, geometryOptions.positions); //Because of the way we currently handle reference properties, //we can't automatically assume the positions are always valid. @@ -454,11 +509,18 @@ define([ vertexFormat = PolylineMaterialAppearance.VERTEX_FORMAT; } - options.vertexFormat = vertexFormat; - options.positions = positions; - options.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined; - options.followSurface = defined(followSurface) ? followSurface.getValue(Iso8601.MINIMUM_VALUE) : undefined; - options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined; + geometryOptions.vertexFormat = vertexFormat; + geometryOptions.positions = positions; + geometryOptions.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined; + geometryOptions.followSurface = defined(followSurface) ? followSurface.getValue(Iso8601.MINIMUM_VALUE) : undefined; + geometryOptions.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined; + + var groundGeometryOptions = this._groundGeometryOptions; + groundGeometryOptions.positions = positions; + groundGeometryOptions.width = geometryOptions.width; + + this._clampToGround = defined(clampToGround) ? clampToGround.getValue(Iso8601.MINIMUM_VALUE) : false; + this._dynamic = false; this._geometryChanged.raiseEvent(this); } @@ -468,22 +530,22 @@ define([ * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true. * * @param {PrimitiveCollection} primitives The primitive collection to use. + * @param {PrimitiveCollection|OrderedGroundPrimitiveCollection} groundPrimitives The primitive collection to use for ordered ground primitives. * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame. * * @exception {DeveloperError} This instance does not represent dynamic geometry. */ - PolylineGeometryUpdater.prototype.createDynamicUpdater = function(primitives) { + PolylineGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) { //>>includeStart('debug', pragmas.debug); + Check.defined('primitives', primitives); + Check.defined('groundPrimitives', groundPrimitives); + if (!this._dynamic) { throw new DeveloperError('This instance does not represent dynamic geometry.'); } - - if (!defined(primitives)) { - throw new DeveloperError('primitives is required.'); - } //>>includeEnd('debug'); - return new DynamicGeometryUpdater(primitives, this); + return new DynamicGeometryUpdater(primitives, groundPrimitives, this); }; /** @@ -496,10 +558,25 @@ define([ ellipsoid : undefined }; - function DynamicGeometryUpdater(primitives, geometryUpdater) { - var sceneId = geometryUpdater._scene.id; + function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) { + this._line = undefined; + this._primitives = primitives; + this._groundPrimitives = groundPrimitives; + this._groundPolylinePrimitive = undefined; + this._material = undefined; + this._geometryUpdater = geometryUpdater; + this._positions = []; + this._terrainHeightsReady = false; + } + function getLine(dynamicGeometryUpdater) { + if (defined(dynamicGeometryUpdater._line)) { + return dynamicGeometryUpdater._line; + } + + var sceneId = dynamicGeometryUpdater._geometryUpdater._scene.id; var polylineCollection = polylineCollections[sceneId]; + var primitives = dynamicGeometryUpdater._primitives; if (!defined(polylineCollection) || polylineCollection.isDestroyed()) { polylineCollection = new PolylineCollection(); polylineCollections[sceneId] = polylineCollection; @@ -509,27 +586,87 @@ define([ } var line = polylineCollection.add(); - line.id = geometryUpdater._entity; - - this._line = line; - this._primitives = primitives; - this._geometryUpdater = geometryUpdater; - this._positions = []; + line.id = dynamicGeometryUpdater._geometryUpdater._entity; + dynamicGeometryUpdater._line = line; + return line; } DynamicGeometryUpdater.prototype.update = function(time) { var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var polyline = entity.polyline; - var line = this._line; + + var positionsProperty = polyline.positions; + var positions = Property.getValueOrUndefined(positionsProperty, time, this._positions); + + // Synchronize with geometryUpdater for GroundPolylinePrimitive + geometryUpdater._clampToGround = Property.getValueOrDefault(polyline._clampToGround, time, false); + geometryUpdater._groundGeometryOptions.positions = positions; + geometryUpdater._groundGeometryOptions.width = Property.getValueOrDefault(polyline._width, time, 1); + + var groundPrimitives = this._groundPrimitives; + + if (defined(this._groundPolylinePrimitive)) { + groundPrimitives.remove(this._groundPolylinePrimitive); // destroys by default + this._groundPolylinePrimitive = undefined; + } + + if (geometryUpdater.clampToGround) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) { + return; + } + + if (!defined(positions) || positions.length < 2) { + return; + } + + var that = this; + + // Load terrain heights + if (!this._terrainHeightsReady) { + if (!GroundPolylinePrimitive._isInitialized()) { + GroundPolylinePrimitive.initializeTerrainHeights() + .then(function() { + that._terrainHeightsReady = true; + }); + return; + } + this._terrainHeightsReady = true; + } + + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var appearance; + if (fillMaterialProperty instanceof ColorMaterialProperty) { + appearance = new PolylineColorAppearance(); + } else { + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); + appearance = new PolylineMaterialAppearance({ + material : material, + translucent : material.isTranslucent() + }); + this._material = material; + } + + this._groundPolylinePrimitive = groundPrimitives.add(new GroundPolylinePrimitive({ + geometryInstances : geometryUpdater.createFillGeometryInstance(time), + appearance : appearance, + asynchronous : false + }), Property.getValueOrUndefined(geometryUpdater.zIndex, time)); + + // Hide the polyline in the collection, if any + if (defined(this._line)) { + this._line.show = false; + } + return; + } + + var line = getLine(this); if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) { line.show = false; return; } - var positionsProperty = polyline.positions; - var positions = Property.getValueOrUndefined(positionsProperty, time, this._positions); if (!defined(positions) || positions.length < 2) { line.show = false; return; @@ -557,11 +694,29 @@ define([ Check.defined('result', result); //>>includeEnd('debug'); - var line = this._line; - if (line.show && line.positions.length > 0) { - BoundingSphere.fromPoints(line.positions, result); + if (!this._geometryUpdater.clampToGround) { + var line = getLine(this); + if (line.show && line.positions.length > 0) { + BoundingSphere.fromPoints(line.positions, result); + return BoundingSphereState.DONE; + } + } else { + var groundPolylinePrimitive = this._groundPolylinePrimitive; + if (defined(groundPolylinePrimitive) && groundPolylinePrimitive.show && groundPolylinePrimitive.ready) { + var attributes = groundPolylinePrimitive.getGeometryInstanceAttributes(this._geometryUpdater._entity); + if (defined(attributes) && defined(attributes.boundingSphere)) { + BoundingSphere.clone(attributes.boundingSphere, result); + return BoundingSphereState.DONE; + } + } + + if ((defined(groundPolylinePrimitive) && !groundPolylinePrimitive.ready)) { + return BoundingSphereState.PENDING; + } + return BoundingSphereState.DONE; } + return BoundingSphereState.FAILED; }; @@ -573,10 +728,15 @@ define([ var geometryUpdater = this._geometryUpdater; var sceneId = geometryUpdater._scene.id; var polylineCollection = polylineCollections[sceneId]; - polylineCollection.remove(this._line); - if (polylineCollection.length === 0) { - this._primitives.removeAndDestroy(polylineCollection); - delete polylineCollections[sceneId]; + if (defined(polylineCollection)) { + polylineCollection.remove(this._line); + if (polylineCollection.length === 0) { + this._primitives.removeAndDestroy(polylineCollection); + delete polylineCollections[sceneId]; + } + } + if (defined(this._groundPolylinePrimitive)) { + this._groundPrimitives.remove(this._groundPolylinePrimitive); } destroyObject(this); }; diff --git a/Source/DataSources/PolylineGraphics.js b/Source/DataSources/PolylineGraphics.js index 5b290a6c7621..3700d18ec149 100644 --- a/Source/DataSources/PolylineGraphics.js +++ b/Source/DataSources/PolylineGraphics.js @@ -17,9 +17,9 @@ define([ 'use strict'; /** - * Describes a polyline defined as a line strip. The first two positions define a line segment, + * Describes a polyline. The first two positions define a line segment, * and each additional position defines a line segment from the previous position. The segments - * can be linear connected points or great arcs. + * can be linear connected points, great arcs, or clamped to terrain. * * @alias PolylineGraphics * @constructor @@ -27,6 +27,7 @@ define([ * @param {Object} [options] Object with the following properties: * @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the line strip. * @param {Property} [options.followSurface=true] A boolean Property specifying whether the line segments should be great arcs or linearly connected. + * @param {Property} [options.clampToGround=false] A boolean Property specifying whether the Polyline should be clamped to the ground. * @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels. * @param {Property} [options.show=true] A boolean Property specifying the visibility of the polyline. * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the polyline. @@ -34,6 +35,7 @@ define([ * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if followSurface is true. * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polyline casts or receives shadows from each light source. * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polyline will be displayed. + * @param {Property} [options.zIndex=0] A Property specifying the zIndex used for ordering ground geometry. Only has an effect if `clampToGround` is true and polylines on terrain is supported. * * @see Entity * @demo {@link https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline.html|Cesium Sandcastle Polyline Demo} @@ -49,6 +51,8 @@ define([ this._positionsSubscription = undefined; this._followSurface = undefined; this._followSurfaceSubscription = undefined; + this._clampToGround = undefined; + this._clampToGroundSubscription = undefined; this._granularity = undefined; this._granularitySubscription = undefined; this._widthSubscription = undefined; @@ -58,6 +62,9 @@ define([ this._shadowsSubscription = undefined; this._distanceDisplayCondition = undefined; this._distanceDisplayConditionSubscription = undefined; + this._zIndex = undefined; + this._zIndexSubscription = undefined; + this._definitionChanged = new Event(); this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); @@ -131,7 +138,16 @@ define([ followSurface : createPropertyDescriptor('followSurface'), /** - * Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if followSurface is true. + * Gets or sets the boolean Property specifying whether the polyline + * should be clamped to the ground. + * @memberof PolylineGraphics.prototype + * @type {Property} + * @default false + */ + clampToGround : createPropertyDescriptor('clampToGround'), + + /** + * Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if followSurface is true and clampToGround is false. * @memberof PolylineGraphics.prototype * @type {Property} * @default Cesium.Math.RADIANS_PER_DEGREE @@ -152,7 +168,15 @@ define([ * @memberof PolylineGraphics.prototype * @type {Property} */ - distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition') + distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition'), + + /** + * Gets or sets the zIndex Property specifying the ordering of the polyline. Only has an effect if `clampToGround` is true and polylines on terrain is supported. + * @memberof RectangleGraphics.prototype + * @type {ConstantProperty} + * @default 0 + */ + zIndex : createPropertyDescriptor('zIndex') }); /** @@ -171,9 +195,12 @@ define([ result.positions = this.positions; result.width = this.width; result.followSurface = this.followSurface; + result.clampToGround = this.clampToGround; result.granularity = this.granularity; result.shadows = this.shadows; result.distanceDisplayCondition = this.distanceDisplayCondition; + result.zIndex = this.zIndex; + return result; }; @@ -196,9 +223,11 @@ define([ this.positions = defaultValue(this.positions, source.positions); this.width = defaultValue(this.width, source.width); this.followSurface = defaultValue(this.followSurface, source.followSurface); + this.clampToGround = defaultValue(this.clampToGround, source.clampToGround); this.granularity = defaultValue(this.granularity, source.granularity); this.shadows = defaultValue(this.shadows, source.shadows); this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition); + this.zIndex = defaultValue(this.zIndex, source.zIndex); }; return PolylineGraphics; diff --git a/Source/DataSources/PolylineVisualizer.js b/Source/DataSources/PolylineVisualizer.js index f36794bbf58c..2c97009e248f 100644 --- a/Source/DataSources/PolylineVisualizer.js +++ b/Source/DataSources/PolylineVisualizer.js @@ -2,6 +2,7 @@ define([ '../Core/AssociativeArray', '../Core/BoundingSphere', '../Core/Check', + '../Core/defaultValue', '../Core/defined', '../Core/destroyObject', '../Scene/PolylineColorAppearance', @@ -12,11 +13,13 @@ define([ './DynamicGeometryBatch', './PolylineGeometryUpdater', './StaticGeometryColorBatch', - './StaticGeometryPerMaterialBatch' + './StaticGeometryPerMaterialBatch', + './StaticGroundPolylinePerMaterialBatch' ], function( AssociativeArray, BoundingSphere, Check, + defaultValue, defined, destroyObject, PolylineColorAppearance, @@ -27,7 +30,8 @@ define([ DynamicGeometryBatch, PolylineGeometryUpdater, StaticGeometryColorBatch, - StaticGeometryPerMaterialBatch) { + StaticGeometryPerMaterialBatch, + StaticGroundPolylinePerMaterialBatch) { 'use strict'; var emptyArray = []; @@ -47,6 +51,11 @@ define([ return; } + if (updater.clampToGround) { // Also checks for support + that._groundBatch.add(time, updater); + return; + } + var shadows; if (updater.fillEnabled) { shadows = updater.shadowsProperty.getValue(time); @@ -78,13 +87,16 @@ define([ * * @param {Scene} scene The scene the primitives will be rendered in. * @param {EntityCollection} entityCollection The entityCollection to visualize. + * @param {PrimitiveCollection|OrderedGroundPrimitiveCollection} [groundPrimitives] A collection to add ground primitives related to the entities */ - function PolylineVisualizer(scene, entityCollection) { + function PolylineVisualizer(scene, entityCollection, groundPrimitives) { //>>includeStart('debug', pragmas.debug); Check.defined('scene', scene); Check.defined('entityCollection', entityCollection); //>>includeEnd('debug'); + groundPrimitives = defaultValue(groundPrimitives, scene.groundPrimitives); + var primitives = scene.primitives; this._scene = scene; @@ -109,9 +121,11 @@ define([ this._materialBatches[i + numberOfShadowModes * 2] = new StaticGeometryPerMaterialBatch(primitives, PolylineMaterialAppearance, PolylineMaterialAppearance, false, i); } - this._dynamicBatch = new DynamicGeometryBatch(primitives); + this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives); + // Only available for terrain classification + this._groundBatch = new StaticGroundPolylinePerMaterialBatch(groundPrimitives); - this._batches = this._colorBatches.concat(this._materialBatches, this._dynamicBatch); + this._batches = this._colorBatches.concat(this._materialBatches, this._dynamicBatch, this._groundBatch); this._subscriptions = new AssociativeArray(); this._updaters = new AssociativeArray(); diff --git a/Source/DataSources/StaticGroundPolylinePerMaterialBatch.js b/Source/DataSources/StaticGroundPolylinePerMaterialBatch.js new file mode 100644 index 000000000000..af3e4dfafe15 --- /dev/null +++ b/Source/DataSources/StaticGroundPolylinePerMaterialBatch.js @@ -0,0 +1,365 @@ +define([ + '../Core/AssociativeArray', + '../Core/defined', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/GroundPolylinePrimitive', + '../Scene/PolylineColorAppearance', + '../Scene/PolylineMaterialAppearance', + './BoundingSphereState', + './ColorMaterialProperty', + './MaterialProperty', + './Property' + ], function( + AssociativeArray, + defined, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + GroundPolylinePrimitive, + PolylineColorAppearance, + PolylineMaterialAppearance, + BoundingSphereState, + ColorMaterialProperty, + MaterialProperty, + Property) { + 'use strict'; + + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + var defaultDistanceDisplayCondition = new DistanceDisplayCondition(); + + // Encapsulates a Primitive and all the entities that it represents. + function Batch(orderedGroundPrimitives, materialProperty, zIndex) { + var appearanceType; + if (materialProperty instanceof ColorMaterialProperty) { + appearanceType = PolylineColorAppearance; + } else { + appearanceType = PolylineMaterialAppearance; + } + + this.orderedGroundPrimitives = orderedGroundPrimitives; // scene level primitive collection + this.appearanceType = appearanceType; + this.materialProperty = materialProperty; + this.updaters = new AssociativeArray(); + this.createPrimitive = true; + this.primitive = undefined; // a GroundPolylinePrimitive encapsulating all the entities + this.oldPrimitive = undefined; + this.geometry = new AssociativeArray(); + this.material = undefined; + this.updatersWithAttributes = new AssociativeArray(); + this.attributes = new AssociativeArray(); + this.invalidated = false; + this.removeMaterialSubscription = materialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this); + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); + this.zIndex = zIndex; + } + + Batch.prototype.onMaterialChanged = function() { + this.invalidated = true; + }; + + // Check if the given updater's material is compatible with this batch + Batch.prototype.isMaterial = function(updater) { + var material = this.materialProperty; + var updaterMaterial = updater.fillMaterialProperty; + + if (updaterMaterial === material || + (updaterMaterial instanceof ColorMaterialProperty && material instanceof ColorMaterialProperty)) { + return true; + } + return defined(material) && material.equals(updaterMaterial); + }; + + Batch.prototype.add = function(time, updater, geometryInstance) { + var id = updater.id; + this.updaters.set(id, updater); + this.geometry.set(id, geometryInstance); + // Updaters with dynamic attributes must be tracked separately, may exit the batch + if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { + this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + // Listen for show changes. These will be synchronized in updateShows. + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(updater.id, updater); + } + })); + } + this.createPrimitive = true; + }; + + Batch.prototype.remove = function(updater) { + var id = updater.id; + this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; + if (this.updaters.remove(id)) { + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); + } + return true; + } + return false; + }; + + Batch.prototype.update = function(time) { + var isUpdated = true; + var primitive = this.primitive; + var orderedGroundPrimitives = this.orderedGroundPrimitives; + var geometries = this.geometry.values; + var attributes; + var i; + + if (this.createPrimitive) { + var geometriesLength = geometries.length; + if (geometriesLength > 0) { + if (defined(primitive)) { + // Keep a handle to the old primitive so it can be removed when the updated version is ready. + if (!defined(this.oldPrimitive)) { + this.oldPrimitive = primitive; + } else { + // For if the new primitive changes again before it is ready. + orderedGroundPrimitives.remove(primitive); + } + } + + for (i = 0; i < geometriesLength; i++) { + var geometry = geometries[i]; + var originalAttributes = geometry.attributes; + attributes = this.attributes.get(geometry.id.id); + + if (defined(attributes)) { + if (defined(originalAttributes.show)) { + originalAttributes.show.value = attributes.show; + } + if (defined(originalAttributes.color)) { + originalAttributes.color.value = attributes.color; + } + } + } + + primitive = new GroundPolylinePrimitive({ + show : false, + asynchronous : true, + geometryInstances : geometries, + appearance : new this.appearanceType() + }); + + if (this.appearanceType === PolylineMaterialAppearance) { + this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + primitive.appearance.material = this.material; + } + + orderedGroundPrimitives.add(primitive, this.zIndex); + isUpdated = false; + } else { + if (defined(primitive)) { + orderedGroundPrimitives.remove(primitive); + primitive = undefined; + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + orderedGroundPrimitives.remove(oldPrimitive); + this.oldPrimitive = undefined; + } + } + + this.attributes.removeAll(); + this.primitive = primitive; + this.createPrimitive = false; + } else if (defined(primitive) && primitive.ready) { + primitive.show = true; + if (defined(this.oldPrimitive)) { + orderedGroundPrimitives.remove(this.oldPrimitive); + this.oldPrimitive = undefined; + } + + if (this.appearanceType === PolylineMaterialAppearance) { + this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + this.primitive.appearance.material = this.material; + } + var updatersWithAttributes = this.updatersWithAttributes.values; + var length = updatersWithAttributes.length; + for (i = 0; i < length; i++) { + var updater = updatersWithAttributes[i]; + var entity = updater.entity; + var instance = this.geometry.get(updater.id); + + attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + + var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; + if (!Property.isConstant(distanceDisplayConditionProperty)) { + var distanceDisplayCondition = Property.getValueOrDefault(distanceDisplayConditionProperty, time, defaultDistanceDisplayCondition, distanceDisplayConditionScratch); + if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { + attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); + attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + } + } + } + + this.updateShows(primitive); + } else if (defined(primitive) && !primitive.ready) { + isUpdated = false; + } + return isUpdated; + }; + + Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var entity = updater.entity; + var instance = this.geometry.get(updater.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + } + this.showsUpdated.removeAll(); + }; + + Batch.prototype.contains = function(updater) { + return this.updaters.contains(updater.id); + }; + + Batch.prototype.getBoundingSphere = function(updater, result) { + var primitive = this.primitive; + if (!primitive.ready) { + return BoundingSphereState.PENDING; + } + var attributes = primitive.getGeometryInstanceAttributes(updater.entity); + if (!defined(attributes) || !defined(attributes.boundingSphere) || + (defined(attributes.show) && attributes.show[0] === 0)) { + return BoundingSphereState.FAILED; + } + attributes.boundingSphere.clone(result); + return BoundingSphereState.DONE; + }; + + Batch.prototype.destroy = function() { + var primitive = this.primitive; + var orderedGroundPrimitives = this.orderedGroundPrimitives; + if (defined(primitive)) { + orderedGroundPrimitives.remove(primitive); + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + orderedGroundPrimitives.remove(oldPrimitive); + } + this.removeMaterialSubscription(); + }; + + /** + * @private + */ + function StaticGroundPolylinePerMaterialBatch(orderedGroundPrimitives) { + this._items = []; + this._orderedGroundPrimitives = orderedGroundPrimitives; + } + + StaticGroundPolylinePerMaterialBatch.prototype.add = function(time, updater) { + var items = this._items; + var length = items.length; + var geometryInstance = updater.createFillGeometryInstance(time); + var zIndex = Property.getValueOrDefault(updater.zIndex, 0); + // Check if the Entity represented by the updater has the same material or a material representable with per-instance color. + for (var i = 0; i < length; ++i) { + var item = items[i]; + if (item.isMaterial(updater) && + item.zIndex === zIndex) { + item.add(time, updater, geometryInstance); + return; + } + } + // If a compatible batch wasn't found, create a new batch. + var batch = new Batch(this._orderedGroundPrimitives, updater.fillMaterialProperty, zIndex); + batch.add(time, updater, geometryInstance); + items.push(batch); + }; + + StaticGroundPolylinePerMaterialBatch.prototype.remove = function(updater) { + var items = this._items; + var length = items.length; + for (var i = length - 1; i >= 0; i--) { + var item = items[i]; + if (item.remove(updater)) { + if (item.updaters.length === 0) { + items.splice(i, 1); + item.destroy(); + } + break; + } + } + }; + + StaticGroundPolylinePerMaterialBatch.prototype.update = function(time) { + var i; + var items = this._items; + var length = items.length; + + for (i = length - 1; i >= 0; i--) { + var item = items[i]; + if (item.invalidated) { + items.splice(i, 1); + var updaters = item.updaters.values; + var updatersLength = updaters.length; + for (var h = 0; h < updatersLength; h++) { + this.add(time, updaters[h]); + } + item.destroy(); + } + } + + var isUpdated = true; + for (i = 0; i < length; i++) { + isUpdated = items[i].update(time) && isUpdated; + } + return isUpdated; + }; + + StaticGroundPolylinePerMaterialBatch.prototype.getBoundingSphere = function(updater, result) { + var items = this._items; + var length = items.length; + for (var i = 0; i < length; i++) { + var item = items[i]; + if (item.contains(updater)){ + return item.getBoundingSphere(updater, result); + } + } + return BoundingSphereState.FAILED; + }; + + StaticGroundPolylinePerMaterialBatch.prototype.removeAllPrimitives = function() { + var items = this._items; + var length = items.length; + for (var i = 0; i < length; i++) { + items[i].destroy(); + } + this._items.length = 0; + }; + + return StaticGroundPolylinePerMaterialBatch; +}); diff --git a/Source/Scene/GroundPolylinePrimitive.js b/Source/Scene/GroundPolylinePrimitive.js index 72b2df985065..b00f2a4bac4e 100644 --- a/Source/Scene/GroundPolylinePrimitive.js +++ b/Source/Scene/GroundPolylinePrimitive.js @@ -364,6 +364,17 @@ define([ return GroundPolylinePrimitive._initPromise; }; + /** + * Synchronous check for if GroundPolylinePrimitive is initialized and + * synchronous GroundPolylinePrimitives can be created. + * + * @returns {Boolean} Whether GroundPolylinePrimitive is initialized. + * @private + */ + GroundPolylinePrimitive._isInitialized = function() { + return GroundPolylinePrimitive._initialized; + }; + // For use with web workers. GroundPolylinePrimitive._initializeTerrainHeightsWorker = function() { var initPromise = GroundPolylinePrimitive._initPromise; diff --git a/Specs/DataSources/PolylineGeometryUpdaterSpec.js b/Specs/DataSources/PolylineGeometryUpdaterSpec.js index d231e63ebb9e..fc2b8dcf8291 100644 --- a/Specs/DataSources/PolylineGeometryUpdaterSpec.js +++ b/Specs/DataSources/PolylineGeometryUpdaterSpec.js @@ -1,17 +1,20 @@ defineSuite([ 'DataSources/PolylineGeometryUpdater', + 'Core/ApproximateTerrainHeights', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', 'Core/DistanceDisplayCondition', 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/GroundPolylineGeometry', 'Core/JulianDate', 'Core/PolylinePipeline', 'Core/ShowGeometryInstanceAttribute', 'Core/TimeInterval', 'Core/TimeIntervalCollection', 'DataSources/BoundingSphereState', + 'DataSources/CallbackProperty', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', 'DataSources/Entity', @@ -22,23 +25,28 @@ defineSuite([ 'DataSources/SampledProperty', 'DataSources/TimeIntervalCollectionProperty', 'Scene/Globe', + 'Scene/GroundPolylinePrimitive', 'Scene/ShadowMode', 'Specs/createDynamicProperty', - 'Specs/createScene' + 'Specs/createScene', + 'Specs/pollToPromise' ], function( PolylineGeometryUpdater, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Color, ColorGeometryInstanceAttribute, DistanceDisplayCondition, DistanceDisplayConditionGeometryInstanceAttribute, + GroundPolylineGeometry, JulianDate, PolylinePipeline, ShowGeometryInstanceAttribute, TimeInterval, TimeIntervalCollection, BoundingSphereState, + CallbackProperty, ColorMaterialProperty, ConstantProperty, Entity, @@ -49,35 +57,46 @@ defineSuite([ SampledProperty, TimeIntervalCollectionProperty, Globe, + GroundPolylinePrimitive, ShadowMode, createDynamicProperty, - createScene) { + createScene, + pollToPromise) { 'use strict'; var scene; beforeAll(function(){ scene = createScene(); scene.globe = new Globe(); + return GroundPolylinePrimitive.initializeTerrainHeights(); }); afterAll(function(){ scene.destroyForSpecs(); + + GroundPolylinePrimitive._initPromise = undefined; + GroundPolylinePrimitive._initialized = false; + + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); beforeEach(function() { scene.primitives.removeAll(); + scene.groundPrimitives.removeAll(); }); var time = JulianDate.now(); - function createBasicPolyline() { - var polyline = new PolylineGraphics(); - polyline.positions = new ConstantProperty(Cartesian3.fromRadiansArray([ + var basicPositions = Cartesian3.fromRadiansArray([ 0, 0, 1, 0, 1, 1, 0, 1 - ])); + ]); + function createBasicPolyline() { + var polyline = new PolylineGraphics(); + polyline.positions = new ConstantProperty(basicPositions); var entity = new Entity(); entity.polyline = polyline; return entity; @@ -100,6 +119,9 @@ defineSuite([ expect(updater.shadowsProperty).toBe(undefined); expect(updater.distanceDisplayConditionProperty).toBe(undefined); expect(updater.isDynamic).toBe(false); + expect(updater.clampToGround).toBe(false); + expect(updater.zIndex).toBe(0); + expect(updater.isOutlineVisible(time)).toBe(false); expect(updater.isFilled(time)).toBe(false); updater.destroy(); @@ -141,6 +163,8 @@ defineSuite([ expect(updater.shadowsProperty).toEqual(new ConstantProperty(ShadowMode.DISABLED)); expect(updater.distanceDisplayConditionProperty).toEqual(new ConstantProperty(new DistanceDisplayCondition())); expect(updater.isDynamic).toBe(false); + expect(updater.clampToGround).toBe(false); + expect(updater.zIndex).toEqual(new ConstantProperty(0)); }); it('Polyline material is correctly exposed.', function() { @@ -202,8 +226,25 @@ defineSuite([ expect(updater.isDynamic).toBe(true); }); + it('A time-varying clampToGround causes geometry to be dynamic', function() { + var entity = createBasicPolyline(); + var updater = new PolylineGeometryUpdater(entity, scene); + entity.polyline.clampToGround = new SampledProperty(Number); + entity.polyline.clampToGround.addSample(time, true); + expect(updater.isDynamic).toBe(true); + }); + + it('A time-varying zIndex causes geometry to be dynamic', function() { + var entity = createBasicPolyline(); + var updater = new PolylineGeometryUpdater(entity, scene); + entity.polyline.zIndex = new SampledProperty(Number); + entity.polyline.zIndex.addSample(time, 1); + expect(updater.isDynamic).toBe(true); + }); + function validateGeometryInstance(options) { var entity = createBasicPolyline(); + var clampToGround = options.clampToGround; var polyline = entity.polyline; polyline.show = new ConstantProperty(options.show); @@ -214,6 +255,7 @@ defineSuite([ polyline.followSurface = new ConstantProperty(options.followSurface); polyline.granularity = new ConstantProperty(options.granularity); polyline.distanceDisplayCondition = options.distanceDisplayCondition; + polyline.clampToGround = new ConstantProperty(clampToGround); var updater = new PolylineGeometryUpdater(entity, scene); @@ -222,21 +264,27 @@ defineSuite([ var attributes; instance = updater.createFillGeometryInstance(time); geometry = instance.geometry; - expect(geometry._width).toEqual(options.width); - expect(geometry._followSurface).toEqual(options.followSurface); - expect(geometry._granularity).toEqual(options.granularity); - attributes = instance.attributes; + + if (clampToGround) { + expect(geometry.width).toEqual(options.width); + } else { + expect(geometry._width).toEqual(options.width); + expect(geometry._followSurface).toEqual(options.followSurface); + expect(geometry._granularity).toEqual(options.granularity); + + if (options.depthFailMaterial && options.depthFailMaterial instanceof ColorMaterialProperty) { + expect(attributes.depthFailColor.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.depthFailMaterial.color.getValue(time))); + } else { + expect(attributes.depthFailColor).toBeUndefined(); + } + } + if (options.material instanceof ColorMaterialProperty) { expect(attributes.color.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.material.color.getValue(time))); } else { expect(attributes.color).toBeUndefined(); } - if (options.depthFailMaterial && options.depthFailMaterial instanceof ColorMaterialProperty) { - expect(attributes.depthFailColor.value).toEqual(ColorGeometryInstanceAttribute.toValue(options.depthFailMaterial.color.getValue(time))); - } else { - expect(attributes.depthFailColor).toBeUndefined(); - } expect(attributes.show.value).toEqual(ShowGeometryInstanceAttribute.toValue(options.show)); if (options.distanceDisplayCondition) { expect(attributes.distanceDisplayCondition.value).toEqual(DistanceDisplayConditionGeometryInstanceAttribute.toValue(options.distanceDisplayCondition)); @@ -249,6 +297,21 @@ defineSuite([ material : new ColorMaterialProperty(Color.RED), width : 3, followSurface : false, + clampToGround : false, + granularity : 1.0 + }); + + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + // On terrain + validateGeometryInstance({ + show : true, + material : new ColorMaterialProperty(Color.RED), + width : 3, + followSurface : false, + clampToGround : true, granularity : 1.0 }); }); @@ -260,6 +323,7 @@ defineSuite([ depthFailMaterial : new ColorMaterialProperty(Color.BLUE), width : 3, followSurface : false, + clampToGround : false, granularity : 1.0 }); }); @@ -271,6 +335,7 @@ defineSuite([ depthFailMaterial : new GridMaterialProperty(), width : 3, followSurface : false, + clampToGround : false, granularity : 1.0 }); }); @@ -281,6 +346,21 @@ defineSuite([ material : new GridMaterialProperty(), width : 4, followSurface : true, + clampToGround : false, + granularity : 0.5 + }); + + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + // On terrain + validateGeometryInstance({ + show : true, + material : new GridMaterialProperty(), + width : 4, + followSurface : true, + clampToGround : true, granularity : 0.5 }); }); @@ -292,6 +372,7 @@ defineSuite([ depthFailMaterial : new ColorMaterialProperty(Color.BLUE), width : 4, followSurface : true, + clampToGround : false, granularity : 0.5 }); }); @@ -303,6 +384,7 @@ defineSuite([ depthFailMaterial : new GridMaterialProperty(), width : 4, followSurface : true, + clampToGround : false, granularity : 0.5 }); }); @@ -313,6 +395,22 @@ defineSuite([ material : new ColorMaterialProperty(Color.RED), width : 3, followSurface : false, + clampToGround : false, + granularity : 1.0, + distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) + }); + + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + // On terrain + validateGeometryInstance({ + show : true, + material : new ColorMaterialProperty(Color.RED), + width : 3, + followSurface : false, + clampToGround : true, granularity : 1.0, distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0) }); @@ -390,12 +488,12 @@ defineSuite([ var primitives = scene.primitives; expect(primitives.length).toBe(0); - var dynamicUpdater = updater.createDynamicUpdater(primitives); + var dynamicUpdater = updater.createDynamicUpdater(primitives, scene.groundPrimitives); expect(dynamicUpdater.isDestroyed()).toBe(false); - expect(primitives.length).toBe(1); dynamicUpdater.update(time2); + expect(primitives.length).toBe(1); var polylineCollection = primitives.get(0); var primitive = polylineCollection.get(0); @@ -416,6 +514,111 @@ defineSuite([ updater.destroy(); }); + it('clampToGround can be dynamic', function() { + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + var entity = new Entity(); + var polyline = new PolylineGraphics(); + entity.polyline = polyline; + + var time = new JulianDate(0, 0); + + var isClampedToGround = true; + var clampToGround = new CallbackProperty(function() { + return isClampedToGround; + }, false); + + polyline.show = new ConstantProperty(true); + polyline.width = new ConstantProperty(1.0); + polyline.positions = new ConstantProperty([Cartesian3.fromDegrees(0, 0, 0), Cartesian3.fromDegrees(0, 1, 0)]); + polyline.material = new ColorMaterialProperty(Color.RED); + polyline.followSurface = new ConstantProperty(false); + polyline.granularity = new ConstantProperty(0.001); + polyline.clampToGround = clampToGround; + + var updater = new PolylineGeometryUpdater(entity, scene); + + var groundPrimitives = scene.groundPrimitives; + expect(groundPrimitives.length).toBe(0); + + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, groundPrimitives); + expect(dynamicUpdater.isDestroyed()).toBe(false); + expect(groundPrimitives.length).toBe(0); + + dynamicUpdater.update(time); + + expect(groundPrimitives.length).toBe(1); + var primitive = groundPrimitives.get(0); + + expect(primitive.show).toEqual(true); + + isClampedToGround = false; + dynamicUpdater.update(time); + + expect(groundPrimitives.length).toBe(0); + + dynamicUpdater.destroy(); + updater.destroy(); + }); + + it('initializes terrain heights when clampToGround is dynamic', function() { + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + var approximateTerrainHeightsInitPromise = ApproximateTerrainHeights._initPromise; + var approximateTerrainHeightsTerrainHeights = ApproximateTerrainHeights._terrainHeights; + var groundPolylinePrimitiveInitPromise = GroundPolylinePrimitive._initPromise; + + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + GroundPolylinePrimitive._initPromise = undefined; + GroundPolylinePrimitive._initialized = false; + + var entity = new Entity(); + var polyline = new PolylineGraphics(); + entity.polyline = polyline; + + var time = new JulianDate(0, 0); + + var isClampedToGround = true; + var clampToGround = new CallbackProperty(function() { + return isClampedToGround; + }, false); + + polyline.show = new ConstantProperty(true); + polyline.width = new ConstantProperty(1.0); + polyline.positions = new ConstantProperty([Cartesian3.fromDegrees(0, 0, 0), Cartesian3.fromDegrees(0, 1, 0)]); + polyline.material = new ColorMaterialProperty(Color.RED); + polyline.followSurface = new ConstantProperty(false); + polyline.granularity = new ConstantProperty(0.001); + polyline.clampToGround = clampToGround; + + var updater = new PolylineGeometryUpdater(entity, scene); + + var groundPrimitives = scene.groundPrimitives; + expect(groundPrimitives.length).toBe(0); + + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, groundPrimitives); + expect(dynamicUpdater.isDestroyed()).toBe(false); + expect(groundPrimitives.length).toBe(0); + + dynamicUpdater.update(time); + + expect(ApproximateTerrainHeights._initPromise).toBeDefined(); + expect(GroundPolylinePrimitive._initPromise).toBeDefined(); + + dynamicUpdater.destroy(); + updater.destroy(); + + ApproximateTerrainHeights._initPromise = approximateTerrainHeightsInitPromise; + ApproximateTerrainHeights._terrainHeights = approximateTerrainHeightsTerrainHeights; + GroundPolylinePrimitive._initPromise = groundPolylinePrimitiveInitPromise; + GroundPolylinePrimitive._initialized = true; + }); + it('geometryChanged event is raised when expected', function() { var entity = createBasicPolyline(); var updater = new PolylineGeometryUpdater(entity, scene); @@ -473,7 +676,7 @@ defineSuite([ var entity = createBasicPolyline(); var updater = new PolylineGeometryUpdater(entity, scene); expect(function() { - return updater.createDynamicUpdater(scene.primitives); + return updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); }).toThrowDeveloperError(); updater.destroy(); }); @@ -485,7 +688,19 @@ defineSuite([ var updater = new PolylineGeometryUpdater(entity, scene); expect(updater.isDynamic).toBe(true); expect(function() { - return updater.createDynamicUpdater(undefined); + return updater.createDynamicUpdater(undefined, scene.groundPrimitives); + }).toThrowDeveloperError(); + updater.destroy(); + }); + + it('createDynamicUpdater throws if groundPrimitives undefined', function() { + var entity = createBasicPolyline(); + entity.polyline.width = new SampledProperty(Number); + entity.polyline.width.addSample(time, 4); + var updater = new PolylineGeometryUpdater(entity, scene); + expect(updater.isDynamic).toBe(true); + expect(function() { + return updater.createDynamicUpdater(scene.primitives); }).toThrowDeveloperError(); updater.destroy(); }); @@ -495,7 +710,7 @@ defineSuite([ entity.polyline.width = new SampledProperty(Number); entity.polyline.width.addSample(time, 4); var updater = new PolylineGeometryUpdater(entity, scene); - var dynamicUpdater = updater.createDynamicUpdater(scene.primitives); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); expect(function() { dynamicUpdater.update(undefined); }).toThrowDeveloperError(); @@ -520,7 +735,7 @@ defineSuite([ entity.polyline.width = createDynamicProperty(1); var updater = new PolylineGeometryUpdater(entity, scene); - var dynamicUpdater = updater.createDynamicUpdater(scene.primitives); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); dynamicUpdater.update(time); var result = new BoundingSphere(0); @@ -535,11 +750,43 @@ defineSuite([ scene.primitives.removeAll(); }); + it('Computes dynamic geometry bounding sphere on terrain.', function() { + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + var entity = createBasicPolyline(); + entity.polyline.width = createDynamicProperty(1); + entity.polyline.clampToGround = true; + + var updater = new PolylineGeometryUpdater(entity, scene); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); + dynamicUpdater.update(time); + + var result = new BoundingSphere(0); + var state = dynamicUpdater.getBoundingSphere(result); + expect(state).toBe(BoundingSphereState.PENDING); + + return pollToPromise(function() { + scene.initializeFrame(); + scene.render(); + state = dynamicUpdater.getBoundingSphere(result); + return state !== BoundingSphereState.PENDING; + }).then(function() { + var primitive = scene.groundPrimitives.get(0); + expect(state).toBe(BoundingSphereState.DONE); + var attributes = primitive.getGeometryInstanceAttributes(entity); + expect(result).toEqual(attributes.boundingSphere); + + updater.destroy(); + }); + }); + it('Fails dynamic geometry bounding sphere for entity without billboard.', function() { var entity = createBasicPolyline(); entity.polyline.width = createDynamicProperty(1); var updater = new PolylineGeometryUpdater(entity, scene); - var dynamicUpdater = updater.createDynamicUpdater(scene.primitives); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); var result = new BoundingSphere(); var state = dynamicUpdater.getBoundingSphere(result); @@ -553,7 +800,7 @@ defineSuite([ var entity = createBasicPolyline(); entity.polyline.width = createDynamicProperty(1); var updater = new PolylineGeometryUpdater(entity, scene); - var dynamicUpdater = updater.createDynamicUpdater(scene.primitives); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); expect(function() { dynamicUpdater.getBoundingSphere(undefined); @@ -568,7 +815,7 @@ defineSuite([ entity.polyline.width = createDynamicProperty(1); scene.globe = undefined; var updater = new PolylineGeometryUpdater(entity, scene); - var dynamicUpdater = updater.createDynamicUpdater(scene.primitives); + var dynamicUpdater = updater.createDynamicUpdater(scene.primitives, scene.groundPrimitives); spyOn(PolylinePipeline, 'generateCartesianArc').and.callThrough(); dynamicUpdater.update(time); expect(PolylinePipeline.generateCartesianArc).not.toHaveBeenCalled(); @@ -576,4 +823,22 @@ defineSuite([ scene.primitives.removeAll(); scene.globe = new Globe(); }); + + it('clampToGround true without support for polylines on terrain does not generate GroundPolylineGeometry', function() { + spyOn(Entity, 'supportsPolylinesOnTerrain').and.callFake(function() { + return false; + }); + + var entity = createBasicPolyline(); + + var polyline = entity.polyline; + polyline.show = new ConstantProperty(true); + polyline.clampToGround = new ConstantProperty(true); + + var updater = new PolylineGeometryUpdater(entity, scene); + expect(updater.clampToGround).toBe(false); + + var instance = updater.createFillGeometryInstance(time); + expect(instance.geometry instanceof GroundPolylineGeometry).toBe(false); + }); }, 'WebGL'); diff --git a/Specs/DataSources/PolylineGraphicsSpec.js b/Specs/DataSources/PolylineGraphicsSpec.js index 76a12489621b..97a69c5a8a19 100644 --- a/Specs/DataSources/PolylineGraphicsSpec.js +++ b/Specs/DataSources/PolylineGraphicsSpec.js @@ -15,7 +15,7 @@ defineSuite([ ConstantProperty, ShadowMode, testDefinitionChanged, - testMaterialDefinitionChanged) { + testMaterialDefinitionChanged) { 'use strict'; it('creates expected instance from raw assignment and construction', function() { @@ -26,9 +26,11 @@ defineSuite([ show : true, width : 1, followSurface : false, + clampToGround : true, granularity : 2, shadows : ShadowMode.DISABLED, - distanceDisplayCondition : new DistanceDisplayCondition() + distanceDisplayCondition : new DistanceDisplayCondition(), + zIndex : 0 }; var polyline = new PolylineGraphics(options); @@ -38,9 +40,11 @@ defineSuite([ expect(polyline.show).toBeInstanceOf(ConstantProperty); expect(polyline.width).toBeInstanceOf(ConstantProperty); expect(polyline.followSurface).toBeInstanceOf(ConstantProperty); + expect(polyline.clampToGround).toBeInstanceOf(ConstantProperty); expect(polyline.granularity).toBeInstanceOf(ConstantProperty); expect(polyline.shadows).toBeInstanceOf(ConstantProperty); expect(polyline.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); + expect(polyline.zIndex).toBeInstanceOf(ConstantProperty); expect(polyline.material.color.getValue()).toEqual(options.material); expect(polyline.depthFailMaterial.color.getValue()).toEqual(options.depthFailMaterial); @@ -48,9 +52,11 @@ defineSuite([ expect(polyline.show.getValue()).toEqual(options.show); expect(polyline.width.getValue()).toEqual(options.width); expect(polyline.followSurface.getValue()).toEqual(options.followSurface); + expect(polyline.clampToGround.getValue()).toEqual(options.clampToGround); expect(polyline.granularity.getValue()).toEqual(options.granularity); expect(polyline.shadows.getValue()).toEqual(options.shadows); expect(polyline.distanceDisplayCondition.getValue()).toEqual(options.distanceDisplayCondition); + expect(polyline.zIndex.getValue()).toEqual(options.zIndex); }); it('merge assigns unassigned properties', function() { @@ -61,9 +67,11 @@ defineSuite([ source.width = new ConstantProperty(); source.show = new ConstantProperty(); source.followSurface = new ConstantProperty(); + source.clampToGround = new ConstantProperty(); source.granularity = new ConstantProperty(); source.shadows = new ConstantProperty(ShadowMode.ENABLED); source.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition()); + source.zIndex = new ConstantProperty(); var target = new PolylineGraphics(); target.merge(source); @@ -73,9 +81,11 @@ defineSuite([ expect(target.width).toBe(source.width); expect(target.show).toBe(source.show); expect(target.followSurface).toBe(source.followSurface); + expect(target.clampToGround).toBe(source.clampToGround); expect(target.granularity).toBe(source.granularity); expect(target.shadows).toBe(source.shadows); expect(target.distanceDisplayCondition).toBe(source.distanceDisplayCondition); + expect(target.zIndex).toBe(source.zIndex); }); it('merge does not assign assigned properties', function() { @@ -86,9 +96,11 @@ defineSuite([ source.width = new ConstantProperty(); source.show = new ConstantProperty(); source.followSurface = new ConstantProperty(); + source.clampToGround = new ConstantProperty(); source.granularity = new ConstantProperty(); source.shadows = new ConstantProperty(); source.distanceDisplayCondition = new ConstantProperty(); + source.zIndex = new ConstantProperty(); var color = new ColorMaterialProperty(); var depthFailColor = new ColorMaterialProperty(); @@ -96,9 +108,11 @@ defineSuite([ var width = new ConstantProperty(); var show = new ConstantProperty(); var followSurface = new ConstantProperty(); + var clampToGround = new ConstantProperty(); var granularity = new ConstantProperty(); var shadows = new ConstantProperty(); var distanceDisplayCondition = new ConstantProperty(); + var zIndex = new ConstantProperty(); var target = new PolylineGraphics(); target.material = color; @@ -107,9 +121,11 @@ defineSuite([ target.width = width; target.show = show; target.followSurface = followSurface; + target.clampToGround = clampToGround; target.granularity = granularity; target.shadows = shadows; target.distanceDisplayCondition = distanceDisplayCondition; + target.zIndex = zIndex; target.merge(source); expect(target.material).toBe(color); @@ -118,9 +134,11 @@ defineSuite([ expect(target.width).toBe(width); expect(target.show).toBe(show); expect(target.followSurface).toBe(followSurface); + expect(target.clampToGround).toBe(clampToGround); expect(target.granularity).toBe(granularity); expect(target.shadows).toBe(shadows); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); + expect(target.zIndex).toBe(zIndex); }); it('clone works', function() { @@ -131,9 +149,11 @@ defineSuite([ source.positions = new ConstantProperty(); source.show = new ConstantProperty(); source.followSurface = new ConstantProperty(); + source.clampToGround = new ConstantProperty(); source.granularity = new ConstantProperty(); source.shadows = new ConstantProperty(); source.distanceDisplayCondition = new ConstantProperty(); + source.zIndex = new ConstantProperty(); var result = source.clone(); expect(result.material).toBe(source.material); @@ -142,9 +162,11 @@ defineSuite([ expect(result.width).toBe(source.width); expect(result.show).toBe(source.show); expect(result.followSurface).toBe(source.followSurface); + expect(result.clampToGround).toBe(source.clampToGround); expect(result.granularity).toBe(source.granularity); expect(result.shadows).toBe(source.shadows); expect(result.distanceDisplayCondition).toBe(source.distanceDisplayCondition); + expect(result.zIndex).toBe(source.zIndex); }); it('merge throws if source undefined', function() { @@ -162,8 +184,10 @@ defineSuite([ testDefinitionChanged(property, 'positions', [], []); testDefinitionChanged(property, 'width', 3, 4); testDefinitionChanged(property, 'followSurface', false, true); + testDefinitionChanged(property, 'clampToGround', false, true); testDefinitionChanged(property, 'granularity', 2, 1); testDefinitionChanged(property, 'shadows', ShadowMode.ENABLED, ShadowMode.DISABLED); testDefinitionChanged(property, 'distanceDisplayCondition', new DistanceDisplayCondition(), new DistanceDisplayCondition(10.0, 20.0)); + testDefinitionChanged(property, 'zIndex', 20, 5); }); }); diff --git a/Specs/DataSources/PolylineVisualizerSpec.js b/Specs/DataSources/PolylineVisualizerSpec.js index 49a2695a29fe..f2080ee91fd7 100644 --- a/Specs/DataSources/PolylineVisualizerSpec.js +++ b/Specs/DataSources/PolylineVisualizerSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/PolylineVisualizer', + 'Core/ApproximateTerrainHeights', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', @@ -14,12 +15,7 @@ defineSuite([ 'DataSources/Entity', 'DataSources/EntityCollection', 'DataSources/PolylineArrowMaterialProperty', - 'DataSources/PolylineGeometryUpdater', 'DataSources/PolylineGraphics', - 'DataSources/SampledProperty', - 'DataSources/StaticGeometryColorBatch', - 'DataSources/StaticGeometryPerMaterialBatch', - 'DataSources/StaticOutlineGeometryBatch', 'Scene/PolylineColorAppearance', 'Scene/PolylineMaterialAppearance', 'Scene/ShadowMode', @@ -28,6 +24,7 @@ defineSuite([ 'Specs/pollToPromise' ], function( PolylineVisualizer, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Color, @@ -42,12 +39,7 @@ defineSuite([ Entity, EntityCollection, PolylineArrowMaterialProperty, - PolylineGeometryUpdater, PolylineGraphics, - SampledProperty, - StaticGeometryColorBatch, - StaticGeometryPerMaterialBatch, - StaticOutlineGeometryBatch, PolylineColorAppearance, PolylineMaterialAppearance, ShadowMode, @@ -61,10 +53,15 @@ defineSuite([ var scene; beforeAll(function() { scene = createScene(); + + ApproximateTerrainHeights.initialize(); }); afterAll(function() { scene.destroyForSpecs(); + + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); it('Can create and destroy', function() { @@ -155,6 +152,50 @@ defineSuite([ }); }); + it('Creates and removes static polylines on terrain', function() { + if (!Entity.supportsPolylinesOnTerrain(scene)) { + return; + } + + var objects = new EntityCollection(); + var visualizer = new PolylineVisualizer(scene, objects, scene.groundPrimitives); + + var polyline = new PolylineGraphics(); + polyline.positions = new ConstantProperty([Cartesian3.fromDegrees(0.0, 0.0), Cartesian3.fromDegrees(0.0, 1.0)]); + polyline.material = new ColorMaterialProperty(); + polyline.clampToGround = new ConstantProperty(true); + + var entity = new Entity(); + entity.polyline = polyline; + objects.add(entity); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + var primitive = scene.groundPrimitives.get(0); + var attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes).toBeDefined(); + expect(attributes.show).toEqual(ShowGeometryInstanceAttribute.toValue(true)); + expect(attributes.color).toEqual(ColorGeometryInstanceAttribute.toValue(Color.WHITE)); + expect(primitive.appearance).toBeInstanceOf(PolylineColorAppearance); + expect(primitive.appearance.closed).toBe(false); + + objects.remove(entity); + + return pollToPromise(function() { + scene.initializeFrame(); + expect(visualizer.update(time)).toBe(true); + scene.render(time); + return scene.groundPrimitives.length === 0; + }).then(function(){ + visualizer.destroy(); + }); + }); + }); + function createAndRemoveGeometryWithShadows(shadows) { var objects = new EntityCollection(); var visualizer = new PolylineVisualizer(scene, objects); @@ -468,6 +509,7 @@ defineSuite([ it('removes the listener from the entity collection when destroyed', function() { var entityCollection = new EntityCollection(); var visualizer = new PolylineVisualizer(scene, entityCollection); + expect(entityCollection.collectionChanged.numberOfListeners).toEqual(1); visualizer.destroy(); expect(entityCollection.collectionChanged.numberOfListeners).toEqual(0); @@ -529,7 +571,7 @@ defineSuite([ visualizer.destroy(); }); - it('Can remove and entity and then add a new new instance with the same id.', function() { + it('Can remove an entity and then add a new instance with the same id.', function() { var objects = new EntityCollection(); var visualizer = new PolylineVisualizer(scene, objects); diff --git a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js index 3d33aa31f12b..f41164f98254 100644 --- a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js +++ b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js @@ -111,10 +111,10 @@ defineSuite([ var isUpdated = batch.update(time); scene.render(time); return isUpdated; - }).then(function() { - expect(scene.primitives.length).toEqual(2); - batch.removeAllPrimitives(); }); + }).then(function() { + expect(scene.primitives.length).toEqual(2); + batch.removeAllPrimitives(); }); }); diff --git a/Specs/DataSources/StaticGroundPolylinePerMaterialBatchSpec.js b/Specs/DataSources/StaticGroundPolylinePerMaterialBatchSpec.js new file mode 100644 index 000000000000..837d0ecf56c1 --- /dev/null +++ b/Specs/DataSources/StaticGroundPolylinePerMaterialBatchSpec.js @@ -0,0 +1,388 @@ +defineSuite([ + 'DataSources/StaticGroundPolylinePerMaterialBatch', + 'Core/BoundingSphere', + 'Core/Cartesian3', + 'Core/Color', + 'Core/DistanceDisplayCondition', + 'Core/JulianDate', + 'Core/Math', + 'Core/TimeInterval', + 'Core/TimeIntervalCollection', + 'DataSources/BoundingSphereState', + 'DataSources/ConstantProperty', + 'DataSources/Entity', + 'DataSources/PolylineOutlineMaterialProperty', + 'DataSources/PolylineGeometryUpdater', + 'DataSources/PolylineGraphics', + 'DataSources/TimeIntervalCollectionProperty', + 'Scene/GroundPolylinePrimitive', + 'Scene/MaterialAppearance', + 'Specs/createScene', + 'Specs/pollToPromise' + ], function( + StaticGroundPolylinePerMaterialBatch, + BoundingSphere, + Cartesian3, + Color, + DistanceDisplayCondition, + JulianDate, + CesiumMath, + TimeInterval, + TimeIntervalCollection, + BoundingSphereState, + ConstantProperty, + Entity, + PolylineOutlineMaterialProperty, + PolylineGeometryUpdater, + PolylineGraphics, + TimeIntervalCollectionProperty, + GroundPolylinePrimitive, + MaterialAppearance, + createScene, + pollToPromise) { + 'use strict'; + + var time = JulianDate.now(); + var scene; + beforeAll(function() { + scene = createScene(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + function createGroundPolyline() { + var polyline = new PolylineGraphics(); + polyline.clampToGround = new ConstantProperty(true); + polyline.positions = new ConstantProperty(Cartesian3.fromRadiansArray([ + 0, 0, + 1, 0, + 1, 1, + 0, 1 + ])); + return polyline; + } + + it('handles shared material being invalidated with geometry', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives); + + var polyline1 = createGroundPolyline(); + polyline1.material = new PolylineOutlineMaterialProperty(); + + var entity = new Entity({ + polyline : polyline1 + }); + + var polyline2 = createGroundPolyline(); + polyline2.material = new PolylineOutlineMaterialProperty(); + + var entity2 = new Entity({ + polyline : polyline2 + }); + + var updater = new PolylineGeometryUpdater(entity, scene); + var updater2 = new PolylineGeometryUpdater(entity2, scene); + batch.add(time, updater); + batch.add(time, updater2); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + polyline1.material.outlineWidth = new ConstantProperty(0.5); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(2); + batch.removeAllPrimitives(); + }); + }); + }); + + it('updates with sampled distance display condition out of range', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var validTime = JulianDate.fromIso8601('2018-02-14T04:10:00+1100'); + var ddc = new TimeIntervalCollectionProperty(); + ddc.intervals.addInterval(TimeInterval.fromIso8601({ + iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100', + data: new DistanceDisplayCondition(1.0, 2.0) + })); + var polyline = createGroundPolyline(); + polyline.distanceDisplayCondition = ddc; + var entity = new Entity({ + availability: new TimeIntervalCollection([TimeInterval.fromIso8601({iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100'})]), + polyline: polyline + }); + + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives); + + var updater = new PolylineGeometryUpdater(entity, scene); + batch.add(validTime, updater); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(validTime); + scene.render(validTime); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + var attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes.distanceDisplayCondition).toEqualEpsilon([1.0, 2.0], CesiumMath.EPSILON6); + + batch.update(time); + scene.render(time); + + primitive = scene.groundPrimitives.get(0); + attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]); + + batch.removeAllPrimitives(); + }); + }); + + it('shows only one primitive while rebuilding primitive', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives, MaterialAppearance); + + function buildEntity() { + var polyline = createGroundPolyline(); + polyline.material = new PolylineOutlineMaterialProperty({ + color : Color.ORANGE, + outlineWidth : 2, + outlineColor : Color.BLACK + }); + + return new Entity({ + polyline : polyline + }); + } + + function renderScene() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + } + + var entity1 = buildEntity(); + var entity2 = buildEntity(); + + var updater1 = new PolylineGeometryUpdater(entity1, scene); + var updater2 = new PolylineGeometryUpdater(entity2, scene); + + batch.add(time, updater1); + return pollToPromise(renderScene) + .then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + expect(primitive.show).toBeTruthy(); + }) + .then(function() { + batch.add(time, updater2); + }) + .then(function() { + return pollToPromise(function() { + renderScene(); + return scene.groundPrimitives.length === 2; + }); + }) + .then(function() { + var showCount = 0; + expect(scene.groundPrimitives.length).toEqual(2); + showCount += !!scene.groundPrimitives.get(0).show; + showCount += !!scene.groundPrimitives.get(1).show; + expect(showCount).toEqual(1); + }) + .then(function() { + return pollToPromise(renderScene); + }) + .then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + expect(primitive.show).toBeTruthy(); + + batch.removeAllPrimitives(); + }); + }); + + it('batches entities that both use color materials', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives, MaterialAppearance); + var polyline1 = createGroundPolyline(); + polyline1.material = Color.RED; + var entity = new Entity({ + polyline : polyline1 + }); + + var polyline2 = createGroundPolyline(); + polyline2.material = Color.RED; + var entity2 = new Entity({ + polyline : polyline2 + }); + + var updater = new PolylineGeometryUpdater(entity, scene); + var updater2 = new PolylineGeometryUpdater(entity2, scene); + batch.add(time, updater); + batch.add(time, updater2); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + batch.removeAllPrimitives(); + }); + }); + + it('batches entities with the same material but different Z indices separately', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives); + + var polyline1 = createGroundPolyline(); + polyline1.material = new PolylineOutlineMaterialProperty(); + polyline1.zIndex = 0; + + var entity = new Entity({ + polyline : polyline1 + }); + + var polyline2 = createGroundPolyline(); + polyline2.material = new PolylineOutlineMaterialProperty(); + polyline2.zIndex = 1; + + var entity2 = new Entity({ + polyline : polyline2 + }); + + var updater = new PolylineGeometryUpdater(entity, scene); + var updater2 = new PolylineGeometryUpdater(entity2, scene); + batch.add(time, updater); + batch.add(time, updater2); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(2); + + batch.removeAllPrimitives(); + }); + }); + + it('removes entities', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives); + + var polyline1 = createGroundPolyline(); + polyline1.material = new PolylineOutlineMaterialProperty(); + + var entity = new Entity({ + polyline : polyline1 + }); + + var updater = new PolylineGeometryUpdater(entity, scene); + batch.add(time, updater); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + batch.remove(updater); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(0); + batch.removeAllPrimitives(); + }); + }); + + it('gets bounding spheres', function() { + if (!GroundPolylinePrimitive.isSupported(scene)) { + // Don't fail if GroundPolylinePrimitive is not supported + return; + } + + var resultSphere = new BoundingSphere(); + var batch = new StaticGroundPolylinePerMaterialBatch(scene.groundPrimitives); + + var polyline1 = createGroundPolyline(); + polyline1.material = new PolylineOutlineMaterialProperty(); + + var entity = new Entity({ + polyline : polyline1 + }); + + var updater = new PolylineGeometryUpdater(entity, scene); + + var state = batch.getBoundingSphere(updater, resultSphere); + expect(state).toEqual(BoundingSphereState.FAILED); + + batch.add(time, updater); + + batch.update(time); + state = batch.getBoundingSphere(updater, resultSphere); + expect(state).toEqual(BoundingSphereState.PENDING); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + state = batch.getBoundingSphere(updater, resultSphere); + expect(state).toEqual(BoundingSphereState.DONE); + + batch.removeAllPrimitives(); + }); + }); +});