diff --git a/js/data/bucket/symbol_bucket.js b/js/data/bucket/symbol_bucket.js index 11fdd34a377..fc62cbfc5a5 100644 --- a/js/data/bucket/symbol_bucket.js +++ b/js/data/bucket/symbol_bucket.js @@ -69,7 +69,7 @@ var programAttributes = [{ type: 'Uint8' }]; -function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom) { +function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, labelangle) { return array.emplaceBack( // pos x, @@ -81,7 +81,7 @@ function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom) tx / 4, // tex ty / 4, // tex (labelminzoom || 0) * 10, // labelminzoom - 0, + labelangle, // labelangle // data2 (minzoom || 0) * 10, // minzoom Math.min(maxzoom || 25, 25) * 10); // minzoom @@ -462,11 +462,10 @@ SymbolBucket.prototype.addSymbols = function(programName, quadsStart, quadsEnd, for (var k = quadsStart; k < quadsEnd; k++) { - var symbol = this.symbolQuadsArray.get(k).SymbolQuad, - angle = symbol.angle; + var symbol = this.symbolQuadsArray.get(k).SymbolQuad; // drop upside down versions of glyphs - var a = (angle + placementAngle + Math.PI) % (Math.PI * 2); + var a = (symbol.anchorAngle + placementAngle + Math.PI) % (Math.PI * 2); if (keepUpright && alongLine && (a <= Math.PI / 2 || a > Math.PI * 3 / 2)) continue; var tl = symbol.tl, @@ -484,10 +483,13 @@ SymbolBucket.prototype.addSymbols = function(programName, quadsStart, quadsEnd, // Lower min zoom so that while fading out the label it can be shown outside of collision-free zoom levels if (minZoom === placementZoom) minZoom = 0; - var index = addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom); - addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, minZoom, maxZoom, placementZoom); - addVertex(vertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, minZoom, maxZoom, placementZoom); - addVertex(vertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, minZoom, maxZoom, placementZoom); + // Encode angle of glyph + var glyphAngle = Math.round((symbol.glyphAngle / (Math.PI * 2)) * 256); + + var index = addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom, glyphAngle); + addVertex(vertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, minZoom, maxZoom, placementZoom, glyphAngle); + addVertex(vertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle); + addVertex(vertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle); elementArray.emplaceBack(index, index + 1, index + 2); elementArray.emplaceBack(index + 1, index + 2, index + 3); @@ -624,7 +626,8 @@ SymbolBucket.prototype.addSymbolQuad = function(symbolQuad) { symbolQuad.tex.x, symbolQuad.tex.y, //angle - symbolQuad.angle, + symbolQuad.anchorAngle, + symbolQuad.glyphAngle, // scales symbolQuad.maxScale, symbolQuad.minScale); diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index 9bf42783855..271e2097fbb 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -34,6 +34,9 @@ function drawSymbols(painter, source, layer, coords) { layer.paint['icon-translate'], layer.paint['icon-translate-anchor'], layer.layout['icon-rotation-alignment'], + // icon-pitch-alignment is not yet implemented + // and we simply inherit the rotation alignment + layer.layout['icon-rotation-alignment'], layer.layout['icon-size'], layer.paint['icon-halo-width'], layer.paint['icon-halo-color'], @@ -45,6 +48,7 @@ function drawSymbols(painter, source, layer, coords) { layer.paint['text-translate'], layer.paint['text-translate-anchor'], layer.layout['text-rotation-alignment'], + layer.layout['text-pitch-alignment'], layer.layout['text-size'], layer.paint['text-halo-width'], layer.paint['text-halo-color'], @@ -61,6 +65,7 @@ function drawLayerSymbols(painter, source, layer, coords, isText, translate, translateAnchor, rotationAlignment, + pitchAlignment, size, haloWidth, haloColor, @@ -83,6 +88,7 @@ function drawLayerSymbols(painter, source, layer, coords, isText, translate, translateAnchor, rotationAlignment, + pitchAlignment, size, haloWidth, haloColor, @@ -96,21 +102,24 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex translate, translateAnchor, rotationAlignment, + pitchAlignment, size, haloWidth, haloColor, haloBlur, opacity, color) { + var gl = painter.gl; var tr = painter.transform; - var alignedWithMap = rotationAlignment === 'map'; + var rotateWithMap = rotationAlignment === 'map'; + var pitchWithMap = pitchAlignment === 'map'; var defaultSize = isText ? 24 : 1; var fontScale = size / defaultSize; var extrudeScale, s, gammaScale; - if (alignedWithMap) { + if (pitchWithMap) { s = pixelsToTileUnits(tile, 1, painter.transform.zoom) * fontScale; gammaScale = 1 / Math.cos(tr._pitch); extrudeScale = [s, s]; @@ -125,7 +134,8 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex var program = painter.useProgram(sdf ? 'sdf' : 'icon'); gl.uniformMatrix4fv(program.u_matrix, false, painter.translatePosMatrix(posMatrix, tile, translate, translateAnchor)); - gl.uniform1i(program.u_skewed, alignedWithMap); + gl.uniform1i(program.u_rotate_with_map, rotateWithMap); + gl.uniform1i(program.u_pitch_with_map, pitchWithMap); gl.uniform2fv(program.u_extrude_scale, extrudeScale); gl.activeTexture(gl.TEXTURE0); @@ -142,7 +152,7 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex } else { var mapMoving = painter.options.rotating || painter.options.zooming; var iconScaled = fontScale !== 1 || browser.devicePixelRatio !== painter.spriteAtlas.pixelRatio || iconsNeedLinear; - var iconTransformed = alignedWithMap || painter.transform.pitch; + var iconTransformed = pitchWithMap || painter.transform.pitch; painter.spriteAtlas.bind(gl, sdf || mapMoving || iconScaled || iconTransformed); gl.uniform2f(program.u_texsize, painter.spriteAtlas.width / 4, painter.spriteAtlas.height / 4); } @@ -181,6 +191,9 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex gl.uniform4fv(program.u_color, color); gl.uniform1f(program.u_opacity, opacity); gl.uniform1f(program.u_buffer, (256 - 64) / 256); + gl.uniform1f(program.u_pitch, tr.pitch / 360 * 2 * Math.PI); + gl.uniform1f(program.u_bearing, tr.bearing / 360 * 2 * Math.PI); + gl.uniform1f(program.u_aspect_ratio, tr.width / tr.height); for (var i = 0; i < bufferGroups.length; i++) { group = bufferGroups[i]; diff --git a/js/style/style_layer/symbol_style_layer.js b/js/style/style_layer/symbol_style_layer.js index 3ebf4412d48..4d1ba0c9c46 100644 --- a/js/style/style_layer/symbol_style_layer.js +++ b/js/style/style_layer/symbol_style_layer.js @@ -30,6 +30,9 @@ SymbolStyleLayer.prototype = util.inherit(StyleLayer, { this.getLayoutValue('symbol-placement', globalProperties, featureProperties) === 'line' && !this.getLayoutProperty('icon-rotation-alignment')) { return 'map'; + // If unspecified `text-pitch-alignment` inherits `text-rotation-alignment` + } else if (name === 'text-pitch-alignment' && !this.getLayoutProperty('text-pitch-alignment')) { + return this.getLayoutValue('text-rotation-alignment'); } else { return StyleLayer.prototype.getLayoutValue.apply(this, arguments); } diff --git a/js/symbol/quads.js b/js/symbol/quads.js index 24ff5d755f4..a664991db97 100644 --- a/js/symbol/quads.js +++ b/js/symbol/quads.js @@ -21,21 +21,23 @@ var minScale = 0.5; // underscale by 1 zoom level * @param {Point} bl The offset of the bottom left corner from the anchor. * @param {Point} br The offset of the bottom right corner from the anchor. * @param {Object} tex The texture coordinates. - * @param {number} angle The angle of the label at it's center, not the angle of this quad. + * @param {number} anchorAngle The angle of the label at it's center, not the angle of this quad. + * @param {number} glyphAngle The angle of the glyph to be positioned in the quad. * @param {number} minScale The minimum scale, relative to the tile's intended scale, that the glyph can be shown at. * @param {number} maxScale The maximum scale, relative to the tile's intended scale, that the glyph can be shown at. * * @class SymbolQuad * @private */ -function SymbolQuad(anchorPoint, tl, tr, bl, br, tex, angle, minScale, maxScale) { +function SymbolQuad(anchorPoint, tl, tr, bl, br, tex, anchorAngle, glyphAngle, minScale, maxScale) { this.anchorPoint = anchorPoint; this.tl = tl; this.tr = tr; this.bl = bl; this.br = br; this.tex = tex; - this.angle = angle; + this.anchorAngle = anchorAngle; + this.glyphAngle = glyphAngle; this.minScale = minScale; this.maxScale = maxScale; } @@ -88,7 +90,7 @@ function getIconQuads(anchor, shapedIcon, boxScale, line, layout, alongLine) { br = br.matMult(matrix); } - return [new SymbolQuad(new Point(anchor.x, anchor.y), tl, tr, bl, br, shapedIcon.image.rect, 0, minScale, Infinity)]; + return [new SymbolQuad(new Point(anchor.x, anchor.y), tl, tr, bl, br, shapedIcon.image.rect, 0, 0, minScale, Infinity)]; } /** @@ -155,12 +157,11 @@ function getGlyphQuads(anchor, shaping, boxScale, line, layout, alongLine) { tl = otl, tr = otr, bl = obl, - br = obr, - angle = instance.angle + textRotate; + br = obr; - if (angle) { - var sin = Math.sin(angle), - cos = Math.cos(angle), + if (textRotate) { + var sin = Math.sin(textRotate), + cos = Math.cos(textRotate), matrix = [cos, -sin, sin, cos]; tl = tl.matMult(matrix); @@ -172,9 +173,9 @@ function getGlyphQuads(anchor, shaping, boxScale, line, layout, alongLine) { // Prevent label from extending past the end of the line var glyphMinScale = Math.max(instance.minScale, labelMinScale); - var glyphAngle = (anchor.angle + textRotate + instance.offset + 2 * Math.PI) % (2 * Math.PI); - quads.push(new SymbolQuad(instance.anchorPoint, tl, tr, bl, br, rect, glyphAngle, glyphMinScale, instance.maxScale)); - + var anchorAngle = (anchor.angle + textRotate + instance.offset + 2 * Math.PI) % (2 * Math.PI); + var glyphAngle = (instance.angle + textRotate + instance.offset + 2 * Math.PI) % (2 * Math.PI); + quads.push(new SymbolQuad(instance.anchorPoint, tl, tr, bl, br, rect, anchorAngle, glyphAngle, glyphMinScale, instance.maxScale)); } } @@ -219,7 +220,6 @@ function getSegmentGlyphs(glyphs, anchor, offset, line, segment, forward) { // Get the angle of the line segment var angle = Math.atan2(end.y - newAnchorPoint.y, end.x - newAnchorPoint.x); if (!forward) angle += Math.PI; - if (upsideDown) angle += Math.PI; glyphs.push({ anchorPoint: newAnchorPoint, diff --git a/js/symbol/symbol_quads.js b/js/symbol/symbol_quads.js index 4b06f1bfae5..001260d0c81 100644 --- a/js/symbol/symbol_quads.js +++ b/js/symbol/symbol_quads.js @@ -41,8 +41,10 @@ var SymbolQuadsArray = module.exports = new StructArrayType({ { type: 'Int16', name: 'texX' }, { type: 'Int16', name: 'texY' }, - //the angle of the label at it's center, not the angle of this quad. - { type: 'Float32', name: 'angle' }, + // the angle of the label at it's center, not the angle of this quad. + { type: 'Float32', name: 'anchorAngle' }, + // the angle of this quad. + { type: 'Float32', name: 'glyphAngle' }, // quad is only valid for scales < maxScale && scale > minScale. { type: 'Float32', name: 'maxScale' }, @@ -61,7 +63,8 @@ util.extendAll(SymbolQuadsArray.prototype.StructType.prototype, { new Point(this.blX, this.blY), new Point(this.brX, this.brY), { x: this.texX, y: this.texY, h: this.texH, w: this.texW, height: this.texH, width: this.texW }, - this.angle, + this.anchorAngle, + this.glyphAngle, this.minScale, this.maxScale); } diff --git a/package.json b/package.json index 21faa83ffec..9868142348f 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "grid-index": "^0.1.0", "mapbox-gl-function": "^1.2.1", "mapbox-gl-js-supported": "^1.1.0", - "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#f0b94dcc5f782958e9aade61d592a207d8a46e0f", - "mapbox-gl-style-spec": "^8.7.0", + "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#30caf388dbddd02cfc4f967ffc94c1338c30fbf8", + "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#2461efc3d883f2f2e56a6c6b2bfd7d54bbfe9f86", "minifyify": "^7.0.1", "pbf": "^1.3.2", "pngjs": "^2.2.0", @@ -57,7 +57,7 @@ "highlight.js": "9.3.0", "istanbul": "^0.4.2", "lodash": "^4.13.1", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#6d3bcff5d51f6acea41230ffabad6bebdea49fa3", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#5587d796c99145991ea2a7b749a8782b7a0cb483", "nyc": "^6.1.1", "remark": "4.2.2", "remark-html": "3.0.0", diff --git a/test/js/style/style_layer.test.js b/test/js/style/style_layer.test.js index f745550042f..d22bd8ca593 100644 --- a/test/js/style/style_layer.test.js +++ b/test/js/style/style_layer.test.js @@ -539,6 +539,66 @@ test('StyleLayer#serialize', function(t) { t.end(); }); +test('StyleLayer#getLayoutValue (default exceptions)', function(assert) { + assert.test('symbol-placement:point => *-rotation-alignment:viewport', function(assert) { + var layer = StyleLayer.create({ + "type": "symbol", + "layout": { + "symbol-placement": "point" + } + }); + assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'viewport'); + assert.equal(layer.getLayoutValue('icon-rotation-alignment'), 'viewport'); + assert.end(); + }); + assert.test('symbol-placement:line => *-rotation-alignment:map', function(assert) { + var layer = StyleLayer.create({ + "type": "symbol", + "layout": { + "symbol-placement": "line" + } + }); + assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'map'); + assert.equal(layer.getLayoutValue('icon-rotation-alignment'), 'map'); + assert.end(); + }); + assert.test('text-rotation-alignment:map => text-pitch-alignment:map', function(assert) { + var layer = StyleLayer.create({ + "type": "symbol", + "layout": { + "text-rotation-alignment": "map" + } + }); + assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'map'); + assert.equal(layer.getLayoutValue('text-pitch-alignment'), 'map'); + assert.end(); + }); + assert.test('text-rotation-alignment:viewport => text-pitch-alignment:viewport', function(assert) { + var layer = StyleLayer.create({ + "type": "symbol", + "layout": { + "text-rotation-alignment": "viewport" + } + }); + assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'viewport'); + assert.equal(layer.getLayoutValue('text-pitch-alignment'), 'viewport'); + assert.end(); + }); + assert.test('text-pitch-alignment respected when set', function(assert) { + var layer = StyleLayer.create({ + "type": "symbol", + "layout": { + "text-rotation-alignment": "viewport", + "text-pitch-alignment": "map" + } + }); + assert.equal(layer.getLayoutValue('text-rotation-alignment'), 'viewport'); + assert.equal(layer.getLayoutValue('text-pitch-alignment'), 'map'); + assert.end(); + }); + assert.end(); +}); + function createAnimationLoop() { return { set: function() {}, diff --git a/test/js/symbol/quads.test.js b/test/js/symbol/quads.test.js index 1831f617cfa..32df909791d 100644 --- a/test/js/symbol/quads.test.js +++ b/test/js/symbol/quads.test.js @@ -29,7 +29,8 @@ test('getIconQuads', function(t) { bl: { x: -8, y: 5 }, br: { x: 7, y: 5 }, tex: { w: 15, h: 11 }, - angle: 0, + anchorAngle: 0, + glyphAngle: 0, minScale: 0.5, maxScale: Infinity } ]); t.end(); @@ -45,7 +46,8 @@ test('getIconQuads', function(t) { bl: { x: -8, y: 5 }, br: { x: 7, y: 5 }, tex: { w: 15, h: 11 }, - angle: 0, + anchorAngle: 0, + glyphAngle: 0, minScale: 0.5, maxScale: Infinity }]); t.end();