Skip to content

Commit

Permalink
Merge pull request #1049 from mapbox/perspective
Browse files Browse the repository at this point in the history
basic perspective rendering
  • Loading branch information
ansis committed Mar 6, 2015
2 parents ac5e324 + 98d1407 commit bdaa1d0
Show file tree
Hide file tree
Showing 18 changed files with 270 additions and 77 deletions.
119 changes: 112 additions & 7 deletions js/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

var LatLng = require('./lat_lng'),
Point = require('point-geometry'),
wrap = require('../util/util').wrap;
wrap = require('../util/util').wrap,
mat4 = require('gl-matrix').mat4;

module.exports = Transform;

Expand All @@ -21,6 +22,8 @@ function Transform(minZoom, maxZoom) {
this.zoom = 0;
this.center = new LatLng(0, 0);
this.angle = 0;
this._altitude = 1.5;
this._pitch = 0;
}

Transform.prototype = {
Expand Down Expand Up @@ -55,6 +58,20 @@ Transform.prototype = {
this.angle = -wrap(bearing, -180, 180) * Math.PI / 180;
},

get pitch() {
return this._pitch / Math.PI * 180;
},
set pitch(pitch) {
this._pitch = Math.min(60, pitch) / 180 * Math.PI;
},

get altitude() {
return this._altitude;
},
set altitude(altitude) {
this._altitude = Math.max(0.75, altitude);
},

get zoom() { return this._zoom; },
set zoom(zoom) {
zoom = Math.min(Math.max(zoom, this.minZoom), this.maxZoom);
Expand Down Expand Up @@ -143,18 +160,84 @@ Transform.prototype = {
};
},

pointCoordinate: function(tileCenter, p) {
var zoomFactor = this.zoomScale(this.zoomFraction),
kt = this.zoomScale(this.tileZoom - tileCenter.zoom),
p2 = this.centerPoint._sub(p)._rotate(-this.angle)._div(this.tileSize * zoomFactor);
pointCoordinate: function(_, p) {
var m = this.coordinatePointMatrix(this.tileZoom);


// We know:
// the matrix, unprojected z, y (0, 1), and projected x, y (point)
// We don't know:
// the unprojected x, y (which we want), and the projected z, y
//
// Solve 3 equations with three unknowns
//
// We could invert the matrix and use that to unproject, but then we
// need to know the projected z value. We only know x, y.

// Terrible temporary hack to avoid division by 0
if (p.x === 0) p.x = 1;
if (p.y === 0) p.y = 1;

var f1 = m[0] / m[1];
var g1 = p.x - f1 * p.y;
// 0 = a1 * x + b1 * y + c1
var a1 = m[3];
var b1 = m[7] - (m[4] - f1 * m[5]) / g1;
var c1 = m[15] - (m[12] - f1 * m[13]) / g1;

if (m[1] === 0) {
a1 = m[3];
b1 = m[7] - m[5] / p.y;
c1 = m[15] - m[13] / p.y;
}

var f2 = m[4] / m[5];
var g2 = p.x - f2 * p.y;
// 0 = a2 * x + b2 * y + c2
var a2 = m[3] - (m[0] - f2 * m[1]) / g2;
var b2 = m[7];
var c2 = m[15] - (m[12] - f2 * m[13]) / g2;

if (m[5] === 0) {
a2 = m[3] - m[1] / p.y;
b2 = m[7];
c2 = m[15] - m[13] / p.y;
}

var f3 = a1 / a2;
var b3 = b1 - f3 * b2;
var c3 = c1 - f3 * c2;
var y = -c3 / b3;

var x = a1 !== 0 ?
-(b1 * y + c1) / a1 :
-(b2 * y + c2) / a2;

return {
column: tileCenter.column * kt - p2.x,
row: tileCenter.row * kt - p2.y,
column: x,
row: y,
zoom: this.tileZoom
};
},

coordinatePointMatrix: function(z) {
var proj = this.getProjMatrix();
var tileScale = Math.pow(2, z); // number of tiles along an edge at that z level
var scale = this.worldSize / tileScale;
mat4.scale(proj, proj, [scale, scale, 1]);
mat4.multiply(proj, this.getPixelMatrix(), proj);
return proj;
},

// converts pixel points to gl coords
getPixelMatrix: function() {
// gl coords to screen coords
var m = mat4.create();
mat4.scale(m, m, [this.width / 2, -this.height / 2, 1]);
mat4.translate(m, m, [1, -1, 0]);
return m;
},

_constrain: function() {
if (!this.center) return;

Expand Down Expand Up @@ -206,5 +289,27 @@ Transform.prototype = {
x2 !== undefined ? x2 : this.x,
y2 !== undefined ? y2 : this.y));
}
},

