Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic perspective rendering #1049

Merged
merged 21 commits into from
Mar 6, 2015
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
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