Skip to content

Commit

Permalink
improve line label legibility in pitched views
Browse files Browse the repository at this point in the history
The correct position of a glyph along is now found by doing the work on
the CPU where we have access to the entire line geometry.

For performance, as much work as possible is left to be done in the
shaders. This includes all the work for point horizontal labels.

ref #4718
  • Loading branch information
ansis committed Jun 2, 2017
1 parent 7d2b30b commit dcdddfc
Show file tree
Hide file tree
Showing 34 changed files with 644 additions and 600 deletions.
8 changes: 8 additions & 0 deletions src/data/array_group.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class Segment {
* A group has:
*
* * A "layout" vertex array, with fixed attributes, containing values calculated from layout properties.
* * Zero or one dynamic "layout" vertex arrays, with fixed attributes containing values that can be
* * recalculated each frame on the cpu.
* * Zero, one, or two element arrays, with fixed layout, for eventual `gl.drawElements` use.
* * Zero or more "paint" vertex arrays keyed by layer ID, each with a dynamic layout which depends
* on which paint properties of that layer use data-driven-functions (property functions or
Expand All @@ -38,6 +40,11 @@ class ArrayGroup {
const LayoutVertexArrayType = createVertexArrayType(programInterface.layoutAttributes);
this.layoutVertexArray = new LayoutVertexArrayType();

if (programInterface.dynamicLayoutAttributes) {
const DynamicLayoutVertexArrayType = createVertexArrayType(programInterface.dynamicLayoutAttributes);
this.dynamicLayoutVertexArray = new DynamicLayoutVertexArrayType();
}

const ElementArrayType = programInterface.elementArrayType;
if (ElementArrayType) this.elementArray = new ElementArrayType();

Expand Down Expand Up @@ -99,6 +106,7 @@ class ArrayGroup {
serialize(transferables) {
return {
layoutVertexArray: this.layoutVertexArray.serialize(transferables),
dynamicLayoutVertexArray: this.dynamicLayoutVertexArray && this.dynamicLayoutVertexArray.serialize(transferables),
elementArray: this.elementArray && this.elementArray.serialize(transferables),
elementArray2: this.elementArray2 && this.elementArray2.serialize(transferables),
paintVertexArrays: serializePaintVertexArrays(this.layerData, transferables),
Expand Down
159 changes: 110 additions & 49 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,57 @@ const CollisionFeature = require('../../symbol/collision_feature');
const findPoleOfInaccessibility = require('../../util/find_pole_of_inaccessibility');
const classifyRings = require('../../util/classify_rings');
const VectorTileFeature = require('vector-tile').VectorTileFeature;
const createStructArrayType = require('../../util/struct_array');

const shapeText = Shaping.shapeText;
const shapeIcon = Shaping.shapeIcon;
const WritingMode = Shaping.WritingMode;
const getGlyphQuads = Quads.getGlyphQuads;
const getIconQuads = Quads.getIconQuads;

const PlacedSymbolArray = createStructArrayType({
members: [
{ type: 'Int16', name: 'anchorX' },
{ type: 'Int16', name: 'anchorY' },
{ type: 'Uint16', name: 'glyphStartIndex' },
{ type: 'Uint16', name: 'numGlyphs' },
{ type: 'Uint32', name: 'lineStartIndex' },
{ type: 'Uint32', name: 'lineLength' },
{ type: 'Uint16', name: 'segment' },
{ type: 'Uint16', name: 'lowerSize' },
{ type: 'Uint16', name: 'upperSize' },
{ type: 'Float32', name: 'placementZoom' },
{ type: 'Uint8', name: 'vertical' }
]
});

const GlyphOffsetArray = createStructArrayType({
members: [
{ type: 'Float32', name: 'offsetX' }
]
});

const LineVertexArray = createStructArrayType({
members: [
{ type: 'Int16', name: 'x' },
{ type: 'Int16', name: 'y' }
]});

const elementArrayType = createElementArrayType();

const layoutAttributes = [
{name: 'a_pos_offset', components: 4, type: 'Int16'},
{name: 'a_label_pos', components: 2, type: 'Int16'},
{name: 'a_data', components: 4, type: 'Uint16'}
];

const dynamicLayoutAttributes = [
{ name: 'a_projected_pos', components: 3, type: 'Float32' }
];

const symbolInterfaces = {
glyph: {
layoutAttributes: layoutAttributes,
dynamicLayoutAttributes: dynamicLayoutAttributes,
elementArrayType: elementArrayType,
paintAttributes: [
{name: 'a_fill_color', property: 'text-color', type: 'Uint8'},
Expand All @@ -50,6 +83,7 @@ const symbolInterfaces = {
},
icon: {
layoutAttributes: layoutAttributes,
dynamicLayoutAttributes: dynamicLayoutAttributes,
elementArrayType: elementArrayType,
paintAttributes: [
{name: 'a_fill_color', property: 'icon-color', type: 'Uint8'},
Expand All @@ -70,34 +104,26 @@ const symbolInterfaces = {
}
};

function addVertex(array, x, y, ox, oy, labelX, labelY, tx, ty, sizeVertex, minzoom, maxzoom, labelminzoom, labelangle) {
function addVertex(array, anchorX, anchorY, ox, oy, tx, ty, sizeVertex, labelminzoom) {
array.emplaceBack(
// a_pos_offset
x,
y,
anchorX,
anchorY,
Math.round(ox * 64),
Math.round(oy * 64),

// a_label_pos
labelX,
labelY,

// a_data
tx, // x coordinate of symbol on glyph atlas texture
ty, // y coordinate of symbol on glyph atlas texture
packUint8ToFloat(
(labelminzoom || 0) * 10, // labelminzoom
labelangle % 256 // labelangle
),
packUint8ToFloat(
(minzoom || 0) * 10, // minzoom
Math.min(maxzoom || 25, 25) * 10 // maxzoom
0 // unused 8 bits
),
0, // unused 16 bits

// a_size
sizeVertex ? sizeVertex[0] : undefined,
sizeVertex ? sizeVertex[1] : undefined,
sizeVertex ? sizeVertex[2] : undefined
sizeVertex ? sizeVertex[1] : undefined
);
}

Expand Down Expand Up @@ -188,6 +214,12 @@ class SymbolBucket {
}
this.textSizeData = options.textSizeData;
this.iconSizeData = options.iconSizeData;

this.placedGlyphArray = new PlacedSymbolArray(options.placedGlyphArray);
this.placedIconArray = new PlacedSymbolArray(options.placedIconArray);
this.glyphOffsetArray = new GlyphOffsetArray(options.glyphOffsetArray);
this.lineVertexArray = new LineVertexArray(options.lineVertexArray);

} else {
this.textSizeData = getSizeData(this.zoom, layer, 'text-size');
this.iconSizeData = getSizeData(this.zoom, layer, 'icon-size');
Expand Down Expand Up @@ -294,6 +326,10 @@ class SymbolBucket {
textSizeData: this.textSizeData,
iconSizeData: this.iconSizeData,
fontstack: this.fontstack,
placedGlyphArray: this.placedGlyphArray.serialize(transferables),
placedIconArray: this.placedIconArray.serialize(transferables),
glyphOffsetArray: this.glyphOffsetArray.serialize(transferables),
lineVertexArray: this.lineVertexArray.serialize(transferables),
arrays: util.mapObject(this.arrays, (a) => a.isEmpty() ? null : a.serialize(transferables))
};
}
Expand Down Expand Up @@ -529,6 +565,11 @@ class SymbolBucket {

this.createArrays();

this.placedGlyphArray = new PlacedSymbolArray();
this.placedIconArray = new PlacedSymbolArray();
this.glyphOffsetArray = new GlyphOffsetArray();
this.lineVertexArray = new LineVertexArray();

const layer = this.layers[0];
const layout = layer.layout;

Expand Down Expand Up @@ -603,6 +644,14 @@ class SymbolBucket {


// Insert final placement into collision tree and add glyphs/icons to buffers
if (!hasText && !hasIcon) continue;
const line = symbolInstance.line;
const lineStartIndex = this.lineVertexArray.length;
for (let i = 0; i < line.length; i++) {
this.lineVertexArray.emplaceBack(line[i].x, line[i].y);
}
const lineLength = this.lineVertexArray.length - lineStartIndex;


if (hasText) {
collisionTile.insertCollisionFeature(textCollisionFeature, glyphScale, layout['text-ignore-placement']);
Expand All @@ -622,7 +671,10 @@ class SymbolBucket {
collisionTile.angle,
symbolInstance.featureProperties,
symbolInstance.writingModes,
symbolInstance.anchor);
symbolInstance.anchor,
lineStartIndex,
lineLength,
this.placedGlyphArray);
}
}

Expand All @@ -645,7 +697,10 @@ class SymbolBucket {
collisionTile.angle,
symbolInstance.featureProperties,
null,
symbolInstance.anchor
symbolInstance.anchor,
lineStartIndex,
lineLength,
this.placedIconArray
);
}
}
Expand All @@ -655,55 +710,62 @@ class SymbolBucket {
if (showCollisionBoxes) this.addToDebugBuffers(collisionTile);
}

addSymbols(arrays, quads, scale, sizeVertex, keepUpright, alongLine, placementAngle, featureProperties, writingModes, labelAnchor) {
addSymbols(arrays, quads, scale, sizeVertex, keepUpright, alongLine, placementAngle, featureProperties, writingModes, labelAnchor, lineStartIndex, lineLength, placedSymbolArray) {
const elementArray = arrays.elementArray;
const layoutVertexArray = arrays.layoutVertexArray;
const dynamicLayoutVertexArray = arrays.dynamicLayoutVertexArray;

const zoom = this.zoom;
const placementZoom = Math.max(Math.log(scale) / Math.LN2 + zoom, 0);

const glyphOffsetArrayStart = this.glyphOffsetArray.length;

const labelAngle = Math.abs((labelAnchor.angle + placementAngle) % Math.PI);
const inVerticalRange = labelAngle > Math.PI / 4 && labelAngle <= Math.PI * 3 / 4;
const useVerticalMode = Boolean(writingModes & WritingMode.vertical) && inVerticalRange;

for (const symbol of quads) {
// drop incorrectly oriented glyphs
const a = (symbol.anchorAngle + placementAngle + Math.PI) % (Math.PI * 2);
if (writingModes & WritingMode.vertical) {
if (alongLine && symbol.writingMode === WritingMode.vertical) {
if (keepUpright && alongLine && a <= (Math.PI * 5 / 4) || a > (Math.PI * 7 / 4)) continue;
} else if (keepUpright && alongLine && a <= (Math.PI * 3 / 4) || a > (Math.PI * 5 / 4)) continue;
} else if (keepUpright && alongLine && (a <= Math.PI / 2 || a > Math.PI * 3 / 2)) continue;

if (alongLine && keepUpright) {
// drop incorrectly oriented glyphs
if ((symbol.writingMode === WritingMode.vertical) !== useVerticalMode) continue;
}

const tl = symbol.tl,
tr = symbol.tr,
bl = symbol.bl,
br = symbol.br,
tex = symbol.tex,
anchorPoint = symbol.anchorPoint;

let minZoom = Math.max(zoom + Math.log(symbol.minScale) / Math.LN2, placementZoom);
const maxZoom = Math.min(zoom + Math.log(symbol.maxScale) / Math.LN2, 25);

if (maxZoom <= minZoom) continue;

// Lower min zoom so that while fading out the label it can be shown outside of collision-free zoom levels
if (minZoom === placementZoom) minZoom = 0;

// Encode angle of glyph
const glyphAngle = Math.round((symbol.glyphAngle / (Math.PI * 2)) * 256);
tex = symbol.tex;

const segment = arrays.prepareSegment(4);
const index = segment.vertexLength;

addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, labelAnchor.x, labelAnchor.y, tex.x, tex.y, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, labelAnchor.x, labelAnchor.y, tex.x + tex.w, tex.y, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, labelAnchor.x, labelAnchor.y, tex.x, tex.y + tex.h, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, labelAnchor.x, labelAnchor.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
const y = symbol.glyphOffsetY;
addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, tl.x, y + tl.y, tex.x, tex.y, sizeVertex, placementZoom);
addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, tr.x, y + tr.y, tex.x + tex.w, tex.y, sizeVertex, placementZoom);
addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex, placementZoom);
addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, placementZoom);

dynamicLayoutVertexArray.emplaceBack(labelAnchor.x, labelAnchor.y, 0);
dynamicLayoutVertexArray.emplaceBack(labelAnchor.x, labelAnchor.y, 0);
dynamicLayoutVertexArray.emplaceBack(labelAnchor.x, labelAnchor.y, 0);
dynamicLayoutVertexArray.emplaceBack(labelAnchor.x, labelAnchor.y, 0);

elementArray.emplaceBack(index, index + 1, index + 2);
elementArray.emplaceBack(index + 1, index + 2, index + 3);

segment.vertexLength += 4;
segment.primitiveLength += 2;

this.glyphOffsetArray.emplaceBack(symbol.glyphOffsetX);
}

placedSymbolArray.emplaceBack(labelAnchor.x, labelAnchor.y,
glyphOffsetArrayStart, this.glyphOffsetArray.length - glyphOffsetArrayStart,
lineStartIndex, lineLength, labelAnchor.segment,
sizeVertex ? sizeVertex[0] : 0, sizeVertex ? sizeVertex[1] : 0,
placementZoom, useVerticalMode);

arrays.populatePaintArrays(featureProperties);
}

Expand Down Expand Up @@ -788,7 +850,7 @@ class SymbolBucket {
if (!shapedTextOrientations[writingMode]) continue;
glyphQuads = glyphQuads.concat(addToBuffers ?
getGlyphQuads(anchor, shapedTextOrientations[writingMode],
textBoxScale, line, layer, textAlongLine,
layer, textAlongLine,
globalProperties, featureProperties) :
[]);
textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedTextOrientations[writingMode], textBoxScale, textPadding, textAlongLine, false);
Expand All @@ -799,7 +861,7 @@ class SymbolBucket {

if (shapedIcon) {
iconQuads = addToBuffers ?
getIconQuads(anchor, shapedIcon, iconBoxScale, line, layer,
getIconQuads(anchor, shapedIcon, layer,
iconAlongLine, shapedTextOrientations[WritingMode.horizontal],
globalProperties, featureProperties) :
[];
Expand All @@ -825,6 +887,7 @@ class SymbolBucket {
glyphQuads,
iconQuads,
anchor,
line,
featureIndex,
featureProperties,
writingModes
Expand Down Expand Up @@ -894,10 +957,9 @@ function getSizeAttributeDeclarations(layer, sizeProperty) {
) {
// composite function:
// [ text-size(lowerZoomStop, feature),
// text-size(upperZoomStop, feature),
// layoutSize == text-size(layoutZoomLevel, feature) ]
// text-size(upperZoomStop, feature)]
return [{
name: 'a_size', components: 3, type: 'Uint16'
name: 'a_size', components: 2, type: 'Uint16'
}];
}
// constant or camera function
Expand All @@ -920,8 +982,7 @@ function getSizeVertexData(layer, tileZoom, stopZoomLevels, sizeProperty, featur
// composite function
return [
10 * layer.getLayoutValue(sizeProperty, {zoom: stopZoomLevels[0]}, featureProperties),
10 * layer.getLayoutValue(sizeProperty, {zoom: stopZoomLevels[1]}, featureProperties),
10 * layer.getLayoutValue(sizeProperty, {zoom: 1 + tileZoom}, featureProperties)
10 * layer.getLayoutValue(sizeProperty, {zoom: stopZoomLevels[1]}, featureProperties)
];
}
// camera function or constant
Expand Down
Loading

0 comments on commit dcdddfc

Please sign in to comment.