diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-08-04 18:24:22 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-08-04 18:24:22 +0200 |
commit | 36181b0922bf4b5bed5b5ca68679338d22dfcf31 (patch) | |
tree | bb4d78934e41b59eb80842414908846b2789dc4c | |
parent | 9dd50a29e1e8b975e09172b19b303063a96ba20f (diff) | |
download | qtlocation-mapboxgl-36181b0922bf4b5bed5b5ca68679338d22dfcf31.tar.gz |
update symbol placement to be more like js
-rw-r--r-- | include/mbgl/geometry/interpolate.hpp | 2 | ||||
-rw-r--r-- | include/mbgl/map/tile_parser.hpp | 4 | ||||
-rw-r--r-- | include/mbgl/map/vector_tile.hpp | 2 | ||||
-rw-r--r-- | include/mbgl/renderer/symbol_bucket.hpp | 38 | ||||
-rw-r--r-- | include/mbgl/style/style_bucket.hpp | 1 | ||||
-rw-r--r-- | include/mbgl/text/collision.hpp | 17 | ||||
-rw-r--r-- | include/mbgl/text/glyph.hpp | 9 | ||||
-rw-r--r-- | include/mbgl/text/placement.hpp | 32 | ||||
-rw-r--r-- | include/mbgl/text/types.hpp | 29 | ||||
-rw-r--r-- | src/geometry/interpolate.cpp | 27 | ||||
-rw-r--r-- | src/map/tile_parser.cpp | 4 | ||||
-rw-r--r-- | src/map/vector_tile_data.cpp | 1 | ||||
-rw-r--r-- | src/renderer/painter_symbol.cpp | 50 | ||||
-rw-r--r-- | src/renderer/symbol_bucket.cpp | 403 | ||||
-rw-r--r-- | src/style/style_parser.cpp | 1 | ||||
-rw-r--r-- | src/text/collision.cpp | 184 | ||||
-rw-r--r-- | src/text/glyph_store.cpp | 6 | ||||
-rw-r--r-- | src/text/placement.cpp | 335 |
18 files changed, 574 insertions, 571 deletions
diff --git a/include/mbgl/geometry/interpolate.hpp b/include/mbgl/geometry/interpolate.hpp index cebbcc7028..998cba31cd 100644 --- a/include/mbgl/geometry/interpolate.hpp +++ b/include/mbgl/geometry/interpolate.hpp @@ -7,7 +7,7 @@ namespace mbgl { Anchors interpolate(const std::vector<Coordinate> &vertices, float spacing, - float minScale = 0.0f, int start = 0); + float minScale, float maxScale, float tilePixelRatio, int start = 0); } #endif diff --git a/include/mbgl/map/tile_parser.hpp b/include/mbgl/map/tile_parser.hpp index b407502434..9aa0f4dc66 100644 --- a/include/mbgl/map/tile_parser.hpp +++ b/include/mbgl/map/tile_parser.hpp @@ -4,7 +4,7 @@ #include <mbgl/map/vector_tile.hpp> #include <mbgl/style/filter_expression.hpp> #include <mbgl/text/glyph.hpp> -#include <mbgl/text/placement.hpp> +#include <mbgl/text/collision.hpp> #include <cstdint> #include <iosfwd> @@ -61,7 +61,7 @@ private: std::shared_ptr<SpriteAtlas> spriteAtlas; std::shared_ptr<Sprite> sprite; - Placement placement; + Collision collision; }; } diff --git a/include/mbgl/map/vector_tile.hpp b/include/mbgl/map/vector_tile.hpp index 96123c154b..1bbe645307 100644 --- a/include/mbgl/map/vector_tile.hpp +++ b/include/mbgl/map/vector_tile.hpp @@ -86,7 +86,7 @@ private: const FilterExpression& filterExpression; }; -std::ostream& operator<<(std::ostream&, const GlyphPlacement& placement); +std::ostream& operator<<(std::ostream&, const PositionedGlyph& placement); class VectorTileLayer { public: diff --git a/include/mbgl/renderer/symbol_bucket.hpp b/include/mbgl/renderer/symbol_bucket.hpp index c2a8b14f38..c003230cae 100644 --- a/include/mbgl/renderer/symbol_bucket.hpp +++ b/include/mbgl/renderer/symbol_bucket.hpp @@ -21,7 +21,7 @@ class Style; class TextShader; class IconShader; class DotShader; -class Placement; +class Collision; class SpriteAtlas; class Sprite; class GlyphAtlas; @@ -35,13 +35,23 @@ public: std::string sprite; }; + +class Symbol { +public: + vec2<float> tl, tr, bl, br; + Rect<uint16_t> tex; + float angle; + float minScale = 0.0f; + float maxScale = std::numeric_limits<float>::infinity(); + CollisionAnchor anchor; +}; + +typedef std::vector<Symbol> Symbols; + + class SymbolBucket : public Bucket { public: - SymbolBucket( - TextVertexBuffer &textVertexBuffer, - IconVertexBuffer& iconVertexBuffer, - TriangleElementsBuffer &triangleElementsBuffer, - const StyleBucketSymbol &properties, Placement &placement); + SymbolBucket(const StyleBucketSymbol &properties, Collision &collision); virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id); virtual bool hasData() const; @@ -55,29 +65,31 @@ public: void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, PlacementRange placementRange, float zoom); - void drawGlyphs(TextShader &shader); void drawIcons(IconShader& shader); - void drawIcons(DotShader& shader); private: std::vector<SymbolFeature> processFeatures(const VectorTileLayer &layer, const FilterExpression &filter, GlyphStore &glyphStore, const Sprite &sprite); - void addFeature(const pbf &geom_pbf, const Shaping &shaping, const Rect<uint16_t> &image); - void addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const Rect<uint16_t> &image); + void addFeature(const pbf &geom_pbf, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image); + void addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image); + + // Adds placed items to the buffer. + template <typename Buffer> + void addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, PlacementRange placementRange); - void addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string, + // Adds glyphs to the glyph atlas so that they have a left/top/width/height coordinates associated to them that we can use for writing to a buffer. + void addGlyphsToAtlas(uint64_t tileid, const std::string stackname, const std::u32string &string, const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face); - void addFeature(const pbf &geom_pbf, const GlyphPositions &face, const Shaping &shaping); public: const StyleBucketSymbol &properties; private: - Placement &placement; + Collision &collision; struct { TextVertexBuffer vertices; diff --git a/include/mbgl/style/style_bucket.hpp b/include/mbgl/style/style_bucket.hpp index af76650e56..a868659ad7 100644 --- a/include/mbgl/style/style_bucket.hpp +++ b/include/mbgl/style/style_bucket.hpp @@ -48,6 +48,7 @@ public: RotationAlignmentType rotation_alignment = RotationAlignmentType::Viewport; float max_size = 1.0f; std::string image; + float rotate = 0.0f; float padding = 2.0f; bool keep_upright = false; vec2<float> offset = {0, 0}; diff --git a/include/mbgl/text/collision.hpp b/include/mbgl/text/collision.hpp index 8911b3b4c6..87ebdb279e 100644 --- a/include/mbgl/text/collision.hpp +++ b/include/mbgl/text/collision.hpp @@ -11,20 +11,17 @@ public: Collision(float zoom, float tileExtent, float tileSize, float placementDepth = 1); ~Collision(); - PlacementProperty place(const GlyphBoxes &boxes, const CollisionAnchor &anchor, - float minPlacementScale, float maxPlacementScale, float padding, - bool horizontal, bool allowOverlap, bool ignorePlacement); - float getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale, - float maxPlacementScale, float pad); - PlacementRange getPlacementRange(const GlyphBoxes &glyphs, - float placementScale, bool horizontal); - void insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, - float placementScale, const PlacementRange &placementRange, - bool horizontal, float padding); + float getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale); + PlacementRange getPlacementRange(const GlyphBoxes &glyphs, float placementScale, + bool horizontal); + void insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, float placementScale, + const PlacementRange &placementRange, bool horizontal); private: void *hTree; void *cTree; + +public: const float tilePixelRatio; const float zoom; const float maxPlacementScale; diff --git a/include/mbgl/text/glyph.hpp b/include/mbgl/text/glyph.hpp index 899c8fffee..4ad2e93679 100644 --- a/include/mbgl/text/glyph.hpp +++ b/include/mbgl/text/glyph.hpp @@ -44,18 +44,17 @@ struct Glyph { typedef std::map<uint32_t, Glyph> GlyphPositions; -class GlyphPlacement { +class PositionedGlyph { public: - inline explicit GlyphPlacement(uint32_t face, uint32_t glyph, uint32_t x, uint32_t y) - : face(face), glyph(glyph), x(x), y(y) {} + inline explicit PositionedGlyph(uint32_t glyph, uint32_t x, uint32_t y) + : glyph(glyph), x(x), y(y) {} - uint32_t face = 0; uint32_t glyph = 0; int32_t x = 0; int32_t y = 0; }; -typedef std::vector<GlyphPlacement> Shaping; +typedef std::vector<PositionedGlyph> Shaping; } #endif diff --git a/include/mbgl/text/placement.hpp b/include/mbgl/text/placement.hpp index 7700f32f0a..28eb8d5317 100644 --- a/include/mbgl/text/placement.hpp +++ b/include/mbgl/text/placement.hpp @@ -1,36 +1,30 @@ #ifndef MBGL_TEXT_PLACEMENT #define MBGL_TEXT_PLACEMENT -#include <mbgl/text/collision.hpp> -#include <mbgl/geometry/geometry.hpp> -#include <mbgl/util/vec.hpp> +#include <mbgl/text/types.hpp> #include <mbgl/text/glyph.hpp> -#include <vector> + +#include <mbgl/util/vec.hpp> namespace mbgl { -class SymbolBucket; +struct Anchor; class StyleBucketSymbol; class Placement { public: - Placement(int8_t zoom, float placementDepth); + static Placement getIcon(Anchor &anchor, const Rect<uint16_t> &image, float iconBoxScale, + const std::vector<Coordinate> &line, const StyleBucketSymbol &props); - void addFeature(SymbolBucket &bucket, const std::vector<Coordinate> &line, - const StyleBucketSymbol &info, - const GlyphPositions &face, - const Shaping &shaping); + static Placement getGlyphs(Anchor &anchor, const vec2<float> &origin, const Shaping &shaping, + const GlyphPositions &face, float boxScale, bool horizontal, + const std::vector<Coordinate> &line, const StyleBucketSymbol &props); -private: - const int8_t zoom; - Collision collision; - const float zOffset; - const float maxPlacementScale; + static const float globalMinScale; -public: - static const int tileExtent; - static const int glyphSize; - static const float minScale; + GlyphBoxes boxes; + PlacedGlyphs shapes; + float minScale; }; } diff --git a/include/mbgl/text/types.hpp b/include/mbgl/text/types.hpp index 027df6d637..e2539bff62 100644 --- a/include/mbgl/text/types.hpp +++ b/include/mbgl/text/types.hpp @@ -37,16 +37,23 @@ struct CollisionRect { // These are the glyph boxes that we want to have placed. struct GlyphBox { explicit GlyphBox() {} - explicit GlyphBox(const CollisionRect &box, float minScale) : box(box), minScale(minScale) {} - explicit GlyphBox(const CollisionRect &box, float minScale, float maxScale, - const CollisionAnchor &anchor) - : anchor(anchor), box(box), minScale(minScale), maxScale(maxScale) {} + explicit GlyphBox(const CollisionRect &box, + const CollisionAnchor &anchor, + float minScale, + float maxScale, + float padding) + : box(box), anchor(anchor), minScale(minScale), maxScale(maxScale), padding(padding) {} + explicit GlyphBox(const CollisionRect &box, + float minScale, + float padding) + : box(box), minScale(minScale), padding(padding) {} - CollisionAnchor anchor; CollisionRect box; - boost::optional<CollisionRect> hBox; + CollisionAnchor anchor; float minScale = 0.0f; float maxScale = std::numeric_limits<float>::infinity(); + float padding = 0.0f; + boost::optional<CollisionRect> hBox; }; typedef std::vector<GlyphBox> GlyphBoxes; @@ -56,19 +63,23 @@ typedef std::vector<GlyphBox> GlyphBoxes; struct PlacedGlyph { explicit PlacedGlyph(const vec2<float> &tl, const vec2<float> &tr, const vec2<float> &bl, const vec2<float> &br, - const Rect<uint16_t> &tex, float angle, const GlyphBox &glyphBox) + const Rect<uint16_t> &tex, float angle, const vec2<float> &anchor, + float minScale, float maxScale) : tl(tl), tr(tr), bl(bl), br(br), tex(tex), angle(angle), - glyphBox(glyphBox) {} + anchor(anchor), + minScale(minScale), + maxScale(maxScale) {} vec2<float> tl, tr, bl, br; Rect<uint16_t> tex; float angle; - GlyphBox glyphBox; + vec2<float> anchor; + float minScale, maxScale; }; typedef std::vector<PlacedGlyph> PlacedGlyphs; diff --git a/src/geometry/interpolate.cpp b/src/geometry/interpolate.cpp index d9cfdfa1db..af62bd921c 100644 --- a/src/geometry/interpolate.cpp +++ b/src/geometry/interpolate.cpp @@ -1,9 +1,30 @@ #include <mbgl/geometry/interpolate.hpp> +#include <mbgl/util/math.hpp> + +#include <cmath> + namespace mbgl { +const float minScale = 0.5f; +const std::array<std::vector<float>, 4> minScaleArrays = {{ + /*1:*/ { minScale }, + /*2:*/ { minScale, 2 }, + /*4:*/ { minScale, 4, 2, 4 }, + /*8:*/ { minScale, 8, 4, 8, 2, 8, 4, 8 } +}}; + + Anchors interpolate(const std::vector<Coordinate> &vertices, float spacing, - float minScale, int start) { + const float minScale, float maxScale, const float tilePixelRatio, + const int start) { + + maxScale = std::round(std::fmax(std::fmin(8.0f, maxScale / 2.0f), 1.0f)); + spacing *= tilePixelRatio / maxScale; + const size_t index = util::clamp<size_t>(std::floor(std::log(maxScale) / std::log(2)), 0, minScaleArrays.size() - 1); + const std::vector<float> &minScales = minScaleArrays[index]; + const size_t len = minScales.size(); + float distance = 0.0f; float markedDistance = 0.0f; int added = start; @@ -23,9 +44,7 @@ Anchors interpolate(const std::vector<Coordinate> &vertices, float spacing, float t = (markedDistance - distance) / segmentDist, x = util::interp(a.x, b.x, t), y = util::interp(a.y, b.y, t), - s = added % 8 == 0 ? minScale : added % 4 == 0 - ? 2 - : added % 2 == 0 ? 4 : 8; + s = minScales[added % len]; if (x >= 0 && x < 4096 && y >= 0 && y < 4096) { points.emplace_back(x, y, angle, s, i); diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 99ee676237..89ca4a41f3 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -43,7 +43,7 @@ TileParser::TileParser(const std::string &data, VectorTileData &tile, glyphStore(glyphStore), spriteAtlas(spriteAtlas), sprite(sprite), - placement(tile.id.z, tile.id.z >= tile.source.max_zoom ? tile.source.max_zoom - tile.id.z : 1) { + collision(tile.id.z, 4096, tile.source.tile_size, tile.id.z >= tile.source.max_zoom ? tile.source.max_zoom - tile.id.z : 1) { } void TileParser::parse() { @@ -154,7 +154,7 @@ std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& laye } std::unique_ptr<Bucket> TileParser::createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol) { - std::unique_ptr<SymbolBucket> bucket = std::make_unique<SymbolBucket>(tile.textVertexBuffer, tile.iconVertexBuffer, tile.triangleElementsBuffer, symbol, placement); + std::unique_ptr<SymbolBucket> bucket = std::make_unique<SymbolBucket>(symbol, collision); bucket->addFeatures(layer, filter, tile.id, *spriteAtlas, *sprite, *glyphAtlas, *glyphStore); return obsolete() ? nullptr : std::move(bucket); } diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index 99c521b8ab..cc4133e453 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -20,6 +20,7 @@ VectorTileData::~VectorTileData() { } void VectorTileData::beforeParse() { + parser = std::make_unique<TileParser>(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getGlyphStore(), map.getSpriteAtlas(), map.getSprite()); } diff --git a/src/renderer/painter_symbol.cpp b/src/renderer/painter_symbol.cpp index 8201e4b7f1..ee55cfe36a 100644 --- a/src/renderer/painter_symbol.cpp +++ b/src/renderer/painter_symbol.cpp @@ -172,31 +172,31 @@ void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> lay } if (bucket.hasIconData()) { - SpriteAtlas &spriteAtlas = *map.getSpriteAtlas(); - - useProgram(iconShader->program); - iconShader->setMatrix(matrix); - - // TODO: update - iconShader->setColor({{1, 1, 1, 1}}); - iconShader->setImage(0); - iconShader->setRatio(map.getState().getPixelRatio()); - iconShader->setDimension({{ - spriteAtlas.getTextureWidth(), spriteAtlas.getTextureHeight(), - }}); - - spriteAtlas.bind(map.getState().isChanging()); - - // TODO: remove hardcoded icon size. - const float iconSize = 12 * map.getState().getPixelRatio(); - iconShader->setSize(iconSize); -#ifndef GL_ES_VERSION_2_0 - glPointSize(iconSize); - glEnable(GL_POINT_SPRITE); -#endif - - glDepthRange(strata, 1.0f); - bucket.drawIcons(*iconShader); +// SpriteAtlas &spriteAtlas = *map.getSpriteAtlas(); +// +// useProgram(iconShader->program); +// iconShader->setMatrix(matrix); +// +// // TODO: update +// iconShader->setColor({{1, 1, 1, 1}}); +// iconShader->setImage(0); +// iconShader->setRatio(map.getState().getPixelRatio()); +// iconShader->setDimension({{ +// spriteAtlas.getTextureWidth(), spriteAtlas.getTextureHeight(), +// }}); +// +// spriteAtlas.bind(map.getState().isChanging()); +// +// // TODO: remove hardcoded icon size. +// const float iconSize = 12 * map.getState().getPixelRatio(); +// iconShader->setSize(iconSize); +//#ifndef GL_ES_VERSION_2_0 +// glPointSize(iconSize); +// glEnable(GL_POINT_SPRITE); +//#endif +// +// glDepthRange(strata, 1.0f); +// bucket.drawIcons(*iconShader); } } } diff --git a/src/renderer/symbol_bucket.cpp b/src/renderer/symbol_bucket.cpp index 03e5c91f26..f165515aff 100644 --- a/src/renderer/symbol_bucket.cpp +++ b/src/renderer/symbol_bucket.cpp @@ -4,60 +4,54 @@ #include <mbgl/geometry/glyph_atlas.hpp> #include <mbgl/geometry/sprite_atlas.hpp> #include <mbgl/geometry/geometry.hpp> +#include <mbgl/geometry/anchor.hpp> +#include <mbgl/geometry/interpolate.hpp> #include <mbgl/renderer/painter.hpp> #include <mbgl/text/glyph_store.hpp> #include <mbgl/text/placement.hpp> #include <mbgl/platform/log.hpp> +#include <mbgl/text/collision.hpp> #include <mbgl/map/sprite.hpp> #include <mbgl/util/utf.hpp> #include <mbgl/util/token.hpp> #include <mbgl/util/math.hpp> - namespace mbgl { -SymbolBucket::SymbolBucket(TextVertexBuffer &textVertexBuffer, IconVertexBuffer &iconVertexBuffer, - TriangleElementsBuffer &triangleElementsBuffer, - const StyleBucketSymbol &properties, Placement &placement) - : properties(properties), - placement(placement) - {} +SymbolBucket::SymbolBucket(const StyleBucketSymbol &properties, Collision &collision) + : properties(properties), collision(collision) {} void SymbolBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, - const Tile::ID &id) { - painter.renderSymbol(*this, layer_desc, id); + const Tile::ID &id) { + painter.renderSymbol(*this, layer_desc, id); } -bool SymbolBucket::hasData() const { - return hasTextData() || hasIconData(); -} +bool SymbolBucket::hasData() const { return hasTextData() || hasIconData(); } +bool SymbolBucket::hasTextData() const { return !text.groups.empty(); } -bool SymbolBucket::hasTextData() const { - return !text.groups.empty(); -} - -bool SymbolBucket::hasIconData() const { - return !icon.groups.empty() > 0; -} +bool SymbolBucket::hasIconData() const { return !icon.groups.empty() > 0; } -void SymbolBucket::addGlyph(uint64_t tileid, const std::string stackname, - const std::u32string &string, const FontStack &fontStack, - GlyphAtlas &glyphAtlas, GlyphPositions &face) { +void SymbolBucket::addGlyphsToAtlas(uint64_t tileid, const std::string stackname, + const std::u32string &string, const FontStack &fontStack, + GlyphAtlas &glyphAtlas, GlyphPositions &face) { const std::map<uint32_t, SDFGlyph> &sdfs = fontStack.getSDFs(); // Loop through all characters and add glyph to atlas, positions. for (uint32_t chr : string) { auto sdf_it = sdfs.find(chr); if (sdf_it != sdfs.end()) { - const SDFGlyph& sdf = sdf_it->second; + const SDFGlyph &sdf = sdf_it->second; const Rect<uint16_t> rect = glyphAtlas.addGlyph(tileid, stackname, sdf); face.emplace(chr, Glyph{rect, sdf.metrics}); } } } -std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer &layer, const FilterExpression &filter, GlyphStore &glyphStore, const Sprite &sprite) { +std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer &layer, + const FilterExpression &filter, + GlyphStore &glyphStore, + const Sprite &sprite) { const bool text = properties.text.field.size(); const bool icon = properties.icon.image.size(); @@ -74,14 +68,14 @@ std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer & FilteredVectorTileLayer filtered_layer(layer, filter); for (const pbf &feature_pbf : filtered_layer) { - const VectorTileFeature feature {feature_pbf, layer}; + const VectorTileFeature feature{feature_pbf, layer}; SymbolFeature ft; if (text) { std::string u8string = util::replaceTokens(properties.text.field, feature.properties); - auto& convert = std::use_facet<std::ctype<char>>(std::locale()); + auto &convert = std::use_facet<std::ctype<char>>(std::locale()); if (properties.text.transform == TextTransformType::Uppercase) { convert.toupper(&u8string[0], &u8string[0] + u8string.size()); } else if (properties.text.transform == TextTransformType::Lowercase) { @@ -114,33 +108,42 @@ std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer & return features; } - - - void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter, - const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, GlyphAtlas &glyphAtlas, - GlyphStore &glyphStore) { + const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, + GlyphAtlas &glyphAtlas, GlyphStore &glyphStore) { const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore, sprite); - float horizontalAlign = 0.5; - if (properties.text.horizontal_align == TextHorizontalAlignType::Right) horizontalAlign = 1; - else if (properties.text.horizontal_align == TextHorizontalAlignType::Left) horizontalAlign = 0; + if (properties.text.horizontal_align == TextHorizontalAlignType::Right) + horizontalAlign = 1; + else if (properties.text.horizontal_align == TextHorizontalAlignType::Left) + horizontalAlign = 0; float verticalAlign = 0.5; - if (properties.text.vertical_align == TextVerticalAlignType::Bottom) verticalAlign = 1; - else if (properties.text.vertical_align == TextVerticalAlignType::Top) verticalAlign = 0; + if (properties.text.vertical_align == TextVerticalAlignType::Bottom) + verticalAlign = 1; + else if (properties.text.vertical_align == TextVerticalAlignType::Top) + verticalAlign = 0; const FontStack &fontStack = glyphStore.getFontStack(properties.text.font); for (const SymbolFeature &feature : features) { Shaping shaping; Rect<uint16_t> image; + GlyphPositions face; // if feature has text, shape the text if (feature.label.length()) { - shaping = fontStack.getShaping(feature.label, properties.text.max_width, properties.text.line_height, horizontalAlign, verticalAlign, properties.text.letter_spacing); + shaping = fontStack.getShaping(feature.label, properties.text.max_width, + properties.text.line_height, horizontalAlign, + verticalAlign, properties.text.letter_spacing); + + // Add the glyphs we need for this label to the glyph atlas. + if (shaping.size()) { + addGlyphsToAtlas(id.to_uint64(), properties.text.font, feature.label, fontStack, + glyphAtlas, face); + } } // if feature has icon, get sprite atlas position @@ -150,12 +153,13 @@ void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpress // if either shaping or icon position is present, add the feature if (shaping.size() || image) { - addFeature(feature.geometry, shaping, image); + addFeature(feature.geometry, shaping, face, image); } } } -void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping, const Rect<uint16_t> &image) { +void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping, + const GlyphPositions &face, const Rect<uint16_t> &image) { // Decode all lines. std::vector<Coordinate> line; Geometry::command cmd; @@ -165,167 +169,155 @@ void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping, const Geometry geometry(geom); int32_t x, y; while ((cmd = geometry.next(x, y)) != Geometry::end) { - if (cmd == Geometry::move_to) { - if (!line.empty()) { - addFeature(line, shaping, image); - line.clear(); - } - } - line.emplace_back(x, y); + if (cmd == Geometry::move_to) { + if (!line.empty()) { + addFeature(line, shaping, face, image); + line.clear(); + } + } + line.emplace_back(x, y); } if (line.size()) { - addFeature(line, shaping, image); + addFeature(line, shaping, face, image); } } -void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const Rect<uint16_t> &image) { - const float minScale = 0.5f; - const float glyphSize = 24.0f; +bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; } +const PlacementRange fullRange{{2 * M_PI, 0}}; - const bool horizontalText = properties.text.rotation_alignment == RotationAlignmentType::Viewport; - const bool horizontalIcon = properties.icon.rotation_alignment == RotationAlignmentType::Viewport; - const float fontScale = properties.text.max_size / glyphSize; - const float textBoxScale = placement.collision.tilePixelRatio * fontScale, - iconBoxScale = collision.tilePixelRatio * info['icon-max-size'], - iconWithoutText = info['text-optional'] || !shaping, - textWithoutIcon = info['icon-optional'] || !image; -} +void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, + const GlyphPositions &face, const Rect<uint16_t> &image) { + assert(line.size()); + const float minScale = 0.5f; + const float glyphSize = 24.0f; -// -// GlyphPositions face; -// -// -// // Shape and place all labels. -// for (const std::pair<std::u32string, pbf> &label : labels) { -// -// // Shape labels. -// const Shaping shaping = fontStack.getShaping(label.first, properties.text.max_width, -// properties.text.line_height, horizontalAlign, -// verticalAlign, properties.text.letter_spacing); -// -// addGlyph(id.to_uint64(), properties.text.font, label.first, fontStack, glyphAtlas, face); -// -// // Place labels. -// addFeature(label.second, face, shaping); -// } -} - -void SymbolBucket::addFeature(const pbf &geom_pbf, const GlyphPositions &face, - const Shaping &shaping) { - // Decode all lines. - std::vector<Coordinate> line; - Geometry::command cmd; - - Coordinate coord; - pbf geom(geom_pbf); - Geometry geometry(geom); - int32_t x, y; - while ((cmd = geometry.next(x, y)) != Geometry::end) { - if (cmd == Geometry::move_to) { - if (!line.empty()) { - placement.addFeature(*this, line, properties, face, shaping); - line.clear(); - } - } - line.emplace_back(x, y); - } - if (line.size()) { - placement.addFeature(*this, line, properties, face, shaping); - } -} - + const bool horizontalText = + properties.text.rotation_alignment == RotationAlignmentType::Viewport; + const bool horizontalIcon = + properties.icon.rotation_alignment == RotationAlignmentType::Viewport; + const float fontScale = properties.text.max_size / glyphSize; + const float textBoxScale = collision.tilePixelRatio * fontScale; + const float iconBoxScale = collision.tilePixelRatio * properties.icon.max_size; + const bool iconWithoutText = properties.text.optional || !shaping.size(); + const bool textWithoutIcon = properties.icon.optional || !image; -class Symbol { -public: - vec2<float> tl, tr, bl, br; - Rect<uint16_t> tex; - float angle; - float minScale = 0.0f; - float maxScale = std::numeric_limits<float>::infinity(); - CollisionAnchor anchor; -}; + Anchors anchors; -typedef std::vector<Symbol> Symbols; + if (properties.placement == PlacementType::Line) { + // Line labels + anchors = interpolate(line, properties.min_distance, minScale, collision.maxPlacementScale, + collision.tilePixelRatio); -template <typename Buffer> -void SymbolBucket::addSymbols(Buffer &buffer, const Symbols &symbols, float placementZoom, PlacementRange placementRange, float zoom) { - placementZoom += zoom; + // Sort anchors by segment so that we can start placement with the + // anchors that can be shown at the lowest zoom levels. + std::sort(anchors.begin(), anchors.end(), byScale); - for (const Symbol &symbol : symbols) { - const auto &tl = symbol.tl; - const auto &tr = symbol.tr; - const auto &bl = symbol.bl; - const auto &br = symbol.br; - const auto &tex = symbol.tex; - const auto &angle = symbol.angle; - - float minZoom = util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), - placementZoom); - float maxZoom = - util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f); - const auto &glyphAnchor = symbol.anchor; - - if (maxZoom <= minZoom) - continue; + } else { + // Point labels + anchors = {Anchor{float(line[0].x), float(line[0].y), 0, minScale}}; + } - // 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; + // TODO: figure out correct ascender height. + const vec2<float> origin = {0, -17}; + + for (Anchor &anchor : anchors) { + + // Calculate the scales at which the text and icons can be first shown without overlap + Placement glyphPlacement; + Placement iconPlacement; + float glyphScale = 0; + float iconScale = 0; + + if (shaping.size()) { + glyphPlacement = Placement::getGlyphs(anchor, origin, shaping, face, textBoxScale, + horizontalText, line, properties); + glyphScale = + properties.text.allow_overlap + ? glyphPlacement.minScale + : collision.getPlacementScale(glyphPlacement.boxes, glyphPlacement.minScale); + if (!glyphScale && !iconWithoutText) + continue; } - const int glyph_vertex_length = 4; - - if (!triangleGroups.size() || - (triangleGroups.back().vertex_length + glyph_vertex_length > 65535)) { - // Move to a new group because the old one can't hold the geometry. - triangleGroups.emplace_back(); + if (image) { + iconPlacement = Placement::getIcon(anchor, image, iconBoxScale, line, properties); + iconScale = + properties.icon.allow_overlap + ? iconPlacement.minScale + : collision.getPlacementScale(iconPlacement.boxes, iconPlacement.minScale); + if (!iconScale && !textWithoutIcon) + continue; } - // We're generating triangle fans, so we always start with the first - // coordinate in this polygon. - triangle_group_type &triangleGroup = triangleGroups.back(); - uint32_t triangleIndex = triangleGroup.vertex_length; + if (!iconWithoutText && !textWithoutIcon) { + iconScale = glyphScale = util::max(iconScale, glyphScale); + } else if (!textWithoutIcon && glyphScale) { + glyphScale = util::max(iconScale, glyphScale); + } else if (!iconWithoutText && iconScale) { + iconScale = util::max(iconScale, glyphScale); + } - // coordinates (2 triangles) - buffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom, - placementRange, maxZoom, placementZoom); - buffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle, - minZoom, placementRange, maxZoom, placementZoom); - buffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle, - minZoom, placementRange, maxZoom, placementZoom); - buffer.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, - angle, minZoom, placementRange, maxZoom, placementZoom); + // Get the rotation ranges it is safe to show the glyphs + PlacementRange glyphRange = + (!glyphScale || properties.text.allow_overlap) + ? fullRange + : collision.getPlacementRange(glyphPlacement.boxes, glyphScale, horizontalText); + PlacementRange iconRange = + (!iconScale || properties.icon.allow_overlap) + ? fullRange + : collision.getPlacementRange(iconPlacement.boxes, iconScale, horizontalIcon); + + const PlacementRange maxRange = {{ + util::min(iconRange[0], glyphRange[0]), util::max(iconRange[1], glyphRange[1]), + }}; + + if (!iconWithoutText && !textWithoutIcon) { + iconRange = glyphRange = maxRange; + } else if (!textWithoutIcon) { + glyphRange = maxRange; + } else if (!iconWithoutText) { + iconRange = maxRange; + } - // add the two triangles, referencing the four coordinates we just inserted. - triangleElementsBuffer.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); - triangleElementsBuffer.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); + // Insert final placement into collision tree and add glyphs/icons to buffers + if (glyphScale) { + if (!properties.text.ignore_placement) { + collision.insert(glyphPlacement.boxes, anchor, glyphScale, glyphRange, + horizontalText); + } + addSymbols(text, glyphPlacement.shapes, glyphScale, glyphRange); + } - triangleGroup.vertex_length += glyph_vertex_length; - triangleGroup.elements_length += 2; + if (iconScale) { + if (!properties.icon.ignore_placement) { + collision.insert(iconPlacement.boxes, anchor, iconScale, iconRange, horizontalIcon); + } + addSymbols(icon, iconPlacement.shapes, iconScale, iconRange); + } } } +template <typename Buffer> +void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, + PlacementRange placementRange) { + const float zoom = collision.zoom; -void SymbolBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, - PlacementRange placementRange, float zoom) { - placementZoom += zoom; + const float placementZoom = std::log(scale) / std::log(2) + zoom; - for (const PlacedGlyph &glyph : glyphs) { - const auto &tl = glyph.tl; - const auto &tr = glyph.tr; - const auto &bl = glyph.bl; - const auto &br = glyph.br; - const auto &tex = glyph.tex; - const auto &angle = glyph.angle; + for (const PlacedGlyph &symbol : symbols) { + const auto &tl = symbol.tl; + const auto &tr = symbol.tr; + const auto &bl = symbol.bl; + const auto &br = symbol.br; + const auto &tex = symbol.tex; + const auto &angle = symbol.angle; - float minZoom = util::max(static_cast<float>(zoom + log(glyph.glyphBox.minScale) / log(2)), - placementZoom); - float maxZoom = - util::min(static_cast<float>(zoom + log(glyph.glyphBox.maxScale) / log(2)), 25.0f); - const auto &glyphAnchor = glyph.glyphBox.anchor; + float minZoom = + util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), placementZoom); + float maxZoom = util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f); + const auto &glyphAnchor = symbol.anchor; if (maxZoom <= minZoom) continue; @@ -338,30 +330,30 @@ void SymbolBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, const int glyph_vertex_length = 4; - if (!triangleGroups.size() || - (triangleGroups.back().vertex_length + glyph_vertex_length > 65535)) { + if (!buffer.groups.size() || + (buffer.groups.back().vertex_length + glyph_vertex_length > 65535)) { // Move to a new group because the old one can't hold the geometry. - triangleGroups.emplace_back(); + buffer.groups.emplace_back(); } // We're generating triangle fans, so we always start with the first // coordinate in this polygon. - triangle_group_type &triangleGroup = triangleGroups.back(); + ElementGroup &triangleGroup = buffer.groups.back(); uint32_t triangleIndex = triangleGroup.vertex_length; // coordinates (2 triangles) - textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom, - placementRange, maxZoom, placementZoom); - textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle, - minZoom, placementRange, maxZoom, placementZoom); - textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle, - minZoom, placementRange, maxZoom, placementZoom); - textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, - angle, minZoom, placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom, + placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle, + minZoom, placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle, + minZoom, placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, + angle, minZoom, placementRange, maxZoom, placementZoom); // add the two triangles, referencing the four coordinates we just inserted. - triangleElementsBuffer.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); - triangleElementsBuffer.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); + buffer.triangles.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); + buffer.triangles.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); triangleGroup.vertex_length += glyph_vertex_length; triangleGroup.elements_length += 2; @@ -369,27 +361,24 @@ void SymbolBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, } void SymbolBucket::drawGlyphs(TextShader &shader) { - char *vertex_index = BUFFER_OFFSET(text_vertex_start * textVertexBuffer.itemSize); - char *elements_index = - BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); - for (triangle_group_type &group : triangleGroups) { - group.array.bind(shader, textVertexBuffer, triangleElementsBuffer, vertex_index); - glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); - vertex_index += group.vertex_length * textVertexBuffer.itemSize; - elements_index += group.elements_length * triangleElementsBuffer.itemSize; - } -} - -void SymbolBucket::drawIcons(IconShader& shader) { - char *vertex_index = BUFFER_OFFSET(icon_vertex_start * iconVertexBuffer.itemSize); - array.bind(shader, iconVertexBuffer, vertex_index); - glDrawArrays(GL_POINTS, 0, (GLsizei)(icon_vertex_end - icon_vertex_start)); + char *vertex_index = BUFFER_OFFSET(0); + char *elements_index = BUFFER_OFFSET(0); + for (ElementGroup &group : text.groups) { + group.array.bind(shader, text.vertices, text.triangles, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * text.vertices.itemSize; + elements_index += group.elements_length * text.triangles.itemSize; + } } -void SymbolBucket::drawIcons(DotShader& shader) { - char *vertex_index = BUFFER_OFFSET(icon_vertex_start * iconVertexBuffer.itemSize); - array.bind(shader, iconVertexBuffer, vertex_index); - glDrawArrays(GL_POINTS, 0, (GLsizei)(icon_vertex_end - icon_vertex_start)); +void SymbolBucket::drawIcons(IconShader &shader) { + char *vertex_index = BUFFER_OFFSET(0); + char *elements_index = BUFFER_OFFSET(0); + for (ElementGroup &group : icon.groups) { + group.array.bind(shader, icon.vertices, icon.triangles, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * icon.vertices.itemSize; + elements_index += group.elements_length * icon.triangles.itemSize; + } } - } diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index 3377185d32..27deca689e 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -885,6 +885,7 @@ void StyleParser::parseRender(JSVal value, std::shared_ptr<StyleLayer> &layer) { parseRenderProperty<RotationAlignmentTypeClass>(value, render.icon.rotation_alignment, "icon-rotation-alignment"); parseRenderProperty(value, render.icon.max_size, "icon-max-size"); parseRenderProperty(value, render.icon.image, "icon-image"); + parseRenderProperty(value, render.icon.rotate, "icon-rotate"); parseRenderProperty(value, render.icon.padding, "icon-padding"); parseRenderProperty(value, render.icon.keep_upright, "icon-keep-upright"); parseRenderProperty(value, render.icon.offset, "icon-offset"); diff --git a/src/text/collision.cpp b/src/text/collision.cpp index 362ac92e27..70712f0651 100644 --- a/src/text/collision.cpp +++ b/src/text/collision.cpp @@ -48,24 +48,26 @@ Collision::Collision(float zoom, float tileExtent, float tileSize, float placeme // We don't want to place labels all the way to 25.5. This lets too many // glyphs be placed, slowing down collision checking. Only place labels if // they will show up within the intended zoom range of the tile. - maxPlacementScale(std::exp(std::log(2) * util::min(3.0f, placementDepth, 25.5f - zoom))) -{ + maxPlacementScale(std::exp(std::log(2) * util::min(3.0f, placementDepth, 25.5f - zoom))) { const float m = 4096; const float edge = m * tilePixelRatio * 2; // Hack to prevent cross-tile labels - insert( - {{GlyphBox{ - CollisionRect{CollisionPoint{0, 0}, CollisionPoint{0, m * 8}}, 0}, - GlyphBox{ - CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}}, 0}}}, - CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false, 2); - - insert({{GlyphBox{ - CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}}, 0}, - GlyphBox{ - CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}}, 0}}}, - CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2); + insert({GlyphBox{/* box */ CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{0, edge}}, + /* minScale */ 0, + /* padding */ 2}, + GlyphBox{/* box */ CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{edge, 0}}, + /* minScale */ 0, + /* padding */ 2}}, + CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false); + + insert({GlyphBox{/* box*/ CollisionRect{CollisionPoint{-edge, 0}, CollisionPoint{edge, edge}}, + /* minScale */ 0, + /* padding */ 2}, + GlyphBox{/* box */ CollisionRect{CollisionPoint{0, -edge}, CollisionPoint{edge, edge}}, + /* minScale */ 0, + /* padding */ 2}}, + CollisionAnchor(m, m), 1, {{M_PI * 2, 0}}, false); } GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) { @@ -81,99 +83,51 @@ GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) box.tl.y = util::min(box.tl.y, gbox.tl.y); box.br.x = util::max(box.br.x, gbox.br.x); box.br.y = util::max(box.br.y, gbox.br.y); - mergedGlyphs.minScale = - util::max(mergedGlyphs.minScale, glyph.minScale); + mergedGlyphs.minScale = util::max(mergedGlyphs.minScale, glyph.minScale); } return mergedGlyphs; } -PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAnchor &anchor, - float minPlacementScale, float maxPlacementScale, float padding, - bool horizontal, bool allowOverlap, bool ignorePlacement) { - float minScale = std::numeric_limits<float>::infinity(); - for (const GlyphBox &glyphBox : boxes) { - minScale = util::min(minScale, glyphBox.minScale); - } - minPlacementScale = util::max(minPlacementScale, minScale); - - GlyphBoxes glyphs; - if (horizontal) { - // Collision checks between rotating and fixed labels are relatively expensive, - // so we use one box per label, not per glyph for horizontal labels. - glyphs.push_back(getMergedGlyphs(boxes, anchor)); - - // for all horizontal labels, calculate bbox covering all rotated positions - const CollisionRect &box = glyphs.front().box; - float x12 = box.tl.x * box.tl.x, - y12 = box.tl.y * box.tl.y, - x22 = box.br.x * box.br.x, - y22 = box.br.y * box.br.y, - diag = std::sqrt(util::max(x12 + y12, x12 + y22, x22 + y12, x22 + y22)); - - glyphs.front().hBox = CollisionRect{ - {-diag, -diag}, - {diag, diag} - }; - } - - // Calculate the minimum scale the entire label can be shown without - // collisions - float scale = - allowOverlap ? minPlacementScale : getPlacementScale(glyphs, minPlacementScale, - maxPlacementScale, padding); - - // Return if the label can never be placed without collision - if (scale < 0 || scale == std::numeric_limits<float>::infinity()) { - return PlacementProperty{}; - } - - // Calculate the range it is safe to rotate all glyphs - PlacementRange rotationRange = allowOverlap ? PlacementRange{{2 * M_PI, 0.0f}} - : getPlacementRange(glyphs, scale, horizontal); - - if (!ignorePlacement) insert(glyphs, anchor, scale, rotationRange, horizontal, padding); - - float zoom = log(scale) / log(2); - - return PlacementProperty{zoom, rotationRange}; -} +Box getBox(const CollisionAnchor &anchor, const CollisionRect &bbox, float minScale, + float maxScale) { + return Box{ + Point{ + anchor.x + util::min(bbox.tl.x / minScale, bbox.tl.x / maxScale), + anchor.y + util::min(bbox.tl.y / minScale, bbox.tl.y / maxScale), + }, + Point{ + anchor.x + util::max(bbox.br.x / minScale, bbox.br.x / maxScale), + anchor.y + util::max(bbox.br.y / minScale, bbox.br.y / maxScale), + }, + }; +}; -float Collision::getPlacementScale(const GlyphBoxes &glyphs, - float minPlacementScale, - float maxPlacementScale, float pad) { +float Collision::getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale) { for (const GlyphBox &glyph : glyphs) { const CollisionRect &box = glyph.box; const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box; const CollisionAnchor &anchor = glyph.anchor; + const float pad = glyph.padding; - if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || - anchor.y > 4096) { + if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096) { return -1; } float minScale = std::fmax(minPlacementScale, glyph.minScale); - float maxScale = glyph.maxScale; + float maxScale = glyph.maxScale || std::numeric_limits<float>::infinity(); if (minScale >= maxScale) { continue; } // Compute the scaled bounding box of the unrotated glyph - float minPlacedX = anchor.x + bbox.tl.x / minScale; - float minPlacedY = anchor.y + bbox.tl.y / minScale; - float maxPlacedX = anchor.x + bbox.br.x / minScale; - float maxPlacedY = anchor.y + bbox.br.y / minScale; - - Box query_box{Point{minPlacedX, minPlacedY}, - Point{maxPlacedX, maxPlacedY}}; + const Box searchBox = getBox(anchor, bbox, minScale, maxScale); std::vector<PlacementValue> blocking; - ((Tree *)cTree) - ->query(bgi::intersects(query_box), std::back_inserter(blocking)); - ((Tree *)hTree) - ->query(bgi::intersects(query_box), std::back_inserter(blocking)); + ((Tree *)cTree)->query(bgi::intersects(searchBox), std::back_inserter(blocking)); + ((Tree *)hTree)->query(bgi::intersects(searchBox), std::back_inserter(blocking)); if (blocking.size()) { const CollisionAnchor &na = anchor; // new anchor @@ -195,10 +149,14 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs, float padding = std::fmax(pad, placement.padding) * 8.0f; // Original algorithm: - float s1 = (ob.tl.x - nb.br.x - padding) / (na.x - oa.x); // scale at which new box is to the left of old box - float s2 = (ob.br.x - nb.tl.x + padding) / (na.x - oa.x); // scale at which new box is to the right of old box - float s3 = (ob.tl.y - nb.br.y - padding) / (na.y - oa.y); // scale at which new box is to the top of old box - float s4 = (ob.br.y - nb.tl.y + padding) / (na.y - oa.y); // scale at which new box is to the bottom of old box + float s1 = (ob.tl.x - nb.br.x - padding) / + (na.x - oa.x); // scale at which new box is to the left of old box + float s2 = (ob.br.x - nb.tl.x + padding) / + (na.x - oa.x); // scale at which new box is to the right of old box + float s3 = (ob.tl.y - nb.br.y - padding) / + (na.y - oa.y); // scale at which new box is to the top of old box + float s4 = (ob.br.y - nb.tl.y + padding) / + (na.y - oa.y); // scale at which new box is to the bottom of old box if (isnan(s1) || isnan(s2)) { s1 = s2 = 1; @@ -211,10 +169,8 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs, // Only update label's min scale if the glyph was // restricted by a collision - if (collisionFreeScale > minPlacementScale && - collisionFreeScale > minScale && - collisionFreeScale < maxScale && - collisionFreeScale < placement.maxScale) { + if (collisionFreeScale > minPlacementScale && collisionFreeScale > minScale && + collisionFreeScale < maxScale && collisionFreeScale < placement.maxScale) { minPlacementScale = collisionFreeScale; } @@ -228,8 +184,7 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs, return minPlacementScale; } -PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, - float placementScale, +PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, float placementScale, bool horizontal) { PlacementRange placementRange = {{2.0f * M_PI, 0}}; @@ -242,16 +197,13 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, float maxPlacedX = anchor.x + bbox.br.x / placementScale; float maxPlacedY = anchor.y + bbox.br.y / placementScale; - Box query_box{Point{minPlacedX, minPlacedY}, - Point{maxPlacedX, maxPlacedY}}; + Box query_box{Point{minPlacedX, minPlacedY}, Point{maxPlacedX, maxPlacedY}}; std::vector<PlacementValue> blocking; - ((Tree *)hTree) - ->query(bgi::intersects(query_box), std::back_inserter(blocking)); + ((Tree *)hTree)->query(bgi::intersects(query_box), std::back_inserter(blocking)); if (horizontal) { - ((Tree *)cTree) - ->query(bgi::intersects(query_box), std::back_inserter(blocking)); + ((Tree *)cTree)->query(bgi::intersects(query_box), std::back_inserter(blocking)); } for (const PlacementValue &value : blocking) { @@ -275,10 +227,8 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, x2 = anchor.x + bbox.br.x / b.placementScale; y2 = anchor.y + bbox.br.y / b.placementScale; - intersectX = x1 < s.max_corner().get<0>() && - x2 > s.min_corner().get<0>(); - intersectY = y1 < s.max_corner().get<1>() && - y2 > s.min_corner().get<1>(); + intersectX = x1 < s.max_corner().get<0>() && x2 > s.min_corner().get<0>(); + intersectY = y1 < s.max_corner().get<1>() && y2 > s.min_corner().get<1>(); } // If they can't intersect, skip more expensive rotation calculation @@ -298,24 +248,22 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, }; void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, - float placementScale, - const PlacementRange &placementRange, bool horizontal, - float padding) { + float placementScale, const PlacementRange &placementRange, + bool horizontal) { assert(placementScale != std::numeric_limits<float>::infinity()); - std::vector<PlacementValue> result; - result.reserve(glyphs.size()); + std::vector<PlacementValue> allBounds; + allBounds.reserve(glyphs.size()); for (const GlyphBox &glyph : glyphs) { const CollisionRect &box = glyph.box; const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box; - float minScale = std::fmax(placementScale, glyph.minScale); + const float minScale = util::max(placementScale, glyph.minScale); + const float maxScale = glyph.maxScale || std::numeric_limits<float>::infinity(); - Box bounds{Point{anchor.x + bbox.tl.x / minScale, - anchor.y + bbox.tl.y / minScale}, - Point{anchor.x + bbox.br.x / minScale, - anchor.y + bbox.br.y / minScale}}; + Box bounds{Point{anchor.x + bbox.tl.x / minScale, anchor.y + bbox.tl.y / minScale}, + Point{anchor.x + bbox.br.x / minScale, anchor.y + bbox.br.y / minScale}}; PlacementBox placement; placement.anchor = anchor; @@ -325,19 +273,19 @@ void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, } placement.placementRange = placementRange; placement.placementScale = minScale; - placement.maxScale = glyph.maxScale; - placement.padding = padding; + placement.maxScale = maxScale; + placement.padding = glyph.padding; assert(!isnan(bounds.min_corner().get<0>()) && !isnan(bounds.min_corner().get<1>())); assert(!isnan(bounds.max_corner().get<0>()) && !isnan(bounds.max_corner().get<1>())); - result.emplace_back(bounds, placement); + allBounds.emplace_back(bounds, placement); } // Bulk-insert all glyph boxes if (horizontal) { - ((Tree *)hTree)->insert(result.begin(), result.end()); + ((Tree *)hTree)->insert(allBounds.begin(), allBounds.end()); } else { - ((Tree *)cTree)->insert(result.begin(), result.end()); + ((Tree *)cTree)->insert(allBounds.begin(), allBounds.end()); } } diff --git a/src/text/glyph_store.cpp b/src/text/glyph_store.cpp index edcecde7ff..83c2beb5cb 100644 --- a/src/text/glyph_store.cpp +++ b/src/text/glyph_store.cpp @@ -40,7 +40,7 @@ const Shaping FontStack::getShaping(const std::u32string &string, const float &m Shaping shaping; // Loop through all characters of this label and shape. for (uint32_t chr : string) { - shaping.emplace_back(0, chr, x, 0); + shaping.emplace_back(chr, x, 0); i++; auto metric = metrics.find(chr); if (metric != metrics.end()) { @@ -55,7 +55,7 @@ const Shaping FontStack::getShaping(const std::u32string &string, const float &m void alignVertically(Shaping &shaping, const uint32_t &lines, const float &lineHeight, const float &verticalAlign) { const float dy = -(lineHeight * (lines - 1) + 24) * verticalAlign - 5; - for (GlyphPlacement &shape : shaping) { + for (PositionedGlyph &shape : shaping) { shape.y += dy; } } @@ -86,7 +86,7 @@ void FontStack::lineWrap(Shaping &shaping, const float &lineHeight, const float uint32_t line = 0; for (uint32_t i = 0; i < shaping.size(); i++) { - GlyphPlacement &shape = shaping[i]; + PositionedGlyph &shape = shaping[i]; shape.x -= lengthBeforeCurrentLine; shape.y += lineHeight * line; diff --git a/src/text/placement.cpp b/src/text/placement.cpp index 75988cc997..448b892934 100644 --- a/src/text/placement.cpp +++ b/src/text/placement.cpp @@ -1,74 +1,33 @@ #include <mbgl/text/placement.hpp> -#include <mbgl/map/vector_tile.hpp> -#include <mbgl/geometry/interpolate.hpp> -#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/geometry/anchor.hpp> +#include <mbgl/text/glyph.hpp> +#include <mbgl/text/placement.hpp> +#include <mbgl/text/glyph_store.hpp> +#include <mbgl/style/style_bucket.hpp> + #include <mbgl/util/math.hpp> -#include <mbgl/util/constants.hpp> - -#include <algorithm> - -using namespace mbgl; - -const int Placement::tileExtent = 4096; -const int Placement::glyphSize = - 24; // size in pixels of this glyphs in the tile -const float Placement::minScale = 0.5; // underscale by 1 zoom level - -Placement::Placement(int8_t zoom, float placementDepth) - : zoom(zoom), - zOffset(log(512.0 / util::tileSize) / log(2)), - - // Calculate the maximum scale we can go down in our fake-3d rtree so that - // placement still makes sense. This is calculated so that the minimum - // placement zoom can be at most 25.5 (we use an unsigned integer x10 to - // store the minimum zoom). - // - // We don't want to place labels all the way to 25.5. This lets too many - // glyphs be placed, slowing down collision checking. Only place labels if - // they will show up within the intended zoom range of the tile. - // TODO make this not hardcoded to 3 - maxPlacementScale(std::exp( - log(2) * - util::min(3.0f, util::min(placementDepth > 0 ? placementDepth : 1.0f, 25.5f - zoom)))) {} - -bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; } - -static const Glyph null_glyph; - -inline const Glyph &getGlyph(const GlyphPlacement &placed, - const GlyphPositions &face) { - auto it = face.find(placed.glyph); - if (it != face.end()) { - return it->second; - } else { - fprintf(stderr, "glyph %d does not exist\n", placed.glyph); - } - return null_glyph; -} +namespace mbgl { + +const float Placement::globalMinScale = 0.5; // underscale by 1 zoom level struct GlyphInstance { explicit GlyphInstance(const vec2<float> &anchor) : anchor(anchor) {} - explicit GlyphInstance(const vec2<float> &anchor, float offset, - float minScale, float maxScale, float angle) - : anchor(anchor), - offset(offset), - minScale(minScale), - maxScale(maxScale), - angle(angle) {} + explicit GlyphInstance(const vec2<float> &anchor, float offset, float minScale, float maxScale, + float angle) + : anchor(anchor), offset(offset), minScale(minScale), maxScale(maxScale), angle(angle) {} const vec2<float> anchor; const float offset = 0.0f; - const float minScale = Placement::minScale; + const float minScale = Placement::globalMinScale; const float maxScale = std::numeric_limits<float>::infinity(); const float angle = 0.0f; }; typedef std::vector<GlyphInstance> GlyphInstances; -void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, - Anchor &anchor, float offset, - const std::vector<Coordinate> &line, int segment, +void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, Anchor &anchor, + float offset, const std::vector<Coordinate> &line, int segment, int8_t direction, float maxAngleDelta) { const bool upsideDown = direction < 0; @@ -94,8 +53,8 @@ void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, while (true) { const float dist = util::dist<float>(newAnchor, end); const float scale = offset / dist; - float angle = -std::atan2(end.x - newAnchor.x, end.y - newAnchor.y) + - direction * M_PI / 2.0f; + float angle = + -std::atan2(end.x - newAnchor.x, end.y - newAnchor.y) + direction * M_PI / 2.0f; if (upsideDown) angle += M_PI; @@ -111,8 +70,7 @@ void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, /* offset */ static_cast<float>(upsideDown ? M_PI : 0.0), /* minScale */ scale, /* maxScale */ prevscale, - /* angle */ static_cast<float>( - std::fmod((angle + 2.0 * M_PI), (2.0 * M_PI)))}; + /* angle */ static_cast<float>(std::fmod((angle + 2.0 * M_PI), (2.0 * M_PI)))}; if (scale <= placementScale) break; @@ -137,49 +95,155 @@ void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, } } -void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, - Anchor &anchor, vec2<float> origin, const Shaping &shaping, - const GlyphPositions &face, float fontScale, - bool horizontal, const std::vector<Coordinate> &line, - float maxAngleDelta, float rotate) { - // The total text advance is the width of this label. - - // TODO: allow setting an alignment - // var alignment = 'center'; - // if (alignment == 'center') { - // origin.x -= advance / 2; - // } else if (alignment == 'right') { - // origin.x -= advance; - // } +GlyphBox getMergedBoxes(const GlyphBoxes &glyphs, const Anchor &anchor) { + // Collision checks between rotating and fixed labels are relatively expensive, + // so we use one box per label, not per glyph for horizontal labels. + + const float inf = std::numeric_limits<float>::infinity(); + + GlyphBox mergedglyphs{/* box */ CollisionRect{inf, inf, -inf, -inf}, + /* anchor */ anchor, + /* minScale */ 0, + /* maxScale */ inf, + /* padding */ -inf}; + + CollisionRect &box = mergedglyphs.box; + + for (const GlyphBox &glyph : glyphs) { + const CollisionRect &gbox = glyph.box; + box.tl.x = util::min(box.tl.x, gbox.tl.x); + box.tl.y = util::min(box.tl.y, gbox.tl.y); + box.br.x = util::max(box.br.x, gbox.br.x); + box.br.y = util::max(box.br.y, gbox.br.y); + mergedglyphs.minScale = util::max(mergedglyphs.minScale, glyph.minScale); + mergedglyphs.padding = util::max(mergedglyphs.padding, glyph.padding); + } + // for all horizontal labels, calculate bbox covering all rotated positions + float x12 = box.tl.x * box.tl.x, y12 = box.tl.y * box.tl.y, x22 = box.br.x * box.br.x, + y22 = box.br.y * box.br.y, + diag = std::sqrt(util::max(x12 + y12, x12 + y22, x22 + y12, x22 + y22)); + + mergedglyphs.hBox = CollisionRect{-diag, -diag, diag, diag}; + + return mergedglyphs; +} + +Placement Placement::getIcon(Anchor &anchor, const Rect<uint16_t> &image, float boxScale, + const std::vector<Coordinate> &line, const StyleBucketSymbol &props) { + const float x = image.w / 2.0f; // No need to divide by image.pixelRatio here? + const float y = image.h / 2.0f; // image.pixelRatio; + + const float dx = props.icon.offset.x; + const float dy = props.icon.offset.y; + float x1 = (dx - x); + float x2 = (dx + x); + float y1 = (dy - y); + float y2 = (dy + y); + + vec2<float> tl{x1, y1}; + vec2<float> tr{x2, y1}; + vec2<float> br{x2, y2}; + vec2<float> bl{x1, y2}; + + float angle = props.icon.rotate * M_PI / 180.0f; + if (anchor.segment >= 0 && props.icon.rotation_alignment != RotationAlignmentType::Viewport) { + const Coordinate &next = line[anchor.segment]; + angle += -std::atan2(next.x - anchor.x, next.y - anchor.y) + M_PI / 2; + } + + if (angle) { + // Compute the transformation matrix. + float angle_sin = std::sin(angle); + float angle_cos = std::cos(angle); + std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}}; + + tl = tl.matMul(matrix); + tr = tr.matMul(matrix); + bl = bl.matMul(matrix); + br = br.matMul(matrix); + + x1 = util::min(tl.x, tr.x, bl.x, br.x); + x2 = util::max(tl.x, tr.x, bl.x, br.x); + y1 = util::min(tl.y, tr.y, bl.y, br.y); + y2 = util::max(tl.y, tr.y, bl.y, br.y); + } + + const CollisionRect box{/* x1 */ x1 * boxScale, + /* y1 */ y1 * boxScale, + /* x2 */ y2 * boxScale, + /* y2 */ y2 * boxScale}; + + Placement placement; + + placement.boxes.emplace_back( + /* box */ box, + /* anchor */ anchor, + /* minScale */ Placement::globalMinScale, + /* maxScale */ std::numeric_limits<float>::infinity(), + /* padding */ props.icon.padding); + + placement.shapes.emplace_back( + /* tl */ tl, + /* tr */ tr, + /* bl */ bl, + /* br */ br, + /* image */ image, + /* angle */ 0, + /* anchors */ anchor, + /* minScale */ Placement::globalMinScale, + /* maxScale */ std::numeric_limits<float>::infinity()); + + placement.minScale = anchor.scale; + + return placement; +} + +Placement Placement::getGlyphs(Anchor &anchor, const vec2<float> &origin, const Shaping &shaping, + const GlyphPositions &face, float boxScale, bool horizontal, + const std::vector<Coordinate> &line, + const StyleBucketSymbol &props) { + const float maxAngleDelta = props.text.max_angle_delta * M_PI / 180; + const float rotate = props.text.rotate * M_PI / 180; + const float padding = props.text.padding; + const bool alongLine = props.text.rotation_alignment != RotationAlignmentType::Viewport; + const bool keepUpright = props.text.keep_upright; + + Placement placement; + + PlacedGlyphs &glyphs = placement.shapes; + GlyphBoxes &boxes = placement.boxes; const uint32_t buffer = 3; - for (const GlyphPlacement &placed : shaping) { - const Glyph &glyph = getGlyph(placed, face); - if (!glyph) { - // This glyph is empty and doesn't have any pixels that we'd need to - // show. + for (const PositionedGlyph &shape : shaping) { + auto face_it = face.find(shape.glyph); + if (face_it == face.end()) + continue; + const Glyph &glyph = face_it->second; + const Rect<uint16_t> &rect = glyph.rect; + + if (!glyph) + continue; + + if (!(rect && rect.w > 0 && rect.h > 0)) continue; - } - float x = - (origin.x + placed.x + glyph.metrics.left - buffer + glyph.rect.w / 2) * - fontScale; + const float x = (origin.x + shape.x + glyph.metrics.left - buffer + rect.w / 2) * boxScale; GlyphInstances glyphInstances; - if (anchor.segment >= 0) { - getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, - line, anchor.segment, 1, maxAngleDelta); - getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, - line, anchor.segment, -1, maxAngleDelta); + if (anchor.segment >= 0 && alongLine) { + getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, line, anchor.segment, 1, + maxAngleDelta); + if (keepUpright) + getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, line, + anchor.segment, -1, maxAngleDelta); } else { - glyphInstances.emplace_back(GlyphInstance{anchor}); } - const float x1 = origin.x + placed.x + glyph.metrics.left - buffer; - const float y1 = origin.y + placed.y - glyph.metrics.top - buffer; + const float x1 = origin.x + shape.x + glyph.metrics.left - buffer; + const float y1 = origin.y + shape.y - glyph.metrics.top - buffer; const float x2 = x1 + glyph.rect.w; const float y2 = y1 + glyph.rect.h; @@ -188,8 +252,7 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, const vec2<float> obl{x1, y2}; const vec2<float> obr{x2, y2}; - const CollisionRect obox{fontScale *x1, fontScale *y1, - fontScale *x2, fontScale *y2}; + const CollisionRect obox{boxScale * x1, boxScale * y1, boxScale * x2, boxScale * y2}; for (const GlyphInstance &instance : glyphInstances) { vec2<float> tl = otl; @@ -206,79 +269,47 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, // Compute the transformation matrix. float angle_sin = std::sin(angle); float angle_cos = std::cos(angle); - std::array<float, 4> matrix = { - {angle_cos, -angle_sin, angle_sin, angle_cos}}; + std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}}; tl = tl.matMul(matrix); tr = tr.matMul(matrix); bl = bl.matMul(matrix); br = br.matMul(matrix); - - // Calculate the rotated glyph's bounding box offsets from the - // anchor point. - box = CollisionRect{ - fontScale * util::min(tl.x, tr.x, bl.x, br.x), - fontScale * util::min(tl.y, tr.y, bl.y, br.y), - fontScale * util::max(tl.x, tr.x, bl.x, br.x), - fontScale * util::max(tl.y, tr.y, bl.y, br.y)}; } - GlyphBox glyphBox = GlyphBox{ - box, - // Prevent label from extending past the end of the line - util::max(instance.minScale, anchor.scale), - instance.maxScale, - instance.anchor - }; + // Prevent label from extending past the end of the line + const float glyphMinScale = std::max(instance.minScale, anchor.scale); // Remember the glyph for later insertion. - glyphs.emplace_back(PlacedGlyph{ - tl, tr, bl, br, glyph.rect, - static_cast<float>( - std::fmod((anchor.angle + rotate + instance.offset + 2 * M_PI), (2 * M_PI))), - glyphBox}); - - if (instance.offset == 0.0f) { - boxes.emplace_back(glyphBox); + glyphs.emplace_back( + tl, tr, bl, br, rect, + float(std::fmod((anchor.angle + rotate + instance.offset + 2 * M_PI), (2 * M_PI))), + instance.anchor, glyphMinScale, instance.maxScale); + + if (!instance.offset) { // not a flipped glyph + if (angle) { + // Calculate the rotated glyph's bounding box offsets from the anchor point. + box = CollisionRect{boxScale * util::min(tl.x, tr.x, bl.x, br.x), + boxScale * util::min(tl.y, tr.y, bl.y, br.y), + boxScale * util::max(tl.x, tr.x, bl.x, br.x), + boxScale * util::max(tl.y, tr.y, bl.y, br.y)}; + } + boxes.emplace_back(box, instance.anchor, glyphMinScale, instance.maxScale, padding); } } } -} - -void Placement::addFeature(SymbolBucket &bucket, const std::vector<Coordinate> &line, - const StyleBucketSymbol &info, const GlyphPositions &face, - const Shaping &shaping) { - const bool horizontal = info.text.rotation_alignment == RotationAlignmentType::Viewport; - const float fontScale = (tileExtent / util::tileSize) / (glyphSize / info.text.max_size); - - Anchors anchors; - if (line.size() == 1) { - // Point labels - anchors = Anchors{{Anchor{ - static_cast<float>(line[0].x), static_cast<float>(line[0].y), - 0, minScale}}}; + // TODO avoid creating the boxes in the first place? + if (horizontal) + boxes = {getMergedBoxes(boxes, anchor)}; - } else { - // Line labels - anchors = interpolate(line, info.min_distance, minScale); - - // Sort anchors by segment so that we can start placement with - // the anchors that can be shown at the lowest zoom levels. - std::sort(anchors.begin(), anchors.end(), byScale); + const float minPlacementScale = anchor.scale; + placement.minScale = std::numeric_limits<float>::infinity(); + for (const GlyphBox &box : boxes) { + placement.minScale = util::min(placement.minScale, box.minScale); } + placement.minScale = util::max(minPlacementScale, Placement::globalMinScale); - for (Anchor anchor : anchors) { - PlacedGlyphs glyphs; - GlyphBoxes boxes; - - getGlyphs(glyphs, boxes, anchor, info.text.offset, shaping, face, fontScale, horizontal, - line, info.text.max_angle_delta * (M_PI / 180), info.text.rotate); - PlacementProperty place = - collision.place(boxes, anchor, anchor.scale, maxPlacementScale, info.text.padding, - horizontal, info.text.allow_overlap, info.text.ignore_placement); - if (place) { - bucket.addGlyphs(glyphs, place.zoom, place.rotationRange, zoom - zOffset); - } - } + return placement; +} } |