mapbox-gl
Version:
A WebGL interactive maps library
171 lines (137 loc) • 4.97 kB
JavaScript
'use strict';
module.exports = LineAtlas;
/**
* A LineAtlas lets us reuse rendered dashed lines
* by writing many of them to a texture and then fetching their positions
* using .getDash.
*
* @param {number} width
* @param {number} height
* @private
*/
function LineAtlas(width, height) {
this.width = width;
this.height = height;
this.nextRow = 0;
this.bytes = 4;
this.data = new Uint8Array(this.width * this.height * this.bytes);
this.positions = {};
}
LineAtlas.prototype.setSprite = function(sprite) {
this.sprite = sprite;
};
/**
* Get or create a dash line pattern.
*
* @param {Array<number>} dasharray
* @param {boolean} round whether to add circle caps in between dash segments
* @returns {Object} position of dash texture in { y, height, width }
* @private
*/
LineAtlas.prototype.getDash = function(dasharray, round) {
var key = dasharray.join(",") + round;
if (!this.positions[key]) {
this.positions[key] = this.addDash(dasharray, round);
}
return this.positions[key];
};
LineAtlas.prototype.addDash = function(dasharray, round) {
var n = round ? 7 : 0;
var height = 2 * n + 1;
var offset = 128;
if (this.nextRow + height > this.height) {
console.warn('LineAtlas out of space');
return null;
}
var length = 0;
for (var i = 0; i < dasharray.length; i++) {
length += dasharray[i];
}
var stretch = this.width / length;
var halfWidth = stretch / 2;
// If dasharray has an odd length, both the first and last parts
// are dashes and should be joined seamlessly.
var oddLength = dasharray.length % 2 === 1;
for (var y = -n; y <= n; y++) {
var row = this.nextRow + n + y;
var index = this.width * row;
var left = oddLength ? -dasharray[dasharray.length - 1] : 0;
var right = dasharray[0];
var partIndex = 1;
for (var x = 0; x < this.width; x++) {
while (right < x / stretch) {
left = right;
right = right + dasharray[partIndex];
if (oddLength && partIndex === dasharray.length - 1) {
right += dasharray[0];
}
partIndex++;
}
var distLeft = Math.abs(x - left * stretch);
var distRight = Math.abs(x - right * stretch);
var dist = Math.min(distLeft, distRight);
var inside = (partIndex % 2) === 1;
var signedDistance;
if (round) {
// Add circle caps
var distMiddle = n ? y / n * (halfWidth + 1) : 0;
if (inside) {
var distEdge = halfWidth - Math.abs(distMiddle);
signedDistance = Math.sqrt(dist * dist + distEdge * distEdge);
} else {
signedDistance = halfWidth - Math.sqrt(dist * dist + distMiddle * distMiddle);
}
} else {
signedDistance = (inside ? 1 : -1) * dist;
}
this.data[3 + (index + x) * 4] = Math.max(0, Math.min(255, signedDistance + offset));
}
}
var pos = {
y: (this.nextRow + n + 0.5) / this.height,
height: 2 * n / this.height,
width: length
};
this.nextRow += height;
this.dirty = true;
return pos;
};
LineAtlas.prototype.bind = function(gl) {
if (!this.texture) {
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.data);
} else {
gl.bindTexture(gl.TEXTURE_2D, this.texture);
if (this.dirty) {
this.dirty = false;
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, gl.RGBA, gl.UNSIGNED_BYTE, this.data);
}
}
};
LineAtlas.prototype.debug = function() {
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.style.position = 'absolute';
canvas.style.top = 0;
canvas.style.left = 0;
canvas.style.background = '#ff0';
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext('2d');
var data = ctx.getImageData(0, 0, this.width, this.height);
for (var i = 0; i < this.data.length; i++) {
if (this.sdf) {
var k = i * 4;
data.data[k] = data.data[k + 1] = data.data[k + 2] = 0;
data.data[k + 3] = this.data[i];
} else {
data.data[i] = this.data[i];
}
}
ctx.putImageData(data, 0, 0);
};