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

Attenuate label size scaling with distance #4547

Merged
merged 21 commits into from
May 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
12cd4e7
Fix "labelMinScale" logic in getGlyphQuads:
ChrisLoer Apr 13, 2017
9b6f46d
Fix comment: Tile.coord is a TileCoord, not a Coordinate
ChrisLoer Apr 26, 2017
8332149
Make render test summary failures searchable
ChrisLoer Apr 27, 2017
abfaa59
Recalculate collision box shape at placement time based on distance f…
ChrisLoer Mar 29, 2017
cf3d37a
Fix short-circuit guard for queries against empty collision tiles. `g…
ChrisLoer Mar 29, 2017
3d33278
Attenuate the effect of perspective on label scale:
ChrisLoer Mar 29, 2017
1e2d4e7
Standardizing on highp precision based on discussion in https://githu…
ChrisLoer Mar 29, 2017
a594f4d
Generate line collision boxes for scales < 1.
ChrisLoer Mar 29, 2017
f0939ba
Throttle symbol re-placement to once every 300ms.
ChrisLoer May 24, 2017
b4aa8eb
Support pitch-scaling attenuation behavior in debug collision boxes.
ChrisLoer Mar 29, 2017
2b05c3a
Add 'max-camera-distance' uniform for hiding labels in the distance t…
ChrisLoer May 12, 2017
1593dce
Render tests for icon and text pitch scaling.
ChrisLoer Mar 29, 2017
88b28eb
Update existing pitched map text rendering tests to match new pitched…
ChrisLoer Mar 29, 2017
648fc0e
Fix type info in comments: Coordinate -> TileCoord
ChrisLoer May 22, 2017
71ca322
Small performance enhancement: don't add "pitch padding" boxes for li…
ChrisLoer May 23, 2017
3fe790c
Don't reuse wrapped tiles in SourceCache.
ChrisLoer May 24, 2017
ff95426
Add pitched-collision render tests, including case of wrapped tiles.
ChrisLoer May 24, 2017
ecfeb4e
Tiny shader logic simplification.
ChrisLoer May 24, 2017
d3b83f1
Make debug collision boxes use same fade texture as the symbols.
ChrisLoer May 24, 2017
6f06f78
Make perspective_zoom_adjust use the same .1 precision used by labelm…
ChrisLoer May 25, 2017
4b0235a
Add simple CanvasSource debug page.
ChrisLoer May 25, 2017
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
80 changes: 80 additions & 0 deletions debug/canvas.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='/dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>

<body>
<canvas id="testCanvasID" width="200" height="100"
style="border:10px solid blue;">
Canvas not supported
</canvas>
<div id='map'></div>

<script type="application/javascript">
function drawToCanvas() {
var canvas = document.getElementById('testCanvasID');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');

ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);

ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);
}
}
</script>
<script src='/dist/mapbox-gl-dev.js'></script>
<script src='/debug/access_token_generated.js'></script>
<script>

var canvasStyle = {
"version": 8,
"sources": {
"canvas": {
"type": "canvas",
"canvas": "testCanvasID",
"coordinates": [
[-122.51596391201019, 37.56238816766053],
[-122.51467645168304, 37.56410183312965],
[-122.51309394836426, 37.563391708549425],
[-122.51423120498657, 37.56161849366671]
]
}
},
"layers": [{
"id": "background",
"type": "background",
"paint": {
"background-color": "rgb(4,7,14)"
}
}, {
"id": "canvas",
"type": "raster",
"source": "canvas"
}]
};

var map = new mapboxgl.Map({
container: 'map',
minZoom: 14,
zoom: 17,
center: [-122.514426, 37.562984],
bearing: -96,
style: canvasStyle,
hash: false
});

drawToCanvas();

</script>
</body>
</html>
55 changes: 38 additions & 17 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ 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'}
];

Expand Down Expand Up @@ -60,22 +61,27 @@ const symbolInterfaces = {
},
collisionBox: { // used to render collision boxes for debugging purposes
layoutAttributes: [
{name: 'a_pos', components: 2, type: 'Int16'},
{name: 'a_extrude', components: 2, type: 'Int16'},
{name: 'a_data', components: 2, type: 'Uint8'}
{name: 'a_pos', components: 2, type: 'Int16'},
{name: 'a_anchor_pos', components: 2, type: 'Int16'},
{name: 'a_extrude', components: 2, type: 'Int16'},
{name: 'a_data', components: 2, type: 'Uint8'}
],
elementArrayType: createElementArrayType(2)
}
};

function addVertex(array, x, y, ox, oy, tx, ty, sizeVertex, minzoom, maxzoom, labelminzoom, labelangle) {
function addVertex(array, x, y, ox, oy, labelX, labelY, tx, ty, sizeVertex, minzoom, maxzoom, labelminzoom, labelangle) {
array.emplaceBack(
// a_pos_offset
x,
y,
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
Expand All @@ -95,11 +101,14 @@ function addVertex(array, x, y, ox, oy, tx, ty, sizeVertex, minzoom, maxzoom, la
);
}

function addCollisionBoxVertex(layoutVertexArray, point, extrude, maxZoom, placementZoom) {
function addCollisionBoxVertex(layoutVertexArray, point, anchor, extrude, maxZoom, placementZoom) {
return layoutVertexArray.emplaceBack(
// pos
point.x,
point.y,
// a_anchor_pos
anchor.x,
anchor.y,
// extrude
Math.round(extrude.x),
Math.round(extrude.y),
Expand Down Expand Up @@ -523,6 +532,10 @@ class SymbolBucket {
const layer = this.layers[0];
const layout = layer.layout;

// Symbols that don't show until greater than the CollisionTile's maxScale won't even be added
// to the buffers. Even though pan operations on a tilted map might cause the symbol to be
// displayable, we have to stay conservative here because the CollisionTile didn't consider
// this scale range.
const maxScale = collisionTile.maxScale;

const textAlongLine = layout['text-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line';
Expand Down Expand Up @@ -608,7 +621,8 @@ class SymbolBucket {
textAlongLine,
collisionTile.angle,
symbolInstance.featureProperties,
symbolInstance.writingModes);
symbolInstance.writingModes,
symbolInstance.anchor);
}
}

Expand All @@ -629,7 +643,9 @@ class SymbolBucket {
layout['icon-keep-upright'],
iconAlongLine,
collisionTile.angle,
symbolInstance.featureProperties
symbolInstance.featureProperties,
null,
symbolInstance.anchor
);
}
}
Expand All @@ -639,7 +655,7 @@ class SymbolBucket {
if (showCollisionBoxes) this.addToDebugBuffers(collisionTile);
}

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

Expand Down Expand Up @@ -676,10 +692,10 @@ class SymbolBucket {
const segment = arrays.prepareSegment(4);
const index = segment.vertexLength;

addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, minZoom, maxZoom, placementZoom, glyphAngle);
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);

elementArray.emplaceBack(index, index + 1, index + 2);
elementArray.emplaceBack(index + 1, index + 2, index + 3);
Expand Down Expand Up @@ -709,7 +725,12 @@ class SymbolBucket {

for (let b = feature.boxStartIndex; b < feature.boxEndIndex; b++) {
const box = this.collisionBoxArray.get(b);
const anchorPoint = box.anchorPoint;
if (collisionTile.perspectiveRatio === 1 && box.maxScale < 1) {
// These boxes aren't used on unpitched maps
// See CollisionTile#insertCollisionFeature
continue;
}
const boxAnchorPoint = box.anchorPoint;

const tl = new Point(box.x1, box.y1 * yStretch)._rotate(angle);
const tr = new Point(box.x2, box.y1 * yStretch)._rotate(angle);
Expand All @@ -722,10 +743,10 @@ class SymbolBucket {
const segment = arrays.prepareSegment(4);
const index = segment.vertexLength;

addCollisionBoxVertex(layoutVertexArray, anchorPoint, tl, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, anchorPoint, tr, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, anchorPoint, br, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, anchorPoint, bl, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, boxAnchorPoint, symbolInstance.anchor, tl, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, boxAnchorPoint, symbolInstance.anchor, tr, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, boxAnchorPoint, symbolInstance.anchor, br, maxZoom, placementZoom);
addCollisionBoxVertex(layoutVertexArray, boxAnchorPoint, symbolInstance.anchor, bl, maxZoom, placementZoom);

elementArray.emplaceBack(index, index + 1);
elementArray.emplaceBack(index + 1, index + 2);
Expand Down
2 changes: 1 addition & 1 deletion src/data/feature_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class FeatureIndex {
if (layerResult === undefined) {
layerResult = result[layerID] = [];
}
layerResult.push(geojsonFeature);
layerResult.push({ featureIndex: index, feature: geojsonFeature });
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,21 @@ class Transform {
return new Float32Array(posMatrix);
}

/**
* Calculate the distance from the center of a tile to the camera
* These distances are in view-space dimensions derived from the size of the
* viewport, similar to this.cameraToCenterDistance
* If the tile is dead-center in the viewport, then cameraToTileDistance == cameraToCenterDistance
*
* @param {Tile} tile
*/
cameraToTileDistance(tile: Object) {
const posMatrix = this.calculatePosMatrix(tile.coord, tile.sourceMaxZoom);
const tileCenter = [tile.tileSize / 2, tile.tileSize / 2, 0, 1];
vec4.transformMat4(tileCenter, tileCenter, posMatrix);
return tileCenter[3];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What are the units here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I hesitate to say "pixels" because that's not quite right, but it's distance in view-space coordinates, which is pixel-derived. It's the same units as "cameraToCenterDistance", which is defined as:

0.5 / Math.tan(this._fov / 2) * this.height (and height is in pixels)

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, can you add a this explanation as a comment?


_constrain() {
if (!this.center || !this.width || !this.height || this._constraining) return;

Expand Down
11 changes: 10 additions & 1 deletion src/render/draw_collision_debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ function drawCollisionDebug(painter, sourceCache, layer, coords) {
gl.enable(gl.STENCIL_TEST);
const program = painter.useProgram('collisionBox');

gl.activeTexture(gl.TEXTURE1);
painter.frameHistory.bind(gl);
gl.uniform1i(program.u_fadetexture, 1);

for (let i = 0; i < coords.length; i++) {
const coord = coords[i];
const tile = sourceCache.getTile(coord);
Expand All @@ -22,7 +26,12 @@ function drawCollisionDebug(painter, sourceCache, layer, coords) {
painter.lineWidth(1);
gl.uniform1f(program.u_scale, Math.pow(2, painter.transform.zoom - tile.coord.z));
gl.uniform1f(program.u_zoom, painter.transform.zoom * 10);
gl.uniform1f(program.u_maxzoom, (tile.coord.z + 1) * 10);
const maxZoom = Math.max(0, Math.min(25, tile.coord.z + Math.log(tile.collisionTile.maxScale) / Math.LN2));
gl.uniform1f(program.u_maxzoom, maxZoom * 10);

gl.uniform1f(program.u_collision_y_stretch, tile.collisionTile.yStretch);
gl.uniform1f(program.u_pitch, painter.transform.pitch / 360 * 2 * Math.PI);
gl.uniform1f(program.u_camera_to_center_distance, painter.transform.cameraToCenterDistance);

for (const segment of buffers.segments) {
segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, null, segment.vertexOffset);
Expand Down
18 changes: 18 additions & 0 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
gl.uniformMatrix4fv(program.u_matrix, false,
painter.translatePosMatrix(coord.posMatrix, tile, translate, translateAnchor));

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

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

Expand Down Expand Up @@ -191,6 +193,22 @@ function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, ro
} 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);
}
}

function drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap) {
Expand Down
18 changes: 16 additions & 2 deletions src/shaders/collision_box.fragment.glsl
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
uniform float u_zoom;
// u_maxzoom is derived from the maximum scale considered by the CollisionTile
// Labels with placement zoom greater than this value will not be placed,
// regardless of perspective effects.
uniform float u_maxzoom;
uniform sampler2D u_fadetexture;

// v_max_zoom is a collision-box-specific value that controls when line-following
// collision boxes are used.
varying float v_max_zoom;
varying float v_placement_zoom;
varying float v_perspective_zoom_adjust;
varying vec2 v_fade_tex;

void main() {

float alpha = 0.5;

// Green = no collisions, label is showing
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0) * alpha;

if (v_placement_zoom > u_zoom) {
// Red = collision, label hidden
if (texture2D(u_fadetexture, v_fade_tex).a < 1.0) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * alpha;
}

if (u_zoom >= v_max_zoom) {
// Faded black = this collision box is not used at this zoom (for curved labels)
if (u_zoom >= v_max_zoom + v_perspective_zoom_adjust) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0) * alpha * 0.25;
}

// Faded blue = the placement scale for this label is beyond the CollisionTile
// max scale, so it's impossible for this label to show without collision detection
// being run again (the label's glyphs haven't even been added to the symbol bucket)
if (v_placement_zoom >= u_maxzoom) {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0) * alpha * 0.2;
}
Expand Down
18 changes: 17 additions & 1 deletion src/shaders/collision_box.vertex.glsl
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
attribute vec2 a_pos;
attribute vec2 a_anchor_pos;
attribute vec2 a_extrude;
attribute vec2 a_data;

uniform mat4 u_matrix;
uniform float u_scale;
uniform float u_pitch;
uniform float u_collision_y_stretch;
uniform float u_camera_to_center_distance;

varying float v_max_zoom;
varying float v_placement_zoom;
varying float v_perspective_zoom_adjust;
varying vec2 v_fade_tex;

void main() {
gl_Position = u_matrix * vec4(a_pos + a_extrude / u_scale, 0.0, 1.0);
vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1);
highp float camera_to_anchor_distance = projectedPoint.w;
highp float collision_perspective_ratio = 1.0 + 0.5 * ((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0);

highp float incidence_stretch = camera_to_anchor_distance / (u_camera_to_center_distance * cos(u_pitch));
highp float collision_adjustment = max(1.0, incidence_stretch / u_collision_y_stretch);

gl_Position = u_matrix * vec4(a_pos + a_extrude * collision_perspective_ratio * collision_adjustment / u_scale, 0.0, 1.0);

v_max_zoom = a_data.x;
v_placement_zoom = a_data.y;

v_perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0);
v_fade_tex = vec2((v_placement_zoom + v_perspective_zoom_adjust) / 255.0, 0.0);
}
Loading