Skip to content
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
1 change: 1 addition & 0 deletions js/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Bucket.prototype.populateBuffers = function() {
* @private
* @param {string} programName the name of the program associated with the buffer that will receive the vertices
* @param {number} vertexLength The number of vertices that will be inserted to the buffer.
* @returns The current element group
*/
Bucket.prototype.makeRoomFor = function(programName, numVertices) {
var groups = this.arrayGroups[programName];
Expand Down
58 changes: 31 additions & 27 deletions js/data/bucket/fill_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
var Bucket = require('../bucket');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var earcut = require('earcut');
var classifyRings = require('../../util/classify_rings');

module.exports = FillBucket;

Expand All @@ -16,6 +18,7 @@ FillBucket.prototype.programInterfaces = {
fill: {
vertexBuffer: true,
elementBuffer: true,
elementBufferComponents: 1,
elementBuffer2: true,
elementBuffer2Components: 2,

Expand All @@ -29,45 +32,46 @@ FillBucket.prototype.programInterfaces = {

FillBucket.prototype.addFeature = function(feature) {
var lines = loadGeometry(feature);
for (var i = 0; i < lines.length; i++) {
this.addFill(lines[i]);
var polygons = classifyRings(lines);
for (var i = 0; i < polygons.length; i++) {
this.addPolygon(polygons[i]);
}
};

FillBucket.prototype.addFill = function(vertices) {
FillBucket.prototype.addPolygon = function(polygon) {
var numVertices = 0;
for (var k = 0; k < polygon.length; k++) {
numVertices += polygon[k].length;
}

// a fill must have at least three vertices
if (vertices.length < 3) return;
var group = this.makeRoomFor('fill', numVertices);
var flattened = [];
var holeIndices = [];
var startIndex = group.layout.vertex.length;

// Calculate the total number of vertices we're going to produce so that we
// can resize the buffer beforehand, or detect whether the current line
// won't fit into the buffer anymore.
// In order to be able to use the vertex buffer for drawing the antialiased
// outlines, we separate all polygon vertices with a degenerate (out-of-
// viewplane) vertex.
for (var r = 0; r < polygon.length; r++) {
var ring = polygon[r];

var len = vertices.length;
if (r > 0) holeIndices.push(flattened.length / 2);

// Expand this geometry buffer to hold all the required vertices.
var group = this.makeRoomFor('fill', len + 1);
for (var v = 0; v < ring.length; v++) {
var vertex = ring[v];

// We're generating triangle fans, so we always start with the first coordinate in this polygon.
var firstIndex, prevIndex;
for (var i = 0; i < vertices.length; i++) {
var currentVertex = vertices[i];
var index = group.layout.vertex.emplaceBack(vertex.x, vertex.y);

var currentIndex = group.layout.vertex.emplaceBack(currentVertex.x, currentVertex.y);
if (i === 0) firstIndex = currentIndex;
if (v >= 1) {
group.layout.element2.emplaceBack(index - 1, index);
}

// Only add triangles that have distinct vertices.
if (i >= 2 && (currentVertex.x !== vertices[0].x || currentVertex.y !== vertices[0].y)) {
group.layout.element.emplaceBack(firstIndex, prevIndex, currentIndex);
// convert to format used by earcut
flattened.push(vertex.x);
flattened.push(vertex.y);
}
}

if (i >= 1) {
group.layout.element2.emplaceBack(prevIndex, currentIndex);
}
var triangleIndices = earcut(flattened, holeIndices);

prevIndex = currentIndex;
for (var i = 0; i < triangleIndices.length; i++) {
group.layout.element.emplaceBack(triangleIndices[i] + startIndex);
}
};
74 changes: 15 additions & 59 deletions js/render/draw_fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function draw(painter, source, layer, coords) {
if (image ? !painter.isOpaquePass : painter.isOpaquePass === (color[3] === 1 && opacity === 1)) {
// Once we switch to earcut drawing we can pull most of the WebGL setup
// outside of this coords loop.
painter.setDepthSublayer(1);
for (var i = 0; i < coords.length; i++) {
drawFill(painter, source, layer, coords[i]);
}
Expand Down Expand Up @@ -82,53 +83,6 @@ function drawFill(painter, source, layer, coord) {
var color = util.premultiply(layer.paint['fill-color']);
var image = layer.paint['fill-pattern'];
var opacity = layer.paint['fill-opacity'];

var posMatrix = coord.posMatrix;
var translatedPosMatrix = painter.translatePosMatrix(posMatrix, tile, layer.paint['fill-translate'], layer.paint['fill-translate-anchor']);

// Draw the stencil mask.
painter.setDepthSublayer(1);

// We're only drawFilling to the first seven bits (== support a maximum of
// 8 overlapping polygons in one place before we get rendering errors).
gl.stencilMask(0x07);
gl.clear(gl.STENCIL_BUFFER_BIT);

// Draw front facing triangles. Wherever the 0x80 bit is 1, we are
// increasing the lower 7 bits by one if the triangle is a front-facing
// triangle. This means that all visible polygons should be in CCW
// orientation, while all holes (see below) are in CW orientation.
painter.enableTileClippingMask(coord);

// When we do a nonzero fill, we count the number of times a pixel is
// covered by a counterclockwise polygon, and subtract the number of
// times it is "uncovered" by a clockwise polygon.
gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP);
gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP);

// When drawFilling a shape, we first drawFill all shapes to the stencil buffer
// and incrementing all areas where polygons are
gl.colorMask(false, false, false, false);
painter.depthMask(false);

// Draw the actual triangle fan into the stencil buffer.
var fillProgram = painter.useProgram('fill');
gl.uniformMatrix4fv(fillProgram.u_matrix, false, translatedPosMatrix);

for (var i = 0; i < bufferGroups.length; i++) {
var group = bufferGroups[i];
group.vaos[layer.id].bind(gl, fillProgram, group.layout.vertex, group.layout.element);
gl.drawElements(gl.TRIANGLES, group.layout.element.length * 3, gl.UNSIGNED_SHORT, 0);
}

// Now that we have the stencil mask in the stencil buffer, we can start
// writing to the color buffer.
gl.colorMask(true, true, true, true);
painter.depthMask(true);

// From now on, we don't want to update the stencil buffer anymore.
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
gl.stencilMask(0x0);
var program;

if (image) {
Expand All @@ -139,24 +93,26 @@ function drawFill(painter, source, layer, coord) {
gl.activeTexture(gl.TEXTURE0);
painter.spriteAtlas.bind(gl, true);

painter.tileExtentPatternVAO.bind(gl, program, painter.tileExtentBuffer);

} else {
// Draw filling rectangle.
program = painter.useProgram('fill');
gl.uniform4fv(fillProgram.u_color, color);
gl.uniform1f(fillProgram.u_opacity, opacity);
painter.tileExtentVAO.bind(gl, program, painter.tileExtentBuffer);
gl.uniform4fv(program.u_color, color);
gl.uniform1f(program.u_opacity, opacity);
}

gl.uniformMatrix4fv(program.u_matrix, false, posMatrix);

// Only draw regions that we marked
gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x07);
gl.uniformMatrix4fv(program.u_matrix, false, painter.translatePosMatrix(
coord.posMatrix,
tile,
layer.paint['fill-translate'],
layer.paint['fill-translate-anchor']
));

gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.tileExtentBuffer.length);
painter.enableTileClippingMask(coord);

gl.stencilMask(0x00);
for (var i = 0; i < bufferGroups.length; i++) {
var group = bufferGroups[i];
group.vaos[layer.id].bind(gl, program, group.layout.vertex, group.layout.element);
gl.drawElements(gl.TRIANGLES, group.layout.element.length, gl.UNSIGNED_SHORT, 0);
}
}

function drawStroke(painter, source, layer, coord) {
Expand Down
8 changes: 8 additions & 0 deletions js/source/worker_tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var CollisionTile = require('../symbol/collision_tile');
var Bucket = require('../data/bucket');
var CollisionBoxArray = require('../symbol/collision_box');
var DictionaryCoder = require('../util/dictionary_coder');
var util = require('../util/util');

module.exports = WorkerTile;

Expand Down Expand Up @@ -76,6 +77,13 @@ WorkerTile.prototype.parse = function(data, layerFamilies, actor, rawTileData, c
// read each layer, and sort its features into buckets
if (data.layers) { // vectortile
for (sourceLayerId in bucketsBySourceLayer) {
if (layer.version === 1) {
util.warnOnce(
'Vector tile source "' + this.source + '" layer "' +
sourceLayerId + '" does not use vector tile spec v2 ' +
'and therefore may have some rendering errors.'
);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Did you commit this unintentionally?

Copy link
Contributor

@lucaswoj lucaswoj May 16, 2016

Choose a reason for hiding this comment

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

@mourner Intentionally (though perhaps not not in good practice). I was debugging some serverside problems with @springmeyer.

layer = data.layers[sourceLayerId];
if (layer) {
sortLayerIntoBuckets(layer, bucketsBySourceLayer[sourceLayerId]);
Expand Down
43 changes: 43 additions & 0 deletions js/util/classify_rings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

module.exports = classifyRings;

// classifies an array of rings into polygons with outer rings and holes

function classifyRings(rings) {
var len = rings.length;

if (len <= 1) return [rings];

var polygons = [],
polygon,
ccw;

for (var i = 0; i < len; i++) {
var area = signedArea(rings[i]);
if (area === 0) continue;

if (ccw === undefined) ccw = area < 0;

if (ccw === area < 0) {
if (polygon) polygons.push(polygon);
polygon = [rings[i]];

} else {
polygon.push(rings[i]);
}
}
if (polygon) polygons.push(polygon);

return polygons;
}

function signedArea(ring) {
var sum = 0;
for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
p1 = ring[i];
p2 = ring[j];
sum += (p2.x - p1.x) * (p1.y + p2.y);
}
return sum;
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"brfs": "^1.4.0",
"csscolorparser": "^1.0.2",
"earcut": "^2.0.3",
"envify": "^3.4.0",
"feature-filter": "^2.1.0",
"geojson-rewind": "^0.1.0",
Expand Down Expand Up @@ -45,7 +46,7 @@
"express": "^4.13.4",
"gl": "^2.1.5",
"istanbul": "^0.4.2",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#0bdc384c9ad9457f2f0f30cd6aae21d18a9b73dc",
"mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#d3dc8569b6782cfeead4a0989bf92efe1514071f",
"nyc": "^6.1.1",
"sinon": "^1.15.4",
"st": "^1.0.0",
Expand Down
16 changes: 12 additions & 4 deletions test/js/data/fill_bucket.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ var StyleLayer = require('../../../js/style/style_layer');
var vt = new VectorTile(new Protobuf(new Uint8Array(fs.readFileSync(path.join(__dirname, '/../../fixtures/mbsv5-6-18-23.vector.pbf')))));
var feature = vt.layers.water.feature(0);

function createFeature(points) {
return {
loadGeometry: function() {
return [points];
}
};
}

test('FillBucket', function(t) {
// Suppress console.warn output.
var warn = console.warn;
Expand All @@ -26,16 +34,16 @@ test('FillBucket', function(t) {
});
bucket.createArrays();

t.equal(bucket.addFill([
t.equal(bucket.addFeature(createFeature([
new Point(0, 0),
new Point(10, 10)
]), undefined);
])), undefined);

t.equal(bucket.addFill([
t.equal(bucket.addFeature(createFeature([
new Point(0, 0),
new Point(10, 10),
new Point(10, 20)
]), undefined);
])), undefined);

t.equal(bucket.addFeature(feature), undefined);

Expand Down