Skip to content

Commit

Permalink
Annotations + extra debug example
Browse files Browse the repository at this point in the history
  • Loading branch information
Lauren Budorick committed Oct 4, 2017
1 parent 78a6852 commit c33c6c2
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 2 deletions.
33 changes: 33 additions & 0 deletions debug/buildings.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!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>
<div id='map'></div>

<script src='/dist/mapbox-gl-dev.js'></script>
<script>

mapboxgl.accessToken = 'pk.eyJ1IjoibGJ1ZCIsImEiOiJjajhjdzl0dGgwaGQyMzBvMHRhcmNyNWgyIn0.Bwk2YXs-RPiyut1Oy8ZCnA';
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 15.7,
center: [-74.006, 40.711],
bearing: -100,
pitch: 60,
style: 'mapbox://styles/lbud/cj8cw07us8mh12spmje1vizdd',
hash: true
});

</script>
</body>
</html>
9 changes: 7 additions & 2 deletions debug/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 12.5,
center: [-77.01866, 38.888],
zoom: 14.25,
center: [15.979, 45.7922],
style: 'mapbox://styles/mapbox/streets-v10',
hash: true
});

map.on('load', () => {
map.showTileBoundaries = true;
});


</script>
</body>
</html>
12 changes: 12 additions & 0 deletions src/data/bucket/circle_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const circleInterface = {
};

