Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for polylines on terrain via entity API #6689

Merged
merged 4 commits into from
Jun 18, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Apps/Sandcastle/gallery/Ground Clamping.html
Original file line number Diff line number Diff line change
@@ -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 () {
5 changes: 3 additions & 2 deletions Apps/Sandcastle/gallery/Polyline.html
Original file line number Diff line number Diff line change
@@ -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
}
});

31 changes: 31 additions & 0 deletions Apps/Sandcastle/gallery/Z-Indexing Geometry.html
Original file line number Diff line number Diff line change
@@ -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,13 +77,39 @@
});

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,
zIndex: 1
}
});

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();
Binary file modified Apps/Sandcastle/gallery/Z-Indexing Geometry.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion Source/DataSources/DataSourceDisplay.js
Original file line number Diff line number Diff line change
@@ -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, {
14 changes: 14 additions & 0 deletions Source/DataSources/Entity.js
Original file line number Diff line number Diff line change
@@ -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;
});
232 changes: 196 additions & 36 deletions Source/DataSources/PolylineGeometryUpdater.js

Large diffs are not rendered by default.

37 changes: 33 additions & 4 deletions Source/DataSources/PolylineGraphics.js
Original file line number Diff line number Diff line change
@@ -17,23 +17,25 @@ 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
*
* @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.
* @param {MaterialProperty} [options.depthFailMaterial] A property specifiying the material used to draw the polyline when it is below the terrain.
* @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;
24 changes: 19 additions & 5 deletions Source/DataSources/PolylineVisualizer.js
Original file line number Diff line number Diff line change
@@ -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();
365 changes: 365 additions & 0 deletions Source/DataSources/StaticGroundPolylinePerMaterialBatch.js
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly copied from StaticGroundGeometryPerMaterialBatch, it just allows a lot more flexibility in batching and needs its own primitive-type.

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;
});
11 changes: 11 additions & 0 deletions Source/Scene/GroundPolylinePrimitive.js
Original file line number Diff line number Diff line change
@@ -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;
313 changes: 289 additions & 24 deletions Specs/DataSources/PolylineGeometryUpdaterSpec.js

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions Specs/DataSources/PolylineGraphicsSpec.js
Original file line number Diff line number Diff line change
@@ -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,19 +40,23 @@ 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);
expect(polyline.positions.getValue()).toEqual(options.positions);
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,19 +96,23 @@ 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();
var positions = new ConstantProperty();
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);
});
});
64 changes: 53 additions & 11 deletions Specs/DataSources/PolylineVisualizerSpec.js
Original file line number Diff line number Diff line change
@@ -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);

6 changes: 3 additions & 3 deletions Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js
Original file line number Diff line number Diff line change
@@ -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();
});
});

388 changes: 388 additions & 0 deletions Specs/DataSources/StaticGroundPolylinePerMaterialBatchSpec.js
Original file line number Diff line number Diff line change
@@ -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();
});
});
});