diff options
author | Ansis Brammanis <brammanis@gmail.com> | 2017-06-16 10:42:33 -0400 |
---|---|---|
committer | Ansis Brammanis <brammanis@gmail.com> | 2017-07-11 09:10:24 -0700 |
commit | e514138b691615be24f484986c40f486223df82a (patch) | |
tree | 4cab221d92f66feefd132818a700c47cc67ef245 /src/mbgl/layout | |
parent | 77734cfe1b9e77a0058fa3e0db79e3c20a264165 (diff) | |
download | qtlocation-mapboxgl-e514138b691615be24f484986c40f486223df82a.tar.gz |
[core] improve legibility of labels that follow lines
port https://github.com/mapbox/mapbox-gl-js/pull/4781
This improves legibility of labels that follow lines in pitched views.
The previous approach used the limited information in the shader to
calculate put the glyph in approximatelyright place. The new approach
does this more accurately by doing it on the cpu where we have access to
the entire line geometry.
Diffstat (limited to 'src/mbgl/layout')
-rw-r--r-- | src/mbgl/layout/symbol_instance.cpp | 23 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_instance.hpp | 9 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 98 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.hpp | 9 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_projection.cpp | 266 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_projection.hpp | 25 |
6 files changed, 361 insertions, 69 deletions
diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index ffb70c7ca2..02fb800df6 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -5,8 +5,8 @@ namespace mbgl { using namespace style; -SymbolInstance::SymbolInstance(Anchor& anchor, - const GeometryCoordinates& line, +SymbolInstance::SymbolInstance(Anchor& anchor_, + GeometryCoordinates line_, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, const SymbolLayoutProperties::Evaluated& layout, @@ -16,33 +16,38 @@ SymbolInstance::SymbolInstance(Anchor& anchor, const float textBoxScale, const float textPadding, const SymbolPlacementType textPlacement, + const std::array<float, 2> textOffset_, const float iconBoxScale, const float iconPadding, const SymbolPlacementType iconPlacement, + const std::array<float, 2> iconOffset_, const GlyphPositionMap& positions, const IndexedSubfeature& indexedFeature, const std::size_t featureIndex_) : - point(anchor.point), + anchor(anchor_), + line(line_), index(index_), hasText(shapedTextOrientations.first || shapedTextOrientations.second), hasIcon(shapedIcon), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), - iconCollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature), - featureIndex(featureIndex_) { + textCollisionFeature(line_, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), + iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature), + featureIndex(featureIndex_), + textOffset(textOffset_), + iconOffset(iconOffset_) { // Create the quads used for rendering the icon and glyphs. if (addToBuffers) { if (shapedIcon) { - iconQuad = getIconQuad(anchor, *shapedIcon, line, layout, layoutTextSize, iconPlacement, shapedTextOrientations.first); + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); } if (shapedTextOrientations.first) { - auto quads = getGlyphQuads(anchor, shapedTextOrientations.first, textBoxScale, line, layout, textPlacement, positions); + auto quads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); } if (shapedTextOrientations.second) { - auto quads = getGlyphQuads(anchor, shapedTextOrientations.second, textBoxScale, line, layout, textPlacement, positions); + auto quads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); } } diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index f199d929df..f1df416cd1 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -13,7 +13,7 @@ class IndexedSubfeature; class SymbolInstance { public: SymbolInstance(Anchor& anchor, - const GeometryCoordinates& line, + GeometryCoordinates line, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, const style::SymbolLayoutProperties::Evaluated&, @@ -23,14 +23,17 @@ public: const float textBoxScale, const float textPadding, style::SymbolPlacementType textPlacement, + const std::array<float, 2> textOffset, const float iconBoxScale, const float iconPadding, style::SymbolPlacementType iconPlacement, + const std::array<float, 2> iconOffset, const GlyphPositionMap&, const IndexedSubfeature&, const std::size_t featureIndex); - Point<float> point; + Anchor anchor; + GeometryCoordinates line; uint32_t index; bool hasText; bool hasIcon; @@ -40,6 +43,8 @@ public: CollisionFeature iconCollisionFeature; WritingModeType writingModes; std::size_t featureIndex; + std::array<float, 2> textOffset; + std::array<float, 2> iconOffset; }; } // namespace mbgl diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index a664957489..e308da618f 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -305,6 +305,8 @@ void SymbolLayout::addFeature(const std::size_t index, const float layoutTextSize = layout.evaluate<TextSize>(zoom + 1, feature); const float layoutIconSize = layout.evaluate<IconSize>(zoom + 1, feature); + const std::array<float, 2> textOffset = layout.evaluate<TextOffset>(zoom, feature); + const std::array<float, 2> iconOffset = layout.evaluate<IconOffset>(zoom, feature); // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. @@ -355,8 +357,8 @@ void SymbolLayout::addFeature(const std::size_t index, symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout.evaluate(zoom, feature), layoutTextSize, addToBuffers, symbolInstances.size(), - textBoxScale, textPadding, textPlacement, - iconBoxScale, iconPadding, iconPlacement, + textBoxScale, textPadding, textPlacement, textOffset, + iconBoxScale, iconPadding, iconPlacement, iconOffset, glyphPositionMap, indexedFeature, index); }; @@ -455,8 +457,8 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) const float cos = std::cos(collisionTile.config.angle); std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) { - const int32_t aRotated = sin * a.point.x + cos * a.point.y; - const int32_t bRotated = sin * b.point.x + cos * b.point.y; + const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y; + const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y; return aRotated != bRotated ? aRotated < bRotated : a.index > b.index; @@ -501,10 +503,21 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) const float placementZoom = util::max(util::log2(glyphScale) + zoom, 0.0f); collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.get<TextIgnorePlacement>()); if (glyphScale < collisionTile.maxScale) { + + const float labelAngle = std::fmod((symbolInstance.anchor.angle + collisionTile.config.angle) + 2 * M_PI, 2 * M_PI); + const bool inVerticalRange = ( + (labelAngle > M_PI * 1.0 / 4.0 && labelAngle <= M_PI * 3.0 / 4) || + (labelAngle > M_PI * 5.0 / 4.0 && labelAngle <= M_PI * 7.0 / 4)); + const bool useVerticalMode = symbolInstance.writingModes & WritingModeType::Vertical && inVerticalRange; + + const Range<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, + symbolInstance.textOffset, placementZoom, useVerticalMode, symbolInstance.line); + for (const auto& symbol : symbolInstance.glyphQuads) { addSymbol( - bucket->text, *bucket->textSizeBinder, symbol, feature, placementZoom, - keepUpright, textPlacement, collisionTile.config.angle, symbolInstance.writingModes, symbolInstance.point); + bucket->text, sizeData, symbol, placementZoom, + keepUpright, textPlacement, symbolInstance.anchor, bucket->text.placedSymbols.back()); } } } @@ -513,9 +526,12 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) const float placementZoom = util::max(util::log2(iconScale) + zoom, 0.0f); collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.get<IconIgnorePlacement>()); if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) { + const Range<float> sizeData = bucket->iconSizeBinder->getVertexSizeData(feature); + bucket->icon.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, + symbolInstance.iconOffset, placementZoom, false, symbolInstance.line); addSymbol( - bucket->icon, *bucket->iconSizeBinder, *symbolInstance.iconQuad, feature, placementZoom, - keepUpright, iconPlacement, collisionTile.config.angle, symbolInstance.writingModes, symbolInstance.point); + bucket->icon, sizeData, *symbolInstance.iconQuad, placementZoom, + keepUpright, iconPlacement, symbolInstance.anchor, bucket->icon.placedSymbols.back()); } } @@ -534,15 +550,13 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) template <typename Buffer> void SymbolLayout::addSymbol(Buffer& buffer, - SymbolSizeBinder& sizeBinder, + const Range<float> sizeData, const SymbolQuad& symbol, - const SymbolFeature& feature, const float placementZoom, const bool keepUpright, const style::SymbolPlacementType placement, - const float placementAngle, - const WritingModeType writingModes, - const Point<float> labelAnchor) { + const Anchor& labelAnchor, + PlacedSymbol& placedSymbol) { constexpr const uint16_t vertexLength = 4; const auto &tl = symbol.tl; @@ -551,30 +565,9 @@ void SymbolLayout::addSymbol(Buffer& buffer, const auto &br = symbol.br; const auto &tex = symbol.tex; - float minZoom = util::max(zoom + util::log2(symbol.minScale), placementZoom); - float maxZoom = util::min(zoom + util::log2(symbol.maxScale), util::MAX_ZOOM_F); - const auto &anchorPoint = symbol.anchorPoint; - - // drop incorrectly oriented glyphs - const float a = std::fmod(symbol.anchorAngle + placementAngle + M_PI, M_PI * 2); - if (writingModes & WritingModeType::Vertical) { - if (placement == style::SymbolPlacementType::Line && symbol.writingMode == WritingModeType::Vertical) { - if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= (M_PI * 5 / 4) || a > (M_PI * 7 / 4))) - return; - } else if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= (M_PI * 3 / 4) || a > (M_PI * 5 / 4))) - return; - } else if (keepUpright && placement == style::SymbolPlacementType::Line && - (a <= M_PI / 2 || a > M_PI * 3 / 2)) { - return; - } - - if (maxZoom <= minZoom) - return; - - // 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; + if (placement == style::SymbolPlacementType::Line && keepUpright) { + // drop incorrectly oriented glyphs + if ((symbol.writingMode == WritingModeType::Vertical) != placedSymbol.useVerticalMode) return; } if (buffer.segments.empty() || buffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { @@ -587,20 +580,17 @@ void SymbolLayout::addSymbol(Buffer& buffer, assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max()); uint16_t index = segment.vertexLength; - // Encode angle of glyph - uint8_t glyphAngle = std::round((symbol.glyphAngle / (M_PI * 2)) * 256); - // coordinates (2 triangles) - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, tl, labelAnchor, tex.x, tex.y, - minZoom, maxZoom, placementZoom, glyphAngle)); - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, tr, labelAnchor, tex.x + tex.w, tex.y, - minZoom, maxZoom, placementZoom, glyphAngle)); - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, bl, labelAnchor, tex.x, tex.y + tex.h, - minZoom, maxZoom, placementZoom, glyphAngle)); - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, br, labelAnchor, tex.x + tex.w, tex.y + tex.h, - minZoom, maxZoom, placementZoom, glyphAngle)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, tl, symbol.glyphOffset.y, tex.x, tex.y, sizeData)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, tr, symbol.glyphOffset.y, tex.x + tex.w, tex.y, sizeData)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, bl, symbol.glyphOffset.y, tex.x, tex.y + tex.h, sizeData)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, br, symbol.glyphOffset.y, tex.x + tex.w, tex.y + tex.h, sizeData)); - sizeBinder.populateVertexVector(feature); + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0, placementZoom); + buffer.dynamicVertices.emplace_back(dynamicVertex); + buffer.dynamicVertices.emplace_back(dynamicVertex); + buffer.dynamicVertices.emplace_back(dynamicVertex); + buffer.dynamicVertices.emplace_back(dynamicVertex); // add the two triangles, referencing the four coordinates we just inserted. buffer.triangles.emplace_back(index + 0, index + 1, index + 2); @@ -608,6 +598,8 @@ void SymbolLayout::addSymbol(Buffer& buffer, segment.vertexLength += vertexLength; segment.indexLength += 6; + + placedSymbol.glyphOffsets.push_back(symbol.glyphOffset.x); } void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& bucket) { @@ -647,10 +639,10 @@ void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& auto& segment = collisionBox.segments.back(); uint16_t index = segment.vertexLength; - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, tl, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, tr, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, br, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, bl, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl, maxZoom, placementZoom)); collisionBox.lines.emplace_back(index + 0, index + 1); collisionBox.lines.emplace_back(index + 1, index + 2); diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 5dc0f3eb76..90f5b3c91d 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -20,6 +20,7 @@ class CollisionTile; class SymbolBucket; class Anchor; class RenderLayer; +class PlacedSymbol; namespace style { class Filter; @@ -58,15 +59,13 @@ private: // Adds placed items to the buffer. template <typename Buffer> void addSymbol(Buffer&, - SymbolSizeBinder& sizeBinder, + const Range<float> sizeData, const SymbolQuad&, - const SymbolFeature& feature, float scale, const bool keepUpright, const style::SymbolPlacementType, - const float placementAngle, - WritingModeType writingModes, - const Point<float> labelAnchor); + const Anchor& labelAnchor, + PlacedSymbol& placedSymbol); // Stores the layer so that we can hold on to GeometryTileFeature instances in SymbolFeature, // which may reference data from this object. diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp new file mode 100644 index 0000000000..99555f7997 --- /dev/null +++ b/src/mbgl/layout/symbol_projection.cpp @@ -0,0 +1,266 @@ +#include <mbgl/layout/symbol_projection.hpp> +#include <mbgl/map/transform_state.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/buckets/symbol_bucket.hpp> +#include <mbgl/renderer/layers/render_symbol_layer.hpp> +#include <mbgl/renderer/frame_history.hpp> +#include <mbgl/util/optional.hpp> +#include <mbgl/util/math.hpp> + +namespace mbgl { + + /* + * # Overview of coordinate spaces + * + * ## Tile coordinate spaces + * Each label has an anchor. Some labels have corresponding line geometries. + * The points for both anchors and lines are stored in tile units. Each tile has it's own + * coordinate space going from (0, 0) at the top left to (EXTENT, EXTENT) at the bottom right. + * + * ## GL coordinate space + * At the end of everything, the vertex shader needs to produce a position in GL coordinate space, + * which is (-1, 1) at the top left and (1, -1) in the bottom right. + * + * ## Map pixel coordinate spaces + * Each tile has a pixel coordinate space. It's just the tile units scaled so that one unit is + * whatever counts as 1 pixel at the current zoom. + * This space is used for pitch-alignment=map, rotation-alignment=map + * + * ## Rotated map pixel coordinate spaces + * Like the above, but rotated so axis of the space are aligned with the viewport instead of the tile. + * This space is used for pitch-alignment=map, rotation-alignment=viewport + * + * ## Viewport pixel coordinate space + * (0, 0) is at the top left of the canvas and (pixelWidth, pixelHeight) is at the bottom right corner + * of the canvas. This space is used for pitch-alignment=viewport + * + * + * # Vertex projection + * It goes roughly like this: + * 1. project the anchor and line from tile units into the correct label coordinate space + * - map pixel space pitch-alignment=map rotation-alignment=map + * - rotated map pixel space pitch-alignment=map rotation-alignment=viewport + * - viewport pixel space pitch-alignment=viewport rotation-alignment=* + * 2. if the label follows a line, find the point along the line that is the correct distance from the anchor. + * 3. add the glyph's corner offset to the point from step 3 + * 4. convert from the label coordinate space to gl coordinates + * + * For horizontal labels we want to do step 1 in the shader for performance reasons (no cpu work). + * This is what `u_label_plane_matrix` is used for. + * For labels aligned with lines we have to steps 1 and 2 on the cpu since we need access to the line geometry. + * This is what `updateLineLabels(...)` does. + * Since the conversion is handled on the cpu we just set `u_label_plane_matrix` to an identity matrix. + * + * Steps 3 and 4 are done in the shaders for all labels. + */ + + /* + * Returns a matrix for converting from tile units to the correct label coordinate space. + */ + mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits) { + mat4 m; + matrix::identity(m); + if (pitchWithMap) { + matrix::scale(m, m, 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1); + if (!rotateWithMap) { + matrix::rotate_z(m, m, state.getAngle()); + } + } else { + matrix::scale(m, m, state.getSize().width / 2.0, -(state.getSize().height / 2.0), 1.0); + matrix::translate(m, m, 1, -1, 0); + matrix::multiply(m, m, posMatrix); + } + return m; + } + + /* + * Returns a matrix for converting from the correct label coordinate space to gl coords. + */ + mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits) { + mat4 m; + matrix::identity(m); + if (pitchWithMap) { + matrix::multiply(m, m, posMatrix); + matrix::scale(m, m, pixelsToTileUnits, pixelsToTileUnits, 1); + if (!rotateWithMap) { + matrix::rotate_z(m, m, -state.getAngle()); + } + } else { + matrix::scale(m, m, 1, -1, 1); + matrix::translate(m, m, -1, -1, 0); + matrix::scale(m, m, 2.0 / state.getSize().width, 2.0 / state.getSize().height, 1.0); + } + return m; + } + + + Point<float> project(const Point<float>& point, const mat4& matrix) { + vec4 pos = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(pos, pos, matrix); + return { static_cast<float>(pos[0] / pos[3]), static_cast<float>(pos[1] / pos[3]) }; + } + + float evaluateSizeForFeature(const ZoomEvaluatedSize& zoomEvaluatedSize, const PlacedSymbol& placedSymbol) { + if (zoomEvaluatedSize.isFeatureConstant) { + return zoomEvaluatedSize.size; + } else { + if (zoomEvaluatedSize.isZoomConstant) { + return placedSymbol.lowerSize; + } else { + return placedSymbol.lowerSize + zoomEvaluatedSize.sizeT * (placedSymbol.upperSize - placedSymbol.lowerSize); + } + } + } + + bool isVisible(const vec4& anchorPos, const float placementZoom, const std::array<double, 2>& clippingBuffer, const FrameHistory& frameHistory) { + const float x = anchorPos[0] / anchorPos[3]; + const float y = anchorPos[1] / anchorPos[3]; + const bool inPaddedViewport = ( + x >= -clippingBuffer[0] && + x <= clippingBuffer[0] && + y >= -clippingBuffer[1] && + y <= clippingBuffer[1]); + return inPaddedViewport && frameHistory.isVisible(placementZoom); + } + + void addDynamicAttributes(const Point<float>& anchorPoint, const float angle, const float placementZoom, + gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) { + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle, placementZoom); + dynamicVertexArray.emplace_back(dynamicVertex); + dynamicVertexArray.emplace_back(dynamicVertex); + dynamicVertexArray.emplace_back(dynamicVertex); + dynamicVertexArray.emplace_back(dynamicVertex); + } + + void hideGlyphs(size_t numGlyphs, gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) { + const Point<float> offscreenPoint = { -INFINITY, -INFINITY }; + for (size_t i = 0; i < numGlyphs; i++) { + addDynamicAttributes(offscreenPoint, 0, 25, dynamicVertexArray); + } + } + + struct PlacedGlyph { + PlacedGlyph(Point<float> point_, float angle_) : point(point_), angle(angle_) {} + Point<float> point; + float angle; + }; + + optional<PlacedGlyph> placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip, + Point<float> anchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const mat4& labelPlaneMatrix) { + + const float combinedOffsetX = flip ? + offsetX - lineOffsetX : + offsetX + lineOffsetX; + + int16_t dir = combinedOffsetX > 0 ? 1 : -1; + + float angle = 0.0; + if (flip) { + // The label needs to be flipped to keep text upright. + // Iterate in the reverse direction. + dir *= -1; + angle = M_PI; + } + + if (dir < 0) angle += M_PI; + + int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1; + + Point<float> current = anchorPoint; + Point<float> prev = anchorPoint; + float distanceToPrev = 0.0; + float currentSegmentDistance = 0.0; + const float absOffsetX = std::abs(combinedOffsetX); + + while (distanceToPrev + currentSegmentDistance <= absOffsetX) { + currentIndex += dir; + + // offset does not fit on the projected line + if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) return {}; + + prev = current; + current = project(convertPoint<float>(line.at(currentIndex)), labelPlaneMatrix); + + distanceToPrev += currentSegmentDistance; + currentSegmentDistance = util::dist<float>(prev, current); + } + + // The point is on the current segment. Interpolate to find it. + const float segmentInterpolationT = (absOffsetX - distanceToPrev) / currentSegmentDistance; + const Point<float> prevToCurrent = current - prev; + Point<float> p = (prevToCurrent * segmentInterpolationT) + prev; + + // offset the point from the line to text-offset and icon-offset + p += util::perp(prevToCurrent) * static_cast<float>(lineOffsetY * dir / util::mag(prevToCurrent)); + + const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x); + + return {{ p, segmentAngle }}; + } + + void placeGlyphsAlongLine(const PlacedSymbol& symbol, const float fontSize, const bool flip, const mat4& labelPlaneMatrix, + gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) { + const float fontScale = fontSize / 24.0; + const float lineOffsetX = symbol.lineOffset[0] * fontSize; + const float lineOffsetY = symbol.lineOffset[1] * fontSize; + + const Point<float> anchorPoint = project(symbol.anchorPoint, labelPlaneMatrix); + + std::vector<PlacedGlyph> placedGlyphs; + for (auto glyphOffsetX : symbol.glyphOffsets) { + auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); + if (placedGlyph) { + placedGlyphs.push_back(*placedGlyph); + } else { + hideGlyphs(symbol.glyphOffsets.size(), dynamicVertexArray); + return; + } + } + + for (auto& placedGlyph : placedGlyphs) { + addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray); + } + } + + void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray, const std::vector<PlacedSymbol>& placedSymbols, + const mat4& posMatrix, const style::SymbolPropertyValues& values, + const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state, const FrameHistory& frameHistory) { + + const ZoomEvaluatedSize partiallyEvaluatedSize = sizeBinder.evaluateForZoom(state.getZoom()); + + const std::array<double, 2> clippingBuffer = {{ 256.0 / state.getSize().width * 2.0 + 1.0, 256.0 / state.getSize().height * 2.0 + 1.0 }}; + + const mat4 labelPlaneMatrix = getLabelPlaneMatrix(posMatrix, values.pitchAlignment == style::AlignmentType::Map, + values.rotationAlignment == style::AlignmentType::Map, state, tile.id.pixelsToTileUnits(1, state.getZoom())); + + dynamicVertexArray.clear(); + + for (auto& placedSymbol : placedSymbols) { + vec4 anchorPos = {{ placedSymbol.anchorPoint.x, placedSymbol.anchorPoint.y, 0, 1 }}; + matrix::transformMat4(anchorPos, anchorPos, posMatrix); + + // Don't bother calculating the correct point for invisible labels. + if (!isVisible(anchorPos, placedSymbol.placementZoom, clippingBuffer, frameHistory)) { + hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); + continue; + } + + bool flip = false; + if (values.keepUpright) { + const Point<float> a = project(convertPoint<float>(placedSymbol.line.at(placedSymbol.segment)), posMatrix); + const Point<float> b = project(convertPoint<float>(placedSymbol.line.at(placedSymbol.segment + 1)), posMatrix); + flip = placedSymbol.useVerticalMode ? b.y > a.y : b.x < a.x; + } + + const float cameraToAnchorDistance = anchorPos[3]; + const float perspectiveRatio = 1 + 0.5 * ((cameraToAnchorDistance / state.getCameraToCenterDistance()) - 1.0); + + const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol); + const float pitchScaledFontSize = values.pitchAlignment == style::AlignmentType::Map ? + fontSize * perspectiveRatio : + fontSize / perspectiveRatio; + + placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, flip, labelPlaneMatrix, dynamicVertexArray); + } + } +} // end namespace mbgl diff --git a/src/mbgl/layout/symbol_projection.hpp b/src/mbgl/layout/symbol_projection.hpp new file mode 100644 index 0000000000..2652fe7ace --- /dev/null +++ b/src/mbgl/layout/symbol_projection.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include <mbgl/util/mat4.hpp> +#include <mbgl/gl/vertex_buffer.hpp> +#include <mbgl/programs/symbol_program.hpp> + +namespace mbgl { + + class TransformState; + class RenderTile; + class FrameHistory; + class SymbolSizeBinder; + class PlacedSymbol; + namespace style { + class SymbolPropertyValues; + } // end namespace style + + mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); + mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); + + void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>&, const std::vector<PlacedSymbol>&, + const mat4& posMatrix, const style::SymbolPropertyValues&, + const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&, const FrameHistory& frameHistory); + +} // end namespace mbgl |