diff --git a/Apps/Sandcastle/gallery/CZML Billboard and Label.html b/Apps/Sandcastle/gallery/CZML Billboard and Label.html index dc6e1521d663..37889f6d28ca 100644 --- a/Apps/Sandcastle/gallery/CZML Billboard and Label.html +++ b/Apps/Sandcastle/gallery/CZML Billboard and Label.html @@ -42,19 +42,19 @@ }, "label" : { "fillColor" : { - "rgba" : [0, 255, 255, 255] + "rgba" : [255, 255, 255, 255] }, - "font" : "11pt Lucida Console", + "font" : "12pt Lucida Console", "horizontalOrigin" : "LEFT", - "outlineColor" : { - "rgba":[0, 0, 0, 255] - }, - "outlineWidth" : 2, "pixelOffset" : { - "cartesian2" : [12, 0] + "cartesian2" : [8, 0] }, - "style" : "FILL_AND_OUTLINE", - "text" : "AGI" + "style" : "FILL", + "text" : "AGI", + "showBackground" : true, + "backgroundColor" : { + "rgba" : [112, 89, 57, 200] + } }, "position" : { "cartesian":[ @@ -65,7 +65,7 @@ var viewer = new Cesium.Viewer('cesiumContainer'); viewer.dataSources.add(Cesium.CzmlDataSource.load(czml)); - //Sandcastle_End +//Sandcastle_End Sandcastle.finishedLoading(); } if (typeof Cesium !== "undefined") { diff --git a/Apps/Sandcastle/gallery/Ground Clamping.html b/Apps/Sandcastle/gallery/Ground Clamping.html index d4081e1d8b1f..81c1d41a123b 100644 --- a/Apps/Sandcastle/gallery/Ground Clamping.html +++ b/Apps/Sandcastle/gallery/Ground Clamping.html @@ -10,10 +10,10 @@ @@ -29,8 +29,8 @@
diff --git a/Apps/Sandcastle/gallery/Labels.html b/Apps/Sandcastle/gallery/Labels.html index 0cbe6839a575..2c4b4b4b7f96 100644 --- a/Apps/Sandcastle/gallery/Labels.html +++ b/Apps/Sandcastle/gallery/Labels.html @@ -64,6 +64,7 @@ }); entity.label.scale = 2.0; + entity.label.showBackground = true; } function offsetByDistance() { @@ -80,6 +81,7 @@ label : { text : 'Label on top of scaling billboard', font : '20px sans-serif', + showBackground : true, horizontalOrigin : Cesium.HorizontalOrigin.CENTER, pixelOffset : new Cesium.Cartesian2(0.0, -image.height), pixelOffsetScaleByDistance : new Cesium.NearFarScalar(1.5e2, 3.0, 1.5e7, 0.5) diff --git a/Apps/Sandcastle/gallery/Terrain.html b/Apps/Sandcastle/gallery/Terrain.html index 6346ceb11117..09eedd03e1c4 100644 --- a/Apps/Sandcastle/gallery/Terrain.html +++ b/Apps/Sandcastle/gallery/Terrain.html @@ -148,11 +148,14 @@ }, label : { text : position.height.toFixed(1), + font : '10pt monospace', horizontalOrigin : Cesium.HorizontalOrigin.CENTER, - scale : 0.3, pixelOffset : new Cesium.Cartesian2(0, -14), - fillColor : Cesium.Color.RED, - outlineColor : Cesium.Color.WHITE + fillColor : Cesium.Color.BLACK, + outlineColor : Cesium.Color.BLACK, + showBackground : true, + backgroundColor : new Cesium.Color(0.9, 0.9, 0.9, 0.7), + backgroundPadding : new Cesium.Cartesian2(4, 3) } }); } diff --git a/Apps/Sandcastle/gallery/development/Labels.html b/Apps/Sandcastle/gallery/development/Labels.html index f865fccc0f1a..d5ab916b0955 100644 --- a/Apps/Sandcastle/gallery/development/Labels.html +++ b/Apps/Sandcastle/gallery/development/Labels.html @@ -77,19 +77,27 @@ labels.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center); labels.add({ position : new Cesium.Cartesian3(0.0, 0.0, 0.0), - text : 'Center' + text : 'Center', + font : '13pt sans-serif', + showBackground : true }); labels.add({ position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0), - text : 'East' + text : 'East', + font : '13pt sans-serif', + showBackground : true }); labels.add({ position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0), - text : 'North' + text : 'North', + font : '13pt sans-serif', + showBackground : true }); labels.add({ position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0), - text : 'Up' + text : 'Up', + font : '13pt sans-serif', + showBackground : true }); } @@ -108,8 +116,9 @@ var labels = scene.primitives.add(new Cesium.LabelCollection()); labels.add({ position : Cesium.Cartesian3.fromDegrees(-75.1641667, 39.9522222), - text : 'Label on top of scaling billboard', + text : 'Label on top of scaling billboard', font : '20px sans-serif', + showBackground : true, horizontalOrigin : Cesium.HorizontalOrigin.CENTER, pixelOffset : new Cesium.Cartesian2(0.0, -image.height), pixelOffsetScaleByDistance : new Cesium.NearFarScalar(1.5e2, 3.0, 1.5e7, 0.5) diff --git a/CHANGES.md b/CHANGES.md index 19d888aa92fb..1d754f687786 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ Change Log ### 1.29 - 2017-01-02 * Added the ability to blend a `Model` with a color/translucency. Added `color`, `colorBlendMode`, and `colorBlendAmount` properties to `Model`, `ModelGraphics`, and CZML. Added `ColorBlendMode` enum. [#4547](https://github.com/AnalyticalGraphicsInc/cesium/pull/4547) +* Added new `Label` properties `showBackground`, `backgroundColor`, and `backgroundPadding` to the primitive, Entity, and CZML layers. +* Added new enum `VerticalOrigin.BASELINE`. Previously, `VerticalOrigin.BOTTOM` would sometimes align to the baseline depending on the contents of a label. * Fixed tooltips for gallery thumbnails in Sandcastle [#4702](https://github.com/AnalyticalGraphicsInc/cesium/pull/4702) * Fixed `Rectangle.union` to correctly account for rectangles that cross the IDL [#4732](https://github.com/AnalyticalGraphicsInc/cesium/pull/4732). * Fixed texture rotation for `RectangleGeometry` [#2737](https://github.com/AnalyticalGraphicsInc/cesium/issues/2737) diff --git a/Documentation/Images/Billboard.setVerticalOrigin.png b/Documentation/Images/Billboard.setVerticalOrigin.png index a08e5f8854fc..2b49bc06e0f6 100644 Binary files a/Documentation/Images/Billboard.setVerticalOrigin.png and b/Documentation/Images/Billboard.setVerticalOrigin.png differ diff --git a/Source/DataSources/CzmlDataSource.js b/Source/DataSources/CzmlDataSource.js index 02b3b7ca4bd5..aa5ebaa85c84 100644 --- a/Source/DataSources/CzmlDataSource.js +++ b/Source/DataSources/CzmlDataSource.js @@ -1378,6 +1378,9 @@ define([ processPacketData(String, label, 'font', labelData.font, interval, sourceUri, entityCollection); processPacketData(LabelStyle, label, 'style', labelData.style, interval, sourceUri, entityCollection); processPacketData(Number, label, 'scale', labelData.scale, interval, sourceUri, entityCollection); + processPacketData(Boolean, label, 'showBackground', labelData.showBackground, interval, sourceUri, entityCollection); + processPacketData(Color, label, 'backgroundColor', labelData.backgroundColor, interval, sourceUri, entityCollection); + processPacketData(Cartesian2, label, 'backgroundPadding', labelData.backgroundPadding, interval, sourceUri, entityCollection); processPacketData(Cartesian2, label, 'pixelOffset', labelData.pixelOffset, interval, sourceUri, entityCollection); processPacketData(Cartesian3, label, 'eyeOffset', labelData.eyeOffset, interval, sourceUri, entityCollection); processPacketData(HorizontalOrigin, label, 'horizontalOrigin', labelData.horizontalOrigin, interval, sourceUri, entityCollection); diff --git a/Source/DataSources/EntityCluster.js b/Source/DataSources/EntityCluster.js index e3d4641b965d..b926038278a5 100644 --- a/Source/DataSources/EntityCluster.js +++ b/Source/DataSources/EntityCluster.js @@ -148,7 +148,7 @@ define([ cluster.label.show = true; cluster.label.text = numPoints.toLocaleString(); cluster.billboard.position = cluster.label.position = cluster.point.position = position; - + entityCluster._clusterEvent.raiseEvent(ids, cluster); } diff --git a/Source/DataSources/LabelGraphics.js b/Source/DataSources/LabelGraphics.js index f16fcb5977a9..e3aacdaca9eb 100644 --- a/Source/DataSources/LabelGraphics.js +++ b/Source/DataSources/LabelGraphics.js @@ -35,6 +35,9 @@ define([ * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the outline {@link Color}. * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the outline width. * @param {Property} [options.show=true] A boolean Property specifying the visibility of the label. + * @param {Property} [options.showBackground=true] A boolean Property specifying the visibility of the background behind the label. + * @param {Property} [options.backgroundColor=new Color(0.165, 0.165, 0.165, 0.8)] A Property specifying the background {@link Color}. + * @param {Property} [options.backgroundPadding=new Cartesian2(7, 5)] A {@link Cartesian2} Property specifying the horizontal and vertical background padding in pixels. * @param {Property} [options.scale=1.0] A numeric Property specifying the scale to apply to the text. * @param {Property} [options.horizontalOrigin=HorizontalOrigin.CENTER] A Property specifying the {@link HorizontalOrigin}. * @param {Property} [options.verticalOrigin=VerticalOrigin.CENTER] A Property specifying the {@link VerticalOrigin}. @@ -74,6 +77,12 @@ define([ this._scaleSubscription = undefined; this._show = undefined; this._showSubscription = undefined; + this._showBackground = undefined; + this._showBackgroundSubscription = undefined; + this._backgroundColor = undefined; + this._backgroundColorSubscription = undefined; + this._backgroundPadding = undefined; + this._backgroundPaddingSubscription = undefined; this._translucencyByDistance = undefined; this._translucencyByDistanceSubscription = undefined; this._pixelOffsetScaleByDistance = undefined; @@ -232,6 +241,31 @@ define([ */ show : createPropertyDescriptor('show'), + /** + * Gets or sets the boolean Property specifying the visibility of the background behind the label. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default false + */ + showBackground : createPropertyDescriptor('showBackground'), + + /** + * Gets or sets the Property specifying the background {@link Color}. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default new Color(0.165, 0.165, 0.165, 0.8) + */ + backgroundColor : createPropertyDescriptor('backgroundColor'), + + /** + * Gets or sets the {@link Cartesian2} Property specifying the label's horizontal and vertical + * background padding in pixels. + * @memberof LabelGraphics.prototype + * @type {Property} + * @default new Cartesian2(7, 5) + */ + backgroundPadding : createPropertyDescriptor('backgroundPadding'), + /** * Gets or sets {@link NearFarScalar} Property specifying the translucency of the label based on the distance from the camera. * A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and @@ -279,6 +313,9 @@ define([ result.fillColor = this.fillColor; result.outlineColor = this.outlineColor; result.outlineWidth = this.outlineWidth; + result.showBackground = this.showBackground; + result.backgroundColor = this.backgroundColor; + result.backgroundPadding = this.backgroundPadding; result.scale = this.scale; result.horizontalOrigin = this.horizontalOrigin; result.verticalOrigin = this.verticalOrigin; @@ -311,6 +348,9 @@ define([ this.fillColor = defaultValue(this.fillColor, source.fillColor); this.outlineColor = defaultValue(this.outlineColor, source.outlineColor); this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth); + this.showBackground = defaultValue(this.showBackground, source.showBackground); + this.backgroundColor = defaultValue(this.backgroundColor, source.backgroundColor); + this.backgroundPadding = defaultValue(this.backgroundPadding, source.backgroundPadding); this.scale = defaultValue(this.scale, source.scale); this.horizontalOrigin = defaultValue(this.horizontalOrigin, source.horizontalOrigin); this.verticalOrigin = defaultValue(this.verticalOrigin, source.verticalOrigin); diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 7689eeff192b..019e8527fc77 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -41,6 +41,9 @@ define([ var defaultFillColor = Color.WHITE; var defaultOutlineColor = Color.BLACK; var defaultOutlineWidth = 1.0; + var defaultShowBackground = false; + var defaultBackgroundColor = new Color(0.165, 0.165, 0.165, 0.8); + var defaultBackgroundPadding = new Cartesian2(7, 5); var defaultPixelOffset = Cartesian2.ZERO; var defaultEyeOffset = Cartesian3.ZERO; var defaultHeightReference = HeightReference.NONE; @@ -50,6 +53,8 @@ define([ var position = new Cartesian3(); var fillColor = new Color(); var outlineColor = new Color(); + var backgroundColor = new Color(); + var backgroundPadding = new Cartesian2(); var eyeOffset = new Cartesian3(); var pixelOffset = new Cartesian2(); var translucencyByDistance = new NearFarScalar(); @@ -146,6 +151,9 @@ define([ label.fillColor = Property.getValueOrDefault(labelGraphics._fillColor, time, defaultFillColor, fillColor); label.outlineColor = Property.getValueOrDefault(labelGraphics._outlineColor, time, defaultOutlineColor, outlineColor); label.outlineWidth = Property.getValueOrDefault(labelGraphics._outlineWidth, time, defaultOutlineWidth); + label.showBackground = Property.getValueOrDefault(labelGraphics._showBackground, time, defaultShowBackground); + label.backgroundColor = Property.getValueOrDefault(labelGraphics._backgroundColor, time, defaultBackgroundColor, backgroundColor); + label.backgroundPadding = Property.getValueOrDefault(labelGraphics._backgroundPadding, time, defaultBackgroundPadding, backgroundPadding); label.pixelOffset = Property.getValueOrDefault(labelGraphics._pixelOffset, time, defaultPixelOffset, pixelOffset); label.eyeOffset = Property.getValueOrDefault(labelGraphics._eyeOffset, time, defaultEyeOffset, eyeOffset); label.heightReference = Property.getValueOrDefault(labelGraphics._heightReference, time, defaultHeightReference); diff --git a/Source/Scene/Billboard.js b/Source/Scene/Billboard.js index 814aa5fcbd3d..f50cd4eca1ca 100644 --- a/Source/Scene/Billboard.js +++ b/Source/Scene/Billboard.js @@ -194,6 +194,7 @@ define([ * of removing it and re-adding it to the collection. * @memberof Billboard.prototype * @type {Boolean} + * @default true */ show : { get : function() { @@ -468,7 +469,7 @@ define([ /** * Gets or sets the horizontal origin of this billboard, which determines if the billboard is - * to the left, center, or right of its position. + * to the left, center, or right of its anchor position. *

*
*
@@ -500,10 +501,10 @@ define([ /** * Gets or sets the vertical origin of this billboard, which determines if the billboard is - * to the above, below, or at the center of its position. + * to the above, below, or at the center of its anchor position. *

*
- *
+ *
*
* @memberof Billboard.prototype * @type {VerticalOrigin} @@ -1132,9 +1133,7 @@ define([ return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3); }; - var scratchCartesian2 = new Cartesian2(); var scratchCartesian3 = new Cartesian3(); - var scratchComputePixelOffset = new Cartesian2(); // This function is basically a stripped-down JavaScript version of BillboardCollectionVS.glsl Billboard._computeScreenSpacePosition = function(modelMatrix, position, eyeOffset, pixelOffset, scene, result) { @@ -1148,10 +1147,7 @@ define([ } // Apply pixel offset - pixelOffset = Cartesian2.clone(pixelOffset, scratchComputePixelOffset); - var po = Cartesian2.multiplyByScalar(pixelOffset, scene.context.uniformState.resolutionScale, scratchCartesian2); - positionWC.x += po.x; - positionWC.y += po.y; + Cartesian2.add(positionWC, pixelOffset, positionWC); return positionWC; }; @@ -1227,7 +1223,7 @@ define([ } var y = screenSpacePosition.y; - if (billboard.verticalOrigin === VerticalOrigin.TOP) { + if (billboard.verticalOrigin === VerticalOrigin.BOTTOM || billboard.verticalOrigin === VerticalOrigin.BASELINE) { y -= height; } else if (billboard.verticalOrigin === VerticalOrigin.CENTER) { y -= height * 0.5; diff --git a/Source/Scene/BillboardCollection.js b/Source/Scene/BillboardCollection.js index 34370f8819de..523728ffa6ac 100644 --- a/Source/Scene/BillboardCollection.js +++ b/Source/Scene/BillboardCollection.js @@ -406,12 +406,23 @@ define([ * position : Cesium.Cartesian3.ZERO, * pixelOffset : Cesium.Cartesian2.ZERO, * eyeOffset : Cesium.Cartesian3.ZERO, + * heightReference : Cesium.HeightReference.NONE, * horizontalOrigin : Cesium.HorizontalOrigin.CENTER, * verticalOrigin : Cesium.VerticalOrigin.CENTER, * scale : 1.0, * image : 'url/to/image', + * imageSubRegion : undefined, * color : Cesium.Color.WHITE, - * id : undefined + * id : undefined, + * rotation : 0.0, + * alignedAxis : Cesium.Cartesian3.ZERO, + * width : undefined, + * height : undefined, + * scaleByDistance : undefined, + * translucencyByDistance : undefined, + * pixelOffsetScaleByDistance : undefined, + * sizeInMeters : false, + * distanceDisplayCondition : undefined * }); * * @example @@ -813,6 +824,11 @@ define([ show = false; } + // Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that. + if (verticalOrigin === VerticalOrigin.BASELINE) { + verticalOrigin = VerticalOrigin.BOTTOM; + } + billboardCollection._allHorizontalCenter = billboardCollection._allHorizontalCenter && horizontalOrigin === HorizontalOrigin.CENTER; billboardCollection._allVerticalCenter = billboardCollection._allVerticalCenter && verticalOrigin === VerticalOrigin.CENTER; diff --git a/Source/Scene/HorizontalOrigin.js b/Source/Scene/HorizontalOrigin.js index 17199472a51d..db1737fcf688 100644 --- a/Source/Scene/HorizontalOrigin.js +++ b/Source/Scene/HorizontalOrigin.js @@ -6,13 +6,19 @@ define([ 'use strict'; /** - * The horizontal location of an origin relative to an object, e.g., a {@link Billboard}. - * For example, the horizontal origin is used to display a billboard to the left or right (in - * screen space) of the actual position. + * The horizontal location of an origin relative to an object, e.g., a {@link Billboard} + * or {@link Label}. For example, setting the horizontal origin to LEFT + * or RIGHT will display a billboard to the left or right (in screen space) + * of the anchor position. + *

+ *
+ *
+ *
* * @exports HorizontalOrigin * * @see Billboard#horizontalOrigin + * @see Label#horizontalOrigin */ var HorizontalOrigin = { /** diff --git a/Source/Scene/Label.js b/Source/Scene/Label.js index 4eaf305735eb..ca1b68d2e8ef 100644 --- a/Source/Scene/Label.js +++ b/Source/Scene/Label.js @@ -86,8 +86,11 @@ define([ this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE)); this._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.BLACK)); this._outlineWidth = defaultValue(options.outlineWidth, 1.0); + this._showBackground = defaultValue(options.showBackground, false); + this._backgroundColor = defaultValue(options.backgroundColor, new Color(0.165, 0.165, 0.165, 0.8)); + this._backgroundPadding = defaultValue(options.backgroundPadding, new Cartesian2(7, 5)); this._style = defaultValue(options.style, LabelStyle.FILL); - this._verticalOrigin = defaultValue(options.verticalOrigin, VerticalOrigin.BOTTOM); + this._verticalOrigin = defaultValue(options.verticalOrigin, VerticalOrigin.BASELINE); this._horizontalOrigin = defaultValue(options.horizontalOrigin, HorizontalOrigin.LEFT); this._pixelOffset = Cartesian2.clone(defaultValue(options.pixelOffset, Cartesian2.ZERO)); this._eyeOffset = Cartesian3.clone(defaultValue(options.eyeOffset, Cartesian3.ZERO)); @@ -101,6 +104,7 @@ define([ this._labelCollection = labelCollection; this._glyphs = []; + this._backgroundBillboard = undefined; this._rebindAllGlyphs = true; this._repositionAllGlyphs = true; @@ -120,6 +124,7 @@ define([ * of removing it and re-adding it to the collection. * @memberof Label.prototype * @type {Boolean} + * @default true */ show : { get : function() { @@ -142,6 +147,10 @@ define([ billboard.show = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.show = value; + } } } }, @@ -173,6 +182,10 @@ define([ billboard.position = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.position = value; + } if (this._heightReference !== HeightReference.NONE) { this._updateClamping(); @@ -185,6 +198,7 @@ define([ * Gets or sets the height reference of this billboard. * @memberof Label.prototype * @type {HeightReference} + * @default HeightReference.NONE */ heightReference : { get : function() { @@ -207,6 +221,10 @@ define([ billboard.heightReference = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.heightReference = value; + } repositionAllGlyphs(this); @@ -242,6 +260,7 @@ define([ * Gets or sets the font used to draw this label. Fonts are specified using the same syntax as the CSS 'font' property. * @memberof Label.prototype * @type {String} + * @default '30px sans-serif' * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles|HTML canvas 2D context text styles} */ font : { @@ -266,6 +285,7 @@ define([ * Gets or sets the fill color of this label. * @memberof Label.prototype * @type {Color} + * @default Color.WHITE * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles} */ fillColor : { @@ -291,6 +311,7 @@ define([ * Gets or sets the outline color of this label. * @memberof Label.prototype * @type {Color} + * @default Color.BLACK * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles} */ outlineColor : { @@ -316,6 +337,7 @@ define([ * Gets or sets the outline width of this label. * @memberof Label.prototype * @type {Number} + * @default 1.0 * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles} */ outlineWidth : { @@ -336,10 +358,90 @@ define([ } }, + /** + * Determines if a background behind this label will be shown. + * @memberof Label.prototype + * @default false + * @type {Boolean} + */ + showBackground : { + get : function() { + return this._showBackground; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + if (this._showBackground !== value) { + this._showBackground = value; + rebindAllGlyphs(this); + } + } + }, + + /** + * Gets or sets the background color of this label. + * @memberof Label.prototype + * @type {Color} + * @default new Color(0.165, 0.165, 0.165, 0.8) + */ + backgroundColor : { + get : function() { + return this._backgroundColor; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + var backgroundColor = this._backgroundColor; + if (!Color.equals(backgroundColor, value)) { + Color.clone(value, backgroundColor); + + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.color = backgroundColor; + } + } + } + }, + + /** + * Gets or sets the background padding, in pixels, of this label. The x value + * controls horizontal padding, and the y value controls vertical padding. + * @memberof Label.prototype + * @type {Cartesian2} + * @default new Cartesian2(7, 5) + */ + backgroundPadding : { + get : function() { + return this._backgroundPadding; + }, + set : function(value) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + var backgroundPadding = this._backgroundPadding; + if (!Cartesian2.equals(backgroundPadding, value)) { + Cartesian2.clone(value, backgroundPadding); + repositionAllGlyphs(this); + } + } + }, + /** * Gets or sets the style of this label. * @memberof Label.prototype * @type {LabelStyle} + * @default LabelStyle.FILL */ style : { get : function() { @@ -374,6 +476,7 @@ define([ *
* @memberof Label.prototype * @type {Cartesian2} + * @default Cartesian2.ZERO */ pixelOffset : { get : function() { @@ -397,6 +500,10 @@ define([ glyph.billboard.pixelOffset = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.pixelOffset = value; + } } } }, @@ -445,6 +552,10 @@ define([ glyph.billboard.translucencyByDistance = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.translucencyByDistance = value; + } } } }, @@ -494,6 +605,10 @@ define([ glyph.billboard.pixelOffsetScaleByDistance = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.pixelOffsetScaleByDistance = value; + } } } }, @@ -519,6 +634,7 @@ define([ * * @memberof Label.prototype * @type {Cartesian3} + * @default Cartesian3.ZERO */ eyeOffset : { get : function() { @@ -542,19 +658,24 @@ define([ glyph.billboard.eyeOffset = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.eyeOffset = value; + } } } }, /** * Gets or sets the horizontal origin of this label, which determines if the label is drawn - * to the left, center, or right of its position. + * to the left, center, or right of its anchor position. *

*
*
*
* @memberof Label.prototype * @type {HorizontalOrigin} + * @default HorizontalOrigin.LEFT * @example * // Use a top, right origin * l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT; @@ -580,13 +701,14 @@ define([ /** * Gets or sets the vertical origin of this label, which determines if the label is - * to the above, below, or at the center of its position. + * to the above, below, or at the center of its anchor position. *

*
- *
+ *
*
* @memberof Label.prototype * @type {VerticalOrigin} + * @default VerticalOrigin.BASELINE * @example * // Use a top, right origin * l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT; @@ -613,6 +735,10 @@ define([ glyph.billboard.verticalOrigin = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.verticalOrigin = value; + } repositionAllGlyphs(this); } @@ -635,6 +761,7 @@ define([ * * @memberof Label.prototype * @type {Number} + * @default 1.0 */ scale : { get : function() { @@ -657,6 +784,10 @@ define([ glyph.billboard.scale = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.scale = value; + } repositionAllGlyphs(this); } @@ -689,6 +820,10 @@ define([ glyph.billboard.distanceDisplayCondition = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.distanceDisplayCondition = value; + } } } }, @@ -713,6 +848,10 @@ define([ glyph.billboard.id = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.id = value; + } } } }, @@ -731,19 +870,23 @@ define([ this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition); var glyphs = this._glyphs; + value = defaultValue(value, this._position); for (var i = 0, len = glyphs.length; i < len; i++) { var glyph = glyphs[i]; if (defined(glyph.billboard)) { // Set all the private values here, because we already clamped to ground // so we don't want to do it again for every glyph - glyph.billboard._clampedPosition = value; - - value = defaultValue(value, this._position); Cartesian3.clone(value, glyph.billboard._position); Cartesian3.clone(value, glyph.billboard._actualPosition); } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard._clampedPosition = value; + Cartesian3.clone(value, backgroundBillboard._position); + Cartesian3.clone(value, backgroundBillboard._actualPosition); + } } }, @@ -751,6 +894,7 @@ define([ * Determines whether or not this label will be shown or hidden because it was clustered. * @memberof Label.prototype * @type {Boolean} + * @default true * @private */ clusterShow : { @@ -765,11 +909,13 @@ define([ for (var i = 0, len = glyphs.length; i < len; i++) { var glyph = glyphs[i]; if (defined(glyph.billboard)) { - // Set all the private values here, because we already clamped to ground - // so we don't want to do it again for every glyph glyph.billboard.clusterShow = value; } } + var backgroundBillboard = this._backgroundBillboard; + if (defined(backgroundBillboard)) { + backgroundBillboard.clusterShow = value; + } } } } @@ -825,38 +971,58 @@ define([ * @private */ Label.getScreenSpaceBoundingBox = function(label, screenSpacePosition, result) { + var x = 0; + var y = 0; var width = 0; var height = 0; - - var glyphs = label._glyphs; - var length = glyphs.length; - for (var i = 0; i < length; ++i) { - var glyph = glyphs[i]; - var billboard = glyph.billboard; - if (!defined(billboard)) { - continue; + var scale = label.scale; + var resolutionScale = label._labelCollection._resolutionScale; + + var backgroundBillboard = label._backgroundBillboard; + if (defined(backgroundBillboard)) { + x = screenSpacePosition.x + (backgroundBillboard._translate.x / resolutionScale); + y = screenSpacePosition.y - (backgroundBillboard._translate.y / resolutionScale); + width = backgroundBillboard.width * scale; + height = backgroundBillboard.height * scale; + + if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) { + y -= height; + } else if (label.verticalOrigin === VerticalOrigin.CENTER) { + y -= height * 0.5; } + } else { + x = Number.POSITIVE_INFINITY; + y = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + var glyphs = label._glyphs; + var length = glyphs.length; + for (var i = 0; i < length; ++i) { + var glyph = glyphs[i]; + var billboard = glyph.billboard; + if (!defined(billboard)) { + continue; + } - width += billboard.width; - height = Math.max(height, billboard.height); - } + var glyphX = screenSpacePosition.x + (billboard._translate.x / resolutionScale); + var glyphY = screenSpacePosition.y - (billboard._translate.y / resolutionScale); + var glyphWidth = billboard.width * scale; + var glyphHeight = billboard.height * scale; - var scale = label.scale; - width *= scale; - height *= scale; - - var x = screenSpacePosition.x; - if (label.horizontalOrigin === HorizontalOrigin.RIGHT) { - x -= width; - } else if (label.horizontalOrigin === HorizontalOrigin.CENTER) { - x -= width * 0.5; - } + if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) { + glyphY -= glyphHeight; + } else if (label.verticalOrigin === VerticalOrigin.CENTER) { + glyphY -= glyphHeight * 0.5; + } + + x = Math.min(x, glyphX); + y = Math.min(y, glyphY); + maxX = Math.max(maxX, glyphX + glyphWidth); + maxY = Math.max(maxY, glyphY + glyphHeight); + } - var y = screenSpacePosition.y; - if (label.verticalOrigin === VerticalOrigin.TOP) { - y -= height; - } else if (label.verticalOrigin === VerticalOrigin.CENTER) { - y -= height * 0.5; + width = maxX - x; + height = maxY - y; } if (!defined(result)) { @@ -883,6 +1049,8 @@ define([ defined(other) && this._show === other._show && this._scale === other._scale && + this._outlineWidth === other._outlineWidth && + this._showBackground === other._showBackground && this._style === other._style && this._verticalOrigin === other._verticalOrigin && this._horizontalOrigin === other._horizontalOrigin && @@ -892,6 +1060,8 @@ define([ Cartesian3.equals(this._position, other._position) && Color.equals(this._fillColor, other._fillColor) && Color.equals(this._outlineColor, other._outlineColor) && + Color.equals(this._backgroundColor, other._backgroundColor) && + Cartesian2.equals(this._backgroundPadding, other._backgroundPadding) && Cartesian2.equals(this._pixelOffset, other._pixelOffset) && Cartesian3.equals(this._eyeOffset, other._eyeOffset) && NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) && diff --git a/Source/Scene/LabelCollection.js b/Source/Scene/LabelCollection.js index ef069035280d..2ff096b9706f 100644 --- a/Source/Scene/LabelCollection.js +++ b/Source/Scene/LabelCollection.js @@ -1,6 +1,8 @@ /*global define*/ define([ + '../Core/BoundingRectangle', '../Core/Cartesian2', + '../Core/Color', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -15,7 +17,9 @@ define([ './TextureAtlas', './VerticalOrigin' ], function( + BoundingRectangle, Cartesian2, + Color, defaultValue, defined, defineProperties, @@ -51,6 +55,24 @@ define([ this.dimensions = dimensions; } + var whitePixelCanvasId = 'ID_WHITE_PIXEL'; + var whitePixelSize = new Cartesian2(4, 4); + var whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1); + + function addWhitePixelCanvas(textureAtlas, labelCollection) { + var canvas = document.createElement('canvas'); + canvas.width = whitePixelSize.x; + canvas.height = whitePixelSize.y; + + var context2D = canvas.getContext('2d'); + context2D.fillStyle = '#fff'; + context2D.fillRect(0, 0, canvas.width, canvas.height); + + textureAtlas.addImage(whitePixelCanvasId, canvas).then(function(index) { + labelCollection._whitePixelIndex = index; + }); + } + // reusable object for calling writeTextToCanvas var writeTextToCanvasParameters = {}; function createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin) { @@ -59,13 +81,13 @@ define([ writeTextToCanvasParameters.strokeColor = outlineColor; writeTextToCanvasParameters.strokeWidth = outlineWidth; - if (verticalOrigin === VerticalOrigin.BOTTOM) { - writeTextToCanvasParameters.textBaseline = 'bottom'; + if (verticalOrigin === VerticalOrigin.CENTER) { + writeTextToCanvasParameters.textBaseline = 'middle'; } else if (verticalOrigin === VerticalOrigin.TOP) { writeTextToCanvasParameters.textBaseline = 'top'; } else { - // VerticalOrigin.CENTER - writeTextToCanvasParameters.textBaseline = 'middle'; + // VerticalOrigin.BOTTOM and VerticalOrigin.BASELINE + writeTextToCanvasParameters.textBaseline = 'bottom'; } writeTextToCanvasParameters.fill = style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE; @@ -113,6 +135,40 @@ define([ // presize glyphs to match the new text length glyphs.length = textLength; + var showBackground = label._showBackground && (glyphs.length > 0); + var backgroundBillboard = label._backgroundBillboard; + var backgroundBillboardCollection = labelCollection._backgroundBillboardCollection; + if (!showBackground) { + if (defined(backgroundBillboard)) { + backgroundBillboardCollection.remove(backgroundBillboard); + label._backgroundBillboard = backgroundBillboard = undefined; + } + } else { + if (!defined(backgroundBillboard)) { + backgroundBillboard = backgroundBillboardCollection.add({ + collection : labelCollection, + image : whitePixelCanvasId, + imageSubRegion : whitePixelBoundingRegion + }); + label._backgroundBillboard = backgroundBillboard; + } + + backgroundBillboard.color = label._backgroundColor; + backgroundBillboard.show = label._show; + backgroundBillboard.position = label._position; + backgroundBillboard.eyeOffset = label._eyeOffset; + backgroundBillboard.pixelOffset = label._pixelOffset; + backgroundBillboard.horizontalOrigin = HorizontalOrigin.LEFT; + backgroundBillboard.verticalOrigin = label._verticalOrigin; + backgroundBillboard.heightReference = label._heightReference; + backgroundBillboard.scale = label._scale; + backgroundBillboard.pickPrimitive = label; + backgroundBillboard.id = label._id; + backgroundBillboard.translucencyByDistance = label._translucencyByDistance; + backgroundBillboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance; + backgroundBillboard.distanceDisplayCondition = label._distanceDisplayCondition; + } + var glyphTextureCache = labelCollection._glyphTextureCache; // walk the text looking for new characters (creating new glyphs for each) @@ -177,9 +233,10 @@ define([ // if we have a texture, configure the existing billboard, or obtain one if (glyphTextureInfo.index !== -1) { var billboard = glyph.billboard; + var spareBillboards = labelCollection._spareBillboards; if (!defined(billboard)) { - if (labelCollection._spareBillboards.length > 0) { - billboard = labelCollection._spareBillboards.pop(); + if (spareBillboards.length > 0) { + billboard = spareBillboards.pop(); } else { billboard = labelCollection._billboardCollection.add({ collection : labelCollection @@ -210,22 +267,31 @@ define([ label._repositionAllGlyphs = true; } - // reusable Cartesian2 instance + // reusable Cartesian2 instances var glyphPixelOffset = new Cartesian2(); + var scratchBackgroundPadding = new Cartesian2(); function repositionAllGlyphs(label, resolutionScale) { var glyphs = label._glyphs; var glyph; var dimensions; var totalWidth = 0; - var maxHeight = 0; + var maxDescent = Number.NEGATIVE_INFINITY; + var maxY = 0; + + var backgroundBillboard = label._backgroundBillboard; + var backgroundPadding = scratchBackgroundPadding; + Cartesian2.clone( + (defined(backgroundBillboard) ? label._backgroundPadding : Cartesian2.ZERO), + backgroundPadding); var glyphIndex = 0; var glyphLength = glyphs.length; for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) { glyph = glyphs[glyphIndex]; dimensions = glyph.dimensions; - maxHeight = Math.max(maxHeight, dimensions.height); + maxY = Math.max(maxY, dimensions.height - dimensions.descent); + maxDescent = Math.max(maxDescent, dimensions.descent); //Computing the total width must also account for the kering that occurs between letters. totalWidth += dimensions.width - dimensions.bounds.minx; @@ -233,6 +299,7 @@ define([ totalWidth += glyphs[glyphIndex + 1].dimensions.bounds.minx; } } + var maxHeight = maxY + maxDescent; var scale = label._scale; var horizontalOrigin = label._horizontalOrigin; @@ -240,7 +307,9 @@ define([ if (horizontalOrigin === HorizontalOrigin.CENTER) { widthOffset -= totalWidth / 2 * scale; } else if (horizontalOrigin === HorizontalOrigin.RIGHT) { - widthOffset -= totalWidth * scale; + widthOffset -= (totalWidth + backgroundPadding.x) * scale; + } else { + widthOffset += backgroundPadding.x * scale; } glyphPixelOffset.x = widthOffset * resolutionScale; @@ -251,12 +320,15 @@ define([ glyph = glyphs[glyphIndex]; dimensions = glyph.dimensions; - if (verticalOrigin === VerticalOrigin.BOTTOM || dimensions.height === maxHeight) { + if (verticalOrigin === VerticalOrigin.BASELINE) { glyphPixelOffset.y = -dimensions.descent * scale; } else if (verticalOrigin === VerticalOrigin.TOP) { - glyphPixelOffset.y = -(maxHeight - dimensions.height) * scale - dimensions.descent * scale; + glyphPixelOffset.y = -(maxY - dimensions.height + dimensions.descent + backgroundPadding.y) * scale; } else if (verticalOrigin === VerticalOrigin.CENTER) { - glyphPixelOffset.y = -(maxHeight - dimensions.height) / 2 * scale - dimensions.descent * scale; + glyphPixelOffset.y = -(maxY - dimensions.height) / 2 * scale - dimensions.descent * scale; + } else { + // VerticalOrigin.BOTTOM + glyphPixelOffset.y = (maxDescent - dimensions.descent + backgroundPadding.y) * scale; } glyphPixelOffset.y *= resolutionScale; @@ -273,6 +345,24 @@ define([ glyphPixelOffset.x += ((dimensions.width - dimensions.bounds.minx) + nextGlyph.dimensions.bounds.minx) * scale * resolutionScale; } } + + if (defined(backgroundBillboard) && (glyphLength > 0)) { + glyphPixelOffset.x = (widthOffset - backgroundPadding.x * scale) * resolutionScale; + if (verticalOrigin === VerticalOrigin.BASELINE) { + glyphPixelOffset.y = -backgroundPadding.y * scale - maxDescent * scale; + } else if (verticalOrigin === VerticalOrigin.TOP) { + glyphPixelOffset.y = -(maxY - maxHeight) * scale - maxDescent * scale; + } else if (verticalOrigin === VerticalOrigin.CENTER) { + glyphPixelOffset.y = -(maxY - maxHeight) / 2 * scale - maxDescent * scale; + } else { + // VerticalOrigin.BOTTOM + glyphPixelOffset.y = 0; + } + glyphPixelOffset.y *= resolutionScale; + backgroundBillboard.width = totalWidth + (backgroundPadding.x * 2); + backgroundBillboard.height = maxHeight + (backgroundPadding.y * 2); + backgroundBillboard._setTranslate(glyphPixelOffset); + } } function destroyLabel(labelCollection, label) { @@ -280,6 +370,10 @@ define([ for ( var i = 0, len = glyphs.length; i < len; ++i) { unbindGlyph(labelCollection, glyphs[i]); } + if (defined(label._backgroundBillboard)) { + labelCollection._backgroundBillboardCollection.remove(label._backgroundBillboard); + label._backgroundBillboard = undefined; + } label._labelCollection = undefined; if (defined(label._removeCallbackFunc)) { @@ -339,6 +433,13 @@ define([ this._scene = options.scene; this._textureAtlas = undefined; + this._backgroundTextureAtlas = undefined; + this._whitePixelIndex = undefined; + + this._backgroundBillboardCollection = new BillboardCollection({ + scene : this._scene + }); + this._backgroundBillboardCollection.destroyTextureAtlas = false; this._billboardCollection = new BillboardCollection({ scene : this._scene @@ -435,12 +536,20 @@ define([ * font : '30px sans-serif', * fillColor : Cesium.Color.WHITE, * outlineColor : Cesium.Color.BLACK, + * outlineWidth : 1.0, + * showBackground : false, + * backgroundColor : new Cesium.Color(0.165, 0.165, 0.165, 0.8), + * backgroundPadding : new Cesium.Cartesian2(7, 5), * style : Cesium.LabelStyle.FILL, * pixelOffset : Cesium.Cartesian2.ZERO, * eyeOffset : Cesium.Cartesian3.ZERO, * horizontalOrigin : Cesium.HorizontalOrigin.LEFT, - * verticalOrigin : Cesium.VerticalOrigin.BOTTOM, - * scale : 1.0 + * verticalOrigin : Cesium.VerticalOrigin.BASELINE, + * scale : 1.0, + * translucencyByDistance : undefined, + * pixelOffsetScaleByDistance : undefined, + * heightReference : HeightReference.NONE, + * distanceDisplayCondition : undefined * }); * * @example @@ -581,9 +690,12 @@ define([ */ LabelCollection.prototype.update = function(frameState) { var billboardCollection = this._billboardCollection; + var backgroundBillboardCollection = this._backgroundBillboardCollection; billboardCollection.modelMatrix = this.modelMatrix; billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume; + backgroundBillboardCollection.modelMatrix = this.modelMatrix; + backgroundBillboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume; var context = frameState.context; @@ -594,6 +706,15 @@ define([ billboardCollection.textureAtlas = this._textureAtlas; } + if (!defined(this._backgroundTextureAtlas)) { + this._backgroundTextureAtlas = new TextureAtlas({ + context : context, + initialSize : whitePixelSize + }); + backgroundBillboardCollection.textureAtlas = this._backgroundTextureAtlas; + addWhitePixelCanvas(this._backgroundTextureAtlas, this); + } + var uniformState = context.uniformState; var resolutionScale = uniformState.resolutionScale; var resolutionChanged = this._resolutionScale !== resolutionScale; @@ -630,6 +751,7 @@ define([ } this._labelsToUpdate.length = 0; + backgroundBillboardCollection.update(frameState); billboardCollection.update(frameState); }; @@ -669,6 +791,8 @@ define([ this.removeAll(); this._billboardCollection = this._billboardCollection.destroy(); this._textureAtlas = this._textureAtlas && this._textureAtlas.destroy(); + this._backgroundBillboardCollection = this._backgroundBillboardCollection.destroy(); + this._backgroundTextureAtlas = this._backgroundTextureAtlas && this._backgroundTextureAtlas.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/VerticalOrigin.js b/Source/Scene/VerticalOrigin.js index b72103652dc6..1e109364d60f 100644 --- a/Source/Scene/VerticalOrigin.js +++ b/Source/Scene/VerticalOrigin.js @@ -6,17 +6,23 @@ define([ 'use strict'; /** - * The vertical location of an origin relative to an object, e.g., a {@link Billboard}. - * For example, the vertical origin is used to display a billboard above or below (in - * screen space) of the actual position. + * The vertical location of an origin relative to an object, e.g., a {@link Billboard} + * or {@link Label}. For example, setting the vertical origin to TOP + * or BOTTOM will display a billboard above or below (in screen space) + * the anchor position. + *

+ *
+ *
+ *
* * @exports VerticalOrigin * * @see Billboard#verticalOrigin + * @see Label#verticalOrigin */ var VerticalOrigin = { /** - * The origin is at the vertical center of the object. + * The origin is at the vertical center between BASELINE and TOP. * * @type {Number} * @constant @@ -31,6 +37,14 @@ define([ */ BOTTOM : 1, + /** + * If the object contains text, the origin is at the baseline of the text, else the origin is at the bottom of the object. + * + * @type {Number} + * @constant + */ + BASELINE : 2, + /** * The origin is at the top of the object. * diff --git a/Specs/Scene/BillboardCollectionSpec.js b/Specs/Scene/BillboardCollectionSpec.js index bb0e68eb2a11..dc0c9b432c5a 100644 --- a/Specs/Scene/BillboardCollectionSpec.js +++ b/Specs/Scene/BillboardCollectionSpec.js @@ -1154,14 +1154,14 @@ defineSuite([ var bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); expect(bbox.x).toEqual(-halfWidth); - expect(bbox.y).toEqual(0); + expect(bbox.y).toEqual(-height); expect(bbox.width).toEqual(width); expect(bbox.height).toEqual(height); b.verticalOrigin = VerticalOrigin.TOP; bbox = Billboard.getScreenSpaceBoundingBox(b, Cartesian2.ZERO); expect(bbox.x).toEqual(-halfWidth); - expect(bbox.y).toEqual(-height); + expect(bbox.y).toEqual(0); expect(bbox.width).toEqual(width); expect(bbox.height).toEqual(height); }); diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index b23b9f579169..8a87183fa3ab 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -48,6 +48,9 @@ defineSuite([ var labels; var labelsWithHeight; + // This Unicode square block will more reliably cover the center pixel than an 'x' or a 'w' char. + var solidBox = '\u25a0'; + beforeAll(function() { scene = createScene(); camera = scene.camera; @@ -56,6 +59,7 @@ defineSuite([ afterAll(function() { scene.destroyForSpecs(); }); + beforeEach(function() { scene.morphTo3D(0); @@ -81,12 +85,15 @@ defineSuite([ expect(label.fillColor).toEqual(Color.WHITE); expect(label.outlineColor).toEqual(Color.BLACK); expect(label.outlineWidth).toEqual(1); + expect(label.showBackground).toEqual(false); + expect(label.backgroundColor).toEqual(new Color(0.165, 0.165, 0.165, 0.8)); + expect(label.backgroundPadding).toEqual(new Cartesian2(7, 5)); expect(label.style).toEqual(LabelStyle.FILL); expect(label.pixelOffset).toEqual(Cartesian2.ZERO); expect(label.eyeOffset).toEqual(Cartesian3.ZERO); expect(label.heightReference).toEqual(HeightReference.NONE); expect(label.horizontalOrigin).toEqual(HorizontalOrigin.LEFT); - expect(label.verticalOrigin).toEqual(VerticalOrigin.BOTTOM); + expect(label.verticalOrigin).toEqual(VerticalOrigin.BASELINE); expect(label.scale).toEqual(1.0); expect(label.id).not.toBeDefined(); expect(label.translucencyByDistance).not.toBeDefined(); @@ -119,6 +126,9 @@ defineSuite([ var horizontalOrigin = HorizontalOrigin.LEFT; var verticalOrigin = VerticalOrigin.BOTTOM; var scale = 2.0; + var showBackground = true; + var backgroundColor = Color.BLUE; + var backgroundPadding = new Cartesian2(11, 12); var translucency = new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0); var pixelOffsetScale = new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0); var distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); @@ -131,6 +141,9 @@ defineSuite([ outlineColor : outlineColor, outlineWidth : outlineWidth, style : style, + showBackground : showBackground, + backgroundColor : backgroundColor, + backgroundPadding : backgroundPadding, pixelOffset : pixelOffset, eyeOffset : eyeOffset, horizontalOrigin : horizontalOrigin, @@ -150,6 +163,9 @@ defineSuite([ expect(label.outlineColor).toEqual(outlineColor); expect(label.outlineWidth).toEqual(outlineWidth); expect(label.style).toEqual(style); + expect(label.showBackground).toEqual(showBackground); + expect(label.backgroundColor).toEqual(backgroundColor); + expect(label.backgroundPadding).toEqual(backgroundPadding); expect(label.pixelOffset).toEqual(pixelOffset); expect(label.eyeOffset).toEqual(eyeOffset); expect(label.horizontalOrigin).toEqual(horizontalOrigin); @@ -330,19 +346,19 @@ defineSuite([ it('can render after adding a label', function() { labels.add({ position : Cartesian3.ZERO, - text : 'w', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER }); var actual = scene.renderForSpecs(); - expect(actual[0]).toBeGreaterThan(10); - expect(actual[1]).toBeGreaterThan(10); - expect(actual[2]).toBeGreaterThan(10); + expect(actual[0]).toBeGreaterThan(200); + expect(actual[1]).toBeGreaterThan(200); + expect(actual[2]).toBeGreaterThan(200); labels.add({ position : new Cartesian3(1.0, 0.0, 0.0), // Closer to camera - text : 'x', + text : solidBox, fillColor : { red : 1.0, green : 0.0, @@ -354,7 +370,7 @@ defineSuite([ }); actual = scene.renderForSpecs(); - expect(actual[0]).toBeGreaterThan(10); + expect(actual[0]).toBeGreaterThan(200); expect(actual[1]).toBeLessThan(10); expect(actual[2]).toBeLessThan(10); }); @@ -429,6 +445,22 @@ defineSuite([ expect(scene.renderForSpecs()[0]).toBeGreaterThan(10); }); + it('can render a label background', function() { + var label = labels.add({ + position : Cartesian3.ZERO, + text : '_', + horizontalOrigin : HorizontalOrigin.CENTER, + verticalOrigin : VerticalOrigin.CENTER, + showBackground : true, + backgroundColor : Color.BLUE + }); + + expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); + + labels.remove(label); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + }); + it('does not render labels with show set to false', function() { var label = labels.add({ position : Cartesian3.ZERO, @@ -446,6 +478,25 @@ defineSuite([ expect(scene.renderForSpecs()[0]).toBeGreaterThan(10); }); + it('does not render label background with show set to false', function() { + var label = labels.add({ + position : Cartesian3.ZERO, + text : '_', + horizontalOrigin : HorizontalOrigin.CENTER, + verticalOrigin : VerticalOrigin.CENTER, + showBackground : true, + backgroundColor : Color.BLUE + }); + + expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); + + label.show = false; + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + label.show = true; + expect(scene.renderForSpecs()).toEqual([0, 0, 255, 255]); + }); + it('does not render labels that are behind the viewer', function() { var label = labels.add({ position : new Cartesian3(20.0, 0.0, 0.0), // Behind camera @@ -495,7 +546,7 @@ defineSuite([ labels.add({ position : Cartesian3.ZERO, pixelOffset : new Cartesian2(1.0, 0.0), - text : 'x', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER, pixelOffsetScaleByDistance: new NearFarScalar(2.0, 0.0, 4.0, 1000.0) @@ -511,7 +562,7 @@ defineSuite([ it('renders label with distanceDisplayCondition', function() { labels.add({ position : Cartesian3.ZERO, - text : 'm', + text : solidBox, distanceDisplayCondition : new DistanceDisplayCondition(10.0, 100.0), horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER @@ -549,7 +600,7 @@ defineSuite([ it('can pick a label', function() { var label = labels.add({ position : Cartesian3.ZERO, - text : 'x', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER, id : 'id' @@ -563,7 +614,7 @@ defineSuite([ it('can change pick id', function() { var label = labels.add({ position : Cartesian3.ZERO, - text : 'x', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER, id : 'id' @@ -584,7 +635,7 @@ defineSuite([ labels.add({ show : false, position : Cartesian3.ZERO, - text : 'x', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER }); @@ -596,7 +647,7 @@ defineSuite([ it('picks a label using translucencyByDistance', function() { var label = labels.add({ position : Cartesian3.ZERO, - text : 'x', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER }); @@ -619,7 +670,7 @@ defineSuite([ var label = labels.add({ position : Cartesian3.ZERO, pixelOffset : new Cartesian2(0.0, 100.0), - text : 'x', + text : solidBox, horizontalOrigin : HorizontalOrigin.CENTER, verticalOrigin : VerticalOrigin.CENTER }); @@ -718,14 +769,34 @@ defineSuite([ }); scene.renderForSpecs(); expect(labels._billboardCollection.length).toEqual(3); + expect(labels._spareBillboards.length).toEqual(0); label.text = 'a'; scene.renderForSpecs(); expect(labels._billboardCollection.length).toEqual(3); + expect(labels._spareBillboards.length).toEqual(2); label.text = 'def'; scene.renderForSpecs(); expect(labels._billboardCollection.length).toEqual(3); + expect(labels._spareBillboards.length).toEqual(0); + }); + + it('should not reuse background billboards that are not needed any more', function() { + var label = labels.add({ + text : 'abc', + showBackground : true + }); + scene.renderForSpecs(); + expect(labels._backgroundBillboardCollection.length).toEqual(1); + + label.showBackground = false; + scene.renderForSpecs(); + expect(labels._backgroundBillboardCollection.length).toEqual(0); + + label.showBackground = true; + scene.renderForSpecs(); + expect(labels._backgroundBillboardCollection.length).toEqual(1); }); describe('Label', function() { @@ -863,30 +934,13 @@ defineSuite([ }); scene.renderForSpecs(); - var width = 0; - var height = 0; - - var glyphs = label._glyphs; - var length = glyphs.length; - for (var i = 0; i < length; ++i) { - var glyph = glyphs[i]; - var billboard = glyph.billboard; - if (!defined(billboard)) { - continue; - } - - width += billboard.width; - height = Math.max(height, billboard.height); - } - - width *= scale; - height *= scale; - var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); - expect(bbox.x).toEqual(0); - expect(bbox.y).toEqual(0); - expect(bbox.width).toEqual(width); - expect(bbox.height).toEqual(height); + expect(bbox.x).toBeDefined(); + expect(bbox.y).toBeDefined(); + expect(bbox.width).toBeGreaterThan(30); + expect(bbox.width).toBeLessThan(200); + expect(bbox.height).toBeGreaterThan(10); + expect(bbox.height).toBeLessThan(50); }); it('computes screen space bounding box with result', function() { @@ -898,35 +952,18 @@ defineSuite([ }); scene.renderForSpecs(); - var width = 0; - var height = 0; - - var glyphs = label._glyphs; - var length = glyphs.length; - for (var i = 0; i < length; ++i) { - var glyph = glyphs[i]; - var billboard = glyph.billboard; - if (!defined(billboard)) { - continue; - } - - width += billboard.width; - height = Math.max(height, billboard.height); - } - - width *= scale; - height *= scale; - var result = new BoundingRectangle(); var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO, result); - expect(bbox.x).toEqual(0); - expect(bbox.y).toEqual(0); - expect(bbox.width).toEqual(width); - expect(bbox.height).toEqual(height); + expect(bbox.x).toBeDefined(); + expect(bbox.y).toBeDefined(); + expect(bbox.width).toBeGreaterThan(30); + expect(bbox.width).toBeLessThan(200); + expect(bbox.height).toBeGreaterThan(10); + expect(bbox.height).toBeLessThan(50); expect(bbox).toBe(result); }); - it('computes screen space bounding box with vertical origin', function() { + it('computes screen space bounding box with vertical origin center', function() { var scale = 1.5; var label = labels.add({ @@ -936,39 +973,39 @@ defineSuite([ }); scene.renderForSpecs(); - var width = 0; - var height = 0; + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.y).toBeGreaterThan(bbox.height * -0.9); + expect(bbox.y).toBeLessThan(bbox.height * -0.3); + }); - var glyphs = label._glyphs; - var length = glyphs.length; - for (var i = 0; i < length; ++i) { - var glyph = glyphs[i]; - var billboard = glyph.billboard; - if (!defined(billboard)) { - continue; - } + it('computes screen space bounding box with vertical origin top', function() { + var scale = 1.5; - width += billboard.width; - height = Math.max(height, billboard.height); - } + var label = labels.add({ + text : 'abc', + scale : scale, + verticalOrigin : VerticalOrigin.TOP + }); + scene.renderForSpecs(); - width *= scale; - height *= scale; + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.y).toBeLessThan(5); + expect(bbox.y).toBeGreaterThan(-5); + }); - var halfHeight = height * 0.5; + it('computes screen space bounding box with vertical origin baseline', function() { + var scale = 1.5; - var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); - expect(bbox.x).toEqual(0); - expect(bbox.y).toEqual(-halfHeight); - expect(bbox.width).toEqual(width); - expect(bbox.height).toEqual(height); + var label = labels.add({ + text : 'abc', + scale : scale, + verticalOrigin : VerticalOrigin.BASELINE + }); + scene.renderForSpecs(); - label.verticalOrigin = VerticalOrigin.TOP; - bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); - expect(bbox.x).toEqual(0); - expect(bbox.y).toEqual(-height); - expect(bbox.width).toEqual(width); - expect(bbox.height).toEqual(height); + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.y).toBeLessThan(bbox.height * -0.8); + expect(bbox.y).toBeGreaterThan(bbox.height * -1.2); }); it('computes screen space bounding box with horizontal origin', function() { @@ -981,37 +1018,47 @@ defineSuite([ }); scene.renderForSpecs(); - var width = 0; - var height = 0; + var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toBeLessThan(bbox.width * -0.3); + expect(bbox.x).toBeGreaterThan(bbox.width * -0.7); - var glyphs = label._glyphs; - var length = glyphs.length; - for (var i = 0; i < length; ++i) { - var glyph = glyphs[i]; - var billboard = glyph.billboard; - if (!defined(billboard)) { - continue; - } + label.horizontalOrigin = HorizontalOrigin.RIGHT; + scene.renderForSpecs(); + bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); + expect(bbox.x).toBeLessThan(bbox.width * -0.8); + expect(bbox.x).toBeGreaterThan(bbox.width * -1.2); + }); - width += billboard.width; - height = Math.max(height, billboard.height); - } + it('computes screen space bounding box with padded background', function() { + var scale = 1.5; - width *= scale; - height *= scale; + var label = labels.add({ + text : 'abc', + scale : scale, + showBackground : true, + backgroundPadding : new Cartesian2(15, 10) + }); + scene.renderForSpecs(); - var halfWidth = width * 0.5; + var backgroundBillboard = label._backgroundBillboard; + var width = backgroundBillboard.width * scale; + var height = backgroundBillboard.height * scale; + var x = backgroundBillboard._translate.x; + var y = -(backgroundBillboard._translate.y + height); var bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); - expect(bbox.x).toEqual(-halfWidth); - expect(bbox.y).toEqual(0); + expect(bbox.x).toEqual(x); + expect(bbox.y).toEqual(y); expect(bbox.width).toEqual(width); expect(bbox.height).toEqual(height); - label.horizontalOrigin = HorizontalOrigin.RIGHT; + label.verticalOrigin = VerticalOrigin.CENTER; + scene.renderForSpecs(); + y = -(backgroundBillboard._translate.y + height * 0.5); + bbox = Label.getScreenSpaceBoundingBox(label, Cartesian2.ZERO); - expect(bbox.x).toEqual(-width); - expect(bbox.y).toEqual(0); + expect(bbox.x).toEqual(x); + expect(bbox.y).toEqual(y); expect(bbox.width).toEqual(width); expect(bbox.height).toEqual(height); }); @@ -1094,7 +1141,7 @@ defineSuite([ var eyeOffset1 = new Cartesian3(6.0, 7.0, 8.0); var eyeOffset2 = new Cartesian3(16.0, 17.0, 18.0); var verticalOrigin1 = VerticalOrigin.TOP; - var verticalOrigin2 = VerticalOrigin.BOTTOM; + var verticalOrigin2 = VerticalOrigin.BASELINE; var scale1 = 2.0; var scale2 = 3.0; var id1 = 'id1'; @@ -1113,7 +1160,8 @@ defineSuite([ scale : scale1, id : id1, translucencyByDistance : translucency1, - pixelOffsetScaleByDistance : pixelOffsetScale1 + pixelOffsetScaleByDistance : pixelOffsetScale1, + showBackground : true }); scene.renderForSpecs(); @@ -1160,7 +1208,8 @@ defineSuite([ scale : 2.0, id : 'id1', translucencyByDistance : new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0), - pixelOffsetScaleByDistance : new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0) + pixelOffsetScaleByDistance : new NearFarScalar(1.0e4, 1.0, 1.0e6, 0.0), + showBackground : true }); scene.renderForSpecs(); }); @@ -1228,6 +1277,26 @@ defineSuite([ }); }); + it('showBackground', function() { + expect(label.showBackground).toEqual(true); + label.showBackground = false; + expect(label.showBackground).toEqual(false); + }); + + it('backgroundColor', function() { + var newValue = Color.RED; + expect(label.backgroundColor).not.toEqual(newValue); + label.backgroundColor = newValue; + expect(label.backgroundColor).toEqual(newValue); + }); + + it('backgroundPadding', function() { + var newValue = new Cartesian2(8, 5); + expect(label.backgroundPadding).not.toEqual(newValue); + label.backgroundPadding = newValue; + expect(label.backgroundPadding).toEqual(newValue); + }); + it('id', function() { var newValue = 'id2'; expect(label.id).not.toEqual(newValue); @@ -1282,6 +1351,12 @@ defineSuite([ expect(billboard.pixelOffsetScaleByDistance).toEqual(label.pixelOffsetScaleByDistance); }); }); + + it('clusterShow', function() { + expect(label.clusterShow).toEqual(true); + label.clusterShow = false; + expect(label.clusterShow).toEqual(false); + }); }); it('should set vertexTranslate of billboards correctly when vertical origin is changed', function() { @@ -1300,10 +1375,8 @@ defineSuite([ label.verticalOrigin = VerticalOrigin.TOP; scene.renderForSpecs(); - // vertical origin TOP should decrease (or equal) Y offset compared to CENTER - expect(getGlyphBillboardVertexTranslate(label, 0).y).toBeLessThanOrEqualTo(offset0.y); - expect(getGlyphBillboardVertexTranslate(label, 1).y).toBeLessThanOrEqualTo(offset1.y); - expect(getGlyphBillboardVertexTranslate(label, 2).y).toBeLessThanOrEqualTo(offset2.y); + // Because changing the label's vertical origin also changes the vertical origin of each + // individual glyph, it is not safe to assume anything about Y offsets being more or less. // X offset should be unchanged expect(getGlyphBillboardVertexTranslate(label, 0).x).toEqual(offset0.x); @@ -1313,11 +1386,6 @@ defineSuite([ label.verticalOrigin = VerticalOrigin.BOTTOM; scene.renderForSpecs(); - // vertical origin BOTTOM should increase (or equal) Y offset compared to CENTER - expect(getGlyphBillboardVertexTranslate(label, 0).y).toBeGreaterThanOrEqualTo(offset0.y); - expect(getGlyphBillboardVertexTranslate(label, 1).y).toBeGreaterThanOrEqualTo(offset1.y); - expect(getGlyphBillboardVertexTranslate(label, 2).y).toBeGreaterThanOrEqualTo(offset2.y); - // X offset should be unchanged expect(getGlyphBillboardVertexTranslate(label, 0).x).toEqual(offset0.x); expect(getGlyphBillboardVertexTranslate(label, 1).x).toEqual(offset1.x); @@ -1394,20 +1462,9 @@ defineSuite([ offset1 = getGlyphBillboardVertexTranslate(label, 1); offset2 = getGlyphBillboardVertexTranslate(label, 2); - // vertical origin TOP should decrease (or equal) Y offset compared to CENTER - expect(getGlyphBillboardVertexTranslate(label, 0).y).toBeLessThanOrEqualTo(offset0.y); - expect(getGlyphBillboardVertexTranslate(label, 1).y).toBeLessThanOrEqualTo(offset1.y); - expect(getGlyphBillboardVertexTranslate(label, 2).y).toBeLessThanOrEqualTo(offset2.y); + // Because changing the label's vertical origin also changes the vertical origin of each + // individual glyph, it is not safe to assume anything about Y offsets being more or less. - label.verticalOrigin = VerticalOrigin.BOTTOM; - scene.renderForSpecs(); - - // vertical origin BOTTOM should increase (or equal) Y offset compared to CENTER - expect(getGlyphBillboardVertexTranslate(label, 0).y).toBeGreaterThanOrEqualTo(offset0.y); - expect(getGlyphBillboardVertexTranslate(label, 1).y).toBeGreaterThanOrEqualTo(offset1.y); - expect(getGlyphBillboardVertexTranslate(label, 2).y).toBeGreaterThanOrEqualTo(offset2.y); - - label.verticalOrigin = VerticalOrigin.CENTER; label.horizontalOrigin = HorizontalOrigin.LEFT; scene.renderForSpecs();