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

improve pitched legibility of labels that follow lines #4781

Merged
merged 8 commits into from
Jun 13, 2017
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
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
234 changes: 133 additions & 101 deletions src/data/bucket/symbol_bucket.js

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions src/data/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const AttributeType = {
Int8: 'BYTE',
Uint8: 'UNSIGNED_BYTE',
Int16: 'SHORT',
Uint16: 'UNSIGNED_SHORT'
Uint16: 'UNSIGNED_SHORT',
Float32: 'FLOAT'
};

/**
Expand All @@ -22,14 +23,16 @@ class Buffer {
* @param {Object} array A serialized StructArray.
* @param {Object} arrayType A serialized StructArrayType.
* @param {BufferType} type
* @param {boolean} dynamicDraw Whether this buffer will be repeatedly updated.
*/
constructor(array, arrayType, type) {
constructor(array, arrayType, type, dynamicDraw) {
this.arrayBuffer = array.arrayBuffer;
this.length = array.length;
this.attributes = arrayType.members;
this.itemSize = arrayType.bytesPerElement;
this.type = type;
this.arrayType = arrayType;
this.dynamicDraw = dynamicDraw;
}

static fromStructArray(array, type) {
Expand All @@ -47,15 +50,27 @@ class Buffer {
this.gl = gl;
this.buffer = gl.createBuffer();
gl.bindBuffer(type, this.buffer);
gl.bufferData(type, this.arrayBuffer, gl.STATIC_DRAW);
gl.bufferData(type, this.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW);

// dump array buffer once it's bound to gl
this.arrayBuffer = null;
} else {
gl.bindBuffer(type, this.buffer);

if (this.dynamicDraw && this.arrayBuffer) {
gl.bufferSubData(type, 0, this.arrayBuffer);
this.arrayBuffer = null;
}
}
}

/*
* @param {Object} array A serialized StructArray.
*/
updateData(array) {
this.arrayBuffer = array.arrayBuffer;
}

enableAttributes (gl, program) {
for (let j = 0; j < this.attributes.length; j++) {
const member = this.attributes[j];
Expand Down
10 changes: 10 additions & 0 deletions src/data/buffer_group.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class BufferGroup {
this.layoutVertexBuffer = new Buffer(arrays.layoutVertexArray,
LayoutVertexArrayType.serialize(), Buffer.BufferType.VERTEX);

if (arrays.dynamicLayoutVertexArray) {
const DynamicLayoutVertexArrayType = createVertexArrayType(programInterface.dynamicLayoutAttributes);
this.dynamicLayoutVertexArray = new DynamicLayoutVertexArrayType(arrays.dynamicLayoutVertexArray);
this.dynamicLayoutVertexBuffer = new Buffer(arrays.dynamicLayoutVertexArray,
DynamicLayoutVertexArrayType.serialize(), Buffer.BufferType.VERTEX, true);
}

if (arrays.elementArray) {
this.elementBuffer = new Buffer(arrays.elementArray,
programInterface.elementArrayType.serialize(), Buffer.BufferType.ELEMENT);
Expand Down Expand Up @@ -49,6 +56,9 @@ class BufferGroup {
destroy() {
this.layoutVertexBuffer.destroy();

if (this.dynamicLayoutVertexBuffer) {
this.dynamicLayoutVertexBuffer.destroy();
}
if (this.elementBuffer) {
this.elementBuffer.destroy();
}
Expand Down
109 changes: 30 additions & 79 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict';

const assert = require('assert');
const util = require('../util/util');
const drawCollisionDebug = require('./draw_collision_debug');
const pixelsToTileUnits = require('../source/pixels_to_tile_units');
const interpolationFactor = require('../style-spec/function').interpolationFactor;
const symbolProjection = require('../symbol/projection');
const symbolSize = require('../symbol/symbol_size');
const mat4 = require('@mapbox/gl-matrix').mat4;
const identityMat4 = mat4.identity(new Float32Array(16));

module.exports = drawSymbols;

Expand Down Expand Up @@ -39,14 +40,16 @@ function drawSymbols(painter, sourceCache, layer, coords) {
layer.layout['icon-rotation-alignment'],
// icon-pitch-alignment is not yet implemented
// and we simply inherit the rotation alignment
layer.layout['icon-rotation-alignment']
layer.layout['icon-rotation-alignment'],
layer.layout['icon-keep-upright']
);

drawLayerSymbols(painter, sourceCache, layer, coords, true,
layer.paint['text-translate'],
layer.paint['text-translate-anchor'],
layer.layout['text-rotation-alignment'],
layer.layout['text-pitch-alignment']
layer.layout['text-pitch-alignment'],
layer.layout['text-keep-upright']
);

if (sourceCache.map.showCollisionBoxes) {
Expand All @@ -55,7 +58,7 @@ function drawSymbols(painter, sourceCache, layer, coords) {
}

function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor,
rotationAlignment, pitchAlignment) {
rotationAlignment, pitchAlignment, keepUpright) {

if (!isText && painter.style.sprite && !painter.style.sprite.loaded())
return;
Expand All @@ -64,6 +67,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate

const rotateWithMap = rotationAlignment === 'map';
const pitchWithMap = pitchAlignment === 'map';
const alongLine = rotateWithMap && layer.layout['symbol-placement'] === 'line';

const depthOn = pitchWithMap;

Expand Down Expand Up @@ -97,13 +101,23 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate

painter.enableTileClippingMask(coord);

gl.uniformMatrix4fv(program.u_matrix, false,
painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor));
gl.uniformMatrix4fv(program.u_matrix, false, painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor));

const s = pixelsToTileUnits(tile, 1, painter.transform.zoom);
const labelPlaneMatrix = symbolProjection.getLabelPlaneMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s);
const glCoordMatrix = symbolProjection.getGlCoordMatrix(coord.posMatrix, pitchWithMap, rotateWithMap, painter.transform, s);
gl.uniformMatrix4fv(program.u_gl_coord_matrix, false, painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true));

if (alongLine) {
gl.uniformMatrix4fv(program.u_label_plane_matrix, false, identityMat4);
symbolProjection.updateLineLabels(bucket, coord.posMatrix, painter, isText, labelPlaneMatrix, pitchWithMap, keepUpright, s, layer);
} else {
gl.uniformMatrix4fv(program.u_label_plane_matrix, false, labelPlaneMatrix);
}

gl.uniform1f(program.u_collision_y_stretch, tile.collisionTile.yStretch);

drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF,
pitchWithMap);
drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap);

prevFontstack = bucket.fontstack;
}
Expand All @@ -116,7 +130,6 @@ function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, ro
const gl = painter.gl;
const tr = painter.transform;

gl.uniform1i(program.u_rotate_with_map, rotateWithMap);
gl.uniform1i(program.u_pitch_with_map, pitchWithMap);

gl.activeTexture(gl.TEXTURE0);
Expand Down Expand Up @@ -147,89 +160,27 @@ function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, ro
painter.frameHistory.bind(gl);
gl.uniform1i(program.u_fadetexture, 1);

gl.uniform1f(program.u_zoom, tr.zoom);

gl.uniform1f(program.u_pitch, tr.pitch / 360 * 2 * Math.PI);
gl.uniform1f(program.u_bearing, tr.bearing / 360 * 2 * Math.PI);
gl.uniform1f(program.u_aspect_ratio, tr.width / tr.height);

gl.uniform1i(program.u_is_size_zoom_constant, sizeData.isZoomConstant ? 1 : 0);
gl.uniform1i(program.u_is_size_feature_constant, sizeData.isFeatureConstant ? 1 : 0);

if (!sizeData.isZoomConstant && !sizeData.isFeatureConstant) {
// composite function
const t = interpolationFactor(tr.zoom,
sizeData.functionBase,
sizeData.coveringZoomRange[0],
sizeData.coveringZoomRange[1]
);
gl.uniform1f(program.u_size_t, util.clamp(t, 0, 1));
} else if (sizeData.isFeatureConstant && !sizeData.isZoomConstant) {
// camera function
let size;
if (sizeData.functionType === 'interval') {
size = layer.getLayoutValue(isText ? 'text-size' : 'icon-size',
{zoom: tr.zoom});
} else {
assert(sizeData.functionType === 'exponential');
// Even though we could get the exact value of the camera function
// at z = tr.zoom, we intentionally do not: instead, we interpolate
// between the camera function values at a pair of zoom stops covering
// [tileZoom, tileZoom + 1] in order to be consistent with this
// restriction on composite functions
const t = sizeData.functionType === 'interval' ? 0 :
interpolationFactor(tr.zoom,
sizeData.functionBase,
sizeData.coveringZoomRange[0],
sizeData.coveringZoomRange[1]);

const lowerValue = sizeData.coveringStopValues[0];
const upperValue = sizeData.coveringStopValues[1];
size = lowerValue + (upperValue - lowerValue) * util.clamp(t, 0, 1);
}

gl.uniform1f(program.u_size, size);
gl.uniform1f(program.u_layout_size, sizeData.layoutSize);
} else if (sizeData.isFeatureConstant && sizeData.isZoomConstant) {
gl.uniform1f(program.u_size, sizeData.layoutSize);
}
gl.uniform1f(program.u_camera_to_center_distance, tr.cameraToCenterDistance);
if (layer.layout['symbol-placement'] === 'line' &&
layer.layout['text-rotation-alignment'] === 'map' &&
layer.layout['text-pitch-alignment'] === 'viewport' &&
layer.layout['text-field']) {
// We hide line labels with viewport alignment as they move into the distance
// because the approximations we use for drawing their glyphs get progressively worse
// The "1.5" here means we start hiding them when the distance from the label
// to the camera is 50% greater than the distance from the center of the map
// to the camera. Depending on viewport properties, you might expect this to filter
// the top third of the screen at pitch 60, and do almost nothing at pitch 45
gl.uniform1f(program.u_max_camera_distance, 1.5);
} else {
// "10" is effectively infinite at any pitch we support
gl.uniform1f(program.u_max_camera_distance, 10);
}

const size = symbolSize.evaluateSizeForZoom(sizeData, tr, layer, isText);
if (size.uSizeT !== undefined) gl.uniform1f(program.u_size_t, size.uSizeT);
if (size.uSize !== undefined) gl.uniform1f(program.u_size, size.uSize);
}

function drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap) {

const gl = painter.gl;
const tr = painter.transform;

if (pitchWithMap) {
const s = pixelsToTileUnits(tile, 1, tr.zoom);
gl.uniform2f(program.u_extrude_scale, s, s);
} else {
const s = tr.cameraToCenterDistance;
gl.uniform2f(program.u_extrude_scale,
tr.pixelsToGLUnits[0] * s,
tr.pixelsToGLUnits[1] * s);
}

if (isSDF) {
const haloWidthProperty = `${isText ? 'text' : 'icon'}-halo-width`;
const hasHalo = !layer.isPaintValueFeatureConstant(haloWidthProperty) || layer.paint[haloWidthProperty];
const gammaScale = (pitchWithMap ? Math.cos(tr._pitch) : 1) * tr.cameraToCenterDistance;
const gammaScale = (pitchWithMap ? Math.cos(tr._pitch) * tr.cameraToCenterDistance : 1);
gl.uniform1f(program.u_gamma_scale, gammaScale);

if (hasHalo) { // Draw halo underneath the text.
Expand All @@ -248,7 +199,7 @@ function drawSymbolElements(buffers, layer, gl, program) {
const paintVertexBuffer = layerData && layerData.paintVertexBuffer;

for (const segment of buffers.segments) {
segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, paintVertexBuffer, segment.vertexOffset);
segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, paintVertexBuffer, segment.vertexOffset, buffers.dynamicLayoutVertexBuffer);
gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);
}
}
4 changes: 4 additions & 0 deletions src/render/frame_history.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class FrameHistory {
this.previousZoom = zoom;
}

