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

Billboards and Labels get their act together #4675

Merged
merged 17 commits into from
Nov 23, 2016
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,10 @@ Change Log
* Breaking changes
*
* Improved terrain/imagery load ordering, especially when the terrain is already fully loaded and we add a new imagery layer. This results in a 25% reduction in load times in many cases.
* Billboard, Label, and PointPrimitive depth testing changed from `LESS` to `LEQUAL`, allowing label glyphs of equal depths to overlap.
* Billboard sizes were incorrectly rounded up from odd values to even values. This has been corrected, and as a result, odd-width and odd-height billboards will appear one pixel smaller than before.
* Label glyph positions have been adjusted and corrected.
* `TextureAtlas.borderWidthInPixels` has always been applied to the upper and right edges of each internal texture, but is now also applied to the bottom and left edges of the entire TextureAtlas, guaranteeing borders on all sides regardless of position within the atlas.
* Added support for saving html and css in Github Gists. [#4125](https://github.com/AnalyticalGraphicsInc/cesium/issues/4125)
* Fixed `Cartographic.fromCartesian` when the cartesian is not on the ellipsoid surface. [#4611](https://github.com/AnalyticalGraphicsInc/cesium/issues/4611)

26 changes: 19 additions & 7 deletions Source/Core/writeTextToCanvas.js
Original file line number Diff line number Diff line change
@@ -87,16 +87,28 @@ define([
document.body.appendChild(canvas);

var dimensions = measureText(context2D, text, stroke, fill);
dimensions.computedWidth = Math.max(dimensions.width, dimensions.bounds.maxx - dimensions.bounds.minx);
canvas.dimensions = dimensions;

document.body.removeChild(canvas);
canvas.style.visibility = '';

var baseline = dimensions.height - dimensions.ascent + padding;
canvas.width = dimensions.computedWidth + doublePadding;
canvas.height = dimensions.height + doublePadding;
var y = canvas.height - baseline;
//Some characters, such as the letter j, have a non-zero starting position.
//This value is used for kerning later, but we need to take it into account
//now in order to draw the text completely on the canvas
var x = -dimensions.bounds.minx;

//Expand the width to include the starting position.
var width = Math.ceil(dimensions.width) + x + doublePadding;

//While the height of the letter is correct, we need to adjust
//where we start drawing it so that letters like j and y properly dip
//below the line.
var height = dimensions.height + doublePadding;
var baseline = height - dimensions.ascent + doublePadding;
var y = height - baseline + doublePadding;

canvas.width = width;
canvas.height = height;

// Properties must be explicitly set again after changing width and height
context2D.font = font;
@@ -113,13 +125,13 @@ define([
if (stroke) {
var strokeColor = defaultValue(options.strokeColor, Color.BLACK);
context2D.strokeStyle = strokeColor.toCssColorString();
context2D.strokeText(text, padding, y);
context2D.strokeText(text, x + padding, y);
}

if (fill) {
var fillColor = defaultValue(options.fillColor, Color.WHITE);
context2D.fillStyle = fillColor.toCssColorString();
context2D.fillText(text, padding, y);
context2D.fillText(text, x + padding, y);
}

return canvas;
9 changes: 6 additions & 3 deletions Source/Scene/BillboardCollection.js
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ define([
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArrayFacade',
'../Renderer/WebGLConstants',
'../Shaders/BillboardCollectionFS',
'../Shaders/BillboardCollectionVS',
'./Billboard',
@@ -55,6 +56,7 @@ define([
ShaderProgram,
ShaderSource,
VertexArrayFacade,
WebGLConstants,
BillboardCollectionFS,
BillboardCollectionVS,
Billboard,
@@ -915,7 +917,7 @@ define([
}

var textureWidth = billboardCollection._textureAtlas.texture.width;
var imageWidth = Math.ceil(defaultValue(billboard.width, textureWidth * width) * 0.5);
var imageWidth = Math.round(defaultValue(billboard.width, textureWidth * width));
billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth);

var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16);
@@ -970,7 +972,7 @@ define([
}

var dimensions = billboardCollection._textureAtlas.texture.dimensions;
var imageHeight = Math.ceil(defaultValue(billboard.height, dimensions.y * height) * 0.5);
var imageHeight = Math.round(defaultValue(billboard.height, dimensions.y * height));
billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight);

var red = Color.floatToByte(color.red);
@@ -1424,7 +1426,8 @@ define([
if (!defined(this._rs)) {
this._rs = RenderState.fromCache({
depthTest : {
enabled : true
enabled : true,
func : WebGLConstants.LEQUAL // Allows label glyphs and billboards to overlap.
},
blending : BlendingState.ALPHA_BLEND
});
15 changes: 13 additions & 2 deletions Source/Scene/LabelCollection.js
Original file line number Diff line number Diff line change
@@ -227,8 +227,13 @@ define([
for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
glyph = glyphs[glyphIndex];
dimensions = glyph.dimensions;
totalWidth += dimensions.computedWidth;
maxHeight = Math.max(maxHeight, dimensions.height);

//Computing the total width must also account for the kering that occurs between letters.
totalWidth += dimensions.width - dimensions.bounds.minx;
if (glyphIndex < glyphLength - 1) {
totalWidth += glyphs[glyphIndex + 1].dimensions.bounds.minx;
}
}

var scale = label._scale;
@@ -262,7 +267,13 @@ define([
glyph.billboard._setTranslate(glyphPixelOffset);
}

glyphPixelOffset.x += dimensions.computedWidth * scale * resolutionScale;
//Compute the next x offset taking into acocunt the kerning performed
//on both the current letter as well as the next letter to be drawn
//as well as any applied scale.
if (glyphIndex < glyphLength - 1) {
var nextGlyph = glyphs[glyphIndex + 1];
glyphPixelOffset.x += ((dimensions.width - dimensions.bounds.minx) + nextGlyph.dimensions.bounds.minx) * scale * resolutionScale;
}
}
}

5 changes: 4 additions & 1 deletion Source/Scene/PointPrimitiveCollection.js
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ define([
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArrayFacade',
'../Renderer/WebGLConstants',
'../Shaders/PointPrimitiveCollectionFS',
'../Shaders/PointPrimitiveCollectionVS',
'./BlendingState',
@@ -49,6 +50,7 @@ define([
ShaderProgram,
ShaderSource,
VertexArrayFacade,
WebGLConstants,
PointPrimitiveCollectionFS,
PointPrimitiveCollectionVS,
BlendingState,
@@ -852,7 +854,8 @@ define([
if (!defined(this._rs)) {
this._rs = RenderState.fromCache({
depthTest : {
enabled : true
enabled : true,
func : WebGLConstants.LEQUAL
},
blending : BlendingState.ALPHA_BLEND
});
18 changes: 10 additions & 8 deletions Source/Scene/TextureAtlas.js
Original file line number Diff line number Diff line change
@@ -171,18 +171,19 @@ define([
var context = textureAtlas._context;
var numImages = textureAtlas.numberOfImages;
var scalingFactor = 2.0;
var borderWidthInPixels = textureAtlas._borderWidthInPixels;
if (numImages > 0) {
var oldAtlasWidth = textureAtlas._texture.width;
var oldAtlasHeight = textureAtlas._texture.height;
var atlasWidth = scalingFactor * (oldAtlasWidth + image.width + textureAtlas._borderWidthInPixels);
var atlasHeight = scalingFactor * (oldAtlasHeight + image.height + textureAtlas._borderWidthInPixels);
var atlasWidth = scalingFactor * (oldAtlasWidth + image.width + borderWidthInPixels);
var atlasHeight = scalingFactor * (oldAtlasHeight + image.height + borderWidthInPixels);
var widthRatio = oldAtlasWidth / atlasWidth;
var heightRatio = oldAtlasHeight / atlasHeight;

// Create new node structure, putting the old root node in the bottom left.
var nodeBottomRight = new TextureAtlasNode(new Cartesian2(oldAtlasWidth + textureAtlas._borderWidthInPixels, 0.0), new Cartesian2(atlasWidth, oldAtlasHeight));
var nodeBottomRight = new TextureAtlasNode(new Cartesian2(oldAtlasWidth + borderWidthInPixels, borderWidthInPixels), new Cartesian2(atlasWidth, oldAtlasHeight));
var nodeBottomHalf = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, oldAtlasHeight), textureAtlas._root, nodeBottomRight);
var nodeTopHalf = new TextureAtlasNode(new Cartesian2(0.0, oldAtlasHeight + textureAtlas._borderWidthInPixels), new Cartesian2(atlasWidth, atlasHeight));
var nodeTopHalf = new TextureAtlasNode(new Cartesian2(borderWidthInPixels, oldAtlasHeight + borderWidthInPixels), new Cartesian2(atlasWidth, atlasHeight));
var nodeMain = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, atlasHeight), nodeBottomHalf, nodeTopHalf);

// Resize texture coordinates.
@@ -219,8 +220,8 @@ define([
textureAtlas._root = nodeMain;
} else {
// First image exceeds initialSize
var initialWidth = scalingFactor * (image.width + textureAtlas._borderWidthInPixels);
var initialHeight = scalingFactor * (image.height + textureAtlas._borderWidthInPixels);
var initialWidth = scalingFactor * (image.width + 2 * borderWidthInPixels);
var initialHeight = scalingFactor * (image.height + 2 * borderWidthInPixels);
if(initialWidth < textureAtlas._initialSize.x) {
initialWidth = textureAtlas._initialSize.x;
}
@@ -234,7 +235,8 @@ define([
height : initialHeight,
pixelFormat : textureAtlas._pixelFormat
});
textureAtlas._root = new TextureAtlasNode(new Cartesian2(), new Cartesian2(initialWidth, initialHeight));
textureAtlas._root = new TextureAtlasNode(new Cartesian2(borderWidthInPixels, borderWidthInPixels),
new Cartesian2(initialWidth, initialHeight));
}
}

@@ -457,7 +459,7 @@ define([
*
* @example
* atlas = atlas && atlas.destroy();
*
*
* @see TextureAtlas#isDestroyed
*/
TextureAtlas.prototype.destroy = function() {
4 changes: 3 additions & 1 deletion Source/Shaders/BillboardCollectionVS.glsl
Original file line number Diff line number Diff line change
@@ -38,7 +38,9 @@ const float SHIFT_RIGHT1 = 1.0 / 2.0;

vec4 computePositionWindowCoordinates(vec4 positionEC, vec2 imageSize, float scale, vec2 direction, vec2 origin, vec2 translate, vec2 pixelOffset, vec3 alignedAxis, bool validAlignedAxis, float rotation, bool sizeInMeters)
{
vec2 halfSize = imageSize * scale * czm_resolutionScale;
// Note the halfSize cannot be computed in JavaScript because it is sent via
// compressed vertex attributes that coerce it to an integer.
vec2 halfSize = imageSize * scale * czm_resolutionScale * 0.5;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment about why this is happening here so we don't try to "optimize" it out again?

halfSize *= ((direction * 2.0) - 1.0);

vec2 originTranslate = origin * abs(halfSize);
10 changes: 6 additions & 4 deletions Specs/Core/writeTextToCanvasSpec.js
Original file line number Diff line number Diff line change
@@ -80,8 +80,10 @@ defineSuite([
stroke : false
});

// canvas1 is filled, so there should only be two "edges"
expect(getColorChangeCount(canvas1)).toEqual(2);
// canvas1 is filled, completely by the I on the left
// and then has empty space on the right, so there
// should only be one "edge": fill -> outside
expect(getColorChangeCount(canvas1)).toEqual(1);

var canvas2 = writeTextToCanvas('I', {
font : '90px "Open Sans"',
@@ -90,8 +92,8 @@ defineSuite([
strokeColor : Color.BLUE
});

// canvas2 is stroked, so there should be four "edges"
expect(getColorChangeCount(canvas2)).toEqual(4);
// canvas2 is stroked, so there should be three "edges": outline -> inside -> outline -> outside
expect(getColorChangeCount(canvas2)).toEqual(3);
});

it('background color defaults to transparent', function() {
Binary file added Specs/Data/Images/Blue2x2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Specs/Data/Images/Green2x2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Specs/Data/Images/White2x2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 9 additions & 9 deletions Specs/Scene/BillboardCollectionSpec.js
Original file line number Diff line number Diff line change
@@ -73,13 +73,13 @@ defineSuite([
camera = scene.camera;

return when.join(
loadImage('./Data/Images/Green.png').then(function(result) {
loadImage('./Data/Images/Green2x2.png').then(function(result) {
greenImage = result;
}),
loadImage('./Data/Images/Blue.png').then(function(result) {
loadImage('./Data/Images/Blue2x2.png').then(function(result) {
blueImage = result;
}),
loadImage('./Data/Images/White.png').then(function(result) {
loadImage('./Data/Images/White2x2.png').then(function(result) {
whiteImage = result;
}),
loadImage('./Data/Images/Blue10x10.png').then(function(result) {
@@ -1438,11 +1438,11 @@ defineSuite([
scene.renderForSpecs();

var one = billboards.add({
image : './Data/Images/Green.png'
image : './Data/Images/Green2x2.png'
});

expect(one.ready).toEqual(false);
expect(one.image).toEqual('./Data/Images/Green.png');
expect(one.image).toEqual('./Data/Images/Green2x2.png');

return pollToPromise(function() {
return one.ready;
@@ -1501,18 +1501,18 @@ defineSuite([
scene.renderForSpecs();

var one = billboards.add({
image : './Data/Images/Green.png'
image : './Data/Images/Green2x2.png'
});

expect(one.ready).toEqual(false);
expect(one.image).toEqual('./Data/Images/Green.png');
expect(one.image).toEqual('./Data/Images/Green2x2.png');

return pollToPromise(function() {
return one.ready;
}).then(function() {
expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]);

one.image = './Data/Images/Green.png';
one.image = './Data/Images/Green2x2.png';

expect(one.ready).toEqual(true);
expect(scene.renderForSpecs()).toEqual([0, 255, 0, 255]);
@@ -1571,7 +1571,7 @@ defineSuite([

var one = billboards.add({
image : './Data/Images/Red16x16.png',
imageSubRegion : new BoundingRectangle(0.0, 0.0, 1.0, 2.0)
imageSubRegion : new BoundingRectangle(0.0, 0.0, 2.0, 3.0)
});

expect(one.ready).toEqual(false);
32 changes: 16 additions & 16 deletions Specs/Scene/TextureAtlasSpec.js
Original file line number Diff line number Diff line change
@@ -194,8 +194,8 @@ void main() {\n\
expect(texture.height).toEqual(atlasHeight);

var coords = atlas.textureCoordinates[index];
expect(coords.x).toEqual(0.0 / atlasWidth);
expect(coords.y).toEqual(0.0 / atlasHeight);
expect(coords.x).toEqual(1.0 / atlasWidth);
expect(coords.y).toEqual(1.0 / atlasHeight);
expect(coords.width).toEqual(1.0 / atlasWidth);
expect(coords.height).toEqual(1.0 / atlasHeight);
});
@@ -383,23 +383,23 @@ void main() {\n\
expect(texture.width).toEqual(atlasWidth);
expect(texture.height).toEqual(atlasHeight);

expect(c0.x).toEqualEpsilon(0.0 / atlasWidth, CesiumMath.EPSILON16);
expect(c0.y).toEqualEpsilon(0.0 / atlasHeight, CesiumMath.EPSILON16);
expect(c0.x).toEqualEpsilon(2.0 / atlasWidth, CesiumMath.EPSILON16);
expect(c0.y).toEqualEpsilon(2.0 / atlasHeight, CesiumMath.EPSILON16);
expect(c0.width).toEqualEpsilon(greenImage.width / atlasWidth, CesiumMath.EPSILON16);
expect(c0.height).toEqualEpsilon(greenImage.height / atlasHeight, CesiumMath.EPSILON16);

expect(c1.x).toEqualEpsilon((greenImage.width + atlas.borderWidthInPixels) / atlasWidth, CesiumMath.EPSILON16);
expect(c1.y).toEqualEpsilon(0.0 / atlasHeight, CesiumMath.EPSILON16);
expect(c1.x).toEqualEpsilon((greenImage.width + 2 * atlas.borderWidthInPixels) / atlasWidth, CesiumMath.EPSILON16);
expect(c1.y).toEqualEpsilon(2.0 / atlasHeight, CesiumMath.EPSILON16);
expect(c1.width).toEqualEpsilon(blueImage.width / atlasWidth, CesiumMath.EPSILON16);
expect(c1.height).toEqualEpsilon(blueImage.width / atlasHeight, CesiumMath.EPSILON16);

expect(c2.x).toEqualEpsilon((bigRedImage.width + atlas.borderWidthInPixels) / atlasWidth, CesiumMath.EPSILON16);
expect(c2.y).toEqualEpsilon(0.0 / atlasHeight, CesiumMath.EPSILON16);
expect(c2.x).toEqualEpsilon(2.0 / atlasWidth, CesiumMath.EPSILON16);
expect(c2.y).toEqualEpsilon((bigRedImage.height + atlas.borderWidthInPixels) / atlasHeight, CesiumMath.EPSILON16);
expect(c2.width).toEqualEpsilon(bigRedImage.width / atlasWidth, CesiumMath.EPSILON16);
expect(c2.height).toEqualEpsilon(bigRedImage.height / atlasHeight, CesiumMath.EPSILON16);

expect(c3.x).toEqualEpsilon(0.0 / atlasWidth, CesiumMath.EPSILON16);
expect(c3.y).toEqualEpsilon((greenImage.height + atlas.borderWidthInPixels) / atlasHeight, CesiumMath.EPSILON16);
expect(c3.x).toEqualEpsilon(2.0 / atlasWidth, CesiumMath.EPSILON16);
expect(c3.y).toEqualEpsilon((greenImage.height + 2 * atlas.borderWidthInPixels) / atlasHeight, CesiumMath.EPSILON16);
expect(c3.width).toEqualEpsilon(bigBlueImage.width / atlasWidth, CesiumMath.EPSILON16);
expect(c3.height).toEqualEpsilon(bigBlueImage.height / atlasHeight, CesiumMath.EPSILON16);
});
@@ -577,20 +577,20 @@ void main() {\n\
var texture = atlas.texture;
var coordinates = atlas.textureCoordinates;

var atlasWidth = 6.0;
var atlasHeight = 6.0;
var atlasWidth = 10.0;
var atlasHeight = 10.0;
expect(atlas.borderWidthInPixels).toEqual(2);
expect(atlas.numberOfImages).toEqual(2);
expect(texture.width).toEqual(atlasWidth);
expect(texture.height).toEqual(atlasHeight);

expect(coordinates[greenIndex].x).toEqual(0.0 / atlasWidth);
expect(coordinates[greenIndex].y).toEqual(0.0 / atlasHeight);
expect(coordinates[greenIndex].x).toEqual(atlas.borderWidthInPixels / atlasWidth);
expect(coordinates[greenIndex].y).toEqual(atlas.borderWidthInPixels / atlasHeight);
expect(coordinates[greenIndex].width).toEqual(1.0 / atlasWidth);
expect(coordinates[greenIndex].height).toEqual(1.0 / atlasHeight);

expect(coordinates[blueIndex].x).toEqual(3.0 / atlasWidth);
expect(coordinates[blueIndex].y).toEqual(0.0 / atlasHeight);
expect(coordinates[blueIndex].x).toEqual(5.0 / atlasWidth);
expect(coordinates[blueIndex].y).toEqual(2.0 / atlasHeight);
expect(coordinates[blueIndex].width).toEqual(1.0 / atlasWidth);
expect(coordinates[blueIndex].height).toEqual(1.0 / atlasHeight);
});