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
Show file tree
Hide file tree
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
Expand Up @@ -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)

Expand Down
26 changes: 19 additions & 7 deletions Source/Core/writeTextToCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
9 changes: 6 additions & 3 deletions Source/Scene/BillboardCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ define([
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArrayFacade',
'../Renderer/WebGLConstants',
'../Shaders/BillboardCollectionFS',
'../Shaders/BillboardCollectionVS',
'./Billboard',
Expand Down Expand Up @@ -55,6 +56,7 @@ define([
ShaderProgram,
ShaderSource,
VertexArrayFacade,
WebGLConstants,
BillboardCollectionFS,
BillboardCollectionVS,
Billboard,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
});
Expand Down
15 changes: 13 additions & 2 deletions Source/Scene/LabelCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion Source/Scene/PointPrimitiveCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ define([
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArrayFacade',
'../Renderer/WebGLConstants',
'../Shaders/PointPrimitiveCollectionFS',
'../Shaders/PointPrimitiveCollectionVS',
'./BlendingState',
Expand Down Expand Up @@ -49,6 +50,7 @@ define([
ShaderProgram,
ShaderSource,
VertexArrayFacade,
WebGLConstants,
PointPrimitiveCollectionFS,
PointPrimitiveCollectionVS,
BlendingState,
Expand Down Expand Up @@ -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
});
Expand Down
18 changes: 10 additions & 8 deletions Source/Scene/TextureAtlas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand All @@ -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));
}
}

Expand Down Expand Up @@ -457,7 +459,7 @@ define([
*
* @example
* atlas = atlas && atlas.destroy();
*
*
* @see TextureAtlas#isDestroyed
*/
TextureAtlas.prototype.destroy = function() {
Expand Down
4 changes: 3 additions & 1 deletion Source/Shaders/BillboardCollectionVS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 6 additions & 4 deletions Specs/Core/writeTextToCanvasSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"',
Expand All @@ -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() {
Expand Down
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
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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);
Expand Down
32 changes: 16 additions & 16 deletions Specs/Scene/TextureAtlasSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down