isVisible(zoom) {
return this.opacities[Math.floor(zoom * 10)] !== 0;
}

bind(gl) {
if (!this.texture) {
this.texture = gl.createTexture();
Expand Down
28 changes: 23 additions & 5 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,19 +292,37 @@ class Painter {
this.gl.depthRange(nearDepth, farDepth);
}

translatePosMatrix(matrix, tile, translate, anchor) {
/**
* Transform a matrix to incorporate the *-translate and *-translate-anchor properties into it.
* @param {Float32Array} matrix
* @param {Tile} tile
* @param {Array<number>} translate
* @param {string} anchor
* @param {boolean} inViewportPixelUnitsUnits True when the units accepted by the matrix are in viewport pixels instead of tile units.
*
* @returns {Float32Array} matrix
*/
translatePosMatrix(matrix, tile, translate, translateAnchor, inViewportPixelUnitsUnits) {
if (!translate[0] && !translate[1]) return matrix;

if (anchor === 'viewport') {
const sinA = Math.sin(-this.transform.angle);
const cosA = Math.cos(-this.transform.angle);
const angle = inViewportPixelUnitsUnits ?
(translateAnchor === 'map' ? this.transform.angle : 0) :
(translateAnchor === 'viewport' ? -this.transform.angle : 0);

if (angle) {
const sinA = Math.sin(angle);
const cosA = Math.cos(angle);
translate = [
translate[0] * cosA - translate[1] * sinA,
translate[0] * sinA + translate[1] * cosA
];
}

const translation = [
const translation = inViewportPixelUnitsUnits ? [
translate[0],
translate[1],
0
] : [
pixelsToTileUnits(tile, translate[0], this.transform.zoom),
pixelsToTileUnits(tile, translate[1], this.transform.zoom),
0
Expand Down
Loading