mapbox-gl
Version:
A WebGL interactive maps library
222 lines (175 loc) • 8.86 kB
JavaScript
'use strict';
var browser = require('../util/browser');
var util = require('../util/util');
module.exports = draw;
function draw(painter, source, layer, coords) {
var gl = painter.gl;
gl.enable(gl.STENCIL_TEST);
var color = util.premultiply(layer.paint['fill-color'], layer.paint['fill-opacity']);
var image = layer.paint['fill-pattern'];
var strokeColor = util.premultiply(layer.paint['fill-outline-color'], layer.paint['fill-opacity']);
// Draw fill
if (image ? !painter.isOpaquePass : painter.isOpaquePass === (color[3] === 1)) {
// Once we switch to earcut drawing we can pull most of the WebGL setup
// outside of this coords loop.
for (var i = 0; i < coords.length; i++) {
drawFill(painter, source, layer, coords[i]);
}
}
// Draw stroke
if (!painter.isOpaquePass && layer.paint['fill-antialias'] && !(layer.paint['fill-pattern'] && !strokeColor)) {
gl.switchShader(painter.outlineShader);
gl.lineWidth(2 * browser.devicePixelRatio * 10);
if (strokeColor) {
// If we defined a different color for the fill outline, we are
// going to ignore the bits in 0x07 and just care about the global
// clipping mask.
painter.setDepthSublayer(2);
} else {
// Otherwise, we only want to drawFill 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.
painter.setDepthSublayer(0);
}
gl.uniform2f(painter.outlineShader.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.uniform4fv(painter.outlineShader.u_color, strokeColor ? strokeColor : color);
for (var j = 0; j < coords.length; j++) {
drawStroke(painter, source, layer, coords[j]);
}
}
}
function drawFill(painter, source, layer, coord) {
var tile = source.getTile(coord);
if (!tile.buffers) return;
var elementGroups = tile.getElementGroups(layer, 'fill');
if (!elementGroups) return;
var gl = painter.gl;
var color = util.premultiply(layer.paint['fill-color'], layer.paint['fill-opacity']);
var image = layer.paint['fill-pattern'];
var opacity = layer.paint['fill-opacity'];
var posMatrix = painter.calculatePosMatrix(coord, source.maxzoom);
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.
gl.switchShader(painter.fillShader, translatedPosMatrix);
// Draw all buffers
var vertex = tile.buffers.fillVertex;
vertex.bind(gl);
var elements = tile.buffers.fillElement;
elements.bind(gl);
for (var i = 0; i < elementGroups.groups.length; i++) {
var group = elementGroups.groups[i];
var offset = group.vertexStartIndex * vertex.itemSize;
vertex.setAttribPointers(gl, painter.fillShader, offset);
var count = group.elementLength * 3;
var 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);
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 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 imageSizeScaledA = [
(imagePosA.size[0] * image.fromScale),
(imagePosA.size[1] * image.fromScale)
];
var imageSizeScaledB = [
(imagePosB.size[0] * image.toScale),
(imagePosB.size[1] * image.toScale)
];
gl.uniform2fv(shader.u_patternscale_a, [
1 / tile.pixelsToTileUnits(imageSizeScaledA[0], painter.transform.tileZoom),
1 / tile.pixelsToTileUnits(imageSizeScaledA[1], painter.transform.tileZoom)
]);
gl.uniform2fv(shader.u_patternscale_b, [
1 / tile.pixelsToTileUnits(imageSizeScaledB[0], painter.transform.tileZoom),
1 / tile.pixelsToTileUnits(imageSizeScaledB[1], painter.transform.tileZoom)
]);
var tileSizeAtNearestZoom = tile.tileSize * Math.pow(2, painter.transform.tileZoom - tile.coord.z);
// shift images to match at tile boundaries
var offsetAx = ((tileSizeAtNearestZoom / imageSizeScaledA[0]) % 1) * (tile.coord.x + coord.w * Math.pow(2, tile.coord.z));
var offsetAy = ((tileSizeAtNearestZoom / imageSizeScaledA[1]) % 1) * tile.coord.y;
var offsetBx = ((tileSizeAtNearestZoom / imageSizeScaledB[0]) % 1) * (tile.coord.x + coord.w * Math.pow(2, tile.coord.z));
var offsetBy = ((tileSizeAtNearestZoom / imageSizeScaledB[1]) % 1) * tile.coord.y;
gl.uniform2fv(shader.u_offset_a, [offsetAx, offsetAy]);
gl.uniform2fv(shader.u_offset_b, [offsetBx, offsetBy]);
painter.spriteAtlas.bind(gl, true);
} else {
// Draw filling rectangle.
shader = painter.fillShader;
gl.switchShader(shader, posMatrix);
gl.uniform4fv(shader.u_color, color);
}
// Only draw regions that we marked
gl.stencilFunc(gl.NOTEQUAL, 0x0, 0x07);
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);
}
function drawStroke(painter, source, layer, coord) {
var tile = source.getTile(coord);
if (!tile.buffers) return;
if (!tile.elementGroups[layer.ref || layer.id]) return;
var gl = painter.gl;
var elementGroups = tile.elementGroups[layer.ref || layer.id].fill;
gl.setPosMatrix(painter.translatePosMatrix(
painter.calculatePosMatrix(coord, source.maxzoom),
tile,
layer.paint['fill-translate'],
layer.paint['fill-translate-anchor']
));
// Draw all buffers
var vertex = tile.buffers.fillVertex;
var elements = tile.buffers.fillSecondElement;
vertex.bind(gl);
elements.bind(gl);
painter.enableTileClippingMask(coord);
for (var k = 0; k < elementGroups.groups.length; k++) {
var group = elementGroups.groups[k];
var offset = group.vertexStartIndex * vertex.itemSize;
vertex.setAttribPointers(gl, painter.outlineShader, offset);
var count = group.secondElementLength * 2;
var elementOffset = group.secondElementStartIndex * elements.itemSize;
gl.drawElements(gl.LINES, count, gl.UNSIGNED_SHORT, elementOffset);
}
}