From 55da339d699de1e339b230ba25eaa41753779f09 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 14 Jun 2018 15:51:36 -0400 Subject: [PATCH 1/3] add support for polylines on terrain via entity API --- Apps/Sandcastle/gallery/Ground Clamping.html | 30 ++ Apps/Sandcastle/gallery/Polyline.html | 5 +- .../gallery/Z-Indexing Geometry.html | 31 ++ .../gallery/Z-Indexing Geometry.jpg | Bin 18128 -> 20474 bytes CHANGES.md | 6 + Source/DataSources/DataSourceDisplay.js | 2 +- Source/DataSources/Entity.js | 14 + Source/DataSources/PolylineGeometryUpdater.js | 154 ++++++- Source/DataSources/PolylineGraphics.js | 37 +- Source/DataSources/PolylineVisualizer.js | 24 +- .../StaticGroundPolylinePerMaterialBatch.js | 365 ++++++++++++++++ .../PolylineGeometryUpdaterSpec.js | 204 ++++++++- Specs/DataSources/PolylineGraphicsSpec.js | 28 +- Specs/DataSources/PolylineVisualizerSpec.js | 60 ++- ...taticGroundGeometryPerMaterialBatchSpec.js | 6 +- ...taticGroundPolylinePerMaterialBatchSpec.js | 388 ++++++++++++++++++ 16 files changed, 1290 insertions(+), 64 deletions(-) create mode 100644 Source/DataSources/StaticGroundPolylinePerMaterialBatch.js create mode 100644 Specs/DataSources/StaticGroundPolylinePerMaterialBatchSpec.js diff --git a/Apps/Sandcastle/gallery/Ground Clamping.html b/Apps/Sandcastle/gallery/Ground Clamping.html index bba58ebb09e9..d30aae95936d 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 is 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..459e88b2026c 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 is not supported on this platform, Z-index will be ignored'); +} + +if (!Cesium.Entity.supportsMaterialsforEntitiesOnTerrain(viewer.scene)) { + console.log('Textured materials on terrain polygons is 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 caae2ada52e648a428a3f0d2e8ce331172300d2b..e9d0bf936912938e93d90d07964a8991fd27e0f5 100644 GIT binary patch literal 20474 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 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..d375d9c6111f 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,7 +558,7 @@ define([ ellipsoid : undefined }; - function DynamicGeometryUpdater(primitives, geometryUpdater) { + function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) { var sceneId = geometryUpdater._scene.id; var polylineCollection = polylineCollections[sceneId]; @@ -513,8 +575,11 @@ define([ this._line = line; this._primitives = primitives; + this._groundPrimitives = groundPrimitives; + this._groundPolylinePrimitive = undefined; this._geometryUpdater = geometryUpdater; this._positions = []; + this._terrainHeightsReady = false; } DynamicGeometryUpdater.prototype.update = function(time) { @@ -535,6 +600,54 @@ define([ return; } + // 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) { + var that = this; + + // Load terrain heights + if (!this._terrainHeightsReady) { + GroundPolylinePrimitive.initializeTerrainHeights() + .then(function() { + that._terrainHeightsReady = true; + }); + return; + } + + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var appearance; + if (fillMaterialProperty instanceof ColorMaterialProperty) { + appearance = new PolylineColorAppearance(); + } else { + var material = MaterialProperty.getValue(time, fillMaterialProperty, line.material); + appearance = new PolylineMaterialAppearance({ + material : material, + translucent : material.isTranslucent() + }); + line.material = material; + } + + this._groundPolylinePrimitive = groundPrimitives.add(new GroundPolylinePrimitive({ + geometryInstances : geometryUpdater.createFillGeometryInstance(time), + appearance : appearance, + asynchronous : false + }), Property.getValueOrUndefined(geometryUpdater.zIndex, time)); + + // Hide the polyline collection + line.show = false; + return; + } + var followSurface = Property.getValueOrDefault(polyline._followSurface, time, true); var globe = geometryUpdater._scene.globe; if (followSurface && defined(globe)) { @@ -578,6 +691,9 @@ define([ 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/Specs/DataSources/PolylineGeometryUpdaterSpec.js b/Specs/DataSources/PolylineGeometryUpdaterSpec.js index d231e63ebb9e..33c9e0a0d659 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', @@ -27,18 +30,21 @@ defineSuite([ 'Specs/createScene' ], function( PolylineGeometryUpdater, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Color, ColorGeometryInstanceAttribute, DistanceDisplayCondition, DistanceDisplayConditionGeometryInstanceAttribute, + GroundPolylineGeometry, JulianDate, PolylinePipeline, ShowGeometryInstanceAttribute, TimeInterval, TimeIntervalCollection, BoundingSphereState, + CallbackProperty, ColorMaterialProperty, ConstantProperty, Entity, @@ -58,10 +64,14 @@ defineSuite([ beforeAll(function(){ scene = createScene(); scene.globe = new Globe(); + return ApproximateTerrainHeights.initialize(); }); afterAll(function(){ scene.destroyForSpecs(); + + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); beforeEach(function() { @@ -100,6 +110,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 +154,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 +217,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 +246,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 +255,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 +288,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 +314,7 @@ defineSuite([ depthFailMaterial : new ColorMaterialProperty(Color.BLUE), width : 3, followSurface : false, + clampToGround : false, granularity : 1.0 }); }); @@ -271,6 +326,7 @@ defineSuite([ depthFailMaterial : new GridMaterialProperty(), width : 3, followSurface : false, + clampToGround : false, granularity : 1.0 }); }); @@ -281,6 +337,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 +363,7 @@ defineSuite([ depthFailMaterial : new ColorMaterialProperty(Color.BLUE), width : 4, followSurface : true, + clampToGround : false, granularity : 0.5 }); }); @@ -303,6 +375,7 @@ defineSuite([ depthFailMaterial : new GridMaterialProperty(), width : 4, followSurface : true, + clampToGround : false, granularity : 0.5 }); }); @@ -313,6 +386,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,7 +479,7 @@ 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); @@ -416,6 +505,57 @@ 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); + + // Update twice for terrain heights initialization + dynamicUpdater.update(time); + 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('geometryChanged event is raised when expected', function() { var entity = createBasicPolyline(); var updater = new PolylineGeometryUpdater(entity, scene); @@ -473,7 +613,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 +625,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 +647,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 +672,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); @@ -539,7 +691,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); var result = new BoundingSphere(); var state = dynamicUpdater.getBoundingSphere(result); @@ -553,7 +705,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 +720,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 +728,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..83b54bd611fc 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,46 @@ defineSuite([ }); }); + it('Creates and removes static polylines on terrain', function() { + 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 +505,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 +567,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(); + }); + }); +}); From 3d871a65889f9cb5e9288178de9efa971b78e6f1 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 14 Jun 2018 17:01:21 -0400 Subject: [PATCH 2/3] fix failing tests --- Specs/DataSources/PolylineVisualizerSpec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Specs/DataSources/PolylineVisualizerSpec.js b/Specs/DataSources/PolylineVisualizerSpec.js index 83b54bd611fc..f2080ee91fd7 100644 --- a/Specs/DataSources/PolylineVisualizerSpec.js +++ b/Specs/DataSources/PolylineVisualizerSpec.js @@ -153,6 +153,10 @@ 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); From d6c37fa2191ce3a70e372df225cd0b283f553174 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 18 Jun 2018 15:03:45 -0400 Subject: [PATCH 3/3] PR comments --- Apps/Sandcastle/gallery/Ground Clamping.html | 2 +- .../gallery/Z-Indexing Geometry.html | 4 +- Source/DataSources/PolylineGeometryUpdater.js | 112 +++++++++++------ Source/Scene/GroundPolylinePrimitive.js | 11 ++ .../PolylineGeometryUpdaterSpec.js | 115 ++++++++++++++++-- 5 files changed, 197 insertions(+), 47 deletions(-) diff --git a/Apps/Sandcastle/gallery/Ground Clamping.html b/Apps/Sandcastle/gallery/Ground Clamping.html index d30aae95936d..6c45eb7006ca 100644 --- a/Apps/Sandcastle/gallery/Ground Clamping.html +++ b/Apps/Sandcastle/gallery/Ground Clamping.html @@ -223,7 +223,7 @@ onselect : function() { if (!Cesium.Entity.supportsPolylinesOnTerrain(viewer.scene)) { - console.log('Polylines on terrain is not supported on this platform'); + console.log('Polylines on terrain are not supported on this platform'); } viewer.entities.add({ diff --git a/Apps/Sandcastle/gallery/Z-Indexing Geometry.html b/Apps/Sandcastle/gallery/Z-Indexing Geometry.html index 459e88b2026c..6940e7dd4555 100644 --- a/Apps/Sandcastle/gallery/Z-Indexing Geometry.html +++ b/Apps/Sandcastle/gallery/Z-Indexing Geometry.html @@ -86,11 +86,11 @@ }); if (!Cesium.Entity.supportsPolylinesOnTerrain(viewer.scene)) { - console.log('Polylines on terrain is not supported on this platform, Z-index will be ignored'); + 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 is not supported on this platform, Z-index will be ignored'); + console.log('Textured materials on terrain polygons are not supported on this platform, Z-index will be ignored'); } viewer.entities.add({ diff --git a/Source/DataSources/PolylineGeometryUpdater.js b/Source/DataSources/PolylineGeometryUpdater.js index d375d9c6111f..5cd450d676a3 100644 --- a/Source/DataSources/PolylineGeometryUpdater.js +++ b/Source/DataSources/PolylineGeometryUpdater.js @@ -559,9 +559,24 @@ define([ }; function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) { - var sceneId = geometryUpdater._scene.id; + 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; @@ -571,34 +586,18 @@ define([ } var line = polylineCollection.add(); - line.id = geometryUpdater._entity; - - this._line = line; - this._primitives = primitives; - this._groundPrimitives = groundPrimitives; - this._groundPolylinePrimitive = undefined; - this._geometryUpdater = geometryUpdater; - this._positions = []; - this._terrainHeightsReady = false; + 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; - - 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; - } // Synchronize with geometryUpdater for GroundPolylinePrimitive geometryUpdater._clampToGround = Property.getValueOrDefault(polyline._clampToGround, time, false); @@ -613,15 +612,26 @@ define([ } 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) { - GroundPolylinePrimitive.initializeTerrainHeights() - .then(function() { - that._terrainHeightsReady = true; - }); + if (!GroundPolylinePrimitive._isInitialized()) { + GroundPolylinePrimitive.initializeTerrainHeights() + .then(function() { + that._terrainHeightsReady = true; + }); return; + } + this._terrainHeightsReady = true; } var fillMaterialProperty = geometryUpdater.fillMaterialProperty; @@ -629,12 +639,12 @@ define([ if (fillMaterialProperty instanceof ColorMaterialProperty) { appearance = new PolylineColorAppearance(); } else { - var material = MaterialProperty.getValue(time, fillMaterialProperty, line.material); + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); appearance = new PolylineMaterialAppearance({ material : material, translucent : material.isTranslucent() }); - line.material = material; + this._material = material; } this._groundPolylinePrimitive = groundPrimitives.add(new GroundPolylinePrimitive({ @@ -643,7 +653,21 @@ define([ asynchronous : false }), Property.getValueOrUndefined(geometryUpdater.zIndex, time)); - // Hide the polyline collection + // 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; + } + + if (!defined(positions) || positions.length < 2) { line.show = false; return; } @@ -670,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; }; @@ -686,10 +728,12 @@ 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); 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 33c9e0a0d659..fc2b8dcf8291 100644 --- a/Specs/DataSources/PolylineGeometryUpdaterSpec.js +++ b/Specs/DataSources/PolylineGeometryUpdaterSpec.js @@ -25,9 +25,11 @@ defineSuite([ 'DataSources/SampledProperty', 'DataSources/TimeIntervalCollectionProperty', 'Scene/Globe', + 'Scene/GroundPolylinePrimitive', 'Scene/ShadowMode', 'Specs/createDynamicProperty', - 'Specs/createScene' + 'Specs/createScene', + 'Specs/pollToPromise' ], function( PolylineGeometryUpdater, ApproximateTerrainHeights, @@ -55,39 +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 ApproximateTerrainHeights.initialize(); + 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; @@ -481,10 +490,10 @@ defineSuite([ 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); @@ -538,8 +547,6 @@ defineSuite([ expect(dynamicUpdater.isDestroyed()).toBe(false); expect(groundPrimitives.length).toBe(0); - // Update twice for terrain heights initialization - dynamicUpdater.update(time); dynamicUpdater.update(time); expect(groundPrimitives.length).toBe(1); @@ -556,6 +563,62 @@ defineSuite([ 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); @@ -687,6 +750,38 @@ 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);