getProjMatrix: function() {
var m = new Float64Array(16);
mat4.perspective(m, 2 * Math.atan((this.height / 2) / this.altitude), this.width / this.height, 0, this.altitude + 1);

// Subtracting by one z pixel here is weird. I'm not sure exactly what is going on,
// but this fixes tiny rendering differences between top-down (pitch=0) images rendered
// rendered with different altitude values. Without this, images with smaller altitude appear
// a tiny bit more zoomed. The difference is almost imperceptible, but it affects rendering tests.
var onePixelZ = 1 / this.height;

mat4.translate(m, m, [0, 0, -this.altitude - onePixelZ]);

// After the rotateX, z values are in pixel units. Convert them to
// altitude unites. 1 altitude unit = the screen height.
mat4.scale(m, m, [1, -1, 1 / this.height]);

mat4.rotateX(m, m, this._pitch);
mat4.rotateZ(m, m, this.angle);
mat4.translate(m, m, [-this.x, -this.y, 0]);
return m;
}
};
21 changes: 19 additions & 2 deletions js/render/draw_line.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var browser = require('../util/browser');
var mat2 = require('gl-matrix').mat2;

module.exports = function drawLine(painter, layer, posMatrix, tile) {
// No data
Expand Down Expand Up @@ -34,9 +35,22 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) {
var outset = offset + edgeWidth + antialiasing / 2 + shift;

var color = layer.paint['line-color'];
var ratio = painter.transform.scale / (1 << tile.zoom) / 8;
var ratio = painter.transform.scale / (1 << tile.zoom) / (4096 / tile.tileSize);
var vtxMatrix = painter.translateMatrix(posMatrix, tile, layer.paint['line-translate'], layer.paint['line-translate-anchor']);

var tr = painter.transform;


var antialiasingMatrix = mat2.create();
mat2.scale(antialiasingMatrix, antialiasingMatrix, [1, Math.cos(tr._pitch)]);
mat2.rotate(antialiasingMatrix, antialiasingMatrix, painter.transform.angle);

// calculate how much longer the real world distance is at the top of the screen
// than at the middle of the screen.
var topedgelength = Math.sqrt(tr.height * tr.height / 4 * (1 + tr.altitude * tr.altitude));
var x = tr.height / 2 * Math.tan(tr._pitch);
var extra = (topedgelength + x) / topedgelength - 1;

var shader;


Expand Down Expand Up @@ -76,7 +90,7 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) {
var imagePosA = painter.spriteAtlas.getPosition(image.from, true);
var imagePosB = painter.spriteAtlas.getPosition(image.to, true);
if (!imagePosA || !imagePosB) return;
var factor = 8 / Math.pow(2, painter.transform.tileZoom - tile.zoom);
var factor = 4096 / tile.tileSize / Math.pow(2, painter.transform.tileZoom - tile.zoom);

painter.spriteAtlas.bind(gl, true);

Expand All @@ -103,6 +117,9 @@ module.exports = function drawLine(painter, layer, posMatrix, tile) {
gl.uniform2fv(shader.u_linewidth, [ outset, inset ]);
gl.uniform1f(shader.u_ratio, ratio);
gl.uniform1f(shader.u_blur, blur);
gl.uniform1f(shader.u_extra, extra);
gl.uniformMatrix2fv(shader.u_antialiasingmatrix, false, antialiasingMatrix);

gl.uniform4fv(shader.u_color, color);
}

Expand Down
30 changes: 23 additions & 7 deletions js/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,33 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf)

posMatrix = painter.translateMatrix(posMatrix, tile, layer.paint[prefix + '-translate'], layer.paint[prefix + '-translate-anchor']);

var exMatrix = mat4.clone(painter.projectionMatrix);
var tr = painter.transform;
var alignedWithMap = layer.layout[prefix + '-rotation-alignment'] === 'map';
var angleOffset = (alignedWithMap ? painter.transform.angle : 0);
var skewed = alignedWithMap;
var exMatrix, s, gammaScale;

