From 4ee0cf1791f8f4b787793d2612c583bfb8b9c62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 19 Mar 2014 18:58:50 +0100 Subject: [PATCH] port remainder of the the placement algorithm --- include/llmr/geometry/binpack.hpp | 14 +- include/llmr/geometry/buffer.hpp | 2 +- include/llmr/geometry/interpolate.hpp | 45 +-- include/llmr/geometry/text_buffer.hpp | 15 +- include/llmr/map/tile_parser.hpp | 7 +- include/llmr/map/vector_tile.hpp | 25 +- include/llmr/renderer/text_bucket.hpp | 35 +-- include/llmr/style/bucket_description.hpp | 18 ++ include/llmr/style/value.hpp | 2 +- include/llmr/text/collision.hpp | 56 +--- include/llmr/text/glyph.hpp | 58 ++++ include/llmr/text/placement.hpp | 39 +++ include/llmr/text/rotation_range.hpp | 32 +-- include/llmr/text/types.hpp | 108 ++++++++ include/llmr/util/rect.hpp | 16 ++ include/llmr/util/variant.hpp | 4 + include/llmr/util/vec.hpp | 33 +++ src/geometry/glyph_atlas.cpp | 8 +- src/geometry/interpolate.cpp | 42 +++ src/geometry/text_buffer.cpp | 29 +- src/map/tile_parser.cpp | 28 +- src/map/vector_tile.cpp | 28 +- src/renderer/text_bucket.cpp | 122 +++++++-- src/text/collision.cpp | 63 +++-- src/text/placement.cpp | 320 ++++++++++++++++++++++ src/text/rotation_range.cpp | 13 +- 26 files changed, 872 insertions(+), 290 deletions(-) create mode 100644 include/llmr/text/glyph.hpp create mode 100644 include/llmr/text/placement.hpp create mode 100644 include/llmr/text/types.hpp create mode 100644 include/llmr/util/rect.hpp create mode 100644 src/geometry/interpolate.cpp create mode 100644 src/text/placement.cpp diff --git a/include/llmr/geometry/binpack.hpp b/include/llmr/geometry/binpack.hpp index cbc44fa94..11536ee8b 100644 --- a/include/llmr/geometry/binpack.hpp +++ b/include/llmr/geometry/binpack.hpp @@ -2,23 +2,17 @@ #define LLMR_GEOMETRY_BINPACK #include +#include #include #include namespace llmr { -template -struct Rect { - Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {} - T x = 0, y = 0; - T w = 0, h = 0; -}; - template class BinPack : private util::noncopyable { public: BinPack(T width, T height) - : free(1, { 0, 0, width, height }) {} + : free(1, Rect{ 0, 0, width, height }) {} public: Rect allocate(T width, T height) { // Find the smallest free rect angle @@ -35,7 +29,7 @@ public: if (smallest == free.end()) { // There's no space left for this char. - return { 0, 0, 0, 0 }; + return Rect{ 0, 0, 0, 0 }; } else { Rect rect = *smallest; free.erase(smallest); @@ -60,7 +54,7 @@ public: if (rect.h > height) free.emplace_back(rect.x, rect.y + height, width, rect.h - height); } - return { rect.x, rect.y, width, height }; + return Rect{ rect.x, rect.y, width, height }; } } diff --git a/include/llmr/geometry/buffer.hpp b/include/llmr/geometry/buffer.hpp index 819d78f3d..93ffbec26 100644 --- a/include/llmr/geometry/buffer.hpp +++ b/include/llmr/geometry/buffer.hpp @@ -47,7 +47,7 @@ public: protected: // increase the buffer size by at least /required/ bytes. - void *addElement() { + inline void *addElement() { assert("Buffer is already bound to GPU" && buffer == 0); if (length < pos + itemSize) { while (length < pos + itemSize) length += defaultLength; diff --git a/include/llmr/geometry/interpolate.hpp b/include/llmr/geometry/interpolate.hpp index bfc00a513..0014d56aa 100644 --- a/include/llmr/geometry/interpolate.hpp +++ b/include/llmr/geometry/interpolate.hpp @@ -6,49 +6,8 @@ namespace llmr { -template -Anchors interpolate(const Vertices& vertices, float spacing, float minScale = 0.0f, int start = 0) { - - float distance = 0.0f; - float markedDistance = 0.0f; - int added = start; - - Anchors points; - - auto end = vertices.end() - 1; - int i = 0; - for (auto it = vertices.begin(); it != end; it++, i++) { - const auto& a = *(it), - b = *(it + 1); - - float segmentDist = util::dist(a, b); - float angle = util::angle_to(b, a); - - while (markedDistance + spacing < distance + segmentDist) { - markedDistance += 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; - - if (x >= 0 && x < 4096 && y >= 0 && y < 4096) { - points.emplace_back(x, y, angle, s, i); - } - - added++; - } - - distance += segmentDist; - } - - return points; -} - +Anchors interpolate(const std::vector &vertices, float spacing, + float minScale = 0.0f, int start = 0); } #endif - diff --git a/include/llmr/geometry/text_buffer.hpp b/include/llmr/geometry/text_buffer.hpp index e0166666c..8d4cba285 100644 --- a/include/llmr/geometry/text_buffer.hpp +++ b/include/llmr/geometry/text_buffer.hpp @@ -2,21 +2,10 @@ #define LLMR_GEOMETRY_TEXT_BUFFER #include "buffer.hpp" +#include namespace llmr { -class GlyphPosition { -public: - int16_t x = 0, y = 0; - float ox = 0.0f, oy = 0.0f; - uint16_t tx = 0, ty = 0; - float angle = 0.0f; - float minzoom = 0.0f; - float rangefrom = 0.0f, rangeto = 0.0f; - float maxzoom = 25.0f; - float labelminzoom = 0.0f; -}; - class TextVertexBuffer : public Buffer < 16, GL_ARRAY_BUFFER, @@ -27,7 +16,7 @@ public: static const double angleFactor; - size_t add(const GlyphPosition& pos); + size_t add(int16_t x, int16_t y, float ox, float oy, uint16_t tx, uint16_t ty, float angle, float minzoom, std::array range, float maxzoom, float labelminzoom); }; diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index dd3b11337..5789dd9de 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -2,12 +2,10 @@ #define LLMR_MAP_TILE_PARSER #include +#include namespace llmr { -typedef std::map> GlyphPositions; -typedef std::map FaceGlyphPositions; - class TileParser { public: TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas); @@ -28,7 +26,8 @@ private: TileData& tile; const Style& style; GlyphAtlas& glyphAtlas; - FaceGlyphPositions rects; + FaceGlyphPositions faces; + Placement placement; }; } diff --git a/include/llmr/map/vector_tile.hpp b/include/llmr/map/vector_tile.hpp index 67207563e..f57c5d350 100644 --- a/include/llmr/map/vector_tile.hpp +++ b/include/llmr/map/vector_tile.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -71,18 +72,7 @@ private: std::set values; }; -class VectorTilePlacement { -public: - VectorTilePlacement(uint32_t face, uint32_t glyph, uint32_t x, uint32_t y); - - uint32_t face = 0; - uint32_t glyph = 0; - uint32_t x = 0; - uint32_t y = 0; -}; - -std::ostream& operator<<(std::ostream&, const VectorTilePlacement& placement); - +std::ostream& operator<<(std::ostream&, const GlyphPlacement& placement); class VectorTileLayer { public: @@ -94,11 +84,12 @@ public: std::vector keys; std::vector values; std::vector faces; - std::map>> shaping; + std::map> shaping; }; class VectorTileGlyph { public: + VectorTileGlyph(); VectorTileGlyph(pbf data); uint32_t id = 0; @@ -106,12 +97,8 @@ public: // A signed distance field of the glyph with a border of 3 pixels. std::string bitmap; - // Glyph metrics. - uint32_t width = 0; - uint32_t height = 0; - int32_t left = 0; - int32_t top = 0; - uint32_t advance = 0; + // Glyph metrics + GlyphMetrics metrics; }; std::ostream& operator<<(std::ostream&, const VectorTileGlyph& glyph); diff --git a/include/llmr/renderer/text_bucket.hpp b/include/llmr/renderer/text_bucket.hpp index 21c00cbde..770e982cf 100644 --- a/include/llmr/renderer/text_bucket.hpp +++ b/include/llmr/renderer/text_bucket.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -15,46 +17,45 @@ class Style; class TextVertexBuffer; class TriangleElementsBuffer; class TextShader; -class VectorTileFeature; -class VectorTileFace; -class VectorTilePlacement; +class Placement; struct pbf; class TextBucket : public Bucket { typedef ElementGroup triangle_group_type; + public: - TextBucket(const std::shared_ptr& vertexBuffer, - const std::shared_ptr& triangleElementsBuffer, - const BucketDescription& bucket_desc); + TextBucket( + const std::shared_ptr &vertexBuffer, + const std::shared_ptr &triangleElementsBuffer, + const BucketDescription &bucket_desc, Placement &placement); - virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + virtual void render(Painter &painter, const std::string &layer_name, + const Tile::ID &id); - void addFeature(const VectorTileFeature& feature, - const std::vector& faces, - const std::map>& shaping); + void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, + PlacementRange placementRange, float zoom); - void addLabel(const std::vector& line, - const std::vector& faces, - const std::vector& placements); + void addFeature(const VectorTileFeature &feature, + const IndexedFaceGlyphPositions &faces, + const std::map &shapings); bool empty() const; - void drawGlyphs(TextShader& shader); + void drawGlyphs(TextShader &shader); public: - const BucketGeometryDescription geometry; + const BucketGeometryDescription &geom_desc; private: - std::shared_ptr vertexBuffer; std::shared_ptr triangleElementsBuffer; + Placement &placement; const size_t vertex_start; const size_t triangle_elements_start; std::vector triangleGroups; }; - } #endif diff --git a/include/llmr/style/bucket_description.hpp b/include/llmr/style/bucket_description.hpp index 0a750e113..498e935f2 100644 --- a/include/llmr/style/bucket_description.hpp +++ b/include/llmr/style/bucket_description.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "value.hpp" @@ -30,6 +31,11 @@ enum class JoinType { Round = 3 }; +enum class TextPathType { + Horizontal = 0, + Curve = 1 +}; + inline BucketType bucketType(const std::string& type) { if (type == "fill") return BucketType::Fill; @@ -54,6 +60,12 @@ inline JoinType joinType(const std::string& join) { else return JoinType::None; } +inline TextPathType textPathType(const std::string& path) { + if (path == "horizontal") return TextPathType::Horizontal; + else if (path == "curve") return TextPathType::Curve; + else return TextPathType::Horizontal; +}; + class BucketGeometryDescription { public: CapType cap = CapType::None; @@ -63,6 +75,12 @@ public: float font_size = 0.0f; float miter_limit = 2.0f; float round_limit = 1.0f; + TextPathType path = TextPathType::Horizontal; + float padding = 2.0f; + float textMinDistance = 250.0f; + float rotate = 0.0f; // what is this? + float maxAngleDelta = M_PI; + bool alwaysVisible = false; }; class BucketDescription { diff --git a/include/llmr/style/value.hpp b/include/llmr/style/value.hpp index 2e9d66136..a24e0e8e8 100644 --- a/include/llmr/style/value.hpp +++ b/include/llmr/style/value.hpp @@ -6,7 +6,7 @@ namespace llmr { -typedef ::util::variant Value; +typedef util::variant Value; Value parseValue(pbf data); diff --git a/include/llmr/text/collision.hpp b/include/llmr/text/collision.hpp index 941f64ff7..c837738d2 100644 --- a/include/llmr/text/collision.hpp +++ b/include/llmr/text/collision.hpp @@ -1,65 +1,17 @@ #ifndef LLMR_TEXT_COLLISION #define LLMR_TEXT_COLLISION -#include -#include -#include -#include - -#include +#include namespace llmr { -// These are the glyph boxes that we want to have placed. -struct GlyphBox { - explicit GlyphBox() {} - explicit GlyphBox(const CollisionRect &bbox, const CollisionRect &box, - float minScale) - : bbox(bbox), box(box), minScale(minScale) {} - - CollisionAnchor anchor; - CollisionRect bbox; - CollisionRect box; - bool rotate = false; - float minScale = 0.0f; - float maxScale = std::numeric_limits::infinity(); -}; - -typedef std::vector GlyphBoxes; - -// These are the placed boxes contained in the rtree. -struct PlacementBox { - CollisionAnchor anchor; - CollisionRect bbox; - CollisionRect box; - bool rotate = false; - PlacementRange placementRange = {{0.0f, 0.0f}}; - float placementScale = 0.0f; - float maxScale = std::numeric_limits::infinity(); - float padding = 0.0f; -}; - -struct PlacementProperty { - explicit PlacementProperty() {} - explicit PlacementProperty(float zoom, const PlacementRange &rotationRange) - : zoom(zoom), rotationRange(rotationRange) {} - - float zoom = -1.0f; - PlacementRange rotationRange = {{0.0f, 0.0f}}; -}; - class Collision { - typedef boost::geometry::model::point Point; - typedef boost::geometry::model::box Box; - typedef std::pair PlacementValue; - typedef boost::geometry::index::rtree< - PlacementValue, boost::geometry::index::quadratic<16>> Tree; public: Collision(); + ~Collision(); - PlacementProperty place(const GlyphBoxes &boxes, + PlacementProperty place(const PlacedGlyphs &boxes, const CollisionAnchor &anchor, float minPlacementScale, float maxPlacementScale, float padding, bool horizontal); @@ -72,7 +24,7 @@ public: bool horizontal, float padding); private: - Tree tree; + void *tree; }; } diff --git a/include/llmr/text/glyph.hpp b/include/llmr/text/glyph.hpp new file mode 100644 index 000000000..204ce5f14 --- /dev/null +++ b/include/llmr/text/glyph.hpp @@ -0,0 +1,58 @@ +#ifndef LLMR_TEXT_GLYPH +#define LLMR_TEXT_GLYPH + +#include + +#include +#include +#include + +namespace llmr { + +struct GlyphMetrics { + operator bool() const { + return width == 0 && height == 0 && advance == 0; + } + + // Glyph metrics. + uint32_t width = 0; + uint32_t height = 0; + int32_t left = 0; + int32_t top = 0; + uint32_t advance = 0; + +}; + +struct Glyph { + inline explicit Glyph() : rect(0, 0, 0, 0), metrics() {} + inline explicit Glyph(const Rect &rect, + const GlyphMetrics &metrics) + : rect(rect), metrics(metrics) {} + + operator bool() const { + return !metrics; + } + + const Rect rect; + const GlyphMetrics metrics; +}; + +typedef std::vector GlyphPositions; +typedef std::map FaceGlyphPositions; +typedef std::vector IndexedFaceGlyphPositions; + +class GlyphPlacement { +public: + inline explicit GlyphPlacement(uint32_t face, uint32_t glyph, uint32_t x, uint32_t y) + : face(face), glyph(glyph), x(x), y(y) {} + + uint32_t face = 0; + uint32_t glyph = 0; + uint32_t x = 0; + uint32_t y = 0; +}; + +typedef std::vector Shaping; +} + +#endif diff --git a/include/llmr/text/placement.hpp b/include/llmr/text/placement.hpp new file mode 100644 index 000000000..f84417901 --- /dev/null +++ b/include/llmr/text/placement.hpp @@ -0,0 +1,39 @@ +#ifndef LLMR_TEXT_PLACEMENT +#define LLMR_TEXT_PLACEMENT + +#include +#include +#include +#include +#include + +namespace llmr { + +class TextBucket; +class BucketGeometryDescription; + +class Placement { +public: + Placement(int8_t zoom); + + void addFeature(TextBucket &bucket, const std::vector &line, + const BucketGeometryDescription &info, + const IndexedFaceGlyphPositions &faces, + const Shaping &shaping); + float measureText(const IndexedFaceGlyphPositions &faces, + const Shaping &shaping); + +private: + const int8_t zoom; + Collision collision; + const float zOffset; + const float maxPlacementScale; + +public: + static const int tileExtent; + static const int glyphSize; + static const float minScale; +}; +} + +#endif diff --git a/include/llmr/text/rotation_range.hpp b/include/llmr/text/rotation_range.hpp index 9999b1ade..dcdcc0143 100644 --- a/include/llmr/text/rotation_range.hpp +++ b/include/llmr/text/rotation_range.hpp @@ -1,39 +1,14 @@ #ifndef LLMR_TEXT_ROTATION_RANGE #define LLMR_TEXT_ROTATION_RANGE -#include #include +#include -#include #include #include namespace llmr { -typedef vec2 CollisionPoint; -typedef vec2 CollisionAnchor; - -typedef std::array PlacementRange; -typedef float CollisionAngle; -typedef std::vector CollisionAngles; -typedef std::array CollisionRange; -typedef std::vector CollisionList; -typedef std::array CollisionCorners; - -struct CollisionRect { - CollisionPoint tl; - CollisionPoint br; - inline explicit CollisionRect() {} - inline explicit CollisionRect(CollisionPoint::Type ax, - CollisionPoint::Type ay, - CollisionPoint::Type bx, - CollisionPoint::Type by) - : tl(ax, ay), br(bx, by) {} - inline explicit CollisionRect(const CollisionPoint &tl, - const CollisionPoint &br) - : tl(tl), br(br) {} -}; - /* * Combine an array of collision ranges to form a continuous * range that includes 0. Collisions within the ignoreRange are ignored @@ -72,9 +47,8 @@ CollisionList rotatingFixedCollisions(const CollisionRect &rotating, /* * Calculate the range a box conflicts with a second box */ -template -CollisionRange rotationRange(const InsertingBox &inserting, - const BlockingBox &blocker, float scale); +CollisionRange rotationRange(const GlyphBox &inserting, + const PlacementBox &blocker, float scale); } #endif diff --git a/include/llmr/text/types.hpp b/include/llmr/text/types.hpp new file mode 100644 index 000000000..2e363e102 --- /dev/null +++ b/include/llmr/text/types.hpp @@ -0,0 +1,108 @@ +#ifndef LLMR_TEXT_TYPES +#define LLMR_TEXT_TYPES + +#include +#include +#include +#include + +namespace llmr { + +typedef vec2 CollisionPoint; +typedef vec2 CollisionAnchor; + +typedef std::array PlacementRange; +typedef float CollisionAngle; +typedef std::vector CollisionAngles; +typedef std::array CollisionRange; +typedef std::vector CollisionList; +typedef std::array CollisionCorners; + +struct CollisionRect { + CollisionPoint tl; + CollisionPoint br; + inline explicit CollisionRect() {} + inline explicit CollisionRect(CollisionPoint::Type ax, + CollisionPoint::Type ay, + CollisionPoint::Type bx, + CollisionPoint::Type by) + : tl(ax, ay), br(bx, by) {} + inline explicit CollisionRect(const CollisionPoint &tl, + const CollisionPoint &br) + : tl(tl), br(br) {} +}; + +// These are the glyph boxes that we want to have placed. +struct GlyphBox { + explicit GlyphBox() {} + explicit GlyphBox(const CollisionRect &bbox, const CollisionRect &box, + float minScale) + : bbox(bbox), box(box), minScale(minScale) {} + explicit GlyphBox(const CollisionRect &box, float minScale, float maxScale, + const CollisionAnchor &anchor, bool rotate) + : anchor(anchor), + box(box), + rotate(rotate), + minScale(minScale), + maxScale(maxScale) {} + + CollisionAnchor anchor; + CollisionRect bbox; + CollisionRect box; + bool rotate = false; + float minScale = 0.0f; + float maxScale = std::numeric_limits::infinity(); +}; + +typedef std::vector GlyphBoxes; + + +// TODO: Transform the vec2s to vec2 to save bytes +struct PlacedGlyph { + explicit PlacedGlyph(const vec2 &tl, const vec2 &tr, + const vec2 &bl, const vec2 &br, + const Rect &tex, uint32_t width, + uint32_t height, float angle, const GlyphBox &glyphBox) + : tl(tl), + tr(tr), + bl(bl), + br(br), + tex(tex), + width(width), + height(height), + angle(angle), + glyphBox(glyphBox) {} + + vec2 tl, tr, bl, br; + Rect tex; + uint32_t width, height; + float angle; + GlyphBox glyphBox; +}; + +typedef std::vector PlacedGlyphs; + +// These are the placed boxes contained in the rtree. +struct PlacementBox { + CollisionAnchor anchor; + CollisionRect bbox; + CollisionRect box; + bool rotate = false; + PlacementRange placementRange = {{0.0f, 0.0f}}; + float placementScale = 0.0f; + float maxScale = std::numeric_limits::infinity(); + float padding = 0.0f; +}; + +struct PlacementProperty { + explicit PlacementProperty() {} + explicit PlacementProperty(float zoom, const PlacementRange &rotationRange) + : zoom(zoom), rotationRange(rotationRange) {} + + float zoom = -1.0f; + PlacementRange rotationRange = {{0.0f, 0.0f}}; +}; + +} + +#endif diff --git a/include/llmr/util/rect.hpp b/include/llmr/util/rect.hpp new file mode 100644 index 000000000..025f3a9cb --- /dev/null +++ b/include/llmr/util/rect.hpp @@ -0,0 +1,16 @@ +#ifndef LLMR_UTIL_RECT +#define LLMR_UTIL_RECT + +namespace llmr { + +template +struct Rect { + explicit Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {} + T x = 0, y = 0; + T w = 0, h = 0; + + operator bool() const { return w == 0 && h == 0; } +}; +} + +#endif diff --git a/include/llmr/util/variant.hpp b/include/llmr/util/variant.hpp index 170bcad9f..ff83447ff 100644 --- a/include/llmr/util/variant.hpp +++ b/include/llmr/util/variant.hpp @@ -16,6 +16,8 @@ #define VARIANT_INLINE __attribute__((noinline)) #endif +namespace llmr { + namespace util { namespace detail { @@ -377,4 +379,6 @@ operator<< (std::basic_ostream& out, Variant const& rhs) } +} + #endif // UTIL_VARIANT_HPP diff --git a/include/llmr/util/vec.hpp b/include/llmr/util/vec.hpp index 0191e3868..770776160 100644 --- a/include/llmr/util/vec.hpp +++ b/include/llmr/util/vec.hpp @@ -25,12 +25,45 @@ struct vec2 { inline vec2(const vec2& o) : x(o.x), y(o.y) {} + template + inline vec2(const U& u) : x(u.x), y(u.y) {} + inline vec2(T x, T y) : x(x), y(y) {} inline bool operator==(const vec2& rhs) const { return x == rhs.x && y == rhs.y; } + template + inline typename std::enable_if::value, vec2>::type + operator*(O o) const { + return {x * o, y * o}; + } + + template + inline typename std::enable_if::value, vec2>::type & + operator*=(O o) { + x *= o; + y *= o; + } + + template + inline typename std::enable_if::value, vec2>::type + operator-(O o) const { + return {x - o, y - o}; + } + + template + inline typename std::enable_if::value, vec2>::type + operator-(const O &o) const { + return {x - o.x, y - o.y}; + } + + template + inline vec2 matMul(const M &m) const { + return {m[0] * x + m[1] * y, m[2] * x + m[3] * y}; + } + template::has_quiet_NaN, int>::type = 0> inline operator bool() const { return !isnan(x) && !isnan(y); diff --git a/src/geometry/glyph_atlas.cpp b/src/geometry/glyph_atlas.cpp index 05ede1476..df62ced8b 100644 --- a/src/geometry/glyph_atlas.cpp +++ b/src/geometry/glyph_atlas.cpp @@ -39,12 +39,12 @@ Rect GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_na } // The glyph bitmap has zero width. - if (!glyph.width) { - return { 0, 0, 0, 0 }; + if (!glyph.metrics.width) { + return Rect{ 0, 0, 0, 0 }; } - uint16_t buffered_width = glyph.width + buffer * 2; - uint16_t buffered_height = glyph.height + buffer * 2; + uint16_t buffered_width = glyph.metrics.width + buffer * 2; + uint16_t buffered_height = glyph.metrics.height + buffer * 2; // Add a 1px border around every image. uint16_t pack_width = buffered_width; diff --git a/src/geometry/interpolate.cpp b/src/geometry/interpolate.cpp new file mode 100644 index 000000000..cb4165cf2 --- /dev/null +++ b/src/geometry/interpolate.cpp @@ -0,0 +1,42 @@ +#include + +namespace llmr { + +Anchors interpolate(const std::vector &vertices, float spacing, + float minScale, int start) { + float distance = 0.0f; + float markedDistance = 0.0f; + int added = start; + + Anchors points; + + auto end = vertices.end() - 1; + int i = 0; + for (auto it = vertices.begin(); it != end; it++, i++) { + const Coordinate &a = *(it), b = *(it + 1); + + float segmentDist = util::dist(a, b); + float angle = util::angle_to(b, a); + + while (markedDistance + spacing < distance + segmentDist) { + markedDistance += 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; + + if (x >= 0 && x < 4096 && y >= 0 && y < 4096) { + points.emplace_back(x, y, angle, s, i); + } + + added++; + } + + distance += segmentDist; + } + + return points; +} +} diff --git a/src/geometry/text_buffer.cpp b/src/geometry/text_buffer.cpp index 92580cdc3..cf130225e 100644 --- a/src/geometry/text_buffer.cpp +++ b/src/geometry/text_buffer.cpp @@ -6,28 +6,27 @@ using namespace llmr; -const double TextVertexBuffer::angleFactor = 128 / M_PI; +const double TextVertexBuffer::angleFactor = 128.0 / M_PI; - -size_t TextVertexBuffer::add(const GlyphPosition& pos) { +size_t TextVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, uint16_t tx, uint16_t ty, float angle, float minzoom, std::array range, float maxzoom, float labelminzoom) { size_t idx = index(); void *data = addElement(); int16_t *shorts = static_cast(data); - shorts[0] = pos.x; - shorts[1] = pos.y; - shorts[2] = round(pos.ox * 64); // use 1/64 pixels for placement - shorts[3] = round(pos.oy * 64); + shorts[0] = x; + shorts[1] = y; + shorts[2] = round(ox * 64); // use 1/64 pixels for placement + shorts[3] = round(oy * 64); uint8_t *ubytes = static_cast(data); - ubytes[8] = pos.tx / 4; - ubytes[9] = pos.ty / 4; - ubytes[10] = pos.labelminzoom * 10; - ubytes[11] = pos.minzoom * 10; // 1/10 zoom levels: z16 == 160. - ubytes[12] = fmin(pos.maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. - ubytes[13] = (int16_t)round(pos.angle * angleFactor) % 256; - ubytes[14] = util::max((int16_t)round(pos.rangefrom * angleFactor), (int16_t)0) % 256; - ubytes[15] = util::min((int16_t)round(pos.rangeto * angleFactor), (int16_t)255) % 256; + ubytes[8] = tx / 4; + ubytes[9] = ty / 4; + ubytes[10] = labelminzoom * 10; + ubytes[11] = minzoom * 10; // 1/10 zoom levels: z16 == 160. + ubytes[12] = fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. + ubytes[13] = (int16_t)round(angle * angleFactor) % 256; + ubytes[14] = util::max((int16_t)round(range[0] * angleFactor), (int16_t)0) % 256; + ubytes[15] = util::min((int16_t)round(range[1] * angleFactor), (int16_t)255) % 256; return idx; } diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 366d8f772..95868c95a 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -13,7 +13,8 @@ TileParser::TileParser(const std::string& data, TileData& tile, const Style& sty : data(pbf((const uint8_t *)data.data(), data.size())), tile(tile), style(style), - glyphAtlas(glyphAtlas) { + glyphAtlas(glyphAtlas), + placement(tile.id.z) { parseGlyphs(); parseStyleLayers(style.layers); } @@ -24,13 +25,14 @@ bool TileParser::obsolete() const { void TileParser::parseGlyphs() { for (const std::pair pair : data.faces) { - const std::string& name = pair.first; - const VectorTileFace& face = pair.second; + const std::string &name = pair.first; + const VectorTileFace &face = pair.second; - GlyphPositions& rect = rects[name]; - - for (const VectorTileGlyph& glyph : face.glyphs) { - rect.emplace(glyph.id, glyphAtlas.addGlyph((uint64_t)tile.id, name, glyph)); + GlyphPositions &glyphs = faces[name]; + for (const VectorTileGlyph &glyph : face.glyphs) { + const Rect rect = + glyphAtlas.addGlyph((uint64_t)tile.id, name, glyph); + glyphs.emplace_back(Glyph{rect, glyph.metrics}); } } } @@ -137,12 +139,14 @@ std::shared_ptr TileParser::createTextBucket(const VectorTileLayer& laye } // TODO: currently hardcoded to use the first font stack. - const std::map>& shaping = layer.shaping.begin()->second; + const std::map& shaping = layer.shaping.begin()->second; - std::vector faces; + const FaceGlyphPositions& const_faces = faces; + + IndexedFaceGlyphPositions faces; for (const std::string& face : layer.faces) { - auto it = data.faces.find(face); - if (it == data.faces.end()) { + auto it = const_faces.find(face); + if (it == const_faces.end()) { // This layer references an unknown face. return nullptr; } @@ -150,7 +154,7 @@ std::shared_ptr TileParser::createTextBucket(const VectorTileLayer& laye } std::shared_ptr bucket = std::make_shared( - tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc); + tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement); FilteredVectorTileLayer filtered_layer(layer, bucket_desc); for (const pbf& feature_pbf : filtered_layer) { diff --git a/src/map/vector_tile.cpp b/src/map/vector_tile.cpp index b5596cdda..39f9e7e4e 100644 --- a/src/map/vector_tile.cpp +++ b/src/map/vector_tile.cpp @@ -117,6 +117,9 @@ std::ostream& llmr::operator<<(std::ostream& os, const VectorTileFace& face) { } +// -1 overflows to maximum value +VectorTileGlyph::VectorTileGlyph() {} + VectorTileGlyph::VectorTileGlyph(pbf glyph) { while (glyph.next()) { if (glyph.tag == 1) { // id @@ -124,15 +127,15 @@ VectorTileGlyph::VectorTileGlyph(pbf glyph) { } else if (glyph.tag == 2) { // bitmap bitmap = glyph.string(); } else if (glyph.tag == 3) { // width - width = glyph.varint(); + metrics.width = glyph.varint(); } else if (glyph.tag == 4) { // height - height = glyph.varint(); + metrics.height = glyph.varint(); } else if (glyph.tag == 5) { // left - left = glyph.svarint(); + metrics.left = glyph.svarint(); } else if (glyph.tag == 6) { // top - top = glyph.svarint(); + metrics.top = glyph.svarint(); } else if (glyph.tag == 7) { // advance - advance = glyph.varint(); + metrics.advance = glyph.varint(); } else { glyph.skip(); } @@ -140,8 +143,8 @@ VectorTileGlyph::VectorTileGlyph(pbf glyph) { } std::ostream& llmr::operator<<(std::ostream& os, const VectorTileGlyph& glyph) { - os << "Glyph " << glyph.id << ": " << glyph.width << "x" << glyph.height << - " (" << glyph.left << "/" << glyph.top << ") +" << glyph.advance << std::endl; + os << "Glyph " << glyph.id << ": " << glyph.metrics.width << "x" << glyph.metrics.height << + " (" << glyph.metrics.left << "/" << glyph.metrics.top << ") +" << glyph.metrics.advance << std::endl; return os; } @@ -151,7 +154,7 @@ public: uint32_t text = 0; uint32_t stack = 0; - std::vector placements; + std::vector placements; }; VectorTileLabel::VectorTileLabel(pbf label) { @@ -218,14 +221,7 @@ VectorTileLayer::VectorTileLayer(pbf layer) : data(layer) { } } -VectorTilePlacement::VectorTilePlacement(uint32_t face, uint32_t glyph, uint32_t x, uint32_t y) - : face(face), - glyph(glyph), - x(x), - y(y) { -} - -std::ostream& llmr::operator<<(std::ostream& os, const VectorTilePlacement& placement) { +std::ostream& llmr::operator<<(std::ostream& os, const GlyphPlacement& placement) { os << "Glyph " << placement.face << "/" << placement.glyph << ": " << placement.x << "/" << placement.y << std::endl; return os; } diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 2ca7f6156..9f2446e3d 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -7,45 +7,116 @@ #include #include #include +#include #include #include #include -#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#define BUFFER_OFFSET(i) ((char *) nullptr + (i)) #include using namespace llmr; -TextBucket::TextBucket(const std::shared_ptr& vertexBuffer, - const std::shared_ptr& triangleElementsBuffer, - const BucketDescription& bucket_desc) - : geometry(bucket_desc.geometry), +TextBucket::TextBucket( + const std::shared_ptr &vertexBuffer, + const std::shared_ptr &triangleElementsBuffer, + const BucketDescription &bucket_desc, Placement &placement) + : geom_desc(bucket_desc.geometry), vertexBuffer(vertexBuffer), triangleElementsBuffer(triangleElementsBuffer), + placement(placement), vertex_start(vertexBuffer->index()), - triangle_elements_start(triangleElementsBuffer->index()) { -} + triangle_elements_start(triangleElementsBuffer->index()) {} -void TextBucket::addFeature(const VectorTileFeature& feature, - const std::vector& faces, - const std::map>& shaping) { - auto it_prop = feature.properties.find(geometry.text_field); +void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, + PlacementRange placementRange, float zoom) { + placementZoom += 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 &width = glyph.width; + const auto &height = glyph.height; + const auto &angle = glyph.angle; + + float minZoom = util::max( + static_cast(zoom + log(glyph.glyphBox.minScale) / log(2)), + placementZoom); + float maxZoom = util::min( + static_cast(zoom + log(glyph.glyphBox.maxScale) / log(2)), + 25.0f); + const auto &glyphAnchor = glyph.glyphBox.anchor; + + if (maxZoom <= minZoom) + continue; + + // 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; + } + + 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(); + } + + // 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; + + // coordinates (2 triangles) + vertexBuffer->add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, + tex.y, angle, minZoom, placementRange, maxZoom, + placementZoom); + vertexBuffer->add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, + tex.x + width, tex.y, angle, minZoom, placementRange, + maxZoom, placementZoom); + vertexBuffer->add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, + tex.y + height, angle, minZoom, placementRange, + maxZoom, placementZoom); + vertexBuffer->add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, + tex.x + width, tex.y + height, angle, minZoom, + placementRange, maxZoom, placementZoom); + + // add the two triangles, referencing the four coordinates we just + // inserted. + triangleElementsBuffer->add(triangleIndex + 0, triangleIndex + 1, + triangleIndex + 1); + triangleElementsBuffer->add(triangleIndex + 1, triangleIndex + 2, + triangleIndex + 3); + + triangleGroup.vertex_length += glyph_vertex_length; + } +}; + +void TextBucket::addFeature(const VectorTileFeature &feature, + const IndexedFaceGlyphPositions &faces, + const std::map &shapings) { + auto it_prop = feature.properties.find(geom_desc.text_field); if (it_prop == feature.properties.end()) { // feature does not have the correct property return; } - const Value& value = it_prop->second; + const Value &value = it_prop->second; - - auto it_shaping = shaping.find(value); - if (it_shaping == shaping.end()) { + auto it_shaping = shapings.find(value); + if (it_shaping == shapings.end()) { // we lack shaping information for this label return; } - const std::vector& placements = it_shaping->second; + const Shaping &shaping = it_shaping->second; // std::cerr << "we have shaping for " << value << std::endl; @@ -60,31 +131,24 @@ void TextBucket::addFeature(const VectorTileFeature& feature, while ((cmd = geometry.next(x, y)) != Geometry::end) { if (cmd == Geometry::move_to) { if (!line.empty()) { - addLabel(line, faces, placements); + placement.addFeature(*this, line, geom_desc, faces, shaping); line.clear(); } } line.emplace_back(x, y); } if (line.size()) { - addLabel(line, faces, placements); + placement.addFeature(*this, line, geom_desc, faces, shaping); } } -void TextBucket::addLabel(const std::vector& line, - const std::vector& faces, - const std::vector& placements) { - // TODO -} - -void TextBucket::render(Painter& painter, const std::string& layer_name, const Tile::ID& id) { +void TextBucket::render(Painter &painter, const std::string &layer_name, + const Tile::ID &id) { painter.renderText(*this, layer_name, id); } -bool TextBucket::empty() const { - return triangleGroups.empty(); -} +bool TextBucket::empty() const { return triangleGroups.empty(); } -void TextBucket::drawGlyphs(TextShader& shader) { +void TextBucket::drawGlyphs(TextShader &shader) { // noop } diff --git a/src/text/collision.cpp b/src/text/collision.cpp index 1d08b53a2..e02dfe697 100644 --- a/src/text/collision.cpp +++ b/src/text/collision.cpp @@ -1,9 +1,29 @@ #include +#include #include +#include +#include +#include +#include + +namespace llmr { +namespace bg = boost::geometry; +namespace bgm = bg::model; +namespace bgi = bg::index; +typedef bgm::point Point; +typedef bgm::box Box; +typedef std::pair PlacementValue; +typedef bgi::rtree> Tree; +} + using namespace llmr; -Collision::Collision() { +Collision::~Collision() { + delete ((Tree *)tree); +} + +Collision::Collision() : tree(new Tree()) { const float m = 4096; // Hack to prevent cross-tile labels @@ -28,7 +48,7 @@ Collision::Collision() { CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2); } -GlyphBox getMergedGlyphs(const GlyphBoxes &glyphs, bool horizontal, +GlyphBox getMergedGlyphs(const PlacedGlyphs &placed_glyphs, bool horizontal, const CollisionAnchor &anchor) { GlyphBox mergedGlyphs; const float inf = std::numeric_limits::infinity(); @@ -37,28 +57,28 @@ GlyphBox getMergedGlyphs(const GlyphBoxes &glyphs, bool horizontal, mergedGlyphs.anchor = anchor; CollisionRect &box = mergedGlyphs.box; - for (const GlyphBox &glyph : glyphs) { - const CollisionRect &gbox = glyph.box; + for (const PlacedGlyph &placed_glyph : placed_glyphs) { + const CollisionRect &gbox = placed_glyph.glyphBox.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); + util::max(mergedGlyphs.minScale, placed_glyph.glyphBox.minScale); } return mergedGlyphs; } -PlacementProperty Collision::place(const GlyphBoxes &boxes, +PlacementProperty Collision::place(const PlacedGlyphs &placed_glyphs, const CollisionAnchor &anchor, float minPlacementScale, float maxPlacementScale, float padding, bool horizontal) { float minScale = std::numeric_limits::infinity(); - for (const GlyphBox &box : boxes) { - minScale = util::min(minScale, box.minScale); + for (const PlacedGlyph &placed_glyph : placed_glyphs) { + minScale = util::min(minScale, placed_glyph.glyphBox.minScale); } minPlacementScale = util::max(minPlacementScale, minScale); @@ -67,9 +87,11 @@ PlacementProperty Collision::place(const GlyphBoxes &boxes, // for horizontal labels. GlyphBoxes glyphs; if (horizontal) { - glyphs.push_back(getMergedGlyphs(boxes, horizontal, anchor)); + glyphs.push_back(getMergedGlyphs(placed_glyphs, horizontal, anchor)); } else { - glyphs = boxes; + for (const PlacedGlyph &placed_glyph : placed_glyphs) { + glyphs.push_back(placed_glyph.glyphBox); + } } // Calculate bboxes for all the glyphs @@ -89,11 +111,11 @@ PlacementProperty Collision::place(const GlyphBoxes &boxes, // Calculate the minimum scale the entire label can be shown without // collisions - float scale = - getPlacementScale(boxes, minPlacementScale, maxPlacementScale, padding); + float scale = getPlacementScale(glyphs, minPlacementScale, + maxPlacementScale, padding); // Return if the label can never be placed without collision - if (scale < 0) { + if (scale < 0 || scale == std::numeric_limits::infinity()) { return PlacementProperty{}; } @@ -137,8 +159,8 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs, Point{maxPlacedX, maxPlacedY}}; std::vector blocking; - tree.query(boost::geometry::index::intersects(query_box), - std::back_inserter(blocking)); + ((Tree *)tree) + ->query(bgi::intersects(query_box), std::back_inserter(blocking)); if (blocking.size()) { const CollisionAnchor &na = anchor; // new anchor @@ -218,8 +240,8 @@ PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, Point{maxPlacedX, maxPlacedY}}; std::vector blocking; - tree.query(boost::geometry::index::intersects(query_box), - std::back_inserter(blocking)); + ((Tree *)tree) + ->query(bgi::intersects(query_box), std::back_inserter(blocking)); for (const PlacementValue &value : blocking) { const Box &s = value.first; @@ -267,6 +289,8 @@ void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, float placementScale, const PlacementRange &placementRange, bool horizontal, float padding) { + assert(placementScale != std::numeric_limits::infinity()); + for (const GlyphBox &glyph : glyphs) { const CollisionRect &bbox = glyph.bbox; const CollisionRect &box = glyph.box; @@ -288,6 +312,9 @@ void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, placement.maxScale = glyph.maxScale; placement.padding = padding; - tree.insert({bounds, placement}); + 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>())); + + ((Tree *)tree)->insert({bounds, placement}); } } diff --git a/src/text/placement.cpp b/src/text/placement.cpp new file mode 100644 index 000000000..2380e191e --- /dev/null +++ b/src/text/placement.cpp @@ -0,0 +1,320 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace llmr; + +const int Placement::tileExtent = 4096; +const int Placement::glyphSize = + 24; // size in pixels of this glyphs in the tile +const float Placement::minScale = 0.125; // underscale by 3 zoom levels + +Placement::Placement(int8_t zoom) + : zoom(zoom), + zOffset(log(256.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(exp(log(2) * util::min((25.5 - zoom), 3.0))) {} + +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 IndexedFaceGlyphPositions &faces) { + if (placed.face < faces.size()) { + const GlyphPositions &face = *faces[placed.face]; + if (&face) { + if (placed.glyph < face.size()) { + return face[placed.glyph]; + } + } + } + + return null_glyph; +} + +struct GlyphInstance { + explicit GlyphInstance(const vec2 &anchor) : anchor(anchor) {} + explicit GlyphInstance(const vec2 &anchor, float offset, + float minScale, float maxScale, float angle) + : anchor(anchor), + offset(offset), + minScale(minScale), + maxScale(maxScale), + angle(angle) {} + + const vec2 anchor; + const float offset = 0.0f; + const float minScale = Placement::minScale; + const float maxScale = std::numeric_limits::infinity(); + const float angle = 0.0f; +}; + +typedef std::vector GlyphInstances; + +void getSegmentGlyphs(std::back_insert_iterator glyphs, + Anchor &anchor, float offset, + const std::vector &line, int segment, + int8_t direction, float maxAngleDelta) { + const bool upsideDown = direction < 0; + + if (offset < 0) + direction *= -1; + + if (direction > 0) + segment++; + + vec2 newAnchor = anchor; + + if (line.size() <= segment) { + return; + } + vec2 end = line[segment]; + float prevscale = std::numeric_limits::infinity(); + float prevAngle = 0.0f; + + offset = fabs(offset); + + const float placementScale = anchor.scale; + + while (true) { + const float dist = util::dist(newAnchor, end); + const float scale = offset / dist; + float angle = -atan2(end.x - newAnchor.x, end.y - newAnchor.y) + + direction * M_PI / 2.0f; + if (upsideDown) + angle += M_PI; + + // Don't place around sharp corners + float angleDiff = fmod((angle - prevAngle), (2.0f * M_PI)); + if (prevAngle && angleDiff > maxAngleDelta) { + anchor.scale = prevscale; + break; + } + + glyphs = GlyphInstance{ + /* anchor */ newAnchor, + /* offset */ static_cast(upsideDown ? M_PI : 0.0), + /* minScale */ scale, + /* maxScale */ prevscale, + /* angle */ static_cast( + fmod((angle + 2.0 * M_PI), (2.0 * M_PI)))}; + + if (scale <= placementScale) + break; + + newAnchor = end; + + // skip duplicate nodes + while (newAnchor == end) { + segment += direction; + if (line.size() <= segment) { + anchor.scale = scale; + return; + } + end = line[segment]; + } + + vec2 normal = util::normal(newAnchor, end) * dist; + newAnchor = newAnchor - normal; + + prevscale = scale; + prevAngle = angle; + } +} + +PlacedGlyphs getGlyphs(Anchor &anchor, float advance, const Shaping &shaping, + const IndexedFaceGlyphPositions &faces, float fontScale, + bool horizontal, const std::vector &line, + float maxAngleDelta, float rotate) { + // The total text advance is the width of this label. + + // TODO: figure out correct ascender height. + vec2 origin{-advance / 2, -17}; + + // TODO: allow setting an alignment + // var alignment = 'center'; + // if (alignment == 'center') { + // origin.x -= advance / 2; + // } else if (alignment == 'right') { + // origin.x -= advance; + // } + + PlacedGlyphs glyphs; + + const uint32_t buffer = 3; + + for (const GlyphPlacement &placed : shaping) { + const Glyph &glyph = getGlyph(placed, faces); + if (!glyph) { + // This glyph is empty and doesn't have any pixels that we'd need to + // show. + continue; + } + + uint32_t width = glyph.metrics.width; + uint32_t height = glyph.metrics.height; + + width += buffer * 2; + height += buffer * 2; + + // Increase to next number divisible by 4, but at least 1. + // This is so we can scale down the texture coordinates and pack them + // into 2 bytes rather than 4 bytes. + width += (4 - width % 4); + height += (4 - height % 4); + + float x = + (origin.x + placed.x + glyph.metrics.left - buffer + width / 2) * + fontScale; + + 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); + + } 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 x2 = x1 + width; + const float y2 = y1 + height; + + const vec2 otl{x1, y1}; + const vec2 otr{x2, y1}; + const vec2 obl{x1, y2}; + const vec2 obr{x2, y2}; + + const CollisionRect obox{fontScale *x1, fontScale *y1, + fontScale *x2, fontScale *y2}; + + for (const GlyphInstance &instance : glyphInstances) { + vec2 tl = otl; + vec2 tr = otr; + vec2 bl = obl; + vec2 br = obr; + + CollisionRect box = obox; + + // Clamp to -90/+90 degrees + const float angle = instance.angle + rotate; + + if (angle) { + // Compute the transformation matrix. + float angle_sin = sin(angle); + float angle_cos = cos(angle); + std::array 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)}; + } + + // Remember the glyph for later insertion. + glyphs.emplace_back(PlacedGlyph{ + tl, tr, bl, br, glyph.rect, width, height, + static_cast( + fmod((anchor.angle + rotate + instance.offset + 2 * M_PI), + (2 * M_PI))), + GlyphBox{box, instance.minScale, instance.maxScale, + instance.anchor, horizontal}}); + } + } + + // Prevent label from extending past the end of the line + for (PlacedGlyph &g : glyphs) { + g.glyphBox.minScale = util::max(g.glyphBox.minScale, anchor.scale); + } + + return glyphs; +} + +void Placement::addFeature(TextBucket& bucket, + const std::vector &line, + const BucketGeometryDescription &info, + const IndexedFaceGlyphPositions &faces, + const Shaping &shaping) { + + const bool horizontal = info.path == TextPathType::Horizontal; + const float padding = info.padding; + const float maxAngleDelta = info.maxAngleDelta; + const float textMinDistance = info.textMinDistance; + const float rotate = info.rotate; + const float fontScale = + (tileExtent / util::tileSize) / (glyphSize / info.font_size); + + const float advance = measureText(faces, shaping); + Anchors anchors; + + // fprintf(stderr, "adding feature with advance %f\n", advance); + + if (line.size() == 1) { + // Point labels + anchors = Anchors{{Anchor{ + static_cast(line[0].x), static_cast(line[0].y), + 0, minScale}}}; + + } else { + // Line labels + anchors = interpolate(line, textMinDistance, 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); + } + + for (Anchor anchor : anchors) { + PlacedGlyphs glyphs = + getGlyphs(anchor, advance, shaping, faces, fontScale, horizontal, + line, maxAngleDelta, rotate); + PlacementProperty place = + collision.place(glyphs, anchor, anchor.scale, maxPlacementScale, + padding, horizontal); + + if (place.zoom >= 0.0f) { + bucket.addGlyphs(glyphs, place.zoom, place.rotationRange, + zoom - zOffset); + } + } +} + +float Placement::measureText(const IndexedFaceGlyphPositions &faces, + const Shaping &shaping) { + float advance = 0; + + // TODO: advance is not calculated correctly. we should instead use the + // bounding box of the glyph placement. + for (const GlyphPlacement &shape : shaping) { + advance += getGlyph(shape, faces).metrics.advance; + } + + return advance; +} diff --git a/src/text/rotation_range.cpp b/src/text/rotation_range.cpp index d5840edf1..3c5695898 100644 --- a/src/text/rotation_range.cpp +++ b/src/text/rotation_range.cpp @@ -225,13 +225,12 @@ CollisionList rotatingFixedCollisions(const CollisionRect &rotating, /* * Calculate the range a box conflicts with a second box */ -template -CollisionRange rotationRange(const InsertingBox &inserting, - const BlockingBox &blocker, float scale) { +CollisionRange rotationRange(const GlyphBox &inserting, + const PlacementBox &blocker, float scale) { CollisionList collisions; - const InsertingBox &a = inserting; - const BlockingBox &b = blocker; + const GlyphBox &a = inserting; + const PlacementBox &b = blocker; // Instead of scaling the boxes, we move the anchors CollisionAnchor relativeAnchor{ @@ -242,12 +241,12 @@ CollisionRange rotationRange(const InsertingBox &inserting, if (a.rotate && b.rotate) { collisions = rotatingRotatingCollisions(a.box, b.box, relativeAnchor); } else if (a.rotate) { - const CollisionRect box = { + const CollisionRect box { b.box.tl.x + relativeAnchor.x, b.box.tl.y + relativeAnchor.y, b.box.br.x + relativeAnchor.x, b.box.br.y + relativeAnchor.y}; collisions = rotatingFixedCollisions(a.box, box); } else if (b.rotate) { - const CollisionRect box = { + const CollisionRect box { a.box.tl.x - relativeAnchor.x, a.box.tl.y - relativeAnchor.y, a.box.br.x - relativeAnchor.x, a.box.br.y - relativeAnchor.y}; collisions = rotatingFixedCollisions(b.box, box);