update symbol placement to be more like js

This commit is contained in:
Konstantin Käfer
2014-08-04 18:24:22 +02:00
parent 9dd50a29e1
commit 36181b0922
18 changed files with 576 additions and 573 deletions

View File

@@ -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

View File

@@ -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;
};
}

View File

@@ -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:

View File

@@ -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;

View File

@@ -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};

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
};
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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");

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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;
}
}