From c33c6c20be55b1b229bb8736db38ff3e09346eb2 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Wed, 4 Oct 2017 17:16:19 +0200 Subject: [PATCH] Annotations + extra debug example --- debug/buildings.html | 33 +++++++++++++++++++++++ debug/index.html | 9 +++++-- src/data/bucket/circle_bucket.js | 12 +++++++++ src/data/bucket/fill_bucket.js | 20 ++++++++++++++ src/data/bucket/fill_extrusion_bucket.js | 34 ++++++++++++++++++++++++ src/data/bucket/line_bucket.js | 3 +++ src/render/draw_fill.js | 11 ++++++++ src/shaders/circle.vertex.glsl | 13 +++++++++ src/shaders/fill.fragment.glsl | 9 +++++++ src/shaders/fill.vertex.glsl | 4 +++ src/shaders/fill_pattern.fragment.glsl | 2 ++ src/shaders/fill_pattern.vertex.glsl | 4 +++ 12 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 debug/buildings.html diff --git a/debug/buildings.html b/debug/buildings.html new file mode 100644 index 00000000000..0701ff8f3ea --- /dev/null +++ b/debug/buildings.html @@ -0,0 +1,33 @@ + + + + Mapbox GL JS debug page + + + + + + + +
+ + + + + diff --git a/debug/index.html b/debug/index.html index 9c7f3bb641c..faf4b81c876 100644 --- a/debug/index.html +++ b/debug/index.html @@ -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; +}); + + diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index c903278a426..10df7a676f9 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -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)); @@ -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>) { + // 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; @@ -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); diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index e6ca37a4b28..532ba1dd123 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -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>) { + // 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) { @@ -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; @@ -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); @@ -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], diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 57e6c885526..7ec49cb4c68 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -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>) { + // 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) { @@ -131,6 +134,9 @@ 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; @@ -138,29 +144,49 @@ class FillExtrusionBucket implements Bucket { 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); @@ -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; @@ -191,6 +221,7 @@ 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); @@ -198,9 +229,12 @@ class FillExtrusionBucket implements Bucket { } } + // 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], diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index f13a993cd88..aa97e217506 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -178,6 +178,7 @@ class LineBucket implements Bucket { } } + // addLine is run on each individual geometry feature contained in a line layer. addLine(vertices: Array, feature: VectorTileFeature, join: string, cap: string, miterLimit: number, roundLimit: number) { const isPolygon = vectorTileFeatureTypes[feature.type] === 'Polygon'; @@ -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; } diff --git a/src/render/draw_fill.js b/src/render/draw_fill.js index 11b83e1431a..b5abafd1d5e 100644 --- a/src/render/draw_fill.js +++ b/src/render/draw_fill.js @@ -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); @@ -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, @@ -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'); @@ -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) { @@ -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'], diff --git a/src/shaders/circle.vertex.glsl b/src/shaders/circle.vertex.glsl index e3a08ce29fb..890d2e65a34 100644 --- a/src/shaders/circle.vertex.glsl +++ b/src/shaders/circle.vertex.glsl @@ -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) { @@ -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; } } diff --git a/src/shaders/fill.fragment.glsl b/src/shaders/fill.fragment.glsl index a156d2c3c57..4110fd4b0b7 100644 --- a/src/shaders/fill.fragment.glsl +++ b/src/shaders/fill.fragment.glsl @@ -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 } diff --git a/src/shaders/fill.vertex.glsl b/src/shaders/fill.vertex.glsl index b4e8277fa19..0436ecc0d33 100644 --- a/src/shaders/fill.vertex.glsl +++ b/src/shaders/fill.vertex.glsl @@ -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. } diff --git a/src/shaders/fill_pattern.fragment.glsl b/src/shaders/fill_pattern.fragment.glsl index 05b6cc729f1..b7c15b318ee 100644 --- a/src/shaders/fill_pattern.fragment.glsl +++ b/src/shaders/fill_pattern.fragment.glsl @@ -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); diff --git a/src/shaders/fill_pattern.vertex.glsl b/src/shaders/fill_pattern.vertex.glsl index 8672a7cffc6..d264bf83272 100644 --- a/src/shaders/fill_pattern.vertex.glsl +++ b/src/shaders/fill_pattern.vertex.glsl @@ -19,6 +19,10 @@ void main() { gl_Position = u_matrix * vec4(a_pos, 0, 1); + // fill-patterns are slightly more complicated than fills: here we calculate + // the position within the sprite of the pattern we're looking for. (There + // are two calculations here in case we're at a threshold where we need to + // crossfade.) v_pos_a = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, u_scale_a * u_pattern_size_a, u_tile_units_to_pixels, a_pos); v_pos_b = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, u_scale_b * u_pattern_size_b, u_tile_units_to_pixels, a_pos); }