update shaders and backport some collision changes
This commit is contained in:
@@ -6,13 +6,13 @@
|
||||
namespace mbgl {
|
||||
|
||||
class IconVertexBuffer : public Buffer<
|
||||
4 + // int16 x/y coordinates per vertex (== 4 bytes)
|
||||
4 // uint16 x/y coordinates of icon in sprite (== 4 bytes)
|
||||
20
|
||||
> {
|
||||
public:
|
||||
typedef int16_t vertex_type;
|
||||
static const double angleFactor;
|
||||
|
||||
size_t add(int16_t x, int16_t y, float ox, float oy, int16_t tx, int16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom);
|
||||
|
||||
void add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "bucket.hpp"
|
||||
#include <mbgl/geometry/vao.hpp>
|
||||
#include <mbgl/geometry/elements_buffer.hpp>
|
||||
#include <mbgl/geometry/text_buffer.hpp>
|
||||
#include <mbgl/geometry/icon_buffer.hpp>
|
||||
#include <mbgl/map/vector_tile.hpp>
|
||||
#include <mbgl/text/types.hpp>
|
||||
#include <mbgl/text/glyph.hpp>
|
||||
@@ -16,9 +18,6 @@
|
||||
namespace mbgl {
|
||||
|
||||
class Style;
|
||||
class TextVertexBuffer;
|
||||
class IconVertexBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class TextShader;
|
||||
class IconShader;
|
||||
class DotShader;
|
||||
@@ -29,9 +28,14 @@ class GlyphAtlas;
|
||||
class GlyphStore;
|
||||
class FontStack;
|
||||
|
||||
class SymbolBucket : public Bucket {
|
||||
typedef ElementGroup triangle_group_type;
|
||||
class SymbolFeature {
|
||||
public:
|
||||
pbf geometry;
|
||||
std::u32string label;
|
||||
std::string sprite;
|
||||
};
|
||||
|
||||
class SymbolBucket : public Bucket {
|
||||
public:
|
||||
SymbolBucket(
|
||||
TextVertexBuffer &textVertexBuffer,
|
||||
@@ -57,6 +61,14 @@ public:
|
||||
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 addGlyph(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);
|
||||
@@ -65,18 +77,20 @@ public:
|
||||
const StyleBucketSymbol &properties;
|
||||
|
||||
private:
|
||||
TextVertexBuffer& textVertexBuffer;
|
||||
IconVertexBuffer& iconVertexBuffer;
|
||||
TriangleElementsBuffer& triangleElementsBuffer;
|
||||
Placement &placement;
|
||||
|
||||
const size_t text_vertex_start;
|
||||
const size_t icon_vertex_start;
|
||||
const size_t triangle_elements_start;
|
||||
size_t icon_vertex_end = 0;
|
||||
VertexArrayObject array;
|
||||
struct {
|
||||
TextVertexBuffer vertices;
|
||||
TriangleElementsBuffer triangles;
|
||||
std::vector<ElementGroup> groups;
|
||||
} text;
|
||||
|
||||
struct {
|
||||
IconVertexBuffer vertices;
|
||||
TriangleElementsBuffer triangles;
|
||||
std::vector<ElementGroup> groups;
|
||||
} icon;
|
||||
|
||||
std::vector<triangle_group_type> triangleGroups;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,30 +11,53 @@ public:
|
||||
|
||||
void bind(char *offset);
|
||||
|
||||
void setImage(int32_t image);
|
||||
void setColor(const std::array<float, 4>& color);
|
||||
void setDimension(const std::array<float, 2>& dimension);
|
||||
void setSize(float size);
|
||||
void setRatio(float ratio);
|
||||
void setExtrudeMatrix(const std::array<float, 16>& exmatrix);
|
||||
void setAngle(float angle);
|
||||
void setZoom(float zoom);
|
||||
void setFlip(float flip);
|
||||
void setFadeDist(float fadedist);
|
||||
void setMinFadeZoom(float minfadezoom);
|
||||
void setMaxFadeZoom(float maxfadezoom);
|
||||
void setFadeZoom(float fadezoom);
|
||||
void setOpacity(float opacity);
|
||||
|
||||
private:
|
||||
int32_t a_pos = -1;
|
||||
int32_t a_offset = -1;
|
||||
int32_t a_tex = -1;
|
||||
int32_t a_angle = -1;
|
||||
int32_t a_minzoom = -1;
|
||||
int32_t a_maxzoom = -1;
|
||||
int32_t a_rangeend = -1;
|
||||
int32_t a_rangestart = -1;
|
||||
int32_t a_labelminzoom = -1;
|
||||
|
||||
int32_t image = -1;
|
||||
int32_t u_image = -1;
|
||||
std::array<float, 16> exmatrix = {{}};
|
||||
int32_t u_exmatrix = -1;
|
||||
|
||||
std::array<float, 4> color = {{}};
|
||||
int32_t u_color = -1;
|
||||
float angle = 0;
|
||||
int32_t u_angle = -1;
|
||||
|
||||
std::array<float, 2> dimension = {{}};
|
||||
int32_t u_dimension = -1;
|
||||
float zoom = 0;
|
||||
int32_t u_zoom = -1;
|
||||
|
||||
float size = 0;
|
||||
int32_t u_size = -1;
|
||||
float flip = 0;
|
||||
int32_t u_flip = -1;
|
||||
|
||||
float ratio = 0;
|
||||
int32_t u_ratio = -1;
|
||||
float fadedist = 0;
|
||||
int32_t u_fadedist = -1;
|
||||
|
||||
float minfadezoom = 0;
|
||||
int32_t u_minfadezoom = -1;
|
||||
|
||||
float maxfadezoom = 0;
|
||||
int32_t u_maxfadezoom = -1;
|
||||
|
||||
float fadezoom = 0;
|
||||
int32_t u_fadezoom = -1;
|
||||
|
||||
float opacity = 0;
|
||||
int32_t u_opacity = -1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,13 +8,12 @@ namespace mbgl {
|
||||
class Collision {
|
||||
|
||||
public:
|
||||
Collision();
|
||||
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 alwaysVisible);
|
||||
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,
|
||||
@@ -24,8 +23,11 @@ public:
|
||||
bool horizontal, float padding);
|
||||
|
||||
private:
|
||||
void *cTree;
|
||||
void *hTree;
|
||||
void *cTree;
|
||||
const float tilePixelRatio;
|
||||
const float zoom;
|
||||
const float maxPlacementScale;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ namespace mbgl {
|
||||
|
||||
template <typename T>
|
||||
struct Rect {
|
||||
explicit Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {}
|
||||
inline Rect() {}
|
||||
inline Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {}
|
||||
T x = 0, y = 0;
|
||||
T w = 0, h = 0;
|
||||
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
#include <mbgl/geometry/icon_buffer.hpp>
|
||||
#include <mbgl/platform/gl.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace mbgl;
|
||||
namespace mbgl {
|
||||
|
||||
void IconVertexBuffer::add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty) {
|
||||
const double IconVertexBuffer::angleFactor = 128.0 / M_PI;
|
||||
|
||||
size_t IconVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, int16_t tx, int16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom) {
|
||||
const size_t idx = index();
|
||||
void *data = addElement();
|
||||
|
||||
vertex_type *vertices = static_cast<vertex_type *>(data);
|
||||
vertices[0] = x;
|
||||
vertices[1] = y;
|
||||
int16_t *shorts = static_cast<int16_t *>(data);
|
||||
shorts[0] /* pos */ = x;
|
||||
shorts[1] /* pos */ = y;
|
||||
shorts[2] /* offset */ = std::round(ox * 64); // use 1/64 pixels for placement
|
||||
shorts[3] /* offset */ = std::round(oy * 64);
|
||||
|
||||
uint8_t *ubytes = static_cast<uint8_t *>(data);
|
||||
ubytes[8] /* labelminzoom */ = labelminzoom * 10;
|
||||
ubytes[9] /* minzoom */ = minzoom * 10; // 1/10 zoom levels: z16 == 160.
|
||||
ubytes[10] /* maxzoom */ = std::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160.
|
||||
ubytes[11] /* angle */ = (int16_t)std::round(angle * angleFactor) % 256;
|
||||
ubytes[12] /* rangeend */ = util::max((int16_t)std::round(range[0] * angleFactor), (int16_t)0) % 256;
|
||||
ubytes[13] /* rangestart */ = util::min((int16_t)std::round(range[1] * angleFactor), (int16_t)255) % 256;
|
||||
|
||||
shorts[8] /* tex */ = tx;
|
||||
shorts[9] /* tex */ = ty;
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
uint16_t *texture = static_cast<uint16_t *>(data);
|
||||
texture[2] = tx;
|
||||
texture[3] = ty;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mbgl/text/glyph_store.hpp>
|
||||
#include <mbgl/text/placement.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <mbgl/map/sprite.hpp>
|
||||
|
||||
#include <mbgl/util/utf.hpp>
|
||||
#include <mbgl/util/token.hpp>
|
||||
@@ -20,13 +21,8 @@ SymbolBucket::SymbolBucket(TextVertexBuffer &textVertexBuffer, IconVertexBuffer
|
||||
TriangleElementsBuffer &triangleElementsBuffer,
|
||||
const StyleBucketSymbol &properties, Placement &placement)
|
||||
: properties(properties),
|
||||
textVertexBuffer(textVertexBuffer),
|
||||
iconVertexBuffer(iconVertexBuffer),
|
||||
triangleElementsBuffer(triangleElementsBuffer),
|
||||
placement(placement),
|
||||
text_vertex_start(textVertexBuffer.index()),
|
||||
icon_vertex_start(iconVertexBuffer.index()),
|
||||
triangle_elements_start(triangleElementsBuffer.index()) {}
|
||||
placement(placement)
|
||||
{}
|
||||
|
||||
void SymbolBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc,
|
||||
const Tile::ID &id) {
|
||||
@@ -39,11 +35,11 @@ bool SymbolBucket::hasData() const {
|
||||
|
||||
|
||||
bool SymbolBucket::hasTextData() const {
|
||||
return !triangleGroups.empty();
|
||||
return !text.groups.empty();
|
||||
}
|
||||
|
||||
bool SymbolBucket::hasIconData() const {
|
||||
return icon_vertex_end > 0;
|
||||
return !icon.groups.empty() > 0;
|
||||
}
|
||||
|
||||
void SymbolBucket::addGlyph(uint64_t tileid, const std::string stackname,
|
||||
@@ -61,74 +57,72 @@ void SymbolBucket::addGlyph(uint64_t tileid, const std::string stackname,
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter,
|
||||
const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, GlyphAtlas &glyphAtlas,
|
||||
GlyphStore &glyphStore) {
|
||||
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();
|
||||
|
||||
std::vector<SymbolFeature> features;
|
||||
|
||||
if (!text && !icon) {
|
||||
return features;
|
||||
}
|
||||
|
||||
util::utf8_to_utf32 ucs4conv;
|
||||
|
||||
std::vector<std::pair<std::u32string, pbf>> labels;
|
||||
|
||||
// Determine and load glyph ranges
|
||||
{
|
||||
std::set<GlyphRange> ranges;
|
||||
std::set<GlyphRange> ranges;
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, filter);
|
||||
for (const pbf &feature_pbf : filtered_layer) {
|
||||
VectorTileFeature feature {feature_pbf, layer};
|
||||
FilteredVectorTileLayer filtered_layer(layer, filter);
|
||||
for (const pbf &feature_pbf : filtered_layer) {
|
||||
const VectorTileFeature feature {feature_pbf, layer};
|
||||
|
||||
if (text) {
|
||||
std::string u8string = util::replaceTokens(properties.text.field, feature.properties);
|
||||
SymbolFeature ft;
|
||||
|
||||
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) {
|
||||
convert.tolower(&u8string[0], &u8string[0] + u8string.size());
|
||||
}
|
||||
if (text) {
|
||||
std::string u8string = util::replaceTokens(properties.text.field, feature.properties);
|
||||
|
||||
std::u32string string = ucs4conv.convert(u8string);
|
||||
|
||||
if (string.size()) {
|
||||
// Loop through all characters of this text and collect unique codepoints.
|
||||
for (char32_t chr : string) {
|
||||
ranges.insert(getGlyphRange(chr));
|
||||
}
|
||||
|
||||
labels.emplace_back(string, feature.geometry);
|
||||
}
|
||||
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) {
|
||||
convert.tolower(&u8string[0], &u8string[0] + u8string.size());
|
||||
}
|
||||
|
||||
// TODO: refactor for simultaneous placement
|
||||
// TODO: refactor to use quads instead of GL_POINTS
|
||||
if (icon) {
|
||||
std::string field = util::replaceTokens(properties.icon.image, feature.properties);
|
||||
ft.label = ucs4conv.convert(u8string);
|
||||
|
||||
const Rect<uint16_t> rect = spriteAtlas.waitForImage(field, sprite);
|
||||
const uint16_t tx = rect.x + rect.w / 2;
|
||||
const uint16_t ty = rect.y + rect.h / 2;
|
||||
|
||||
Geometry::command cmd;
|
||||
pbf geom = feature.geometry;
|
||||
Geometry geometry(geom);
|
||||
int32_t x, y;
|
||||
while ((cmd = geometry.next(x, y)) != Geometry::end) {
|
||||
if (cmd == Geometry::move_to) {
|
||||
iconVertexBuffer.add(x, y, tx, ty);
|
||||
} else {
|
||||
Log::Warning(Event::ParseTile, "other command than move_to in icon geometry");
|
||||
}
|
||||
if (ft.label.size()) {
|
||||
// Loop through all characters of this text and collect unique codepoints.
|
||||
for (char32_t chr : ft.label) {
|
||||
ranges.insert(getGlyphRange(chr));
|
||||
}
|
||||
|
||||
icon_vertex_end = iconVertexBuffer.index();
|
||||
}
|
||||
}
|
||||
|
||||
glyphStore.waitForGlyphRanges(properties.text.font, ranges);
|
||||
if (icon) {
|
||||
ft.sprite = util::replaceTokens(properties.icon.image, feature.properties);
|
||||
}
|
||||
|
||||
if (ft.label.length() || ft.sprite.length()) {
|
||||
ft.geometry = feature.geometry;
|
||||
features.push_back(std::move(ft));
|
||||
}
|
||||
}
|
||||
|
||||
glyphStore.waitForGlyphRanges(properties.text.font, ranges);
|
||||
sprite.waitUntilLoaded();
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter,
|
||||
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;
|
||||
@@ -138,28 +132,86 @@ void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpress
|
||||
if (properties.text.vertical_align == TextVerticalAlignType::Bottom) verticalAlign = 1;
|
||||
else if (properties.text.vertical_align == TextVerticalAlignType::Top) verticalAlign = 0;
|
||||
|
||||
|
||||
|
||||
// Create a copy!
|
||||
const FontStack &fontStack = glyphStore.getFontStack(properties.text.font);
|
||||
GlyphPositions face;
|
||||
|
||||
for (const SymbolFeature &feature : features) {
|
||||
Shaping shaping;
|
||||
Rect<uint16_t> image;
|
||||
|
||||
// Shape and place all labels.
|
||||
for (const std::pair<std::u32string, pbf> &label : labels) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Shape labels.
|
||||
const Shaping shaping = fontStack.getShaping(label.first, properties.text.max_width,
|
||||
properties.text.line_height, horizontalAlign,
|
||||
verticalAlign, properties.text.letter_spacing);
|
||||
// if feature has icon, get sprite atlas position
|
||||
if (feature.sprite.length()) {
|
||||
image = spriteAtlas.waitForImage(feature.sprite, sprite);
|
||||
}
|
||||
|
||||
addGlyph(id.to_uint64(), properties.text.font, label.first, fontStack, glyphAtlas, face);
|
||||
|
||||
// Place labels.
|
||||
addFeature(label.second, face, shaping);
|
||||
// if either shaping or icon position is present, add the feature
|
||||
if (shaping.size() || image) {
|
||||
addFeature(feature.geometry, shaping, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping, const Rect<uint16_t> &image) {
|
||||
// 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()) {
|
||||
addFeature(line, shaping, image);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
line.emplace_back(x, y);
|
||||
}
|
||||
if (line.size()) {
|
||||
addFeature(line, shaping, image);
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const Rect<uint16_t> &image) {
|
||||
const float minScale = 0.5f;
|
||||
const float glyphSize = 24.0f;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 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.
|
||||
@@ -184,6 +236,79 @@ void SymbolBucket::addFeature(const pbf &geom_pbf, const GlyphPositions &face,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
for (const Symbol &symbol : symbols) {
|
||||
const auto &tl = symbol.tl;
|
||||
const auto &tr = symbol.tr;
|
||||
const auto &bl = symbol.bl;
|
||||
const auto &br = symbol.br;
|
||||
const auto &tex = symbol.tex;
|
||||
const auto &angle = symbol.angle;
|
||||
|
||||
float minZoom = util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)),
|
||||
placementZoom);
|
||||
float maxZoom =
|
||||
util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f);
|
||||
const auto &glyphAnchor = symbol.anchor;
|
||||
|
||||
if (maxZoom <= minZoom)
|
||||
continue;
|
||||
|
||||
// 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)
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
uniform sampler2D u_image;
|
||||
uniform vec2 u_dimension;
|
||||
uniform vec4 u_color;
|
||||
uniform float u_size;
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
varying vec2 v_tex;
|
||||
varying float v_alpha;
|
||||
|
||||
void main() {
|
||||
vec2 pos = (v_tex + (gl_PointCoord - 0.5) * u_size) / u_dimension;
|
||||
gl_FragColor = u_color * texture2D(u_image, pos);
|
||||
gl_FragColor = texture2D(u_texture, v_tex);
|
||||
gl_FragColor.a *= v_alpha;
|
||||
gl_FragColor.rgb *= gl_FragColor.a;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,73 @@
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_offset;
|
||||
attribute vec2 a_tex;
|
||||
attribute float a_angle;
|
||||
attribute float a_minzoom;
|
||||
attribute float a_maxzoom;
|
||||
attribute float a_rangeend;
|
||||
attribute float a_rangestart;
|
||||
attribute float a_labelminzoom;
|
||||
|
||||
uniform mat4 u_matrix;
|
||||
uniform float u_size;
|
||||
uniform float u_ratio;
|
||||
|
||||
// posmatrix is for the vertex position, exmatrix is for rotating and projecting
|
||||
// the extrusion vector.
|
||||
uniform mat4 u_posmatrix;
|
||||
uniform mat4 u_exmatrix;
|
||||
uniform float u_angle;
|
||||
uniform float u_zoom;
|
||||
uniform float u_flip;
|
||||
uniform float u_fadedist;
|
||||
uniform float u_minfadezoom;
|
||||
uniform float u_maxfadezoom;
|
||||
uniform float u_fadezoom;
|
||||
uniform float u_opacity;
|
||||
|
||||
uniform vec2 u_texsize;
|
||||
|
||||
varying vec2 v_tex;
|
||||
varying float v_alpha;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_matrix * vec4(a_pos, 0, 1);
|
||||
gl_PointSize = u_size;
|
||||
v_tex = a_tex * u_ratio;
|
||||
|
||||
float a_fadedist = 10.0;
|
||||
float rev = 0.0;
|
||||
|
||||
// u_angle is angle of the map, -128..128 representing 0..2PI
|
||||
// a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text
|
||||
float rotated = mod(a_angle + u_angle, 256.0);
|
||||
// if the label rotates with the map, and if the rotated label is upside down, hide it
|
||||
if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0;
|
||||
|
||||
// If the label should be invisible, we move the vertex outside
|
||||
// of the view plane so that the triangle gets clipped. This makes it easier
|
||||
// for us to create degenerate triangle strips.
|
||||
// u_zoom is the current zoom level adjusted for the change in font size
|
||||
float z = 2.0 - step(a_minzoom, u_zoom) - (1.0 - step(a_maxzoom, u_zoom)) + rev;
|
||||
|
||||
// fade out labels
|
||||
float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0);
|
||||
|
||||
if (u_fadedist >= 0.0) {
|
||||
v_alpha = alpha;
|
||||
} else {
|
||||
v_alpha = 1.0 - alpha;
|
||||
}
|
||||
if (u_maxfadezoom < a_labelminzoom) {
|
||||
v_alpha = 0.0;
|
||||
}
|
||||
if (u_minfadezoom >= a_labelminzoom) {
|
||||
v_alpha = 1.0;
|
||||
}
|
||||
|
||||
// if label has been faded out, clip it
|
||||
z += step(v_alpha, 0.0);
|
||||
|
||||
// all the angles are 0..256 representing 0..2PI
|
||||
// hide if (angle >= a_rangeend && angle < rangestart)
|
||||
z += step(a_rangeend, u_angle) * (1.0 - step(a_rangestart, u_angle));
|
||||
|
||||
gl_Position = u_posmatrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);
|
||||
v_tex = a_tex / u_texsize;
|
||||
|
||||
v_alpha *= u_opacity;
|
||||
}
|
||||
|
||||
@@ -17,62 +17,130 @@ IconShader::IconShader()
|
||||
}
|
||||
|
||||
a_pos = glGetAttribLocation(program, "a_pos");
|
||||
a_offset = glGetAttribLocation(program, "a_offset");
|
||||
a_tex = glGetAttribLocation(program, "a_tex");
|
||||
a_angle = glGetAttribLocation(program, "a_angle");
|
||||
a_minzoom = glGetAttribLocation(program, "a_minzoom");
|
||||
a_maxzoom = glGetAttribLocation(program, "a_maxzoom");
|
||||
a_rangeend = glGetAttribLocation(program, "a_rangeend");
|
||||
a_rangestart = glGetAttribLocation(program, "a_rangestart");
|
||||
a_labelminzoom = glGetAttribLocation(program, "a_labelminzoom");
|
||||
|
||||
u_matrix = glGetUniformLocation(program, "u_matrix");
|
||||
u_color = glGetUniformLocation(program, "u_color");
|
||||
u_size = glGetUniformLocation(program, "u_size");
|
||||
u_ratio = glGetUniformLocation(program, "u_ratio");
|
||||
u_dimension = glGetUniformLocation(program, "u_dimension");
|
||||
u_exmatrix = glGetUniformLocation(program, "u_exmatrix");
|
||||
u_angle = glGetUniformLocation(program, "u_angle");
|
||||
u_zoom = glGetUniformLocation(program, "u_zoom");
|
||||
u_flip = glGetUniformLocation(program, "u_flip");
|
||||
u_fadedist = glGetUniformLocation(program, "u_fadedist");
|
||||
u_minfadezoom = glGetUniformLocation(program, "u_minfadezoom");
|
||||
u_maxfadezoom = glGetUniformLocation(program, "u_maxfadezoom");
|
||||
u_fadezoom = glGetUniformLocation(program, "u_fadezoom");
|
||||
u_opacity = glGetUniformLocation(program, "u_opacity");
|
||||
|
||||
// fprintf(stderr, "IconShader:\n");
|
||||
// fprintf(stderr, " - u_matrix: %d\n", u_matrix);
|
||||
// fprintf(stderr, " - u_color: %d\n", u_color);
|
||||
// fprintf(stderr, " - u_size: %d\n", u_size);
|
||||
// fprintf(stderr, " - u_ratio: %d\n", u_ratio);
|
||||
// fprintf(stderr, " - u_dimension: %d\n", u_dimension);
|
||||
// fprintf(stderr, " - u_image: %d\n", u_image);
|
||||
// fprintf(stderr, " - u_exmatrix: %d\n", u_exmatrix);
|
||||
// fprintf(stderr, " - u_angle: %d\n", u_angle);
|
||||
// fprintf(stderr, " - u_zoom: %d\n", u_zoom);
|
||||
// fprintf(stderr, " - u_flip: %d\n", u_flip);
|
||||
// fprintf(stderr, " - u_fadedist: %d\n", u_fadedist);
|
||||
// fprintf(stderr, " - u_minfadezoom: %d\n", u_minfadezoom);
|
||||
// fprintf(stderr, " - u_maxfadezoom: %d\n", u_maxfadezoom);
|
||||
// fprintf(stderr, " - u_fadezoom: %d\n", u_fadezoom);
|
||||
// fprintf(stderr, " - u_opacity: %d\n", u_opacity);
|
||||
}
|
||||
|
||||
void IconShader::bind(char *offset) {
|
||||
const int stride = 20;
|
||||
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, stride, offset + 0);
|
||||
|
||||
glEnableVertexAttribArray(a_offset);
|
||||
glVertexAttribPointer(a_offset, 2, GL_SHORT, false, stride, offset + 4);
|
||||
|
||||
glEnableVertexAttribArray(a_labelminzoom);
|
||||
glVertexAttribPointer(a_labelminzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 8);
|
||||
|
||||
glEnableVertexAttribArray(a_minzoom);
|
||||
glVertexAttribPointer(a_minzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 9);
|
||||
|
||||
glEnableVertexAttribArray(a_maxzoom);
|
||||
glVertexAttribPointer(a_maxzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 10);
|
||||
|
||||
glEnableVertexAttribArray(a_angle);
|
||||
glVertexAttribPointer(a_angle, 1, GL_UNSIGNED_BYTE, false, stride, offset + 11);
|
||||
|
||||
glEnableVertexAttribArray(a_rangeend);
|
||||
glVertexAttribPointer(a_rangeend, 1, GL_UNSIGNED_BYTE, false, stride, offset + 12);
|
||||
|
||||
glEnableVertexAttribArray(a_rangestart);
|
||||
glVertexAttribPointer(a_rangestart, 1, GL_UNSIGNED_BYTE, false, stride, offset + 13);
|
||||
|
||||
glEnableVertexAttribArray(a_tex);
|
||||
glVertexAttribPointer(a_tex, 2, GL_UNSIGNED_SHORT, false, 8, offset + 4);
|
||||
glVertexAttribPointer(a_tex, 2, GL_SHORT, false, stride, offset + 16);
|
||||
}
|
||||
|
||||
void IconShader::setImage(int32_t new_image) {
|
||||
if (image != new_image) {
|
||||
glUniform1i(u_image, new_image);
|
||||
image = new_image;
|
||||
void IconShader::setExtrudeMatrix(const std::array<float, 16>& new_exmatrix) {
|
||||
if (exmatrix != new_exmatrix) {
|
||||
glUniformMatrix4fv(u_exmatrix, 1, GL_FALSE, new_exmatrix.data());
|
||||
exmatrix = new_exmatrix;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setColor(const std::array<float, 4>& new_color) {
|
||||
if (color != new_color) {
|
||||
glUniform4fv(u_color, 1, new_color.data());
|
||||
color = new_color;
|
||||
void IconShader::setAngle(float new_angle) {
|
||||
if (angle != new_angle) {
|
||||
glUniform1f(u_angle, new_angle);
|
||||
angle = new_angle;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setSize(float new_size) {
|
||||
if (size != new_size) {
|
||||
glUniform1f(u_size, new_size);
|
||||
size = new_size;
|
||||
void IconShader::setZoom(float new_zoom) {
|
||||
if (zoom != new_zoom) {
|
||||
glUniform1f(u_zoom, new_zoom);
|
||||
zoom = new_zoom;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setRatio(float new_ratio) {
|
||||
if (ratio != new_ratio) {
|
||||
glUniform1f(u_ratio, new_ratio);
|
||||
ratio = new_ratio;
|
||||
void IconShader::setFlip(float new_flip) {
|
||||
if (flip != new_flip) {
|
||||
glUniform1f(u_flip, new_flip);
|
||||
flip = new_flip;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setDimension(const std::array<float, 2>& new_dimension) {
|
||||
if (dimension != new_dimension) {
|
||||
glUniform2fv(u_dimension, 1, new_dimension.data());
|
||||
dimension = new_dimension;
|
||||
void IconShader::setFadeDist(float new_fadedist) {
|
||||
if (fadedist != new_fadedist) {
|
||||
glUniform1f(u_fadedist, new_fadedist);
|
||||
fadedist = new_fadedist;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setMinFadeZoom(float new_minfadezoom) {
|
||||
if (minfadezoom != new_minfadezoom) {
|
||||
glUniform1f(u_minfadezoom, new_minfadezoom);
|
||||
minfadezoom = new_minfadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setMaxFadeZoom(float new_maxfadezoom) {
|
||||
if (maxfadezoom != new_maxfadezoom) {
|
||||
glUniform1f(u_maxfadezoom, new_maxfadezoom);
|
||||
maxfadezoom = new_maxfadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setFadeZoom(float new_fadezoom) {
|
||||
if (fadezoom != new_fadezoom) {
|
||||
glUniform1f(u_fadezoom, new_fadezoom);
|
||||
fadezoom = new_fadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setOpacity(float new_opacity) {
|
||||
if (opacity != new_opacity) {
|
||||
glUniform1f(u_opacity, new_opacity);
|
||||
opacity = new_opacity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,24 +31,40 @@ Collision::~Collision() {
|
||||
delete ((Tree *)hTree);
|
||||
}
|
||||
|
||||
Collision::Collision() : cTree(new Tree()), hTree(new Tree()) {
|
||||
Collision::Collision(float zoom, float tileExtent, float tileSize, float placementDepth)
|
||||
: hTree(new Tree()), // tree for horizontal labels
|
||||
cTree(new Tree()), // tree for glyphs from curved labels
|
||||
|
||||
// tile pixels per screen pixels at the tile's zoom level
|
||||
tilePixelRatio(tileExtent / tileSize),
|
||||
|
||||
zoom(zoom),
|
||||
|
||||
// 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.
|
||||
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}}},
|
||||
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}}, 0}}},
|
||||
CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false, 2);
|
||||
|
||||
insert({{GlyphBox{
|
||||
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}},
|
||||
0},
|
||||
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}}, 0},
|
||||
GlyphBox{
|
||||
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}},
|
||||
0}}},
|
||||
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}}, 0}}},
|
||||
CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2);
|
||||
}
|
||||
|
||||
@@ -74,7 +90,7 @@ GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor)
|
||||
|
||||
PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAnchor &anchor,
|
||||
float minPlacementScale, float maxPlacementScale, float padding,
|
||||
bool horizontal, bool alwaysVisible) {
|
||||
bool horizontal, bool allowOverlap, bool ignorePlacement) {
|
||||
float minScale = std::numeric_limits<float>::infinity();
|
||||
for (const GlyphBox &glyphBox : boxes) {
|
||||
minScale = util::min(minScale, glyphBox.minScale);
|
||||
@@ -95,17 +111,17 @@ PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAncho
|
||||
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}};
|
||||
|
||||
} else {
|
||||
glyphs = boxes;
|
||||
glyphs.front().hBox = CollisionRect{
|
||||
{-diag, -diag},
|
||||
{diag, diag}
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate the minimum scale the entire label can be shown without
|
||||
// collisions
|
||||
float scale = alwaysVisible ? minPlacementScale
|
||||
: getPlacementScale(glyphs, minPlacementScale,
|
||||
maxPlacementScale, padding);
|
||||
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()) {
|
||||
@@ -113,8 +129,10 @@ PlacementProperty Collision::place(const GlyphBoxes &boxes, const CollisionAncho
|
||||
}
|
||||
|
||||
// Calculate the range it is safe to rotate all glyphs
|
||||
PlacementRange rotationRange = getPlacementRange(glyphs, scale, horizontal);
|
||||
insert(glyphs, anchor, scale, rotationRange, horizontal, padding);
|
||||
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);
|
||||
|
||||
@@ -177,18 +195,10 @@ 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;
|
||||
|
||||
@@ -272,10 +272,11 @@ void Placement::addFeature(SymbolBucket &bucket, const std::vector<Coordinate> &
|
||||
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.ignore_placement);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user