if (angleOffset) {
mat4.rotateZ(exMatrix, exMatrix, angleOffset);
if (skewed) {
exMatrix = mat4.create();
s = 4096 / tile.tileSize / Math.pow(2, painter.transform.zoom - tile.zoom);
gammaScale = 1 / Math.cos(tr._pitch);
} else {
exMatrix = mat4.clone(tile.exMatrix);
s = painter.transform.altitude;
gammaScale = 1;
}
mat4.scale(exMatrix, exMatrix, [s, s, 1]);

// If layer.paint.size > layer.layout[prefix + '-max-size'] then labels may collide
var fontSize = layer.paint[prefix + '-size'] || layer.layout[prefix + '-max-size'];
var fontScale = fontSize / defaultSizes[prefix];
mat4.scale(exMatrix, exMatrix, [ fontScale, fontScale, 1 ]);

// calculate how much longer the real world distance is at the top of the screen
// than at the middle of the screen.
var topedgelength = Math.sqrt(tr.height * tr.height / 4 * (1 + tr.altitude * tr.altitude));
var x = tr.height / 2 * Math.tan(tr._pitch);
var extra = (topedgelength + x) / topedgelength - 1;

var text = prefix === 'text';
var shader, buffer, texsize;

Expand All @@ -65,14 +79,16 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf)
texsize = [painter.glyphAtlas.width / 4, painter.glyphAtlas.height / 4];
} else {
painter.spriteAtlas.bind(gl, alignedWithMap || painter.options.rotating ||
painter.options.zooming || fontScale !== 1 || sdf);
painter.options.zooming || fontScale !== 1 || sdf || painter.transform.pitch);
buffer = tile.buffers.iconVertex;
texsize = [painter.spriteAtlas.width / 4, painter.spriteAtlas.height / 4];
}

gl.switchShader(shader, posMatrix, exMatrix);
gl.uniform1i(shader.u_texture, 0);
gl.uniform2fv(shader.u_texsize, texsize);
gl.uniform1i(shader.u_skewed, skewed);
gl.uniform1f(shader.u_extra, extra);

buffer.bind(gl, shader);

Expand Down Expand Up @@ -102,14 +118,14 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf)
var haloOffset = 6;
var gamma = 0.105 * defaultSizes[prefix] / fontSize / browser.devicePixelRatio;

gl.uniform1f(shader.u_gamma, gamma);
gl.uniform1f(shader.u_gamma, gamma * gammaScale);
gl.uniform4fv(shader.u_color, layer.paint[prefix + '-color']);
gl.uniform1f(shader.u_buffer, (256 - 64) / 256);
gl.drawArrays(gl.TRIANGLES, begin, len);

