mapbox-gl
Version:
A WebGL interactive maps library
174 lines (135 loc) • 6.98 kB
JavaScript
'use strict';
var browser = require('../util/browser');
var mat3 = require('gl-matrix').mat3;
module.exports = drawFill;
function drawFill(painter, layer, posMatrix, tile) {
// No data
if (!tile.buffers) return;
var elementGroups = tile.elementGroups[layer.ref || layer.id];
if (!elementGroups) return;
var gl = painter.gl;
var translatedPosMatrix = painter.translateMatrix(posMatrix, tile, layer.paint['fill-translate'], layer.paint['fill-translate-anchor']);
var color = layer.paint['fill-color'];
var vertex, elements, group, count;
// Draw the stencil mask.
// We're only drawing to the first seven bits (== support a maximum of
// 127 overlapping polygons in one place before we get rendering errors).
gl.stencilMask(0x3F);
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.
gl.stencilFunc(gl.NOTEQUAL, 0x80, 0x80);
// 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.INCR_WRAP, gl.KEEP, gl.KEEP);
gl.stencilOpSeparate(gl.BACK, gl.DECR_WRAP, gl.KEEP, gl.KEEP);
// When drawing a shape, we first draw all shapes to the stencil buffer
// and incrementing all areas where polygons are
gl.colorMask(false, false, false, false);
// Draw the actual triangle fan into the stencil buffer.
gl.switchShader(painter.fillShader, translatedPosMatrix);
// Draw all buffers
vertex = tile.buffers.fillVertex;
vertex.bind(gl);
elements = tile.buffers.fillElement;
elements.bind(gl);
var offset, elementOffset;
gl.disableVertexAttribArray(painter.fillShader.a_color);
for (var i = 0; i < elementGroups.groups.length; i++) {
group = elementGroups.groups[i];
offset = group.vertexStartIndex * vertex.itemSize;
gl.vertexAttribPointer(painter.fillShader.a_pos, 2, gl.SHORT, false, 4, offset + 0);
count = group.elementLength * 3;
elementOffset = group.elementStartIndex * elements.itemSize;
gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, elementOffset);
}
// 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);
// 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 strokeColor = layer.paint['fill-outline-color'];
// Because we're drawing top-to-bottom, and we update the stencil mask
// below, we have to draw the outline first (!)
if (layer.paint['fill-antialias'] === true && !(layer.paint['fill-image'] && !strokeColor)) {
gl.switchShader(painter.outlineShader, translatedPosMatrix);
gl.lineWidth(2 * browser.devicePixelRatio);
if (strokeColor) {
// If we defined a different color for the fill outline, we are
// going to ignore the bits in 0x3F and just care about the global
// clipping mask.
gl.stencilFunc(gl.EQUAL, 0x80, 0x80);
} else {
// Otherwise, we only want to draw the antialiased parts that are
// *outside* the current shape. This is important in case the fill
// or stroke color is translucent. If we wouldn't clip to outside
// the current shape, some pixels from the outline stroke overlapped
// the (non-antialiased) fill.
gl.stencilFunc(gl.EQUAL, 0x80, 0xBF);
}
gl.uniform2f(painter.outlineShader.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight);
// Draw all buffers
vertex = tile.buffers.fillVertex;
elements = tile.buffers.outlineElement;
elements.bind(gl);
gl.disableVertexAttribArray(painter.outlineShader.a_color);
gl.vertexAttrib4fv(painter.outlineShader.a_color, strokeColor ? strokeColor : color);
for (var k = 0; k < elementGroups.groups.length; k++) {
group = elementGroups.groups[k];
offset = group.vertexStartIndex * vertex.itemSize;
gl.vertexAttribPointer(painter.outlineShader.a_pos, 2, gl.SHORT, false, 4, offset + 0);
count = group.secondElementLength * 2;
elementOffset = group.secondElementStartIndex * elements.itemSize;
gl.drawElements(gl.LINES, count, gl.UNSIGNED_SHORT, elementOffset);
}
}
var image = layer.paint['fill-image'];
var opacity = layer.paint['fill-opacity'] || 1;
var shader;
if (image) {
// Draw texture fill
var imagePosA = painter.spriteAtlas.getPosition(image.from, true);
var imagePosB = painter.spriteAtlas.getPosition(image.to, true);
if (!imagePosA || !imagePosB) return;
shader = painter.patternShader;
gl.switchShader(shader, posMatrix);
gl.uniform1i(shader.u_image, 0);
gl.uniform2fv(shader.u_pattern_tl_a, imagePosA.tl);
gl.uniform2fv(shader.u_pattern_br_a, imagePosA.br);
gl.uniform2fv(shader.u_pattern_tl_b, imagePosB.tl);
gl.uniform2fv(shader.u_pattern_br_b, imagePosB.br);
gl.uniform1f(shader.u_opacity, opacity);
gl.uniform1f(shader.u_mix, image.t);
var factor = (tile.tileExtent / tile.tileSize) / Math.pow(2, painter.transform.tileZoom - tile.coord.z);
var matrixA = mat3.create();
mat3.scale(matrixA, matrixA, [
1 / (imagePosA.size[0] * factor * image.fromScale),
1 / (imagePosA.size[1] * factor * image.fromScale)
]);
var matrixB = mat3.create();
mat3.scale(matrixB, matrixB, [
1 / (imagePosB.size[0] * factor * image.toScale),
1 / (imagePosB.size[1] * factor * image.toScale)
]);
gl.uniformMatrix3fv(shader.u_patternmatrix_a, false, matrixA);
gl.uniformMatrix3fv(shader.u_patternmatrix_b, false, matrixB);
painter.spriteAtlas.bind(gl, true);
} else {
// Draw filling rectangle.
shader = painter.fillShader;
gl.switchShader(shader, posMatrix);
gl.disableVertexAttribArray(shader.a_color);
gl.vertexAttrib4fv(shader.a_color, color);
}
// Only draw regions that we marked
gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x3F);
gl.bindBuffer(gl.ARRAY_BUFFER, painter.tileExtentBuffer);
gl.vertexAttribPointer(shader.a_pos, painter.tileExtentBuffer.itemSize, gl.SHORT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, painter.tileExtentBuffer.itemCount);
gl.stencilMask(0x00);
gl.stencilFunc(gl.EQUAL, 0x80, 0x80);
}