function addCircleVertex(layoutVertexArray, x, y, extrudeX, extrudeY) {
// Here we calculate vertices based on the actual geometry center (x, y)
// and "extrude" numbers that dictate whether to move the vertex up or
// down and left and right.
layoutVertexArray.emplaceBack(
(x * 2) + ((extrudeX + 1) / 2),
(y * 2) + ((extrudeY + 1) / 2));
Expand Down Expand Up @@ -116,7 +119,11 @@ class CircleBucket implements Bucket {
this.segments.destroy();
}

// addFeature is run on each individual geometry feature contained in a circle layer.
addFeature(feature: VectorTileFeature, geometry: Array<Array<Point>>) {
// Since theoretically you could create a circle layer from any geometry
// type, we have to iterate a few levels down to that containing raw
// point geometries:
for (const ring of geometry) {
for (const point of ring) {
const x = point.x;
Expand All @@ -137,11 +144,16 @@ class CircleBucket implements Bucket {
const segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray);
const index = segment.vertexLength;

// addCircleVertex (see above) adds vertices to the vertex
// array.
addCircleVertex(this.layoutVertexArray, x, y, -1, -1);
addCircleVertex(this.layoutVertexArray, x, y, 1, -1);
addCircleVertex(this.layoutVertexArray, x, y, 1, 1);
addCircleVertex(this.layoutVertexArray, x, y, -1, 1);

// Here we add the two triangles that make up a circle to the
// triangle index array. (Think of a circle as being really
// just a square, with transparent/sheared corners.)
this.indexArray.emplaceBack(index, index + 1, index + 2);
this.indexArray.emplaceBack(index, index + 3, index + 2);

Expand Down
20 changes: 20 additions & 0 deletions src/data/bucket/fill_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ class FillBucket implements Bucket {
this.segments2.destroy();
}

// addFeature is run on each individual geometry feature contained in a fill layer
addFeature(feature: VectorTileFeature, geometry: Array<Array<Point>>) {
// classifyRings separates the geometry in multipolygons: for a single/
// simple polygon, this only iterates over an array of length 1.
for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) {
let numVertices = 0;
for (const ring of polygon) {
Expand All @@ -127,6 +130,7 @@ class FillBucket implements Bucket {
const flattened = [];
const holeIndices = [];

// Here we iterate over the "rings" (exterior of a polygon + any interior holes)
for (const ring of polygon) {
if (ring.length === 0) {
continue;
Expand All @@ -139,13 +143,22 @@ class FillBucket implements Bucket {
const lineSegment = this.segments2.prepareSegment(ring.length, this.layoutVertexArray, this.indexArray2);
const lineIndex = lineSegment.vertexLength;

// Here we add the coordinates of the first vertex of this polygon
// to the vertex array.
this.layoutVertexArray.emplaceBack(ring[0].x, ring[0].y);
// Fill layers can optionally have outlines that don't use triangles
// but rather just lines, so here we're adding the edge leading up to
// our first vertex to the line index array.
this.indexArray2.emplaceBack(lineIndex + ring.length - 1, lineIndex);

flattened.push(ring[0].x);
flattened.push(ring[0].y);

for (let i = 1; i < ring.length; i++) {
// Now as we iterate over the remaining vertices, we add their
// positions to the vertex array...
this.layoutVertexArray.emplaceBack(ring[i].x, ring[i].y);
// ...then add their edges to the line index array.
this.indexArray2.emplaceBack(lineIndex + i - 1, lineIndex + i);
flattened.push(ring[i].x);
flattened.push(ring[i].y);
Expand All @@ -155,9 +168,16 @@ class FillBucket implements Bucket {
lineSegment.primitiveLength += ring.length;
}

// "earcut" is the algorithm used to do polygon tessellation
// (https://github.com/mapbox/earcut). It produces an array of
// vertex indices that can be used to cover any polygon shape
// with individual triangles.
const indices = earcut(flattened, holeIndices);
assert(indices.length % 3 === 0);

// Now that we've calculated the triangle indices, we add them
// to the triangle index array (the index array used for actually
// filling the polygon):
for (let i = 0; i < indices.length; i += 3) {
this.indexArray.emplaceBack(
triangleIndex + indices[i],
Expand Down
34 changes: 34 additions & 0 deletions src/data/bucket/fill_extrusion_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ class FillExtrusionBucket implements Bucket {
this.segments.destroy();
}

// addFeature is run on each individual geometry feature contained in a fill-extrusion layer
addFeature(feature: VectorTileFeature, geometry: Array<Array<Point>>) {
// As we do in fill_bucket, we need to iterate over potential multipolygons;
// for most cases this will only be a single polygon.
for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) {
let numVertices = 0;
for (const ring of polygon) {
Expand All @@ -131,36 +134,59 @@ class FillExtrusionBucket implements Bucket {

let segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray);

// As we do in fill_bucket, we need to iterate here in case we
// have interior holes. In the case of a simple polygon, this
// is an array of length 1.
for (const ring of polygon) {
if (ring.length === 0) {
continue;
}

let edgeDistance = 0;

// Now we iterate over the individual vertices within a ring to
// add the "walls."
for (let p = 0; p < ring.length; p++) {
const p1 = ring[p];

if (p >= 1) {
const p2 = ring[p - 1];

// isBoundaryEdge does a coarse check to ensure this
// edge isn't one artificially created where a polygon
// feature crosses a tile boundary; if that's the case,
// we don't need to add that wall as it would be occluded
// by the extruded feature on the adjoining tile.
if (!isBoundaryEdge(p1, p2)) {
if (segment.vertexLength + 4 > MAX_VERTEX_ARRAY_LENGTH) {
segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray);
}

// Here we calculate the normal vector (the unit
// vector pointing perpendicularly from the edge);
// this is saved as an attribute on these vertices
// and is used for lighting.
const perp = p1.sub(p2)._perp()._unit();

// Here we add the bottom and top vertices of the
// near end of this wall.
addVertex(this.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 0, edgeDistance);
addVertex(this.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 1, edgeDistance);

// We track distance along the edge of an extrusion
// in order to tile patterns well along walls, in
// the case of patterned extrusions.
edgeDistance += p2.dist(p1);

// Now we add the bottom and top vertices of the next
// end of this wall.
addVertex(this.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 0, edgeDistance);
addVertex(this.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 1, edgeDistance);

const bottomRight = segment.vertexLength;

// Add two triangles to the index array that will
// make up the rectangular wall.
this.indexArray.emplaceBack(bottomRight, bottomRight + 1, bottomRight + 2);
this.indexArray.emplaceBack(bottomRight + 1, bottomRight + 2, bottomRight + 3);

Expand All @@ -179,6 +205,10 @@ class FillExtrusionBucket implements Bucket {
const holeIndices = [];
const triangleIndex = segment.vertexLength;

// Now we'll iterate over the ring again to add the vertices that
// make up the rooftop. (We can't share vertices with the walls
// because these will have different normal vectors in order
// to light these surfaces differently.)
for (const ring of polygon) {
if (ring.length === 0) {
continue;
Expand All @@ -191,16 +221,20 @@ class FillExtrusionBucket implements Bucket {
for (let i = 0; i < ring.length; i++) {
const p = ring[i];

// Add this roof vertex.
addVertex(this.layoutVertexArray, p.x, p.y, 0, 0, 1, 1, 0);

flattened.push(p.x);
flattened.push(p.y);
}
}

// Like in fill_bucket, we use an algorithm called earcut to
// calculate the triangles that will make up the roof polygon.
const indices = earcut(flattened, holeIndices);
assert(indices.length % 3 === 0);

// Now add these triangle indices to the index array:
for (let j = 0; j < indices.length; j += 3) {
this.indexArray.emplaceBack(
triangleIndex + indices[j],
Expand Down
3 changes: 3 additions & 0 deletions src/data/bucket/line_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class LineBucket implements Bucket {
}
}

// addLine is run on each individual geometry feature contained in a line layer.
addLine(vertices: Array<Point>, feature: VectorTileFeature, join: string, cap: string, miterLimit: number, roundLimit: number) {
const isPolygon = vectorTileFeatureTypes[feature.type] === 'Polygon';

Expand Down Expand Up @@ -280,6 +281,8 @@ class LineBucket implements Bucket {
if (prevSegmentLength > 2 * sharpCornerOffset) {
const newPrevVertex = currentVertex.sub(currentVertex.sub(prevVertex)._mult(sharpCornerOffset / prevSegmentLength)._round());
this.distance += newPrevVertex.dist(prevVertex);
// addCurrentVertex is where we handle adding vertices to
// the vertex buffer, handling for different join/end types.
this.addCurrentVertex(newPrevVertex, this.distance, prevNormal.mult(1), 0, 0, false, segment);
prevVertex = newPrevVertex;
}
Expand Down
11 changes: 11 additions & 0 deletions src/render/draw_fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function drawFillTiles(painter, sourceCache, layer, coords, drawFn) {
if (pattern.isPatternMissing(layer.paint['fill-pattern'], painter)) return;

let firstTile = true;
// Each `coord` is a tile coordinate, so here we iterate over tiles on the screen
for (const coord of coords) {
const tile = sourceCache.getTile(coord);
const bucket: ?FillBucket = (tile.getBucket(layer): any);
Expand All @@ -66,8 +67,10 @@ function drawFillTile(painter, sourceCache, layer, tile, coord, bucket, firstTil
const gl = painter.gl;
const programConfiguration = bucket.programConfigurations.get(layer.id);

// In `setFillProgram` we'll set up uniforms, etc: see below
const program = setFillProgram('fill', layer.paint['fill-pattern'], painter, programConfiguration, layer, tile, coord, firstTile);

// Here's the actual draw command
program.draw(
gl,
gl.TRIANGLES,
Expand All @@ -79,6 +82,10 @@ function drawFillTile(painter, sourceCache, layer, tile, coord, bucket, firstTil
}

function drawStrokeTile(painter, sourceCache, layer, tile, coord, bucket, firstTile) {
// In addition to patterned and solid fills, the one other type of drawing
// we handle here is fill strokes (these also have their own shader, and this
// is one of the only places we draw with WebGL's LINES primitive rather
// than TRIANGLES).
const gl = painter.gl;
const programConfiguration = bucket.programConfigurations.get(layer.id);
const usePattern = layer.paint['fill-pattern'] && !layer.getPaintProperty('fill-outline-color');
Expand All @@ -99,6 +106,8 @@ function drawStrokeTile(painter, sourceCache, layer, tile, coord, bucket, firstT
function setFillProgram(programId, usePattern, painter, programConfiguration, layer, tile, coord, firstTile) {
let program;
const prevProgram = painter.currentProgram;
// We use a different shader for patterned or solid fills: detect which one we
// need (and whether we've already got the right one cached from the last draw call).
if (!usePattern) {
program = painter.useProgram(programId, programConfiguration);
if (firstTile || program !== prevProgram) {
Expand All @@ -112,6 +121,8 @@ function setFillProgram(programId, usePattern, painter, programConfiguration, la
}
pattern.setTile(tile, painter, program);
}
// This is what a typical uniform-setting call looks like (in this case, a
// 4-component vector that represents our transformation matrix).
painter.gl.uniformMatrix4fv(program.uniforms.u_matrix, false, painter.translatePosMatrix(
coord.posMatrix, tile,
layer.paint['fill-translate'],
Expand Down
13 changes: 13 additions & 0 deletions src/shaders/circle.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ void main(void) {
// multiply a_pos by 0.5, since we had it * 2 in order to sneak
// in extrusion data
vec2 circle_center = floor(a_pos * 0.5);

// We do some handling here for whether the map should stick flat to the
// map when it's pitched or stand "upright" in relation to the viewport,
// and whether further features should appear smaller in pitched maps.
// Go to the `else` below for the basic case.
if (u_pitch_with_map) {
vec2 corner_position = circle_center;
if (u_scale_with_map) {
Expand All @@ -45,11 +50,19 @@ void main(void) {

gl_Position = u_matrix * vec4(corner_position, 0, 1);
} else {
// Basic case:
// First we set the position to be the center of the circle, so initially
// this will be the same for all 4 vertices (recall circles are made of
// two triangles).
gl_Position = u_matrix * vec4(circle_center, 0, 1);

if (u_scale_with_map) {
gl_Position.xy += extrude * (radius + stroke_width) * u_extrude_scale * u_camera_to_center_distance;
} else {
// Basic case:
// Since we've set the extrude vector differently (+1 or -1) in each
// direction, now we can use it to move each vertex outward, accounting
// for the intended radius of the circle.
gl_Position.xy += extrude * (radius + stroke_width) * u_extrude_scale * gl_Position.w;
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/shaders/fill.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@
#pragma mapbox: define lowp float opacity

void main() {
// These #pragmas are an abstraction in this codebase that means: depending
// on the layer style, it might be set as a uniform (same across all features
// in a layer) or as an attribute (individually set for all different
// features), but the important thing is that when the shader is compiled,
// color and opacity will be correctly set.
#pragma mapbox: initialize highp vec4 color
#pragma mapbox: initialize lowp float opacity

// gl_FragColor is set for each individual pixel (fragment) between the
// three corners (vertices) of this feature. For fills, it's simply the
// color multiplied by the opacity.
gl_FragColor = color * opacity;

#ifdef OVERDRAW_INSPECTOR
// Don't mind this -- just a debugging view.
gl_FragColor = vec4(1.0);
#endif
}
4 changes: 4 additions & 0 deletions src/shaders/fill.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ uniform mat4 u_matrix;
#pragma mapbox: define highp vec4 color
#pragma mapbox: define lowp float opacity

// The fill vertex shaper is very simple: take a vertex's position attribute
// (relative to a tile), multiply it by the transformation matrix (relative
// to the screen) and put it down. (Don't worry about the #pragmas here.)
void main() {
#pragma mapbox: initialize highp vec4 color
#pragma mapbox: initialize lowp float opacity

gl_Position = u_matrix * vec4(a_pos, 0, 1);
// Now go to fill.fragment.glsl.
}
2 changes: 2 additions & 0 deletions src/shaders/fill_pattern.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ varying vec2 v_pos_b;
void main() {
#pragma mapbox: initialize lowp float opacity

// Here we're just calculating the correct pixel within the symbol within
// the sprite, then samling it (again, twice for the sake of crossfading).
vec2 imagecoord = mod(v_pos_a, 1.0);
vec2 pos = mix(u_pattern_tl_a / u_texsize, u_pattern_br_a / u_texsize, imagecoord);
vec4 color1 = texture2D(u_image, pos);
Expand Down

0 comments on commit c33c6c2

Please sign in to comment.