if (layer.paint[prefix + '-halo-color']) {
// Draw halo underneath the text.
gl.uniform1f(shader.u_gamma, layer.paint[prefix + '-halo-blur'] * blurOffset / fontScale / sdfPx + gamma);
gl.uniform1f(shader.u_gamma, (layer.paint[prefix + '-halo-blur'] * blurOffset / fontScale / sdfPx + gamma) * gammaScale);
gl.uniform4fv(shader.u_color, layer.paint[prefix + '-halo-color']);
gl.uniform1f(shader.u_buffer, (haloOffset - layer.paint[prefix + '-halo-width'] / fontScale) / sdfPx);
gl.drawArrays(gl.TRIANGLES, begin, len);
Expand Down
9 changes: 3 additions & 6 deletions js/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ function GLPainter(gl, transform) {
*/
GLPainter.prototype.resize = function(width, height) {
var gl = this.gl;
// Initialize projection matrix
this.projectionMatrix = mat4.create();
mat4.ortho(this.projectionMatrix, 0, width, height, 0, 0, -1);

this.width = width * browser.devicePixelRatio;
this.height = height * browser.devicePixelRatio;
Expand Down Expand Up @@ -68,7 +65,7 @@ GLPainter.prototype.setup = function() {

this.lineShader = gl.initializeShader('line',
['a_pos', 'a_data'],
['u_matrix', 'u_exmatrix', 'u_linewidth', 'u_color', 'u_ratio', 'u_blur']);
['u_matrix', 'u_linewidth', 'u_color', 'u_ratio', 'u_blur', 'u_extra', 'u_antialiasingmatrix']);

this.linepatternShader = gl.initializeShader('linepattern',
['a_pos', 'a_data'],
Expand All @@ -84,11 +81,11 @@ GLPainter.prototype.setup = function() {

this.sdfShader = gl.initializeShader('sdf',
['a_pos', 'a_offset', 'a_data1', 'a_data2'],
['u_matrix', 'u_exmatrix', 'u_texture', 'u_texsize', 'u_color', 'u_gamma', 'u_buffer', 'u_angle', 'u_zoom', 'u_flip', 'u_fadedist', 'u_minfadezoom', 'u_maxfadezoom', 'u_fadezoom']);
['u_matrix', 'u_exmatrix', 'u_texture', 'u_texsize', 'u_color', 'u_gamma', 'u_buffer', 'u_angle', 'u_zoom', 'u_flip', 'u_fadedist', 'u_minfadezoom', 'u_maxfadezoom', 'u_fadezoom', 'u_skewed', 'u_extra']);

this.iconShader = gl.initializeShader('icon',
['a_pos', 'a_offset', 'a_data1', 'a_data2'],
['u_matrix', 'u_exmatrix', 'u_texture', 'u_texsize', 'u_angle', 'u_zoom', 'u_flip', 'u_fadedist', 'u_minfadezoom', 'u_maxfadezoom', 'u_fadezoom', 'u_opacity']);
['u_matrix', 'u_exmatrix', 'u_texture', 'u_texsize', 'u_angle', 'u_zoom', 'u_flip', 'u_fadedist', 'u_minfadezoom', 'u_maxfadezoom', 'u_fadezoom', 'u_opacity', 'u_skewed', 'u_extra']);

this.outlineShader = gl.initializeShader('outline',
['a_pos'],
Expand Down
23 changes: 10 additions & 13 deletions js/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Tile.prototype = {
// todo unhardcode
tileExtent: 4096,

calculateMatrices: function(z, x, y, transform, painter) {
calculateMatrices: function(z, x, y, transform) {

// Initialize model-view matrix that converts from the tile coordinates
// to screen coordinates.
Expand All @@ -34,30 +34,27 @@ Tile.prototype = {
this.scale = scale;

// The position matrix
this.posMatrix = mat4.create();
mat4.translate(this.posMatrix, this.posMatrix, [transform.centerPoint.x, transform.centerPoint.y, 0]);
mat4.rotateZ(this.posMatrix, this.posMatrix, transform.angle);
mat4.translate(this.posMatrix, this.posMatrix, [-transform.centerPoint.x, -transform.centerPoint.y, 0]);

var pixelX = transform.width / 2 - transform.x,
pixelY = transform.height / 2 - transform.y;

mat4.translate(this.posMatrix, this.posMatrix, [pixelX + x * scale, pixelY + y * scale, 1]);
this.posMatrix = new Float64Array(16);
mat4.identity(this.posMatrix);
mat4.translate(this.posMatrix, this.posMatrix, [x * scale, y * scale, 1]);

// Create inverted matrix for interaction
this.invPosMatrix = mat4.create();
mat4.invert(this.invPosMatrix, this.posMatrix);

mat4.scale(this.posMatrix, this.posMatrix, [ scale / this.tileExtent, scale / this.tileExtent, 1 ]);
mat4.multiply(this.posMatrix, painter.projectionMatrix, this.posMatrix);
mat4.multiply(this.posMatrix, transform.getProjMatrix(), this.posMatrix);

// The extrusion matrix.
this.exMatrix = mat4.clone(painter.projectionMatrix);
mat4.rotateZ(this.exMatrix, this.exMatrix, transform.angle);
this.exMatrix = mat4.create();
mat4.ortho(this.exMatrix, 0, transform.width, transform.height, 0, 0, -1);
//mat4.rotateZ(this.exMatrix, this.exMatrix, -transform.angle);

// 2x2 matrix for rotating points
this.rotationMatrix = mat2.create();
mat2.rotate(this.rotationMatrix, this.rotationMatrix, transform.angle);

this.posMatrix = new Float32Array(this.posMatrix);
},

positionAt: function(point) {
Expand Down
Loading

0 comments on commit bdaa1d0

Please sign in to comment.