diff --git a/js/geo/transform.js b/js/geo/transform.js index 7081c4b5e54..56f0feb174d 100644 --- a/js/geo/transform.js +++ b/js/geo/transform.js @@ -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; @@ -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 = { @@ -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); @@ -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; @@ -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; } }; diff --git a/js/render/draw_line.js b/js/render/draw_line.js index 661a7878b5c..62dc141fb9d 100644 --- a/js/render/draw_line.js +++ b/js/render/draw_line.js @@ -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 @@ -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; @@ -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); @@ -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); } diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index ea1377f532f..5be610f9117 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -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; @@ -65,7 +79,7 @@ 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]; } @@ -73,6 +87,8 @@ function drawSymbol(painter, layer, posMatrix, tile, elementGroups, prefix, sdf) 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); @@ -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); diff --git a/js/render/painter.js b/js/render/painter.js index 94e1339b3db..6314aa15c6d 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -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; @@ -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'], @@ -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'], diff --git a/js/source/tile.js b/js/source/tile.js index 8243a30edee..b972e4ab4ad 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -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. @@ -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) { diff --git a/js/ui/easings.js b/js/ui/easings.js index 6c21383b2ed..fec6e3c4891 100644 --- a/js/ui/easings.js +++ b/js/ui/easings.js @@ -194,7 +194,7 @@ util.extend(exports, { this.flyTo(center, zoom, 0, options); }, - easeTo: function(latlng, zoom, bearing, options) { + easeTo: function(latlng, zoom, bearing, pitch, options) { this.stop(); options = util.extend({ @@ -206,11 +206,13 @@ util.extend(exports, { var tr = this.transform, offset = Point.convert(options.offset).rotate(-tr.angle), startZoom = this.getZoom(), - startBearing = this.getBearing(); + startBearing = this.getBearing(), + startPitch = this.getPitch(); latlng = LatLng.convert(latlng); zoom = zoom === undefined ? startZoom : zoom; bearing = bearing === undefined ? startBearing : this._normalizeBearing(bearing, startBearing); + pitch = pitch === undefined ? startPitch : pitch; var scale = tr.zoomScale(zoom - startZoom), from = tr.point, @@ -236,6 +238,10 @@ util.extend(exports, { tr.bearing = interpolate(startBearing, bearing, k); } + if (pitch !== startPitch) { + tr.pitch = interpolate(startPitch, pitch, k); + } + this.animationLoop.set(300); // text fading this._move(zoom !== startZoom, bearing !== startBearing); }, function() { @@ -272,7 +278,7 @@ util.extend(exports, { to = tr.project(latlng).sub(offset.div(scale)); if (options.animate === false) { - return this.setView(latlng, zoom, bearing); + return this.setView(latlng, zoom, bearing, this.getPitch()); } var startWorldSize = tr.worldSize, diff --git a/js/ui/hash.js b/js/ui/hash.js index 28e537fbd78..ff35e02b7d4 100644 --- a/js/ui/hash.js +++ b/js/ui/hash.js @@ -29,7 +29,7 @@ Hash.prototype = { _onHashChange: function() { var loc = location.hash.replace('#', '').split('/'); if (loc.length >= 3) { - this._map.setView([+loc[1], +loc[2]], +loc[0], +(loc[3] || 0)); + this._map.setView([+loc[1], +loc[2]], +loc[0], +(loc[3] || 0), this._map.getPitch()); return true; } return false; diff --git a/js/ui/map.js b/js/ui/map.js index 8f0295dc731..9b83c73975a 100644 --- a/js/ui/map.js +++ b/js/ui/map.js @@ -53,7 +53,7 @@ var Map = module.exports = function(options) { this._hash = options.hash && (new Hash()).addTo(this); // don't set position from options if set through hash if (!this._hash || !this._hash._onHashChange()) { - this.setView(options.center, options.zoom, options.bearing); + this.setView(options.center, options.zoom, options.bearing, options.pitch); } this.sources = {}; @@ -75,6 +75,7 @@ util.extend(Map.prototype, { center: [0, 0], zoom: 0, bearing: 0, + pitch: 0, minZoom: 0, maxZoom: 20, @@ -91,38 +92,45 @@ util.extend(Map.prototype, { }, // Set the map's center, zoom, and bearing - setView: function(center, zoom, bearing) { + setView: function(center, zoom, bearing, pitch) { this.stop(); var tr = this.transform, zoomChanged = tr.zoom !== +zoom, - bearingChanged = tr.bearing !== +bearing; + bearingChanged = tr.bearing !== +bearing, + pitchChanged = tr.pitch !== +pitch; tr.center = LatLng.convert(center); tr.zoom = +zoom; tr.bearing = +bearing; + tr.pitch = +pitch; return this .fire('movestart') - ._move(zoomChanged, bearingChanged) + ._move(zoomChanged, bearingChanged, pitchChanged) .fire('moveend'); }, setCenter: function(center) { - this.setView(center, this.getZoom(), this.getBearing()); + this.setView(center, this.getZoom(), this.getBearing(), this.getPitch()); }, setZoom: function(zoom) { - this.setView(this.getCenter(), zoom, this.getBearing()); + this.setView(this.getCenter(), zoom, this.getBearing(), this.getPitch()); }, setBearing: function(bearing) { - this.setView(this.getCenter(), this.getZoom(), bearing); + this.setView(this.getCenter(), this.getZoom(), bearing, this.getPitch()); + }, + + setPitch: function(pitch) { + this.setView(this.getCenter(), this.getZoom(), this.getBearing(), pitch); }, getCenter: function() { return this.transform.center; }, getZoom: function() { return this.transform.zoom; }, getBearing: function() { return this.transform.bearing; }, + getPitch: function() { return this.transform.pitch; }, addClass: function(klass, options) { if (this._classes[klass]) return; diff --git a/shaders/icon.vertex.glsl b/shaders/icon.vertex.glsl index fd7afd5b7d9..bbd80912261 100644 --- a/shaders/icon.vertex.glsl +++ b/shaders/icon.vertex.glsl @@ -16,6 +16,8 @@ uniform float u_minfadezoom; uniform float u_maxfadezoom; uniform float u_fadezoom; uniform float u_opacity; +uniform bool u_skewed; +uniform float u_extra; uniform vec2 u_texsize; @@ -70,7 +72,15 @@ void main() { // hide if (angle >= a_rangeend && angle < rangestart) z += step(a_rangeend, u_angle) * (1.0 - step(a_rangestart, u_angle)); - gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0); + if (u_skewed) { + vec4 extrude = u_exmatrix * vec4(a_offset / 64.0, 0, 0); + gl_Position = u_matrix * vec4(a_pos + extrude.xy, 0, 1); + gl_Position.z += z * gl_Position.w; + } else { + vec4 extrude = u_exmatrix * vec4(a_offset / 64.0, z, 0); + gl_Position = u_matrix * vec4(a_pos, 0, 1) + extrude; + } + v_tex = a_tex / u_texsize; v_alpha *= u_opacity; diff --git a/shaders/line.fragment.glsl b/shaders/line.fragment.glsl index fd3f964f0dd..7e7d5d1b0e3 100644 --- a/shaders/line.fragment.glsl +++ b/shaders/line.fragment.glsl @@ -5,6 +5,8 @@ uniform float u_blur; uniform vec2 u_dasharray; varying vec2 v_normal; +varying float v_linesofar; +varying float gamma_scale; void main() { // Calculate the distance of the pixel from the line in pixels. @@ -13,7 +15,8 @@ void main() { // Calculate the antialiasing fade factor. This is either when fading in // the line in case of an offset line (v_linewidth.t) or when fading out // (v_linewidth.s) - float alpha = clamp(min(dist - (u_linewidth.t - u_blur), u_linewidth.s - dist) / u_blur, 0.0, 1.0); + float blur = u_blur * gamma_scale; + float alpha = clamp(min(dist - (u_linewidth.t - blur), u_linewidth.s - dist) / blur, 0.0, 1.0); gl_FragColor = u_color * alpha; } diff --git a/shaders/line.vertex.glsl b/shaders/line.vertex.glsl index 1f5432991ce..fc7fc7f771e 100644 --- a/shaders/line.vertex.glsl +++ b/shaders/line.vertex.glsl @@ -12,14 +12,18 @@ attribute vec4 a_data; // matrix is for the vertex position, exmatrix is for rotating and projecting // the extrusion vector. uniform mat4 u_matrix; -uniform mat4 u_exmatrix; // shared uniform float u_ratio; uniform vec2 u_linewidth; uniform vec4 u_color; +uniform float u_extra; +uniform mat2 u_antialiasingmatrix; + varying vec2 v_normal; +varying float v_linesofar; +varying float gamma_scale; void main() { vec2 a_extrude = a_data.xy; @@ -40,5 +44,16 @@ void main() { // model/view matrix. Add the extrusion vector *after* the model/view matrix // because we're extruding the line in pixel space, regardless of the current // tile's zoom level. - gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist; + gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist.xy / u_ratio, 0.0, 1.0); + + // position of y on the screen + float y = gl_Position.y / gl_Position.w; + + // how much features are squished in the y direction by the tilt + float squish_scale = length(a_extrude) / length(u_antialiasingmatrix * a_extrude); + + // how much features are squished in all directions by the perspectiveness + float perspective_scale = 1.0 / (1.0 - y * u_extra); + + gamma_scale = perspective_scale * squish_scale; } diff --git a/shaders/linepattern.vertex.glsl b/shaders/linepattern.vertex.glsl index 987f2e8ce6c..65aed9f26c2 100644 --- a/shaders/linepattern.vertex.glsl +++ b/shaders/linepattern.vertex.glsl @@ -43,7 +43,7 @@ void main() { // model/view matrix. Add the extrusion vector *after* the model/view matrix // because we're extruding the line in pixel space, regardless of the current // tile's zoom level. - gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0) + u_exmatrix * vec4(dist, 0.0, 0.0); + gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist.xy / u_ratio, 0.0, 1.0); v_linesofar = a_linesofar;// * u_ratio; } diff --git a/shaders/linesdfpattern.vertex.glsl b/shaders/linesdfpattern.vertex.glsl index 3c1155f61d8..9da42801b4e 100644 --- a/shaders/linesdfpattern.vertex.glsl +++ b/shaders/linesdfpattern.vertex.glsl @@ -45,7 +45,7 @@ void main() { // model/view matrix. Add the extrusion vector *after* the model/view matrix // because we're extruding the line in pixel space, regardless of the current // tile's zoom level. - gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist; + gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist.xy / u_ratio, 0.0, 1.0); v_tex_a = vec2(a_linesofar * u_patternscale_a.x, normal.y * u_patternscale_a.y + u_tex_y_a); v_tex_b = vec2(a_linesofar * u_patternscale_b.x, normal.y * u_patternscale_b.y + u_tex_y_b); diff --git a/shaders/outline.vertex.glsl b/shaders/outline.vertex.glsl index 29c16e3ded2..535063b5964 100644 --- a/shaders/outline.vertex.glsl +++ b/shaders/outline.vertex.glsl @@ -6,5 +6,5 @@ varying vec2 v_pos; void main() { gl_Position = u_matrix * vec4(a_pos, 0, 1); - v_pos = (gl_Position.xy + 1.0) / 2.0 * u_world; + v_pos = (gl_Position.xy/gl_Position.w + 1.0) / 2.0 * u_world; } diff --git a/shaders/sdf.fragment.glsl b/shaders/sdf.fragment.glsl index d72d61dab19..a7ae7f821f6 100644 --- a/shaders/sdf.fragment.glsl +++ b/shaders/sdf.fragment.glsl @@ -5,9 +5,11 @@ uniform float u_gamma; varying vec2 v_tex; varying float v_alpha; +varying float v_gamma_scale; void main() { + float gamma = u_gamma * v_gamma_scale; float dist = texture2D(u_texture, v_tex).a; - float alpha = smoothstep(u_buffer - u_gamma, u_buffer + u_gamma, dist) * v_alpha; + float alpha = smoothstep(u_buffer - gamma, u_buffer + gamma, dist) * v_alpha; gl_FragColor = u_color * alpha; } diff --git a/shaders/sdf.vertex.glsl b/shaders/sdf.vertex.glsl index bbfc44919e3..3c40b922795 100644 --- a/shaders/sdf.vertex.glsl +++ b/shaders/sdf.vertex.glsl @@ -15,11 +15,14 @@ uniform float u_fadedist; uniform float u_minfadezoom; uniform float u_maxfadezoom; uniform float u_fadezoom; +uniform bool u_skewed; +uniform float u_extra; uniform vec2 u_texsize; varying vec2 v_tex; varying float v_alpha; +varying float v_gamma_scale; void main() { vec2 a_tex = a_data1.xy; @@ -68,6 +71,20 @@ void main() { // hide if (angle >= a_rangeend && angle < rangestart) z += step(a_rangeend, u_angle) * (1.0 - step(a_rangestart, u_angle)); - gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0); + if (u_skewed) { + vec4 extrude = u_exmatrix * vec4(a_offset / 64.0, 0, 0); + gl_Position = u_matrix * vec4(a_pos + extrude.xy, 0, 1); + gl_Position.z += z * gl_Position.w; + } else { + vec4 extrude = u_exmatrix * vec4(a_offset / 64.0, z, 0); + gl_Position = u_matrix * vec4(a_pos, 0, 1) + extrude; + } + + // position of y on the screen + float y = gl_Position.y / gl_Position.w; + // how much features are squished in all directions by the perspectiveness + float perspective_scale = 1.0 / (1.0 - y * u_extra); + v_gamma_scale = perspective_scale; + v_tex = a_tex / u_texsize; } diff --git a/test/js/source/tile_pyramid.test.js b/test/js/source/tile_pyramid.test.js index 089cf836318..1cbfc200172 100644 --- a/test/js/source/tile_pyramid.test.js +++ b/test/js/source/tile_pyramid.test.js @@ -245,8 +245,8 @@ test('TilePyramid#update', function(t) { t.test('loads covering tiles', function(t) { var transform = new Transform(); - transform.width = 512; - transform.height = 512; + transform.width = 511; + transform.height = 511; transform.zoom = 0; var pyramid = createPyramid({}); @@ -258,8 +258,8 @@ test('TilePyramid#update', function(t) { t.test('removes unused tiles', function(t) { var transform = new Transform(); - transform.width = 512; - transform.height = 512; + transform.width = 511; + transform.height = 511; transform.zoom = 0; var pyramid = createPyramid({ @@ -285,8 +285,8 @@ test('TilePyramid#update', function(t) { t.test('retains parent tiles for pending children', function(t) { var transform = new Transform(); - transform.width = 512; - transform.height = 512; + transform.width = 511; + transform.height = 511; transform.zoom = 0; var pyramid = createPyramid({ @@ -313,8 +313,8 @@ test('TilePyramid#update', function(t) { t.test('retains parent tiles for pending children (wrapped)', function(t) { var transform = new Transform(); - transform.width = 512; - transform.height = 512; + transform.width = 511; + transform.height = 511; transform.zoom = 0; transform.center = new LatLng(0, 360); diff --git a/test/js/ui/easings.test.js b/test/js/ui/easings.test.js index 6fbe022b7c3..1cce7c50b80 100644 --- a/test/js/ui/easings.test.js +++ b/test/js/ui/easings.test.js @@ -260,28 +260,28 @@ test('Map', function(t) { t.test('#easeTo', function(t) { t.test('pans to specified location', function(t) { var map = createMap(); - map.easeTo([0, 100], undefined, undefined, { duration: 0 }); + map.easeTo([0, 100], undefined, undefined, undefined, { duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 100 }); t.end(); }); t.test('zooms to specified level', function(t) { var map = createMap(); - map.easeTo(undefined, 3.2, undefined, { duration: 0 }); + map.easeTo(undefined, 3.2, undefined, undefined, { duration: 0 }); t.equal(map.getZoom(), 3.2); t.end(); }); t.test('rotates to specified bearing', function(t) { var map = createMap(); - map.easeTo(undefined, undefined, 90, { duration: 0 }); + map.easeTo(undefined, undefined, 90, undefined, { duration: 0 }); t.equal(map.getBearing(), 90); t.end(); }); t.test('pans and zooms', function(t) { var map = createMap(); - map.easeTo([0, 100], 3.2, undefined, { duration: 0 }); + map.easeTo([0, 100], 3.2, undefined, undefined, { duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 100 }); t.equal(map.getZoom(), 3.2); t.end(); @@ -289,7 +289,7 @@ test('Map', function(t) { t.test('pans and rotates', function(t) { var map = createMap(); - map.easeTo([0, 100], undefined, 90, { duration: 0 }); + map.easeTo([0, 100], undefined, 90, undefined, { duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 100 }); t.equal(map.getBearing(), 90); t.end(); @@ -297,7 +297,7 @@ test('Map', function(t) { t.test('zooms and rotates', function(t) { var map = createMap(); - map.easeTo(undefined, 3.2, 90, { duration: 0 }); + map.easeTo(undefined, 3.2, 90, undefined, { duration: 0 }); t.equal(map.getZoom(), 3.2); t.equal(map.getBearing(), 90); t.end(); @@ -305,7 +305,7 @@ test('Map', function(t) { t.test('pans, zooms, and rotates', function(t) { var map = createMap(); - map.easeTo([0, 100], 3.2, 90, { duration: 0 }); + map.easeTo([0, 100], 3.2, 90, undefined, { duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 100 }); t.equal(map.getZoom(), 3.2); t.equal(map.getBearing(), 90); @@ -314,7 +314,7 @@ test('Map', function(t) { t.test('noop', function(t) { var map = createMap(); - map.easeTo(undefined, undefined, undefined, { duration: 0 }); + map.easeTo(undefined, undefined, undefined, undefined, { duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 0 }); t.equal(map.getZoom(), 0); t.equal(map.getBearing(), 0); @@ -323,7 +323,7 @@ test('Map', function(t) { t.test('noop with offset', function(t) { var map = createMap(); - map.easeTo(undefined, undefined, undefined, { offset: [100, 0], duration: 0 }); + map.easeTo(undefined, undefined, undefined, undefined, { offset: [100, 0], duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 0 }); t.equal(map.getZoom(), 0); t.equal(map.getBearing(), 0); @@ -332,14 +332,14 @@ test('Map', function(t) { t.test('pans with specified offset', function(t) { var map = createMap(); - map.easeTo([0, 100], undefined, undefined, { offset: [100, 0], duration: 0 }); + map.easeTo([0, 100], undefined, undefined, undefined, { offset: [100, 0], duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 29.6875 }); t.end(); }); t.test('pans with specified offset relative to viewport on a rotated map', function(t) { var map = createMap({bearing: 180}); - map.easeTo([0, 100], undefined, undefined, { offset: [100, 0], duration: 0 }); + map.easeTo([0, 100], undefined, undefined, undefined, { offset: [100, 0], duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 170.3125 }); t.end(); }); @@ -375,13 +375,13 @@ test('Map', function(t) { t.end(); }); - map.easeTo([0, 100], 3.2, 90, { duration: 0 }); + map.easeTo([0, 100], 3.2, 90, undefined, { duration: 0 }); }); t.test('stops existing ease', function(t) { var map = createMap(); - map.easeTo([0, 200], undefined, undefined, { duration: 100 }); - map.easeTo([0, 100], undefined, undefined, { duration: 0 }); + map.easeTo([0, 200], undefined, undefined, undefined, { duration: 100 }); + map.easeTo([0, 100], undefined, undefined, undefined, { duration: 0 }); t.deepEqual(map.getCenter(), { lat: 0, lng: 100 }); t.end(); });