From 581dd0f81f2e5e2b7a91a8bd1b57d22f55daeda1 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Wed, 21 Jun 2017 15:34:55 -0700 Subject: [PATCH] Fix issue #4860: apply map rotation to unpitched point labels. --- src/render/draw_symbol.js | 11 ++++- src/shaders/symbol_icon.vertex.glsl | 17 +++++++- src/shaders/symbol_sdf.vertex.glsl | 19 +++++++- .../mapbox-gl-js#4860/expected.png | Bin 0 -> 1767 bytes .../regressions/mapbox-gl-js#4860/style.json | 41 ++++++++++++++++++ 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 test/integration/render-tests/regressions/mapbox-gl-js#4860/expected.png create mode 100644 test/integration/render-tests/regressions/mapbox-gl-js#4860/style.json diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 6e3831c7184..b76558896da 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -68,6 +68,10 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate const rotateWithMap = rotationAlignment === 'map'; const pitchWithMap = pitchAlignment === 'map'; const alongLine = rotateWithMap && layer.layout['symbol-placement'] === 'line'; + // Line label rotation happens in `updateLineLabels` + // Pitched point labels are automatically rotated by the labelPlaneMatrix projection + // Unpitched point labels need to have their rotation applied after projection + const rotateInShader = rotateWithMap && !pitchWithMap && !alongLine; const depthOn = pitchWithMap; @@ -96,7 +100,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate program = painter.useProgram(isSDF ? 'symbolSDF' : 'symbolIcon', programConfiguration); programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); - setSymbolDrawState(program, painter, layer, coord.z, isText, isSDF, rotateWithMap, pitchWithMap, bucket.fontstack, bucket.iconsNeedLinear, sizeData); + setSymbolDrawState(program, painter, layer, coord.z, isText, isSDF, rotateInShader, pitchWithMap, bucket.fontstack, bucket.iconsNeedLinear, sizeData); } painter.enableTileClippingMask(coord); @@ -125,7 +129,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate if (!depthOn) gl.enable(gl.DEPTH_TEST); } -function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, rotateWithMap, pitchWithMap, fontstack, iconsNeedLinear, sizeData) { +function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, rotateInShader, pitchWithMap, fontstack, iconsNeedLinear, sizeData) { const gl = painter.gl; const tr = painter.transform; @@ -170,6 +174,9 @@ function setSymbolDrawState(program, painter, layer, tileZoom, isText, isSDF, ro const size = symbolSize.evaluateSizeForZoom(sizeData, tr, layer, isText); if (size.uSizeT !== undefined) gl.uniform1f(program.u_size_t, size.uSizeT); if (size.uSize !== undefined) gl.uniform1f(program.u_size, size.uSize); + + gl.uniform1f(program.u_aspect_ratio, tr.width / tr.height); + gl.uniform1i(program.u_rotate_symbol, rotateInShader); } function drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap) { diff --git a/src/shaders/symbol_icon.vertex.glsl b/src/shaders/symbol_icon.vertex.glsl index 4d59bb64e8b..c49c42201bc 100644 --- a/src/shaders/symbol_icon.vertex.glsl +++ b/src/shaders/symbol_icon.vertex.glsl @@ -10,6 +10,8 @@ uniform highp float u_size_t; // used to interpolate between zoom stops when siz uniform highp float u_size; // used when size is both zoom and feature constant uniform highp float u_camera_to_center_distance; uniform highp float u_pitch; +uniform bool u_rotate_symbol; +uniform highp float u_aspect_ratio; uniform highp float u_collision_y_stretch; #pragma mapbox: define lowp float opacity @@ -62,8 +64,19 @@ void main() { float fontScale = u_is_text ? size / 24.0 : size; - highp float angle_sin = sin(segment_angle); - highp float angle_cos = cos(segment_angle); + highp float symbol_rotation = 0.0; + if (u_rotate_symbol) { + // See comments in symbol_sdf.vertex + vec4 offsetProjectedPoint = u_matrix * vec4(a_pos + vec2(1, 0), 0, 1); + + vec2 a = projectedPoint.xy / projectedPoint.w; + vec2 b = offsetProjectedPoint.xy / offsetProjectedPoint.w; + + symbol_rotation = atan((b.y - a.y) / u_aspect_ratio, b.x - a.x); + } + + highp float angle_sin = sin(segment_angle + symbol_rotation); + highp float angle_cos = cos(segment_angle + symbol_rotation); mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0); diff --git a/src/shaders/symbol_sdf.vertex.glsl b/src/shaders/symbol_sdf.vertex.glsl index 1a6308dfac4..bb2604cf1b9 100644 --- a/src/shaders/symbol_sdf.vertex.glsl +++ b/src/shaders/symbol_sdf.vertex.glsl @@ -29,6 +29,8 @@ uniform mat4 u_gl_coord_matrix; uniform bool u_is_text; uniform bool u_pitch_with_map; uniform highp float u_pitch; +uniform bool u_rotate_symbol; +uniform highp float u_aspect_ratio; uniform highp float u_camera_to_center_distance; uniform highp float u_collision_y_stretch; @@ -82,8 +84,21 @@ void main() { float fontScale = u_is_text ? size / 24.0 : size; - highp float angle_sin = sin(segment_angle); - highp float angle_cos = cos(segment_angle); + highp float symbol_rotation = 0.0; + if (u_rotate_symbol) { + // Point labels with 'rotation-alignment: map' are horizontal with respect to tile units + // To figure out that angle in projected space, we draw a short horizontal line in tile + // space, project it, and measure its angle in projected space. + vec4 offsetProjectedPoint = u_matrix * vec4(a_pos + vec2(1, 0), 0, 1); + + vec2 a = projectedPoint.xy / projectedPoint.w; + vec2 b = offsetProjectedPoint.xy / offsetProjectedPoint.w; + + symbol_rotation = atan((b.y - a.y) / u_aspect_ratio, b.x - a.x); + } + + highp float angle_sin = sin(segment_angle + symbol_rotation); + highp float angle_cos = cos(segment_angle + symbol_rotation); mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0); diff --git a/test/integration/render-tests/regressions/mapbox-gl-js#4860/expected.png b/test/integration/render-tests/regressions/mapbox-gl-js#4860/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f49923fc06b033f13005ac6ef8d6e14dbe721890 GIT binary patch literal 1767 zcmd5-|5MTj00l)5lr$xj_*R);rsWqbr6e`;8);hF@Z+?GpmL>^*bV72zaye3rIlJ* z>$=UZ$WRyJMI~Q^T&7!Q;dIKEC2MIy?$j>M=KhQB-rale?%n(GJ;C9W1SDcJ0t5mf zsfn>^AP{(6fTjlfO=O)Y$z;x#0QDWiLS>=#T%q1x10F0LiH^m^&8xI7edx zx8>WRkDz?-jCWt^$#sywa`K7ZQqR<6{R{I?)4FCbp_7-@iSpd^-?^Vm=%cQcC^e+B zA*U(kdH$6&l>gh8oU78y$49Sr5N{rPwe`g7#g3&&(ROO{SBzdF#UW_-DZ7Mcz~;o| z$4izRv6{!lmen01(V)#27O67NE~96uQ+Q6R@CtSKNk2%W2c|2Y_Z-uVM)xktAuK~; zp>ivCBaLxi*jbr7OU8Y#px8EbYrV)$V+y2h8#6Dh_sobkCi$AwKErsA3@568`z6!@ z>zzepW&w!d$R=rl4Hw{;KQb~j#rIwGUDvu z-VyD#j)s0FB0>TWH=UfWYb0@?&5!rtVa$e0?iLYu-x{Qg38yje9~Aj=0#OxZqIU>Q z?1(CG^!-vg5aD#QYa2f$y2g9QcQk zUAG{E@C|vo+VYTIjFuikP=5k=@Q&)|7RN~bCS72tCVK8JixEB}!m@*A>RJu6J)_S$ z0ioFxOTnMf?g1L|bUQ*v*FzJs4KCL?9ye;j>07&1wDB+#AE=8O`Ua>dDBAd;1#=>X z^zmpZ=7E|eaZZn)Gw{fcQ9~CjTL_Eh-CRU-_tx(cbr(kQCet?S( zTU{s<+$Y-ysY|Sqk%RW_FMQa0jtd-Qs4-Qz?R1GglkDJR!6y%kLoZGiBE}R`mNZ*H z;86V2DS}uTGhH{8OWXWwVVHm!QhC6Nx4kl*4IkB{v_FKBu=v^xlOIl4U^R0CY4koV zOEhxQzWvmwvWrbvbMi3Ia82Vqpf^m>$T|D=L!(39favWii9-*k88!h`pRG&^@quE2 za zx{M;W#~L)MWzUJ2{JSQLKd$MD3|-D&p?gRfSb`=*S2HewHUty6nn+#K9@b2_nvIN7L`EBj5?K>rS>d8u4KD!E zQ)&NgYk~}WZ~N1HKhxiS*)?zIUazs4^(#9(Q0@L@LUY&;V-1iZ0jz$OL-+v$D05O&Xh0^ zH#OC(K9QCDV6~RLGf+)rcoSM|PaH0QX{ydHjeD3Wnxk^6RV!fiEA$Fjp;IQ zKOOdS5jJIT*i-N0^+oWAZI_~Sn^*g?_izpp_;3uX!IIi0Dlz$=k8lne3=}I=&9ka# z&Ey3xiluju!Lf~nsxfwuG^s4r9#c}H_uHWuFT!(@mMbfTkNyN2y#XQ2${daizSvIp rA`B{I(3M1znUeW6Z*udO@-IkV>>=mA)L8ENbAhOFDY4x#^s4^=Fgg%f literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/regressions/mapbox-gl-js#4860/style.json b/test/integration/render-tests/regressions/mapbox-gl-js#4860/style.json new file mode 100644 index 00000000000..a7618abb5ac --- /dev/null +++ b/test/integration/render-tests/regressions/mapbox-gl-js#4860/style.json @@ -0,0 +1,41 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 256, + "height": 256 + } + }, + "bearing": 45, + "pitch": 45, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "point", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-field": "Test Text", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ] + } + } + ] +}