update symbol placement to be more like js
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
void addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string,
|
||||
// Adds placed items to the buffer.
|
||||
template <typename Buffer>
|
||||
void addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, PlacementRange placementRange);
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,95 +169,144 @@ 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) {
|
||||
bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; }
|
||||
|
||||
const PlacementRange fullRange{{2 * M_PI, 0}};
|
||||
|
||||
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;
|
||||
|
||||
|
||||
const bool horizontalText = properties.text.rotation_alignment == RotationAlignmentType::Viewport;
|
||||
const bool horizontalIcon = properties.icon.rotation_alignment == RotationAlignmentType::Viewport;
|
||||
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;
|
||||
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;
|
||||
|
||||
Anchors anchors;
|
||||
|
||||
if (properties.placement == PlacementType::Line) {
|
||||
// Line labels
|
||||
anchors = interpolate(line, properties.min_distance, minScale, collision.maxPlacementScale,
|
||||
collision.tilePixelRatio);
|
||||
|
||||
// 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);
|
||||
|
||||
} else {
|
||||
// Point labels
|
||||
anchors = {Anchor{float(line[0].x), float(line[0].y), 0, minScale}};
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (iconScale) {
|
||||
if (!properties.icon.ignore_placement) {
|
||||
collision.insert(iconPlacement.boxes, anchor, iconScale, iconRange, horizontalIcon);
|
||||
}
|
||||
addSymbols(icon, iconPlacement.shapes, iconScale, iconRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
template <typename Buffer>
|
||||
void SymbolBucket::addSymbols(Buffer &buffer, const Symbols &symbols, float placementZoom, PlacementRange placementRange, float zoom) {
|
||||
placementZoom += zoom;
|
||||
void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale,
|
||||
PlacementRange placementRange) {
|
||||
const float zoom = collision.zoom;
|
||||
|
||||
for (const Symbol &symbol : symbols) {
|
||||
const float placementZoom = std::log(scale) / std::log(2) + zoom;
|
||||
|
||||
for (const PlacedGlyph &symbol : symbols) {
|
||||
const auto &tl = symbol.tl;
|
||||
const auto &tr = symbol.tr;
|
||||
const auto &bl = symbol.bl;
|
||||
@@ -261,10 +314,9 @@ void SymbolBucket::addSymbols(Buffer &buffer, const Symbols &symbols, float plac
|
||||
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);
|
||||
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)
|
||||
@@ -278,90 +330,30 @@ void SymbolBucket::addSymbols(Buffer &buffer, const Symbols &symbols, float plac
|
||||
|
||||
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)
|
||||
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);
|
||||
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);
|
||||
|
||||
triangleGroup.vertex_length += glyph_vertex_length;
|
||||
triangleGroup.elements_length += 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SymbolBucket::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 &angle = glyph.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;
|
||||
|
||||
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)
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
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(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));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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{/* 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{
|
||||
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, 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);
|
||||
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),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
namespace mbgl {
|
||||
|
||||
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;
|
||||
}
|
||||
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.
|
||||
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.
|
||||
|
||||
// TODO: allow setting an alignment
|
||||
// var alignment = 'center';
|
||||
// if (alignment == 'center') {
|
||||
// origin.x -= advance / 2;
|
||||
// } else if (alignment == 'right') {
|
||||
// origin.x -= advance;
|
||||
// }
|
||||
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;
|
||||
|
||||
float x =
|
||||
(origin.x + placed.x + glyph.metrics.left - buffer + glyph.rect.w / 2) *
|
||||
fontScale;
|
||||
if (!glyph)
|
||||
continue;
|
||||
|
||||
if (!(rect && rect.w > 0 && rect.h > 0))
|
||||
continue;
|
||||
|
||||
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});
|
||||
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 == 0.0f) {
|
||||
boxes.emplace_back(glyphBox);
|
||||
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);
|
||||
// TODO avoid creating the boxes in the first place?
|
||||
if (horizontal)
|
||||
boxes = {getMergedBoxes(boxes, anchor)};
|
||||
|
||||
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}}};
|
||||
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user