Merge pull request #403 from mapbox/symbols

Symbols
This commit is contained in:
Konstantin Käfer
2014-08-06 18:52:00 +02:00
59 changed files with 2474 additions and 3729 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
#include "glfw_view.hpp"
#include <mbgl/util/string.hpp>
GLFWView::GLFWView(bool fullscreen) : fullscreen(fullscreen) {
#ifdef NVIDIA
glDiscardFramebufferEXT = (PFNGLDISCARDFRAMEBUFFEREXTPROC)glfwGetProcAddress("glDiscardFramebufferEXT");
@@ -192,6 +194,12 @@ void GLFWView::make_active() {
void GLFWView::swap() {
glfwPostEmptyEvent();
double lon, lat, zoom;
map->getLonLatZoom(lon, lat, zoom);
const double bearing = map->getBearing();
const std::string title = mbgl::util::sprintf<128>("Mapbox GL – %.2f/%.6f/%.6f/%.1f", zoom, lat, lon, bearing);
glfwSetWindowTitle(window, title.c_str());
}
void GLFWView::notify_map_change(mbgl::MapChange change, mbgl::timestamp delay) {

View File

@@ -3,16 +3,18 @@
#include "buffer.hpp"
#include <array>
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);
};
}

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

@@ -26,17 +26,20 @@ public:
// Changes the pixel ratio.
bool resize(float newRatio);
// Update uninitialized sprites in this atlas from the given sprite.
// Update uninitialized (= outdated) sprites in this atlas from the given sprite.
void update(const Sprite &sprite);
// Returns the coordinates of a square icon. The getter also *creates* new square icons in the
// atlas if they don't exist, but they'll be default-initialized with a a black circle.
Rect<dimension> getIcon(int size, const std::string &name);
// Returns the coordinates of an image that is sourced from the sprite image.
// This getter attempts to read the image from the sprite if it is already loaded.
// In that case, it copies it into the sprite atlas and returns the dimensions.
// Otherwise, it returns a 0/0/0/0 rect.
Rect<dimension> getImage(const std::string &name, const Sprite &sprite);
// Returns the coordinates of an image that is sourced from the sprite image.
// This getter does not create images, as the dimension of the texture us unknown if the
// sprite is not yet loaded. Instead, it returns a 0/0/0/0 rect.
Rect<dimension> getImage(const std::string &name, const Sprite &sprite);
// This getter waits until the sprite image was loaded, copies it into the sprite
// image and returns the dimensions.
// NEVER CALL THIS FUNCTION FROM THE RENDER THREAD! it is blocking.
Rect<dimension> waitForImage(const std::string &name, const Sprite &sprite);
// Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out
// of date.

View File

@@ -21,6 +21,7 @@ class GlyphAtlas;
class GlyphStore;
class LayerDescription;
class SpriteAtlas;
class Sprite;
class Style;
class StyleLayer;
class StyleLayerGroup;
@@ -110,10 +111,11 @@ public:
public:
inline const TransformState &getState() const { return state; }
inline std::shared_ptr<const Style> getStyle() const { return style; }
inline std::shared_ptr<Style> getStyle() const { return style; }
inline std::shared_ptr<GlyphAtlas> getGlyphAtlas() { return glyphAtlas; }
inline std::shared_ptr<GlyphStore> getGlyphStore() { return glyphStore; }
inline std::shared_ptr<SpriteAtlas> getSpriteAtlas() { return spriteAtlas; }
std::shared_ptr<Sprite> getSprite();
inline std::shared_ptr<Texturepool> getTexturepool() { return texturepool; }
inline std::shared_ptr<uv::loop> getLoop() { return loop; }
inline timestamp getAnimationTime() const { return animationTime; }
@@ -172,6 +174,7 @@ private:
std::shared_ptr<GlyphAtlas> glyphAtlas;
std::shared_ptr<GlyphStore> glyphStore;
std::shared_ptr<SpriteAtlas> spriteAtlas;
std::shared_ptr<Sprite> sprite;
std::shared_ptr<Texturepool> texturepool;
Painter painter;

View File

@@ -2,6 +2,7 @@
#define MBGL_STYLE_SPRITE
#include <mbgl/util/image.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <cstdint>
#include <atomic>
@@ -9,6 +10,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <future>
namespace mbgl {
@@ -28,37 +30,47 @@ public:
uint8_t pixelRatio = 1;
};
class Sprite : public std::enable_shared_from_this<Sprite> {
public:
Sprite(Map &map, float pixelRatio = 1);
class Sprite : public std::enable_shared_from_this<Sprite>, private util::noncopyable {
private:
struct Key {};
void load();
void load(const std::string& base_url);
public:
Sprite(const Key &, const std::string& base_url, float pixelRatio);
static std::shared_ptr<Sprite> Create(const std::string& base_url, float pixelRatio);
const SpritePosition &getSpritePosition(const std::string& name) const;
void waitUntilLoaded() const;
bool isLoaded() const;
operator bool() const;
private:
const bool valid;
public:
const float pixelRatio;
const std::string spriteURL;
const std::string jsonURL;
std::unique_ptr<util::Image> raster;
private:
void asyncParseJSON();
void asyncParseImage();
static void parseJSON(std::shared_ptr<Sprite> &sprite);
static void parseImage(std::shared_ptr<Sprite> &sprite);
static void complete(std::shared_ptr<Sprite> &sprite);
void parseJSON();
void parseImage();
void complete();
private:
Map &map;
std::string url;
std::string body;
std::string image;
std::atomic<bool> loadedImage;
std::atomic<bool> loadedJSON;
std::unordered_map<std::string, SpritePosition> pos;
const SpritePosition empty;
std::promise<void> promise;
std::future<void> future;
};
}

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>
@@ -18,12 +18,12 @@ class FontStack;
class GlyphAtlas;
class GlyphStore;
class SpriteAtlas;
class Sprite;
class Style;
class StyleBucket;
class StyleBucketFill;
class StyleBucketIcon;
class StyleBucketLine;
class StyleBucketText;
class StyleBucketSymbol;
class StyleLayerGroup;
class VectorTileData;
@@ -33,25 +33,22 @@ public:
const std::shared_ptr<const Style> &style,
const std::shared_ptr<GlyphAtlas> &glyphAtlas,
const std::shared_ptr<GlyphStore> &glyphStore,
const std::shared_ptr<SpriteAtlas> &spriteAtlas);
const std::shared_ptr<SpriteAtlas> &spriteAtlas,
const std::shared_ptr<Sprite> &sprite);
public:
void parse();
private:
bool obsolete() const;
void parseGlyphs();
void parseStyleLayers(std::shared_ptr<StyleLayerGroup> group);
void addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string, const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face);
std::unique_ptr<Bucket> createBucket(std::shared_ptr<StyleBucket> bucket_desc);
std::unique_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill);
std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line);
std::unique_ptr<Bucket> createIconBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketIcon &icon);
std::unique_ptr<Bucket> createTextBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketText &text);
std::unique_ptr<Bucket> createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol);
template <class Bucket> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter);
template <class Bucket, typename ...Args> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter, Args&& ...args);
template <class Bucket> void addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter);
private:
const VectorTile vector_data;
@@ -62,8 +59,9 @@ private:
std::shared_ptr<GlyphAtlas> glyphAtlas;
std::shared_ptr<GlyphStore> glyphStore;
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

@@ -51,6 +51,9 @@ protected:
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
std::unique_ptr<TileParser> parser;
public:
const float depth;
};
}

View File

@@ -1,53 +0,0 @@
#ifndef MBGL_RENDERER_ICONBUCKET
#define MBGL_RENDERER_ICONBUCKET
#include <mbgl/renderer/bucket.hpp>
#include <mbgl/geometry/elements_buffer.hpp>
#include <mbgl/geometry/icon_buffer.hpp>
#include <mbgl/style/style_bucket.hpp>
#include <vector>
#include <memory>
#ifndef BUFFER_OFFSET
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
#endif
namespace mbgl {
class Style;
class IconVertexBuffer;
class BucketDescription;
class IconShader;
class DotShader;
class SpriteAtlas;
class VectorTileFeature;
class IconBucket : public Bucket {
public:
IconBucket(IconVertexBuffer& vertexBuffer,
const StyleBucketIcon& properties);
virtual void render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
virtual bool hasData() const;
void addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas);
void drawIcons(IconShader& shader);
void drawIcons(DotShader& shader);
public:
const StyleBucketIcon &properties;
private:
IconVertexBuffer& vertexBuffer;
VertexArrayObject array;
const size_t vertex_start;
size_t vertex_end = 0;
};
}
#endif

View File

@@ -38,8 +38,7 @@ class StyleSource;
class FillBucket;
class LineBucket;
class IconBucket;
class TextBucket;
class SymbolBucket;
class RasterBucket;
struct FillProperties;
@@ -83,8 +82,7 @@ public:
void renderFill(FillBucket& bucket, const FillProperties& properties, const Tile::ID& id, const mat4 &mat);
void renderFill(FillBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
void renderLine(LineBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
void renderIcon(IconBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
void renderText(TextBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
void renderSymbol(SymbolBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
void renderRaster(RasterBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id);
void preparePrerender(PrerenderedTexture &texture);
@@ -119,7 +117,7 @@ public:
bool needsAnimation() const;
private:
void setupShaders();
const mat4 &translatedMatrix(const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor = TranslateAnchorType::Default);
const mat4 &translatedMatrix(const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor = TranslateAnchorType::Map);
void prepareTile(const Tile& tile);

View File

@@ -0,0 +1,109 @@
#ifndef MBGL_RENDERER_SYMBOLBUCKET
#define MBGL_RENDERER_SYMBOLBUCKET
#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>
#include <mbgl/style/style_bucket.hpp>
#include <memory>
#include <map>
#include <vector>
namespace mbgl {
class Style;
class TextShader;
class IconShader;
class DotShader;
class Collision;
class SpriteAtlas;
class Sprite;
class GlyphAtlas;
class GlyphStore;
class FontStack;
class SymbolFeature {
public:
pbf geometry;
std::u32string label;
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(const StyleBucketSymbol &properties, Collision &collision);
virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id);
virtual bool hasData() const;
virtual bool hasTextData() const;
virtual bool hasIconData() const;
void addFeatures(const VectorTileLayer &layer, const FilterExpression &filter,
const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite,
GlyphAtlas &glyphAtlas, GlyphStore &glyphStore);
void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, PlacementRange placementRange,
float zoom);
void drawGlyphs(TextShader &shader);
void drawIcons(IconShader& 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 GlyphPositions &face, const Rect<uint16_t> &image);
void addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image);
// Adds placed items to the buffer.
template <typename Buffer>
void addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, PlacementRange placementRange);
// 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);
public:
const StyleBucketSymbol &properties;
private:
Collision &collision;
struct {
TextVertexBuffer vertices;
TriangleElementsBuffer triangles;
std::vector<ElementGroup> groups;
} text;
struct {
IconVertexBuffer vertices;
TriangleElementsBuffer triangles;
std::vector<ElementGroup> groups;
} icon;
};
}
#endif

View File

@@ -1,61 +0,0 @@
#ifndef MBGL_RENDERER_TEXTBUCKET
#define MBGL_RENDERER_TEXTBUCKET
#include "bucket.hpp"
#include <mbgl/geometry/vao.hpp>
#include <mbgl/geometry/elements_buffer.hpp>
#include <mbgl/map/vector_tile.hpp>
#include <mbgl/text/types.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/style/style_bucket.hpp>
#include <memory>
#include <map>
#include <vector>
namespace mbgl {
class Style;
class TextVertexBuffer;
class TriangleElementsBuffer;
class TextShader;
class Placement;
struct pbf;
class TextBucket : public Bucket {
typedef ElementGroup triangle_group_type;
public:
TextBucket(
TextVertexBuffer &vertexBuffer,
TriangleElementsBuffer &triangleElementsBuffer,
const StyleBucketText &properties, Placement &placement);
virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id);
virtual bool hasData() const;
void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom,
PlacementRange placementRange, float zoom);
void addFeature(const pbf &geometry,
const GlyphPositions &face,
const Shaping &shaping);
void drawGlyphs(TextShader &shader);
public:
const StyleBucketText &properties;
private:
TextVertexBuffer& vertexBuffer;
TriangleElementsBuffer& triangleElementsBuffer;
Placement &placement;
const size_t vertex_start;
const size_t triangle_elements_start;
std::vector<triangle_group_type> triangleGroups;
};
}
#endif

View File

@@ -11,30 +11,57 @@ 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);
void setTextureSize(const std::array<float, 2> &texsize);
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;
std::array<float, 2> texsize = {{}};
int32_t u_texsize = -1;
};
}

View File

@@ -30,7 +30,15 @@ enum class PropertyKey {
IconOpacity,
IconRotate,
IconRotateAnchor,
IconSize,
IconColor,
IconHaloColor,
IconHaloWidth,
IconHaloBlur,
IconTranslate, // for transitions only
IconTranslateX,
IconTranslateY,
IconTranslateAnchor,
TextOpacity,
TextSize,
@@ -38,6 +46,10 @@ enum class PropertyKey {
TextHaloColor,
TextHaloWidth,
TextHaloBlur,
TextTranslate, // for transitions only
TextTranslateX,
TextTranslateY,
TextTranslateAnchor,
CompositeOpacity,

View File

@@ -48,13 +48,15 @@ public:
const BackgroundProperties &getBackgroundProperties() const;
const std::string &getSpriteURL() const;
public:
std::shared_ptr<Sprite> sprite;
std::shared_ptr<StyleLayerGroup> layers;
std::vector<std::string> appliedClasses;
std::string sprite_url;
std::string glyph_url;
private:
std::string sprite_url;
private:
PropertyTransition defaultTransition;

View File

@@ -4,8 +4,10 @@
#include <mbgl/style/types.hpp>
#include <mbgl/style/filter_expression.hpp>
#include <mbgl/style/style_source.hpp>
#include <mbgl/util/vec.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <memory>
#include <forward_list>
@@ -16,56 +18,74 @@ class Source;
class StyleBucketFill {
public:
WindingType winding = WindingType::Default;
WindingType winding = WindingType::NonZero;
};
class StyleBucketLine {
public:
CapType cap = CapType::Default;
JoinType join = JoinType::Default;
CapType cap = CapType::Butt;
JoinType join = JoinType::Miter;
float miter_limit = 2.0f;
float round_limit = 1.0f;
};
class StyleBucketIcon {
class StyleBucketSymbol {
public:
uint16_t size = 16;
vec2<float> translate {0, 0};
TranslateAnchorType translate_anchor = TranslateAnchorType::Default;
std::string icon;
float spacing = 0.0f;
float padding = 2.0f;
};
// Make movable only.
inline StyleBucketSymbol() = default;
inline StyleBucketSymbol(StyleBucketSymbol &&) = default;
inline StyleBucketSymbol& operator=(StyleBucketSymbol &&) = default;
inline StyleBucketSymbol(const StyleBucketSymbol &) = delete;
inline StyleBucketSymbol& operator=(const StyleBucketSymbol &) = delete;
class StyleBucketText {
public:
std::string field;
TextPathType path = TextPathType::Default;
TextTransformType transform = TextTransformType::Default;
std::string font;
float max_size = 16.0f;
float max_width = 15.0f * 24;
float line_height = 1.2f * 24;
float letter_spacing = 0.0f;
float alignment = 0.5f;
float vertical_alignment = 0.5;
vec2<float> translate {0, 0};
TranslateAnchorType translate_anchor = TranslateAnchorType::Default;
float max_angle_delta = 45.0f;
PlacementType placement = PlacementType::Point;
float min_distance = 250.0f;
float rotate = 0.0f; // what is this?
float padding = 2.0f;
float slant = 0.0f;
bool always_visible = false;
struct {
bool allow_overlap = false;
bool ignore_placement = false;
bool optional = false;
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};
TranslateAnchorType translate_anchor = TranslateAnchorType::Map;
} icon;
struct {
RotationAlignmentType rotation_alignment = RotationAlignmentType::Viewport;
std::string field;
std::string font;
float max_size = 16.0f;
float max_width = 15.0f * 24 /* em */;
float line_height = 1.2f * 24 /* em */;
float letter_spacing = 0.0f * 24 /* em */;
TextJustifyType justify = TextJustifyType::Center;
TextHorizontalAlignType horizontal_align = TextHorizontalAlignType::Center;
TextVerticalAlignType vertical_align = TextVerticalAlignType::Center;
float max_angle = 45.0f /* degrees */;
float rotate = 0.0f;
float slant = 0.0f;
float padding = 2.0f;
bool keep_upright = true;
TextTransformType transform = TextTransformType::None;
vec2<float> offset = {0, 0};
TranslateAnchorType translate_anchor = TranslateAnchorType::Map;
bool allow_overlap = false;
bool ignore_placement = false;
bool optional = false;
} text;
};
class StyleBucketRaster {
public:
};
typedef util::variant<StyleBucketFill, StyleBucketLine, StyleBucketIcon,
StyleBucketText, StyleBucketRaster,
std::false_type> StyleBucketRender;
typedef util::variant<StyleBucketFill, StyleBucketLine, StyleBucketSymbol,
StyleBucketRaster, std::false_type> StyleBucketRender;
class StyleBucket {

View File

@@ -62,8 +62,8 @@ private:
// Parses optional properties into a render bucket.
template<typename T>
bool parseRenderProperty(JSVal value, T &target, const char *name);
template <typename T, typename Parser>
bool parseRenderProperty(JSVal value, T &target, const char *name, Parser &parser);
template <typename Parser, typename T>
bool parseRenderProperty(JSVal value, T &target, const char *name);
// Parses optional properties into style class properties.
template <typename T>

View File

@@ -19,7 +19,7 @@ struct FillProperties {
Color fill_color = {{ 0, 0, 0, 1 }};
Color stroke_color = {{ 0, 0, 0, -1 }};
std::array<float, 2> translate = {{ 0, 0 }};
TranslateAnchorType translateAnchor = TranslateAnchorType::Default;
TranslateAnchorType translateAnchor = TranslateAnchorType::Map;
std::string image;
inline bool isVisible() const {
@@ -32,7 +32,7 @@ struct LineProperties {
float opacity = 1.0f;
Color color = {{ 0, 0, 0, 1 }};
std::array<float, 2> translate = {{ 0, 0 }};
TranslateAnchorType translateAnchor = TranslateAnchorType::Default;
TranslateAnchorType translateAnchor = TranslateAnchorType::Map;
float width = 1;
float offset = 0;
float blur = 0;
@@ -44,30 +44,38 @@ struct LineProperties {
}
};
struct IconProperties {
inline IconProperties() {}
float opacity = 1.0f;
float rotate = 0.0f;
RotateAnchorType rotate_anchor = RotateAnchorType::Default;
struct SymbolProperties {
inline SymbolProperties() {}
struct {
float opacity = 1.0f;
float rotate = 0.0f;
float size = 1.0f;
Color color = {{ 0, 0, 0, 1 }};
Color halo_color = {{ 0, 0, 0, 0 }};
float halo_width = 0.0f;
float halo_blur = 0.0f;
std::array<float, 2> translate = {{ 0, 0 }};
TranslateAnchorType translate_anchor = TranslateAnchorType::Map;
} icon;
struct {
float opacity = 1.0f;
float size = 12.0f;
Color color = {{ 0, 0, 0, 1 }};
Color halo_color = {{ 0, 0, 0, 0 }};
float halo_width = 0.0f;
float halo_blur = 0.0f;
std::array<float, 2> translate = {{ 0, 0 }};
TranslateAnchorType translate_anchor = TranslateAnchorType::Map;
} text;
inline bool isVisible() const {
return opacity > 0;
return (icon.opacity > 0 && (icon.color[3] > 0 || icon.halo_color[3] > 0) && icon.size > 0) ||
(text.opacity > 0 && (text.color[3] > 0 || text.halo_color[3] > 0) && text.size > 0);
}
};
struct TextProperties {
inline TextProperties() {}
float opacity = 1.0f;
float size = 12.0f;
Color color = {{ 0, 0, 0, 1 }};
Color halo_color = {{ 1, 1, 1, 0.75 }};
float halo_width = 0.25f;
float halo_blur = 1.0f;
inline bool isVisible() const {
return opacity > 0 && (color[3] > 0 || halo_color[3] > 0) && size > 0;
}
};
struct CompositeProperties {
inline CompositeProperties() {}
@@ -100,8 +108,7 @@ struct BackgroundProperties {
typedef util::variant<
FillProperties,
LineProperties,
IconProperties,
TextProperties,
SymbolProperties,
CompositeProperties,
RasterProperties,
BackgroundProperties,

View File

@@ -11,12 +11,13 @@ namespace mbgl {
// Stores a premultiplied color, with all four channels ranging from 0..1
typedef std::array<float, 4> Color;
// -------------------------------------------------------------------------------------------------
enum class StyleLayerType : uint8_t {
Unknown,
Fill,
Line,
Icon,
Text,
Symbol,
Raster,
Composite,
Background
@@ -26,131 +27,172 @@ MBGL_DEFINE_ENUM_CLASS(StyleLayerTypeClass, StyleLayerType, {
{ StyleLayerType::Unknown, "unknown" },
{ StyleLayerType::Fill, "fill" },
{ StyleLayerType::Line, "line" },
{ StyleLayerType::Icon, "icon" },
{ StyleLayerType::Text, "text" },
{ StyleLayerType::Symbol, "symbol" },
{ StyleLayerType::Raster, "raster" },
{ StyleLayerType::Composite, "composite" },
{ StyleLayerType::Background, "background" },
{ StyleLayerType(-1), "unknown" },
});
enum class WindingType : uint8_t {
EvenOdd,
NonZero,
Default = NonZero
};
enum class CapType : uint8_t {
Round,
Butt,
Square,
Default = Butt
};
enum class JoinType : uint8_t {
Miter,
Bevel,
Round,
Default = Miter
};
enum class TextPathType : uint8_t {
Horizontal,
Curve,
Default = Horizontal
};
enum class TextTransformType : uint8_t {
None,
Uppercase,
Lowercase,
Default = None
};
enum class TranslateAnchorType : uint8_t {
Map,
Viewport,
Default = Map
};
enum class RotateAnchorType : uint8_t {
Map,
Viewport,
Default = Viewport
};
// -------------------------------------------------------------------------------------------------
enum class SourceType : uint8_t {
Vector,
Raster,
GeoJSON,
Video,
Default = Vector
Video
};
inline WindingType parseWindingType(const std::string &type) {
if (type == "even-odd") return WindingType::EvenOdd;
if (type == "non-zero") return WindingType::NonZero;
return WindingType::Default;
}
MBGL_DEFINE_ENUM_CLASS(SourceTypeClass, SourceType, {
{ SourceType::Vector, "vector" },
{ SourceType::Raster, "raster" },
{ SourceType::GeoJSON, "geojson" },
{ SourceType::Video, "video" },
});
inline CapType parseCapType(const std::string &cap) {
if (cap == "round") return CapType::Round;
if (cap == "butt") return CapType::Butt;
if (cap == "square") return CapType::Square;
return CapType::Default;
}
// -------------------------------------------------------------------------------------------------
inline JoinType parseJoinType(const std::string &join) {
if (join == "miter") return JoinType::Miter;
if (join == "bevel") return JoinType::Bevel;
if (join == "round") return JoinType::Round;
return JoinType::Default;
}
inline TextPathType parseTextPathType(const std::string &path) {
if (path == "horizontal") return TextPathType::Horizontal;
if (path == "curve") return TextPathType::Curve;
return TextPathType::Default;
}
inline TextTransformType parseTextTransformType(const std::string& transform) {
if (transform == "uppercase") return TextTransformType::Uppercase;
if (transform == "lowercase") return TextTransformType::Lowercase;
return TextTransformType::Default;
enum class WindingType : bool {
EvenOdd,
NonZero,
};
inline TranslateAnchorType parseTranslateAnchorType(const std::string &anchor) {
if (anchor == "map") return TranslateAnchorType::Map;
if (anchor == "viewport") return TranslateAnchorType::Viewport;
return TranslateAnchorType::Default;
}
MBGL_DEFINE_ENUM_CLASS(WindingTypeClass, WindingType, {
{ WindingType::EvenOdd, "even-odd" },
{ WindingType::NonZero, "non-zero" },
});
inline RotateAnchorType parseRotateAnchorType(const std::string &anchor) {
if (anchor == "map") return RotateAnchorType::Map;
if (anchor == "viewport") return RotateAnchorType::Viewport;
return RotateAnchorType::Default;
}
// -------------------------------------------------------------------------------------------------
inline float parseAlignmentType(const std::string &alignment) {
if (alignment == "right") return 1.0f;
if (alignment == "left") return 0.0f;
return 0.5f;
}
enum class CapType : uint8_t {
Round,
Butt,
Square,
};
inline float parseVerticalAlignmentType(const std::string &alignment) {
if (alignment == "bottom") return 1.0f;
if (alignment == "top") return 0.0f;
return 0.5f;
}
MBGL_DEFINE_ENUM_CLASS(CapTypeClass, CapType, {
{ CapType::Round, "round" },
{ CapType::Butt, "butt" },
{ CapType::Square, "square" },
});
inline SourceType parseSourceType(const std::string &source) {
if (source == "vector") return SourceType::Vector;
if (source == "raster") return SourceType::Raster;
if (source == "geojson") return SourceType::GeoJSON;
if (source == "video") return SourceType::Video;
return SourceType::Default;
}
// -------------------------------------------------------------------------------------------------
enum class JoinType : uint8_t {
Miter,
Bevel,
Round
};
MBGL_DEFINE_ENUM_CLASS(JoinTypeClass, JoinType, {
{ JoinType::Miter, "miter" },
{ JoinType::Bevel, "bevel" },
{ JoinType::Round, "round" },
});
// -------------------------------------------------------------------------------------------------
enum class TranslateAnchorType : bool {
Map,
Viewport
};
MBGL_DEFINE_ENUM_CLASS(TranslateAnchorTypeClass, TranslateAnchorType, {
{ TranslateAnchorType::Map, "map" },
{ TranslateAnchorType::Viewport, "viewport" },
});
// -------------------------------------------------------------------------------------------------
enum class RotateAnchorType : bool {
Map,
Viewport,
};
MBGL_DEFINE_ENUM_CLASS(RotateAnchorTypeClass, RotateAnchorType, {
{ RotateAnchorType::Map, "map" },
{ RotateAnchorType::Viewport, "viewport" },
});
// -------------------------------------------------------------------------------------------------
enum class PlacementType : bool {
Point,
Line,
};
MBGL_DEFINE_ENUM_CLASS(PlacementTypeClass, PlacementType, {
{ PlacementType::Point, "point" },
{ PlacementType::Line, "line" },
});
// -------------------------------------------------------------------------------------------------
enum class RotationAlignmentType : bool {
Map,
Viewport,
};
MBGL_DEFINE_ENUM_CLASS(RotationAlignmentTypeClass, RotationAlignmentType, {
{ RotationAlignmentType::Map, "map" },
{ RotationAlignmentType::Viewport, "viewport" },
});
// -------------------------------------------------------------------------------------------------
enum class TextJustifyType : uint8_t {
Center,
Left,
Right
};
MBGL_DEFINE_ENUM_CLASS(TextJustifyTypeClass, TextJustifyType, {
{ TextJustifyType::Center, "center" },
{ TextJustifyType::Left, "left" },
{ TextJustifyType::Right, "right" },
});
// -------------------------------------------------------------------------------------------------
enum class TextHorizontalAlignType : uint8_t {
Left,
Center,
Right,
};
MBGL_DEFINE_ENUM_CLASS(TextHorizontalAlignTypeClass, TextHorizontalAlignType, {
{ TextHorizontalAlignType::Left, "left" },
{ TextHorizontalAlignType::Center, "center" },
{ TextHorizontalAlignType::Right, "right" },
});
// -------------------------------------------------------------------------------------------------
enum class TextVerticalAlignType : uint8_t {
Top,
Center,
Bottom,
};
MBGL_DEFINE_ENUM_CLASS(TextVerticalAlignTypeClass, TextVerticalAlignType, {
{ TextVerticalAlignType::Top, "top" },
{ TextVerticalAlignType::Center, "center" },
{ TextVerticalAlignType::Bottom, "bottom" },
});
// -------------------------------------------------------------------------------------------------
enum class TextTransformType : uint8_t {
None,
Uppercase,
Lowercase,
};
MBGL_DEFINE_ENUM_CLASS(TextTransformTypeClass, TextTransformType, {
{ TextTransformType::None, "none" },
{ TextTransformType::Uppercase, "uppercase" },
{ TextTransformType::Lowercase, "lowercase" },
});
}

View File

@@ -8,24 +8,23 @@ 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);
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 *cTree;
void *hTree;
void *cTree;
public:
const float tilePixelRatio;
const float zoom;
const float maxPlacementScale;
};
}

View File

@@ -16,7 +16,7 @@ GlyphRange getGlyphRange(char32_t glyph);
struct GlyphMetrics {
operator bool() const {
return width == 0 && height == 0 && advance == 0;
return !(width == 0 && height == 0 && advance == 0);
}
// Glyph metrics.
@@ -35,7 +35,7 @@ struct Glyph {
: rect(rect), metrics(metrics) {}
operator bool() const {
return !metrics && !rect;
return metrics || rect;
}
const Rect<uint16_t> rect;
@@ -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

@@ -3,6 +3,7 @@
#include <mbgl/text/glyph.hpp>
#include <mbgl/util/pbf.hpp>
#include <mbgl/util/vec.hpp>
#include <cstdint>
#include <vector>
@@ -30,17 +31,11 @@ public:
void insert(uint32_t id, const SDFGlyph &glyph);
const std::map<uint32_t, GlyphMetrics> &getMetrics() const;
const std::map<uint32_t, SDFGlyph> &getSDFs() const;
const Shaping getShaping(const std::u32string &string,
const float &maxWidth,
const float &lineHeight,
const float &alignment,
const float &verticalAlignment,
const float &letterSpacing) const;
void lineWrap(Shaping &shaping,
const float &lineHeight,
const float &maxWidth,
const float &alignment,
const float &verticalAlignment) const;
const Shaping getShaping(const std::u32string &string, float maxWidth, float lineHeight,
float horizontalAlign, float verticalAlign, float justify,
float spacing, const vec2<float> &translate) const;
void lineWrap(Shaping &shaping, float lineHeight, float maxWidth, float horizontalAlign,
float verticalAlign, float justify) const;
private:
std::map<uint32_t, std::string> bitmaps;
@@ -67,13 +62,15 @@ private:
// Manages Glyphrange PBF loading.
class GlyphStore {
public:
GlyphStore(const std::string &glyphURL);
GlyphStore();
// Block until all specified GlyphRanges of the specified font stack are loaded.
void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges);
FontStack &getFontStack(const std::string &fontStack);
void setURL(const std::string &url);
private:
// Loads an individual glyph range from the font stack and adds it to rangeSets
std::shared_future<GlyphPBF &> loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, GlyphRange range);
@@ -81,7 +78,7 @@ private:
FontStack &createFontStack(const std::string &fontStack);
public:
const std::string glyphURL;
std::string glyphURL;
private:
std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges;

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 TextBucket;
class StyleBucketText;
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(TextBucket &bucket, const std::vector<Coordinate> &line,
const StyleBucketText &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

@@ -6,6 +6,8 @@
#include <array>
#include <vector>
#include <boost/optional.hpp>
namespace mbgl {
typedef vec2<float> CollisionPoint;
@@ -35,23 +37,23 @@ struct CollisionRect {
// These are the glyph boxes that we want to have placed.
struct GlyphBox {
explicit GlyphBox() {}
explicit GlyphBox(const CollisionRect &bbox, const CollisionRect &box,
float minScale)
: bbox(bbox), box(box), minScale(minScale) {}
explicit GlyphBox(const CollisionRect &box, float minScale, float maxScale,
const CollisionAnchor &anchor, bool rotate)
: anchor(anchor),
box(box),
rotate(rotate),
minScale(minScale),
maxScale(maxScale) {}
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 bbox;
CollisionRect box;
bool rotate = false;
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;
@@ -61,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;
@@ -81,9 +87,8 @@ typedef std::vector<PlacedGlyph> PlacedGlyphs;
// These are the placed boxes contained in the rtree.
struct PlacementBox {
CollisionAnchor anchor;
CollisionRect bbox;
CollisionRect box;
bool rotate = false;
boost::optional<CollisionRect> hBox;
PlacementRange placementRange = {{0.0f, 0.0f}};
float placementScale = 0.0f;
float maxScale = std::numeric_limits<float>::infinity();

View File

@@ -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;
@@ -14,7 +15,7 @@ struct Rect {
return Rect(x * value, y * value, w * value, h * value);
}
operator bool() const { return w == 0 || h == 0; }
operator bool() const { return w != 0 && h != 0; }
};
}

View File

@@ -32,5 +32,16 @@
<string>0.0.1</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>mapboxgl</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -4,6 +4,67 @@
#import <Foundation/Foundation.h>
@interface URLHandler : NSObject
@property (nonatomic) mbgl::Map *map;
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
@end
@implementation URLHandler
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [[url query] componentsSeparatedByString:@"&"]) {
NSArray *parts = [param componentsSeparatedByString:@"="];
if([parts count] < 2) continue;
[params setObject:[parts objectAtIndex:1] forKey:[parts objectAtIndex:0]];
}
double latitude = 0, longitude = 0, zoom = 0, bearing = 0;
bool hasCenter = false, hasZoom = false, hasBearing = false;
NSString *centerString = [params objectForKey:@"center"];
if (centerString) {
NSArray *latlon = [centerString componentsSeparatedByString:@","];
if ([latlon count] == 2) {
latitude = [[latlon objectAtIndex:0] doubleValue];
longitude = [[latlon objectAtIndex:1] doubleValue];
hasCenter = true;
}
}
NSString *zoomString = [params objectForKey:@"zoom"];
if (zoomString) {
zoom = [zoomString doubleValue];
hasZoom = true;
}
NSString *bearingString = [params objectForKey:@"bearing"];
if (bearingString) {
bearing = [bearingString doubleValue];
hasBearing = true;
}
if ([self map]) {
if (hasCenter && hasZoom) {
[self map]->setLonLatZoom(longitude, latitude, zoom);
} else if (hasCenter) {
[self map]->setLonLat(longitude, latitude);
} else if (hasZoom) {
[self map]->setZoom(zoom);
}
if (hasBearing) {
[self map]->setBearing(bearing);
}
}
}
@end
int main() {
mbgl::Log::Set<mbgl::NSLogBackend>();
@@ -16,6 +77,9 @@ int main() {
map.setBearing(settings.bearing);
map.setDebug(settings.debug);
URLHandler *handler = [[URLHandler alloc] init];
[handler setMap:&map];
// Set access token if present
NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"];
if ( ! accessToken) mbgl::Log::Warning(mbgl::Event::Setup, "No access token set. Mapbox vector tiles won't work.");
@@ -28,6 +92,10 @@ int main() {
error:nil];
map.setStyleJSON((std::string)[json cStringUsingEncoding:[NSString defaultCStringEncoding]]);
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
[appleEventManager setEventHandler:handler andSelector:@selector(handleGetURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
int ret = view.run();
// Save settings

View File

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

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

@@ -88,27 +88,6 @@ void copy_bitmap(const uint32_t *src, const int src_stride, const int src_x, con
}
}
void draw_circle(uint32_t *dst, const int dst_stride, const int dst_x, const int dst_y,
const int width, const int height, const float blur,
const uint8_t r = 0xFF, const uint8_t g = 0xFF, const uint8_t b = 0xFF) {
const int sprite_stride = dst_stride;
const int radius = util::min(width, height);
for (int y = 0; y < height; y++) {
const int img_y = (dst_y + y) * sprite_stride + dst_x;
for (int x = 0; x < height; x++) {
const float dist = util::length(float(x) / radius - 0.5f, float(y) / radius - 0.5f);
const float t = util::smoothstep(0.5f, 0.5f - blur, dist);
const uint8_t alpha = t * 255;
uint32_t color = (uint32_t(r * t) << 0) |
(uint32_t(g * t) << 8) |
(uint32_t(b * t) << 16) |
(uint32_t(alpha) << 24);
dst[img_y + x] = color;
}
}
}
Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(size_t width, size_t height) {
// We have to allocate a new area in the bin, and store an empty image in it.
// Add a 1px border around every image.
@@ -125,44 +104,6 @@ Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(size_t width, size_t hei
return rect;
}
Rect<SpriteAtlas::dimension> SpriteAtlas::getIcon(const int size, const std::string &name) {
std::lock_guard<std::mutex> lock(mtx);
auto rect_it = images.find(name);
if (rect_it != images.end()) {
return rect_it->second;
}
Rect<dimension> rect = allocateImage(size, size);
if (rect.w == 0) {
if (debug::spriteWarnings) {
fprintf(stderr, "[WARNING] sprite atlas bitmap overflow\n");
}
return rect;
}
images.emplace(name, rect);
allocate();
// Draw an antialiased circle.
draw_circle(
reinterpret_cast<uint32_t *>(data),
width * pixelRatio,
rect.x * pixelRatio,
rect.y * pixelRatio,
size * pixelRatio,
size * pixelRatio,
1.0f / size / pixelRatio
);
uninitialized.emplace(name);
dirty = true;
return rect;
}
Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string &name, const Sprite &sprite) {
std::lock_guard<std::mutex> lock(mtx);
@@ -191,6 +132,12 @@ Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string &name, cons
return rect;
}
Rect<SpriteAtlas::dimension> SpriteAtlas::waitForImage(const std::string &name, const Sprite &sprite) {
sprite.waitUntilLoaded();
return getImage(name, sprite);
}
void SpriteAtlas::allocate() {
if (!data) {
dimension w = static_cast<dimension>(width * pixelRatio);

View File

@@ -33,6 +33,7 @@ Map::Map(View& view)
transform(view),
style(std::make_shared<Style>()),
glyphAtlas(std::make_shared<GlyphAtlas>(1024, 1024)),
glyphStore(std::make_shared<GlyphStore>()),
spriteAtlas(std::make_shared<SpriteAtlas>(512, 512)),
texturepool(std::make_shared<Texturepool>()),
painter(*this),
@@ -185,7 +186,9 @@ void Map::setup() {
void Map::setStyleJSON(std::string newStyleJSON) {
styleJSON.swap(newStyleJSON);
sprite.reset();
style->loadJSON((const uint8_t *)styleJSON.c_str());
glyphStore->setURL(style->glyph_url);
update();
}
@@ -201,6 +204,17 @@ std::string Map::getAccessToken() const {
return accessToken;
}
std::shared_ptr<Sprite> Map::getSprite() {
const float pixelRatio = state.getPixelRatio();
const std::string &sprite_url = style->getSpriteURL();
if (!sprite || sprite->pixelRatio != pixelRatio) {
sprite = Sprite::Create(sprite_url, pixelRatio);
}
return sprite;
}
#pragma mark - Size
// Note: This function is called from another thread. Make sure you only call threadsafe functions!
@@ -502,24 +516,6 @@ void Map::prepare() {
bool dimensionsChanged = oldState.getFramebufferWidth() != state.getFramebufferWidth() ||
oldState.getFramebufferHeight() != state.getFramebufferHeight();
if (pixelRatioChanged) {
style->sprite = std::make_shared<Sprite>(*this, state.getPixelRatio());
if (style->sprite_url.size()) {
style->sprite->load(style->sprite_url);
}
spriteAtlas->resize(state.getPixelRatio());
}
// Create a new glyph store object in case the glyph URL changed.
// TODO: Move this to a less frequently called place; we only need to do this when the
// stylesheet changes.
if (glyphStore && glyphStore->glyphURL != style->glyph_url) {
glyphStore.reset();
}
if (!glyphStore && style->glyph_url.size()) {
glyphStore = std::make_shared<GlyphStore>(style->glyph_url);
}
if (pixelRatioChanged || dimensionsChanged) {
painter.clearFramebuffers();
}
@@ -529,9 +525,8 @@ void Map::prepare() {
style->updateProperties(state.getNormalizedZoom(), animationTime);
// Allow the sprite atlas to potentially pull new sprite images if needed.
if (style->sprite && style->sprite->isLoaded()) {
spriteAtlas->update(*style->sprite);
}
spriteAtlas->resize(state.getPixelRatio());
spriteAtlas->update(*getSprite());
updateTiles();
}
@@ -643,7 +638,7 @@ void Map::renderLayer(std::shared_ptr<StyleLayer> layer_desc, RenderPass pass) {
}
}
}
} else if (layer_desc->id == "background") {
} else if (layer_desc->type == StyleLayerType::Background) {
// This layer defines the background color.
} else {
// This is a singular layer.
@@ -683,13 +678,9 @@ void Map::renderLayer(std::shared_ptr<StyleLayer> layer_desc, RenderPass pass) {
if (pass == Opaque) return;
if (!layer_desc->getProperties<LineProperties>().isVisible()) return;
break;
case StyleLayerType::Icon:
case StyleLayerType::Symbol:
if (pass == Opaque) return;
if (!layer_desc->getProperties<IconProperties>().isVisible()) return;
break;
case StyleLayerType::Text:
if (pass == Opaque) return;
if (!layer_desc->getProperties<TextProperties>().isVisible()) return;
if (!layer_desc->getProperties<SymbolProperties>().isVisible()) return;
break;
case StyleLayerType::Raster:
if (pass == Translucent) return;

View File

@@ -20,48 +20,77 @@ SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t
pixelRatio(pixelRatio) {
}
Sprite::Sprite(Map &map, float pixelRatio)
: pixelRatio(pixelRatio),
raster(),
map(map),
loadedImage(false),
loadedJSON(false) {
std::shared_ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio) {
std::shared_ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio));
sprite->load();
return sprite;
}
void Sprite::load(const std::string& base_url) {
loadedImage = false;
loadedJSON = false;
Sprite::Sprite(const Key &, const std::string& base_url, float pixelRatio)
: valid(base_url.length() > 0),
pixelRatio(pixelRatio),
spriteURL(base_url + (pixelRatio > 1 ? "@2x" : "") + ".png"),
jsonURL(base_url + (pixelRatio > 1 ? "@2x" : "") + ".json"),
raster(),
loadedImage(false),
loadedJSON(false),
future(promise.get_future()) {
}
void Sprite::waitUntilLoaded() const {
future.wait();
}
Sprite::operator bool() const {
return valid && isLoaded() && !pos.empty();
}
// Note: This is a separate function that must be called exactly once after creation
// The reason this isn't part of the constructor is that calling shared_from_this() in
// the constructor fails.
void Sprite::load() {
if (!valid) {
// Treat a non-existent sprite as a successfully loaded empty sprite.
loadedImage = true;
loadedJSON = true;
promise.set_value();
return;
}
url = base_url;
std::shared_ptr<Sprite> sprite = shared_from_this();
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
platform::request_http(jsonURL, [sprite](platform::Response *res) {
if (res->code == 200) {
sprite->body.swap(res->body);
sprite->asyncParseJSON();
sprite->parseJSON();
sprite->complete();
} else {
fprintf(stderr, "failed to load sprite info\n");
fprintf(stderr, "error %d: %s\n", res->code, res->error_message.c_str());
Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res->code, res->error_message.c_str());
if (!sprite->future.valid()) {
sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message)));
}
}
}, map.getLoop());
});
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
platform::request_http(spriteURL, [sprite](platform::Response *res) {
if (res->code == 200) {
sprite->image.swap(res->body);
sprite->asyncParseImage();
sprite->image.swap(res->body);
sprite->parseImage();
sprite->complete();
} else {
fprintf(stderr, "failed to load sprite image\n");
fprintf(stderr, "error %d: %s\n", res->code, res->error_message.c_str());
Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res->code, res->error_message.c_str());
if (!sprite->future.valid()) {
sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message)));
}
}
}, map.getLoop());
});
}
void Sprite::complete(std::shared_ptr<Sprite> &sprite) {
if (sprite->loadedImage && sprite->loadedJSON) {
sprite->map.update();
Log::Info(Event::Sprite, "loaded %s", sprite->url.c_str());
void Sprite::complete() {
if (loadedImage && loadedJSON) {
Log::Info(Event::Sprite, "loaded %s", spriteURL.c_str());
promise.set_value();
}
}
@@ -69,28 +98,20 @@ bool Sprite::isLoaded() const {
return loadedImage && loadedJSON;
}
void Sprite::asyncParseImage() {
new uv::work<std::shared_ptr<Sprite>>(map.getLoop(), parseImage, complete, shared_from_this());
void Sprite::parseImage() {
raster = std::make_unique<util::Image>(image);
image.clear();
loadedImage = true;
}
void Sprite::asyncParseJSON() {
new uv::work<std::shared_ptr<Sprite>>(map.getLoop(), parseJSON, complete, shared_from_this());
}
void Sprite::parseImage(std::shared_ptr<Sprite> &sprite) {
sprite->raster = std::make_unique<util::Image>(sprite->image);
sprite->image.clear();
sprite->loadedImage = true;
}
void Sprite::parseJSON(std::shared_ptr<Sprite> &sprite) {
void Sprite::parseJSON() {
rapidjson::Document d;
d.Parse<0>(sprite->body.c_str());
sprite->body.clear();
if (d.IsObject()) {
std::unordered_map<std::string, SpritePosition> pos;
d.Parse<0>(body.c_str());
body.clear();
if (d.HasParseError()) {
Log::Warning(Event::Sprite, "sprite JSON is invalid");
} else if (d.IsObject()) {
for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
const std::string& name = itr->name.GetString();
const rapidjson::Value& value = itr->value;
@@ -110,10 +131,11 @@ void Sprite::parseJSON(std::shared_ptr<Sprite> &sprite) {
pos.emplace(name, SpritePosition { x, y, width, height, pixelRatio });
}
}
sprite->pos.swap(pos);
sprite->loadedJSON = true;
} else {
Log::Warning(Event::Sprite, "sprite JSON root is not an object");
}
loadedJSON = true;
}
const SpritePosition &Sprite::getSpritePosition(const std::string& name) const {

View File

@@ -6,8 +6,7 @@
#include <mbgl/style/style_layer_group.hpp>
#include <mbgl/renderer/fill_bucket.hpp>
#include <mbgl/renderer/line_bucket.hpp>
#include <mbgl/renderer/icon_bucket.hpp>
#include <mbgl/renderer/text_bucket.hpp>
#include <mbgl/renderer/symbol_bucket.hpp>
#include <mbgl/renderer/raster_bucket.hpp>
#include <mbgl/util/raster.hpp>
#include <mbgl/util/constants.hpp>
@@ -35,14 +34,16 @@ TileParser::TileParser(const std::string &data, VectorTileData &tile,
const std::shared_ptr<const Style> &style,
const std::shared_ptr<GlyphAtlas> &glyphAtlas,
const std::shared_ptr<GlyphStore> &glyphStore,
const std::shared_ptr<SpriteAtlas> &spriteAtlas)
const std::shared_ptr<SpriteAtlas> &spriteAtlas,
const std::shared_ptr<Sprite> &sprite)
: vector_data(pbf((const uint8_t *)data.data(), data.size())),
tile(tile),
style(style),
glyphAtlas(glyphAtlas),
glyphStore(glyphStore),
spriteAtlas(spriteAtlas),
placement(tile.id.z, tile.id.z >= tile.source.max_zoom ? tile.source.max_zoom - tile.id.z : 1) {
sprite(sprite),
collision(tile.id.z, 4096, tile.source.tile_size, tile.depth) {
}
void TileParser::parse() {
@@ -51,21 +52,6 @@ void TileParser::parse() {
bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; }
void TileParser::addGlyph(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 Rect<uint16_t> rect = glyphAtlas.addGlyph(tileid, stackname, sdf);
face.emplace(chr, Glyph{rect, sdf.metrics});
}
}
}
void TileParser::parseStyleLayers(std::shared_ptr<StyleLayerGroup> group) {
if (!group) {
return;
@@ -119,10 +105,8 @@ std::unique_ptr<Bucket> TileParser::createBucket(std::shared_ptr<StyleBucket> bu
return createFillBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketFill>());
} else if (bucket_desc->render.is<StyleBucketLine>()) {
return createLineBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketLine>());
} else if (bucket_desc->render.is<StyleBucketIcon>()) {
return createIconBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketIcon>());
} else if (bucket_desc->render.is<StyleBucketText>()) {
return createTextBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketText>());
} else if (bucket_desc->render.is<StyleBucketSymbol>()) {
return createSymbolBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketSymbol>());
} else if (bucket_desc->render.is<StyleBucketRaster>()) {
return nullptr;
} else {
@@ -140,7 +124,7 @@ std::unique_ptr<Bucket> TileParser::createBucket(std::shared_ptr<StyleBucket> bu
}
template <class Bucket>
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter) {
void TileParser::addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter) {
FilteredVectorTileLayer filtered_layer(layer, filter);
for (pbf feature : filtered_layer) {
if (obsolete())
@@ -157,103 +141,20 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer,
}
}
template <class Bucket, typename... Args>
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter, Args&& ...args) {
FilteredVectorTileLayer filtered_layer(layer, filter);
for (const pbf &feature_pbf : filtered_layer) {
if (obsolete()) {
return;
}
bucket->addFeature({feature_pbf, layer}, std::forward<Args>(args)...);
}
}
std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill) {
std::unique_ptr<FillBucket> bucket = std::make_unique<FillBucket>(tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, fill);
addBucketFeatures(bucket, layer, filter);
addBucketGeometries(bucket, layer, filter);
return obsolete() ? nullptr : std::move(bucket);
}
std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line) {
std::unique_ptr<LineBucket> bucket = std::make_unique<LineBucket>(tile.lineVertexBuffer, tile.triangleElementsBuffer, tile.pointElementsBuffer, line);
addBucketFeatures(bucket, layer, filter);
addBucketGeometries(bucket, layer, filter);
return obsolete() ? nullptr : std::move(bucket);
}
std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketIcon &icon) {
std::unique_ptr<IconBucket> bucket = std::make_unique<IconBucket>(tile.iconVertexBuffer, icon);
addBucketFeatures(bucket, layer, filter, *spriteAtlas);
std::unique_ptr<Bucket> TileParser::createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol) {
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);
}
std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketText &text) {
// Make sure that we always have a valid glyph store. If this is not set, the stylesheet
// doesn't specify a glyph URL.
if (!glyphStore) {
return nullptr;
}
const StyleBucketText &properties = text;
std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>(
tile.textVertexBuffer, tile.triangleElementsBuffer, properties, placement);
util::utf8_to_utf32 ucs4conv;
std::vector<std::pair<std::u32string, pbf>> labels;
// Determine and load glyph ranges
{
std::set<GlyphRange> ranges;
FilteredVectorTileLayer filtered_layer(layer, filter);
for (const pbf &feature_pbf : filtered_layer) {
if (obsolete())
return nullptr;
VectorTileFeature feature{feature_pbf, layer};
std::string u8string = util::replaceTokens(properties.field, feature.properties);
auto& convert = std::use_facet<std::ctype<char>>(std::locale());
if (properties.transform == TextTransformType::Uppercase) {
convert.toupper(&u8string[0], &u8string[0] + u8string.size());
} else if (properties.transform == TextTransformType::Lowercase) {
convert.tolower(&u8string[0], &u8string[0] + u8string.size());
}
std::u32string string = ucs4conv.convert(u8string);
// 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);
}
glyphStore->waitForGlyphRanges(properties.font, ranges);
}
// Create a copy!
const FontStack &fontStack = glyphStore->getFontStack(properties.font);
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.max_width,
properties.line_height, properties.alignment,
properties.vertical_alignment, properties.letter_spacing);
// Place labels.
addGlyph(tile.id.to_uint64(), properties.font, label.first, fontStack, *glyphAtlas,
face);
bucket->addFeature(label.second, face, shaping);
}
return std::move(bucket);
}

View File

@@ -9,7 +9,8 @@
using namespace mbgl;
VectorTileData::VectorTileData(Tile::ID id, Map &map, const SourceInfo &source)
: TileData(id, map, source) {
: TileData(id, map, source),
depth(id.z >= source.max_zoom ? map.getMaxZoom() - id.z : 1) {
}
VectorTileData::~VectorTileData() {
@@ -20,7 +21,8 @@ VectorTileData::~VectorTileData() {
}
void VectorTileData::beforeParse() {
parser = std::make_unique<TileParser>(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getGlyphStore(), map.getSpriteAtlas());
parser = std::make_unique<TileParser>(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getGlyphStore(), map.getSpriteAtlas(), map.getSprite());
}
void VectorTileData::parse() {

View File

@@ -1,79 +0,0 @@
#include <mbgl/renderer/icon_bucket.hpp>
#include <mbgl/geometry/icon_buffer.hpp>
#include <mbgl/geometry/elements_buffer.hpp>
#include <mbgl/geometry/geometry.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/renderer/painter.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/map/vector_tile.hpp>
#include <mbgl/platform/gl.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/token.hpp>
#include <cassert>
struct geometry_too_long_exception : std::exception {};
using namespace mbgl;
IconBucket::IconBucket(IconVertexBuffer& vertexBuffer,
const StyleBucketIcon& properties)
: properties(properties),
vertexBuffer(vertexBuffer),
vertex_start(vertexBuffer.index()) {
}
void IconBucket::addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas) {
std::string field;
if (properties.icon.size()) {
field = util::replaceTokens(properties.icon, feature.properties);
}
if (!field.size()) {
field = "<circle>";
}
const Rect<uint16_t> rect = sprite_atlas.getIcon(properties.size, field);
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) {
vertexBuffer.add(x, y, tx, ty);
} else {
if (debug::tileParseWarnings) {
fprintf(stderr, "[WARNING] other command than move_to in icon geometry\n");
}
}
}
vertex_end = vertexBuffer.index();
}
void IconBucket::render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) {
painter.renderIcon(*this, layer_desc, id);
}
bool IconBucket::hasData() const {
return vertex_end > 0;
}
void IconBucket::drawIcons(IconShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
array.bind(shader, vertexBuffer, vertex_index);
glDrawArrays(GL_POINTS, 0, (GLsizei)(vertex_end - vertex_start));
}
void IconBucket::drawIcons(DotShader& shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
array.bind(shader, vertexBuffer, vertex_index);
glDrawArrays(GL_POINTS, 0, (GLsizei)(vertex_end - vertex_start));
}

View File

@@ -80,7 +80,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
CapType beginCap = properties.cap;
CapType endCap = closed ? CapType::Butt : properties.cap;
JoinType currentJoin = JoinType::Default;
JoinType currentJoin = JoinType::Miter;
Coordinate currentVertex = Coordinate::null(),
prevVertex = Coordinate::null(),

View File

@@ -71,10 +71,10 @@ void Painter::renderFill(FillBucket& bucket, const FillProperties& properties, c
}
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
const std::shared_ptr<Sprite> &sprite = map.getStyle()->sprite;
Sprite &sprite = *map.getSprite();
if (properties.image.size() && sprite) {
SpriteAtlas &spriteAtlas = *map.getSpriteAtlas();
Rect<uint16_t> imagePos = spriteAtlas.getImage(properties.image, *sprite);
Rect<uint16_t> imagePos = spriteAtlas.getImage(properties.image, sprite);
float factor = 8.0 / std::pow(2, map.getState().getIntegerZoom() - id.z);

View File

@@ -1,45 +0,0 @@
#include <mbgl/renderer/painter.hpp>
#include <mbgl/renderer/icon_bucket.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/util/math.hpp>
using namespace mbgl;
void Painter::renderIcon(IconBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) {
// Abort early.
if (!bucket.hasData()) return;
if (pass == Opaque) return;
const IconProperties &properties = layer_desc->getProperties<IconProperties>();
// TODO: when translating icon, are we doing this in the bucket already?
// const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
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());
const float iconSize = bucket.properties.size * 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

@@ -0,0 +1,217 @@
#include <mbgl/renderer/painter.hpp>
#include <mbgl/renderer/symbol_bucket.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/geometry/glyph_atlas.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/util/math.hpp>
#include <cmath>
namespace mbgl {
void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> layer_desc,
const Tile::ID &id) {
// Abort early.
if (pass == Opaque) {
return;
}
const SymbolProperties &properties = layer_desc->getProperties<SymbolProperties>();
if (bucket.hasTextData()) {
mat4 exMatrix;
matrix::copy(exMatrix, projMatrix);
if (bucket.properties.placement == PlacementType::Line) {
matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
}
// If layerStyle.size > bucket.info.fontSize then labels may collide
float fontSize = std::fmin(properties.text.size, bucket.properties.text.max_size);
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
useProgram(textShader->program);
textShader->setMatrix(matrix);
textShader->setExtrudeMatrix(exMatrix);
GlyphAtlas &glyphAtlas = *map.getGlyphAtlas();
glyphAtlas.bind();
textShader->setTextureSize(
{{static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height)}});
// Convert the -pi..pi to an int8 range.
float angle = std::round((map.getState().getAngle()) / M_PI * 128);
// adjust min/max zooms for variable font sies
float zoomAdjust = log(fontSize / bucket.properties.text.max_size) / log(2);
textShader->setAngle((int32_t)(angle + 256) % 256);
textShader->setFlip(bucket.properties.placement == PlacementType::Line ? 1 : 0);
textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) *
10); // current zoom level
// Label fading
const timestamp duration = 300_milliseconds;
const timestamp currentTime = util::now();
std::deque<FrameSnapshot> &history = frameHistory.history;
// Remove frames until only one is outside the duration, or until there are only three
while (history.size() > 3 && history[1].t + duration < currentTime) {
history.pop_front();
}
if (history[1].t + duration < currentTime) {
history[0].z = history[1].z;
}
assert("there should never be less than three frames in the history" &&
(history.size() >= 3));
// Find the range of zoom levels we want to fade between
float startingZ = history.front().z;
const FrameSnapshot lastFrame = history.back();
float endingZ = lastFrame.z;
float lowZ = std::fmin(startingZ, endingZ);
float highZ = std::fmax(startingZ, endingZ);
// Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one
// duration
float zoomDiff = endingZ - history[1].z, timeDiff = lastFrame.t - history[1].t;
float fadedist = zoomDiff / (timeDiff / duration);
#if defined(DEBUG)
if (std::isnan(fadedist))
fprintf(stderr, "fadedist should never be NaN\n");
#endif
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
// bump is how much farther it would have been if it had continued zooming at the same rate
float bump = (currentTime - lastFrame.t) / duration * fadedist;
textShader->setFadeDist(fadedist * 10);
textShader->setMinFadeZoom(std::floor(lowZ * 10));
textShader->setMaxFadeZoom(std::floor(highZ * 10));
textShader->setFadeZoom((map.getState().getNormalizedZoom() + bump) * 10);
// This defines the gamma around the SDF cutoff value.
const float sdfGamma = 1.0f / 10.0f;
// Our signed distance fields are scaled so that 1 pixel is scaled to 32 pixels.
// Our cutoff between positive and negative values is hard coded to 64 (== 2 pixels).
// This means that our 6/8 of the value range lies outside the glyph outline.
const float sdfOffset = (256.0f - 64.0f) / 32.0f;
// Currently, all of our fonts are rendered with a font size of 24px.
const float sdfFontSize = 24.0f;
// The default gamma value has to be adjust for the current pixelratio so that we're not
// drawing
// blurry font on retina screens.
const float gamma = sdfGamma * sdfFontSize / fontSize / map.getState().getPixelRatio();
// We're drawing in the translucent pass which is bottom-to-top, so we need
// to draw the halo first.
if (properties.text.halo_color[3] > 0.0f) {
const float haloWidth = util::clamp(
(sdfOffset - properties.text.halo_width / (fontSize / sdfFontSize)) / 8.0f, 0.0f,
1.0f);
if (properties.text.halo_blur != 0.0f) {
// We are converting the halo_blur value to current screen pixels.
// Then we're dividing it by two because the gamma value is added/subtracted into
// both
// directions in the shader, but the halo_blur describes the entire width of the
// blur.
// Note that this does *not* have to be adjusted for retina screens, because we want
// the
// same blur width when we explicitly specify one.
textShader->setGamma((properties.text.halo_blur / (fontSize / sdfFontSize)) / 8.0f /
2.0f);
} else {
textShader->setGamma(sdfGamma);
}
if (properties.text.opacity < 1.0f) {
Color color = properties.text.halo_color;
color[0] *= properties.text.opacity;
color[1] *= properties.text.opacity;
color[2] *= properties.text.opacity;
color[3] *= properties.text.opacity;
textShader->setColor(color);
} else {
textShader->setColor(properties.text.halo_color);
}
textShader->setBuffer(haloWidth);
glDepthRange(strata, 1.0f);
bucket.drawGlyphs(*textShader);
}
if (properties.text.color[3] > 0.0f) {
// Then, we draw the text over the halo
textShader->setGamma(gamma);
if (properties.text.opacity < 1.0f) {
Color color = properties.text.color;
color[0] *= properties.text.opacity;
color[1] *= properties.text.opacity;
color[2] *= properties.text.opacity;
color[3] *= properties.text.opacity;
textShader->setColor(color);
} else {
textShader->setColor(properties.text.color);
}
textShader->setBuffer((256.0f - 64.0f) / 256.0f);
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawGlyphs(*textShader);
}
}
if (bucket.hasIconData()) {
mat4 exMatrix;
matrix::copy(exMatrix, projMatrix);
const float angleOffset =
bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map
? map.getState().getAngle()
: 0;
if (angleOffset) {
matrix::rotate_z(exMatrix, exMatrix, angleOffset);
}
// If layerStyle.size > bucket.info.fontSize then labels may collide
const float fontSize = properties.icon.size != 0 ? properties.icon.size : bucket.properties.icon.max_size;
const float fontScale = fontSize / 1.0f;
matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);
useProgram(iconShader->program);
iconShader->setMatrix(matrix);
iconShader->setExtrudeMatrix(exMatrix);
SpriteAtlas &spriteAtlas = *map.getSpriteAtlas();
spriteAtlas.bind(map.getState().isChanging() || bucket.properties.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1);
iconShader->setTextureSize(
{{static_cast<float>(spriteAtlas.getWidth()), static_cast<float>(spriteAtlas.getHeight())}});
// Convert the -pi..pi to an int8 range.
const float angle = std::round((map.getState().getAngle()) / M_PI * 128);
// adjust min/max zooms for variable font sies
float zoomAdjust = log(fontSize / bucket.properties.icon.max_size) / log(2);
iconShader->setAngle((int32_t)(angle + 256) % 256);
iconShader->setFlip(bucket.properties.placement == PlacementType::Line ? 1 : 0);
iconShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) *
10); // current zoom level
iconShader->setFadeDist(0 * 10);
iconShader->setMinFadeZoom(map.getState().getNormalizedZoom() * 10);
iconShader->setMaxFadeZoom(map.getState().getNormalizedZoom() * 10);
iconShader->setFadeZoom(map.getState().getNormalizedZoom() * 10);
iconShader->setOpacity(properties.icon.opacity);
glDepthRange(strata, 1.0f);
bucket.drawIcons(*iconShader);
}
}
}

View File

@@ -1,156 +0,0 @@
#include <mbgl/renderer/painter.hpp>
#include <mbgl/renderer/text_bucket.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/geometry/glyph_atlas.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/util/math.hpp>
#include <cmath>
using namespace mbgl;
void Painter::renderText(TextBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) {
// Abort early.
if (pass == Opaque) return;
if (!bucket.hasData()) return;
const TextProperties &properties = layer_desc->getProperties<TextProperties>();
mat4 exMatrix;
matrix::copy(exMatrix, projMatrix);
if (bucket.properties.path == TextPathType::Curve) {
matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
}
// If layerStyle.size > bucket.info.fontSize then labels may collide
float fontSize = std::fmin(properties.size, bucket.properties.max_size);
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
// TODO: figure out whether we actually need to account for this while painting; we might already have
// done this during label placement.
// const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
useProgram(textShader->program);
textShader->setMatrix(matrix);
textShader->setExtrudeMatrix(exMatrix);
GlyphAtlas &glyphAtlas = *map.getGlyphAtlas();
glyphAtlas.bind();
textShader->setTextureSize({{static_cast<float>(glyphAtlas.width),
static_cast<float>(glyphAtlas.height)}});
// Convert the -pi..pi to an int8 range.
float angle = std::round((map.getState().getAngle()) / M_PI * 128);
// adjust min/max zooms for variable font sies
float zoomAdjust = log(fontSize / bucket.properties.max_size) / log(2);
textShader->setAngle((int32_t)(angle + 256) % 256);
textShader->setFlip(bucket.properties.path == TextPathType::Curve ? 1 : 0);
textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
// Label fading
const timestamp duration = 300_milliseconds;
const timestamp currentTime = util::now();
std::deque<FrameSnapshot> &history = frameHistory.history;
// Remove frames until only one is outside the duration, or until there are only three
while (history.size() > 3 && history[1].t + duration < currentTime) {
history.pop_front();
}
if (history[1].t + duration < currentTime) {
history[0].z = history[1].z;
}
assert("there should never be less than three frames in the history" && (history.size() >= 3));
// Find the range of zoom levels we want to fade between
float startingZ = history.front().z;
const FrameSnapshot lastFrame = history.back();
float endingZ = lastFrame.z;
float lowZ = std::fmin(startingZ, endingZ);
float highZ = std::fmax(startingZ, endingZ);
// Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration
float zoomDiff = endingZ - history[1].z,
timeDiff = lastFrame.t - history[1].t;
float fadedist = zoomDiff / (timeDiff / duration);
#if defined(DEBUG)
// if (std::isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
#endif
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
// bump is how much farther it would have been if it had continued zooming at the same rate
float bump = (currentTime - lastFrame.t) / duration * fadedist;
textShader->setFadeDist(fadedist * 10);
textShader->setMinFadeZoom(std::floor(lowZ * 10));
textShader->setMaxFadeZoom(std::floor(highZ * 10));
textShader->setFadeZoom((map.getState().getNormalizedZoom() + bump) * 10);
// This defines the gamma around the SDF cutoff value.
const float sdfGamma = 0.75f / 10.0f;
// Our signed distance fields are scaled so that 1 pixel is scaled to 32 pixels.
// Our cutoff between positive and negative values is hard coded to 64 (== 2 pixels).
// This means that our 6/8 of the value range lies outside the glyph outline.
const float sdfOffset = (256.0f - 64.0f) / 32.0f;
// Currently, all of our fonts are rendered with a font size of 24px.
const float sdfFontSize = 24.0f;
// The default gamma value has to be adjust for the current pixelratio so that we're not drawing
// blurry font on retina screens.
const float gamma = sdfGamma * sdfFontSize / fontSize / map.getState().getPixelRatio();
// We're drawing in the translucent pass which is bottom-to-top, so we need
// to draw the halo first.
if (properties.halo_color[3] > 0.0f) {
const float haloWidth = util::clamp((sdfOffset - properties.halo_width / (fontSize / sdfFontSize)) / 8.0f, 0.0f, 1.0f);
if (properties.halo_blur != 0.0f) {
// We are converting the halo_blur value to current screen pixels.
// Then we're dividing it by two because the gamma value is added/subtracted into both
// directions in the shader, but the halo_blur describes the entire width of the blur.
// Note that this does *not* have to be adjusted for retina screens, because we want the
// same blur width when we explicitly specify one.
textShader->setGamma((properties.halo_blur / (fontSize / sdfFontSize)) / 8.0f / 2.0f);
} else {
textShader->setGamma(sdfGamma);
}
if (properties.opacity < 1.0f) {
Color color = properties.halo_color;
color[0] *= properties.opacity;
color[1] *= properties.opacity;
color[2] *= properties.opacity;
color[3] *= properties.opacity;
textShader->setColor(color);
} else {
textShader->setColor(properties.halo_color);
}
textShader->setBuffer(haloWidth);
glDepthRange(strata, 1.0f);
bucket.drawGlyphs(*textShader);
}
if (properties.color[3] > 0.0f) {
// Then, we draw the text over the halo
textShader->setGamma(gamma);
if (properties.opacity < 1.0f) {
Color color = properties.color;
color[0] *= properties.opacity;
color[1] *= properties.opacity;
color[2] *= properties.opacity;
color[3] *= properties.opacity;
textShader->setColor(color);
} else {
textShader->setColor(properties.color);
}
textShader->setBuffer((256.0f - 64.0f) / 256.0f);
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawGlyphs(*textShader);
}
}

View File

@@ -0,0 +1,394 @@
#include <mbgl/renderer/symbol_bucket.hpp>
#include <mbgl/geometry/text_buffer.hpp>
#include <mbgl/geometry/icon_buffer.hpp>
#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(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);
}
bool SymbolBucket::hasData() const { return hasTextData() || hasIconData(); }
bool SymbolBucket::hasTextData() const { return !text.groups.empty(); }
bool SymbolBucket::hasIconData() const { return !icon.groups.empty(); }
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 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) {
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;
// Determine and load glyph ranges
std::set<GlyphRange> ranges;
FilteredVectorTileLayer filtered_layer(layer, filter);
for (const pbf &feature_pbf : filtered_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());
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());
}
ft.label = ucs4conv.convert(u8string);
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));
}
}
}
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;
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;
float justify = 0.5;
if (properties.text.justify == TextJustifyType::Right) justify = 1;
else if (properties.text.justify == TextJustifyType::Left) justify = 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(
/* string */ feature.label,
/* maxWidth */ properties.text.max_width,
/* lineHeight */ properties.text.line_height,
/* horizontalAlign */ horizontalAlign,
/* verticalAlign */ verticalAlign,
/* justify */ justify,
/* spacing */ properties.text.letter_spacing,
/* translate */ properties.text.offset);
// 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
if (feature.sprite.length()) {
image = spriteAtlas.waitForImage(feature.sprite, sprite);
}
// if either shaping or icon position is present, add the feature
if (shaping.size() || image) {
addFeature(feature.geometry, shaping, face, 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;
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, face, image);
line.clear();
}
}
line.emplace_back(x, y);
}
if (line.size()) {
addFeature(line, shaping, face, 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 float fontScale = properties.text.max_size / glyphSize;
const float textBoxScale = collision.tilePixelRatio * fontScale;
const float iconBoxScale = collision.tilePixelRatio * properties.icon.max_size;
const bool iconWithoutText = properties.text.optional || !shaping.size();
const bool textWithoutIcon = properties.icon.optional || !image;
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);
}
}
}
template <typename Buffer>
void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale,
PlacementRange placementRange) {
const float zoom = collision.zoom;
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;
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 (!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.
buffer.groups.emplace_back();
}
// We're generating triangle fans, so we always start with the first
// coordinate in this polygon.
ElementGroup &triangleGroup = buffer.groups.back();
uint32_t triangleIndex = triangleGroup.vertex_length;
// coordinates (2 triangles)
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.
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;
}
}
void SymbolBucket::drawGlyphs(TextShader &shader) {
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(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;
}
}
}

View File

@@ -1,146 +0,0 @@
#include <mbgl/renderer/text_bucket.hpp>
#include <mbgl/geometry/text_buffer.hpp>
#include <mbgl/geometry/elements_buffer.hpp>
#include <mbgl/geometry/geometry.hpp>
#include <mbgl/renderer/painter.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/map/vector_tile.hpp>
#include <mbgl/text/placement.hpp>
#include <mbgl/text/glyph_store.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/platform/gl.hpp>
#include <iostream>
#include <cassert>
using namespace mbgl;
TextBucket::TextBucket(
TextVertexBuffer &vertexBuffer,
TriangleElementsBuffer &triangleElementsBuffer,
const StyleBucketText &properties, Placement &placement)
: properties(properties),
vertexBuffer(vertexBuffer),
triangleElementsBuffer(triangleElementsBuffer),
placement(placement),
vertex_start(vertexBuffer.index()),
triangle_elements_start(triangleElementsBuffer.index()) {}
void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom,
PlacementRange placementRange, float zoom) {
placementZoom += zoom;
for (const PlacedGlyph &glyph : glyphs) {
const auto &tl = glyph.tl;
const auto &tr = glyph.tr;
const auto &bl = glyph.bl;
const auto &br = glyph.br;
const auto &tex = glyph.tex;
const auto &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)
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x,
tex.y, angle, minZoom, placementRange, maxZoom,
placementZoom);
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y,
tex.x + tex.w, tex.y, angle, minZoom, placementRange,
maxZoom, placementZoom);
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x,
tex.y + tex.h, angle, minZoom, placementRange,
maxZoom, placementZoom);
vertexBuffer.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 TextBucket::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);
}
}
void TextBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc,
const Tile::ID &id) {
painter.renderText(*this, layer_desc, id);
}
bool TextBucket::hasData() const {
return !triangleGroups.empty();
}
void TextBucket::drawGlyphs(TextShader &shader) {
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
char *elements_index =
BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
for (triangle_group_type &group : triangleGroups) {
group.array.bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
vertex_index += group.vertex_length * vertexBuffer.itemSize;
elements_index += group.elements_length * triangleElementsBuffer.itemSize;
}
}

View File

@@ -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);
// Note: We don't need to premultiply here as the image we use is already
// premultiplied in the sprite atlas.
gl_FragColor = texture2D(u_texture, v_tex) * v_alpha;
}

View File

@@ -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;
// posmatrix is for the vertex position, exmatrix is for rotating and projecting
// the extrusion vector.
uniform mat4 u_matrix;
uniform float u_size;
uniform float u_ratio;
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_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);
v_tex = a_tex / u_texsize;
v_alpha *= u_opacity;
}

View File

@@ -17,62 +17,138 @@ 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");
u_texsize = glGetUniformLocation(program, "u_texsize");
// 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;
}
}
void IconShader::setTextureSize(const std::array<float, 2> &new_texsize) {
if (texsize != new_texsize) {
glUniform2fv(u_texsize, 1, new_texsize.data());
texsize = new_texsize;
}
}

View File

@@ -52,6 +52,7 @@ TextShader::TextShader()
// fprintf(stderr, " - u_maxfadezoom: %d\n", u_maxfadezoom);
// fprintf(stderr, " - u_fadezoom: %d\n", u_fadezoom);
// fprintf(stderr, " - u_texsize: %d\n", u_texsize);
// fprintf(stderr, " - u_opacity: %d\n", u_opacity);
}
void TextShader::bind(char *offset) {

View File

@@ -23,16 +23,26 @@ const std::map<PropertyKey, PropertyValue> PropertyFallbackValue::properties = {
{ PropertyKey::LineDashLand, defaultStyleProperties<LineProperties>().dash_array[0] },
{ PropertyKey::LineDashGap, defaultStyleProperties<LineProperties>().dash_array[1] },
{ PropertyKey::IconOpacity, defaultStyleProperties<IconProperties>().opacity },
{ PropertyKey::IconRotate, defaultStyleProperties<IconProperties>().rotate },
{ PropertyKey::IconRotateAnchor, defaultStyleProperties<IconProperties>().rotate_anchor },
{ PropertyKey::IconOpacity, defaultStyleProperties<SymbolProperties>().icon.opacity },
{ PropertyKey::IconRotate, defaultStyleProperties<SymbolProperties>().icon.rotate },
{ PropertyKey::IconSize, defaultStyleProperties<SymbolProperties>().icon.size },
{ PropertyKey::IconColor, defaultStyleProperties<SymbolProperties>().icon.color },
{ PropertyKey::IconHaloColor, defaultStyleProperties<SymbolProperties>().icon.halo_color },
{ PropertyKey::IconHaloWidth, defaultStyleProperties<SymbolProperties>().icon.halo_width },
{ PropertyKey::IconHaloBlur, defaultStyleProperties<SymbolProperties>().icon.halo_blur },
{ PropertyKey::IconTranslateX, defaultStyleProperties<SymbolProperties>().icon.translate[0] },
{ PropertyKey::IconTranslateY, defaultStyleProperties<SymbolProperties>().icon.translate[1] },
{ PropertyKey::IconTranslateAnchor, defaultStyleProperties<SymbolProperties>().icon.translate_anchor },
{ PropertyKey::TextOpacity, defaultStyleProperties<TextProperties>().opacity },
{ PropertyKey::TextSize, defaultStyleProperties<TextProperties>().size },
{ PropertyKey::TextColor, defaultStyleProperties<TextProperties>().color },
{ PropertyKey::TextHaloColor, defaultStyleProperties<TextProperties>().halo_color },
{ PropertyKey::TextHaloWidth, defaultStyleProperties<TextProperties>().halo_width },
{ PropertyKey::TextHaloBlur, defaultStyleProperties<TextProperties>().halo_blur },
{ PropertyKey::TextOpacity, defaultStyleProperties<SymbolProperties>().text.opacity },
{ PropertyKey::TextSize, defaultStyleProperties<SymbolProperties>().text.size },
{ PropertyKey::TextColor, defaultStyleProperties<SymbolProperties>().text.color },
{ PropertyKey::TextHaloColor, defaultStyleProperties<SymbolProperties>().text.halo_color },
{ PropertyKey::TextHaloWidth, defaultStyleProperties<SymbolProperties>().text.halo_width },
{ PropertyKey::TextHaloBlur, defaultStyleProperties<SymbolProperties>().text.halo_blur },
{ PropertyKey::TextTranslateX, defaultStyleProperties<SymbolProperties>().text.translate[0] },
{ PropertyKey::TextTranslateY, defaultStyleProperties<SymbolProperties>().text.translate[1] },
{ PropertyKey::TextTranslateAnchor, defaultStyleProperties<SymbolProperties>().text.translate_anchor },
{ PropertyKey::CompositeOpacity, defaultStyleProperties<CompositeProperties>().opacity },

View File

@@ -1,4 +1,5 @@
#include <mbgl/style/style.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/style/style_layer_group.hpp>
#include <mbgl/style/style_parser.hpp>
#include <mbgl/style/style_bucket.hpp>
@@ -29,6 +30,10 @@ void Style::updateProperties(float z, timestamp now) {
}
}
const std::string &Style::getSpriteURL() const {
return sprite_url;
}
void Style::setDefaultTransitionDuration(uint16_t duration_milliseconds) {
defaultTransition.duration = duration_milliseconds;
}
@@ -93,7 +98,7 @@ void Style::loadJSON(const uint8_t *const data) {
const BackgroundProperties &Style::getBackgroundProperties() const {
if (layers && layers->layers.size()) {
const auto first = layers->layers.front();
if (first && first->id == "background") {
if (first && first->type == StyleLayerType::Background) {
return first->getProperties<BackgroundProperties>();
}
}

View File

@@ -6,8 +6,7 @@ StyleBucket::StyleBucket(StyleLayerType type) {
switch (type) {
case StyleLayerType::Fill: render = StyleBucketFill{}; break;
case StyleLayerType::Line: render = StyleBucketLine{}; break;
case StyleLayerType::Icon: render = StyleBucketIcon{}; break;
case StyleLayerType::Text: render = StyleBucketText{}; break;
case StyleLayerType::Symbol: render = StyleBucketSymbol{}; break;
case StyleLayerType::Raster: render = StyleBucketRaster{}; break;
default: break;
}

View File

@@ -10,7 +10,7 @@ StyleLayer::StyleLayer(const std::string &id, std::map<ClassID, ClassProperties>
: id(id), styles(std::move(styles)), rasterize(std::move(rasterize)) {}
bool StyleLayer::isBackground() const {
return type == StyleLayerType::Background && id == "background";
return type == StyleLayerType::Background;
}
void StyleLayer::setClasses(const std::vector<std::string> &class_names, const timestamp now,
@@ -191,24 +191,30 @@ void StyleLayer::applyStyleProperties<LineProperties>(const float z, const times
}
template <>
void StyleLayer::applyStyleProperties<IconProperties>(const float z, const timestamp now) {
properties.set<IconProperties>();
IconProperties &icon = properties.get<IconProperties>();
applyStyleProperty(PropertyKey::IconOpacity, icon.opacity, z, now);
applyStyleProperty(PropertyKey::IconRotate, icon.rotate, z, now);
applyStyleProperty(PropertyKey::IconRotateAnchor, icon.rotate_anchor, z, now);
}
void StyleLayer::applyStyleProperties<SymbolProperties>(const float z, const timestamp now) {
properties.set<SymbolProperties>();
SymbolProperties &symbol = properties.get<SymbolProperties>();
applyStyleProperty(PropertyKey::IconOpacity, symbol.icon.opacity, z, now);
applyStyleProperty(PropertyKey::IconRotate, symbol.icon.rotate, z, now);
applyStyleProperty(PropertyKey::IconSize, symbol.icon.size, z, now);
applyStyleProperty(PropertyKey::IconColor, symbol.icon.color, z, now);
applyStyleProperty(PropertyKey::IconHaloColor, symbol.icon.halo_color, z, now);
applyStyleProperty(PropertyKey::IconHaloWidth, symbol.icon.halo_width, z, now);
applyStyleProperty(PropertyKey::IconHaloBlur, symbol.icon.halo_blur, z, now);
applyStyleProperty(PropertyKey::IconTranslateX, symbol.icon.translate[0], z, now);
applyStyleProperty(PropertyKey::IconTranslateY, symbol.icon.translate[1], z, now);
applyStyleProperty(PropertyKey::IconTranslateAnchor, symbol.icon.translate_anchor, z, now);
applyStyleProperty(PropertyKey::TextOpacity, symbol.text.opacity, z, now);
applyStyleProperty(PropertyKey::TextSize, symbol.text.size, z, now);
applyStyleProperty(PropertyKey::TextColor, symbol.text.color, z, now);
applyStyleProperty(PropertyKey::TextHaloColor, symbol.text.halo_color, z, now);
applyStyleProperty(PropertyKey::TextHaloWidth, symbol.text.halo_width, z, now);
applyStyleProperty(PropertyKey::TextHaloBlur, symbol.text.halo_blur, z, now);
applyStyleProperty(PropertyKey::TextTranslateX, symbol.text.translate[0], z, now);
applyStyleProperty(PropertyKey::TextTranslateY, symbol.text.translate[1], z, now);
applyStyleProperty(PropertyKey::TextTranslateAnchor, symbol.text.translate_anchor, z, now);
template <>
void StyleLayer::applyStyleProperties<TextProperties>(const float z, const timestamp now) {
properties.set<TextProperties>();
TextProperties &text = properties.get<TextProperties>();
applyStyleProperty(PropertyKey::TextOpacity, text.opacity, z, now);
applyStyleProperty(PropertyKey::TextSize, text.size, z, now);
applyStyleProperty(PropertyKey::TextColor, text.color, z, now);
applyStyleProperty(PropertyKey::TextHaloColor, text.halo_color, z, now);
applyStyleProperty(PropertyKey::TextHaloWidth, text.halo_width, z, now);
applyStyleProperty(PropertyKey::TextHaloBlur, text.halo_blur, z, now);
}
template <>
@@ -248,8 +254,7 @@ void StyleLayer::updateProperties(float z, const timestamp now) {
switch (type) {
case StyleLayerType::Fill: applyStyleProperties<FillProperties>(z, now); break;
case StyleLayerType::Line: applyStyleProperties<LineProperties>(z, now); break;
case StyleLayerType::Icon: applyStyleProperties<IconProperties>(z, now); break;
case StyleLayerType::Text: applyStyleProperties<TextProperties>(z, now); break;
case StyleLayerType::Symbol: applyStyleProperties<SymbolProperties>(z, now); break;
case StyleLayerType::Raster: applyStyleProperties<RasterProperties>(z, now); break;
case StyleLayerType::Composite: applyStyleProperties<CompositeProperties>(z, now); break;
case StyleLayerType::Background: applyStyleProperties<BackgroundProperties>(z, now); break;

View File

@@ -63,6 +63,20 @@ JSVal StyleParser::replaceConstant(JSVal value) {
#pragma mark - Parse Render Properties
template<> bool StyleParser::parseRenderProperty(JSVal value, bool &target, const char *name) {
if (value.HasMember(name)) {
JSVal property = replaceConstant(value[name]);
if (property.IsBool()) {
target = property.GetBool();
return true;
} else {
fprintf(stderr, "[WARNING] '%s' must be a boolean\n", name);
}
}
return false;
}
template<> bool StyleParser::parseRenderProperty(JSVal value, std::string &target, const char *name) {
if (value.HasMember(name)) {
JSVal property = replaceConstant(value[name]);
@@ -139,12 +153,12 @@ template<> bool StyleParser::parseRenderProperty(JSVal value, vec2<float> &targe
return false;
}
template<typename T, typename Parser>
bool StyleParser::parseRenderProperty(JSVal value, T &target, const char *name, Parser &parser) {
template<typename Parser, typename T>
bool StyleParser::parseRenderProperty(JSVal value, T &target, const char *name) {
if (value.HasMember(name)) {
JSVal property = replaceConstant(value[name]);
if (property.IsString()) {
target = parser({ property.GetString(), property.GetStringLength() });
target = Parser({ property.GetString(), property.GetStringLength() });
return true;
} else {
fprintf(stderr, "[WARNING] %s must have one of the enum values\n", name);
@@ -167,7 +181,7 @@ void StyleParser::parseSources(JSVal value) {
int32_t min_zoom = 0;
int32_t max_zoom = 22;
parseRenderProperty(itr->value, type, "type", parseSourceType);
parseRenderProperty<SourceTypeClass>(itr->value, type, "type");
parseRenderProperty(itr->value, url, "url");
if (type == SourceType::Raster) {
parseRenderProperty(itr->value, tile_size, "tileSize");
@@ -348,19 +362,19 @@ template<> std::tuple<bool, std::string> StyleParser::parseProperty(JSVal value,
template<> std::tuple<bool, TranslateAnchorType> StyleParser::parseProperty(JSVal value, const char *property_name) {
if (!value.IsString()) {
fprintf(stderr, "[WARNING] value of '%s' must be a string\n", property_name);
return std::tuple<bool, TranslateAnchorType> { false, TranslateAnchorType::Default };
return std::tuple<bool, TranslateAnchorType> { false, TranslateAnchorType::Map };
}
return std::tuple<bool, TranslateAnchorType> { true, parseTranslateAnchorType({ value.GetString(), value.GetStringLength() }) };
return std::tuple<bool, TranslateAnchorType> { true, TranslateAnchorTypeClass({ value.GetString(), value.GetStringLength() }) };
}
template<> std::tuple<bool, RotateAnchorType> StyleParser::parseProperty<RotateAnchorType>(JSVal value, const char *property_name) {
if (!value.IsString()) {
fprintf(stderr, "[WARNING] value of '%s' must be a string\n", property_name);
return std::tuple<bool, RotateAnchorType> { false, RotateAnchorType::Default };
return std::tuple<bool, RotateAnchorType> { false, RotateAnchorType::Map };
}
return std::tuple<bool, RotateAnchorType> { true, parseRotateAnchorType({ value.GetString(), value.GetStringLength() }) };
return std::tuple<bool, RotateAnchorType> { true, RotateAnchorTypeClass({ value.GetString(), value.GetStringLength() }) };
}
template<> std::tuple<bool, PropertyTransition> StyleParser::parseProperty(JSVal value, const char *property_name) {
@@ -592,7 +606,18 @@ void StyleParser::parseStyle(JSVal value, ClassProperties &klass) {
parseOptionalProperty<Function<float>>("icon-opacity", Key::IconOpacity, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-opacity", Key::IconOpacity, klass, value);
parseOptionalProperty<Function<float>>("icon-rotate", Key::IconRotate, klass, value);
parseOptionalProperty<RotateAnchorType>("icon-rotate-anchor", Key::IconRotateAnchor, klass, value);
parseOptionalProperty<Function<float>>("icon-size", Key::IconSize, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-size", Key::IconSize, klass, value);
parseOptionalProperty<Function<Color>>("icon-color", Key::IconColor, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-color", Key::IconColor, klass, value);
parseOptionalProperty<Function<Color>>("icon-halo-color", Key::IconHaloColor, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-halo-color", Key::IconHaloColor, klass, value);
parseOptionalProperty<Function<float>>("icon-halo-width", Key::IconHaloWidth, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-halo-width", Key::IconHaloWidth, klass, value);
parseOptionalProperty<Function<float>>("icon-halo-blur", Key::IconHaloBlur, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-halo-blur", Key::IconHaloBlur, klass, value);
parseOptionalProperty<Function<float>>("icon-translate", { Key::IconTranslateX, Key::IconTranslateY }, klass, value);
parseOptionalProperty<PropertyTransition>("transition-icon-translate", Key::IconTranslate, klass, value);
parseOptionalProperty<Function<float>>("text-opacity", Key::TextOpacity, klass, value);
parseOptionalProperty<PropertyTransition>("transition-text-opacity", Key::TextOpacity, klass, value);
@@ -606,6 +631,8 @@ void StyleParser::parseStyle(JSVal value, ClassProperties &klass) {
parseOptionalProperty<PropertyTransition>("transition-text-halo-width", Key::TextHaloWidth, klass, value);
parseOptionalProperty<Function<float>>("text-halo-blur", Key::TextHaloBlur, klass, value);
parseOptionalProperty<PropertyTransition>("transition-text-halo-blur", Key::TextHaloBlur, klass, value);
parseOptionalProperty<Function<float>>("text-translate", { Key::TextTranslateX, Key::TextTranslateY }, klass, value);
parseOptionalProperty<PropertyTransition>("transition-text-translate", Key::TextTranslate, klass, value);
parseOptionalProperty<Function<float>>("composite-opacity", Key::CompositeOpacity, klass, value);
parseOptionalProperty<PropertyTransition>("transition-composite-opacity", Key::CompositeOpacity, klass, value);
@@ -834,61 +861,70 @@ void StyleParser::parseRender(JSVal value, std::shared_ptr<StyleLayer> &layer) {
case StyleLayerType::Fill: {
StyleBucketFill &render = bucket.render.get<StyleBucketFill>();
parseRenderProperty(value, render.winding, "fill-winding", parseWindingType);
parseRenderProperty<WindingTypeClass>(value, render.winding, "fill-winding");
} break;
case StyleLayerType::Line: {
StyleBucketLine &render = bucket.render.get<StyleBucketLine>();
parseRenderProperty(value, render.cap, "line-cap", parseCapType);
parseRenderProperty(value, render.join, "line-join", parseJoinType);
parseRenderProperty<CapTypeClass>(value, render.cap, "line-cap");
parseRenderProperty<JoinTypeClass>(value, render.join, "line-join");
parseRenderProperty(value, render.miter_limit, "line-miter-limit");
parseRenderProperty(value, render.round_limit, "line-round-limit");
} break;
case StyleLayerType::Icon: {
StyleBucketIcon &render = bucket.render.get<StyleBucketIcon>();
case StyleLayerType::Symbol: {
StyleBucketSymbol &render = bucket.render.get<StyleBucketSymbol>();
parseRenderProperty(value, render.size, "icon-size");
parseRenderProperty(value, render.icon, "icon-image");
parseRenderProperty(value, render.spacing, "icon-spacing");
parseRenderProperty(value, render.padding, "icon-padding");
if (parseRenderProperty(value, render.translate, "icon-translate")) {
render.translate.x *= 24;
render.translate.y *= -24;
parseRenderProperty<PlacementTypeClass>(value, render.placement, "symbol-placement");
if (render.placement == PlacementType::Line) {
// Change the default value in case of line placement.
render.text.rotation_alignment = RotationAlignmentType::Map;
render.icon.rotation_alignment = RotationAlignmentType::Map;
}
parseRenderProperty(value, render.translate_anchor, "icon-translate-anchor", parseTranslateAnchorType);
} break;
case StyleLayerType::Text: {
StyleBucketText &render = bucket.render.get<StyleBucketText>();
parseRenderProperty(value, render.min_distance, "symbol-min-distance");
parseRenderProperty(value, render.field, "text-field");
parseRenderProperty(value, render.path, "text-path", parseTextPathType);
parseRenderProperty(value, render.transform, "text-transform", parseTextTransformType);
parseRenderProperty(value, render.font, "text-font");
parseRenderProperty(value, render.max_size, "text-max-size");
if (parseRenderProperty(value, render.max_width, "text-max-width")) {
render.max_width *= 24; // em
parseRenderProperty(value, render.icon.allow_overlap, "icon-allow-overlap");
parseRenderProperty(value, render.icon.ignore_placement, "icon-ignore-placement");
parseRenderProperty(value, render.icon.optional, "icon-optional");
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");
parseRenderProperty<TranslateAnchorTypeClass>(value, render.icon.translate_anchor, "icon-translate-anchor");
parseRenderProperty<RotationAlignmentTypeClass>(value, render.text.rotation_alignment, "text-rotation-alignment");
parseRenderProperty(value, render.text.field, "text-field");
parseRenderProperty(value, render.text.font, "text-font");
parseRenderProperty(value, render.text.max_size, "text-max-size");
if (parseRenderProperty(value, render.text.max_width, "text-max-width")) {
render.text.max_width *= 24; // em
}
if (parseRenderProperty(value, render.line_height, "text-line-height")) {
render.line_height *= 24; // em
if (parseRenderProperty(value, render.text.line_height, "text-line-height")) {
render.text.line_height *= 24; // em
}
if (parseRenderProperty(value, render.letter_spacing, "text-letter-spacing")) {
render.letter_spacing *= 24; // em
if (parseRenderProperty(value, render.text.letter_spacing, "text-letter-spacing")) {
render.text.letter_spacing *= 24; // em
}
parseRenderProperty(value, render.alignment, "text-alignment", parseAlignmentType);
parseRenderProperty(value, render.vertical_alignment, "text-vertical-alignment", parseVerticalAlignmentType);
parseRenderProperty(value, render.max_angle_delta, "text-max-angle");
parseRenderProperty(value, render.min_distance, "text-min-distance");
parseRenderProperty(value, render.rotate, "text-rotate");
parseRenderProperty(value, render.slant, "text-slant");
parseRenderProperty(value, render.padding, "text-padding");
if (parseRenderProperty(value, render.translate, "text-translate")) {
render.translate.x *= 24;
render.translate.y *= -24;
}
parseRenderProperty(value, render.translate_anchor, "text-translate-anchor", parseTranslateAnchorType);
parseRenderProperty<TextJustifyTypeClass>(value, render.text.justify, "text-justify");
parseRenderProperty<TextHorizontalAlignTypeClass>(value, render.text.horizontal_align, "text-horizontal-align");
parseRenderProperty<TextVerticalAlignTypeClass>(value, render.text.vertical_align, "text-vertical-align");
parseRenderProperty(value, render.text.max_angle, "text-max-angle");
parseRenderProperty(value, render.text.rotate, "text-rotate");
parseRenderProperty(value, render.text.slant, "text-slant");
parseRenderProperty(value, render.text.padding, "text-padding");
parseRenderProperty(value, render.text.keep_upright, "text-keep-upright");
parseRenderProperty<TextTransformTypeClass>(value, render.text.transform, "text-transform");
parseRenderProperty(value, render.text.offset, "text-offset");
parseRenderProperty<TranslateAnchorTypeClass>(value, render.text.translate_anchor, "text-translate-anchor");
parseRenderProperty(value, render.text.allow_overlap, "text-allow-overlap");
parseRenderProperty(value, render.text.ignore_placement, "text-ignore-placement");
parseRenderProperty(value, render.text.optional, "text-optional");
} break;
default:
// There are no render properties for these layer types.

View File

@@ -4,8 +4,7 @@ namespace mbgl {
template<> const FillProperties &defaultStyleProperties() { static const FillProperties p; return p; }
template<> const LineProperties &defaultStyleProperties() { static const LineProperties p; return p; }
template<> const IconProperties &defaultStyleProperties() { static const IconProperties p; return p; }
template<> const TextProperties &defaultStyleProperties() { static const TextProperties p; return p; }
template<> const SymbolProperties &defaultStyleProperties() { static const SymbolProperties p; return p; }
template<> const CompositeProperties &defaultStyleProperties() { static const CompositeProperties p; return p; }
template<> const RasterProperties &defaultStyleProperties() { static const RasterProperties p; return p; }
template<> const BackgroundProperties &defaultStyleProperties() { static const BackgroundProperties p; return p; }

View File

@@ -31,37 +31,59 @@ 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}},
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{0, m * 8}}, 0},
GlyphBox{
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}},
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}},
0}}},
CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false, 2);
/* glyphs */ {
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}},
/* anchor */ CollisionAnchor(0, 0),
/* placementScale */ 1,
/* placementRange */ {{M_PI * 2, 0}},
/* horizontal */ false);
insert({{GlyphBox{
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}},
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}},
0},
GlyphBox{
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}},
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}},
0}}},
CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2);
insert(
/* glyphs */ {
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}},
/* anchor */ CollisionAnchor(m, m),
/* placementScale */ 1,
/* placementRange */ {{M_PI * 2, 0}},
/* horizontal */ false);
}
GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, bool horizontal,
const CollisionAnchor &anchor) {
GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) {
GlyphBox mergedGlyphs;
const float inf = std::numeric_limits<float>::infinity();
mergedGlyphs.box = CollisionRect{{inf, inf}, {-inf, -inf}};
mergedGlyphs.rotate = horizontal;
mergedGlyphs.anchor = anchor;
CollisionRect &box = mergedGlyphs.box;
@@ -71,105 +93,51 @@ GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, bool horizontal,
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 alwaysVisible) {
Box getBox(const CollisionAnchor &anchor, const CollisionRect &bbox, float minScale,
float maxScale) {
return Box{
Point{
anchor.x + util::min(bbox.tl.x / minScale, bbox.tl.x / maxScale),
anchor.y + util::min(bbox.tl.y / minScale, bbox.tl.y / maxScale),
},
Point{
anchor.x + util::max(bbox.br.x / minScale, bbox.br.x / maxScale),
anchor.y + util::max(bbox.br.y / minScale, bbox.br.y / maxScale),
},
};
};
float minScale = std::numeric_limits<float>::infinity();
for (const GlyphBox &glyphBox : boxes) {
minScale = util::min(minScale, glyphBox.minScale);
}
minPlacementScale = util::max(minPlacementScale, minScale);
// Collision checks between rotating and fixed labels are
// relatively expensive, so we use one box per label, not per glyph
// for horizontal labels.
GlyphBoxes glyphs;
if (horizontal) {
glyphs.push_back(getMergedGlyphs(boxes, horizontal, anchor));
} else {
glyphs = boxes;
}
// Calculate bboxes for all the glyphs
for (GlyphBox &glyph : glyphs) {
if (horizontal) {
const CollisionRect &box = glyph.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));
glyph.bbox = CollisionRect{{-diag, -diag}, {diag, diag}};
} else {
glyph.bbox = glyph.box;
}
}
// Calculate the minimum scale the entire label can be shown without
// collisions
float scale = alwaysVisible ? 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 = getPlacementRange(glyphs, scale, horizontal);
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 &bbox = glyph.bbox;
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) {
return -1;
if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096) {
return 0;
}
float minScale = std::fmax(minPlacementScale, glyph.minScale);
float maxScale = glyph.maxScale;
float maxScale = glyph.maxScale != 0 ? 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 *)hTree)->query(bgi::intersects(searchBox), std::back_inserter(blocking));
((Tree *)cTree)->query(bgi::intersects(searchBox), std::back_inserter(blocking));
if (blocking.size()) {
const CollisionAnchor &na = anchor; // new anchor
@@ -184,7 +152,7 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
// NOTE: this isn't right because there can be glyphs with
// the same anchor but differing box offsets.
if (na == oa) {
return -1;
return 0;
}
// todo: unhardcode the 8 = tileExtent/tileSize
@@ -192,17 +160,13 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
// 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
(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
(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
(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
(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,7 +175,7 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
s3 = s4 = 1;
}
float collisionFreeScale = std::fmin(std::fmax(s1, s2), std::fmax(s3, s4));
const float collisionFreeScale = std::fmin(std::fmax(s1, s2), std::fmax(s3, s4));
// Only update label's min scale if the glyph was
// restricted by a collision
@@ -223,7 +187,7 @@ float Collision::getPlacementScale(const GlyphBoxes &glyphs,
}
if (minPlacementScale > maxPlacementScale) {
return -1;
return 0;
}
}
}
@@ -232,13 +196,12 @@ 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}};
for (const GlyphBox &glyph : glyphs) {
const CollisionRect &bbox = glyph.bbox;
const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
const CollisionAnchor &anchor = glyph.anchor;
float minPlacedX = anchor.x + bbox.tl.x / placementScale;
@@ -246,30 +209,28 @@ 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) {
const Box &s = std::get<0>(value);
const PlacementBox &b = std::get<1>(value);
const CollisionRect &bbox2 = b.hBox ? b.hBox.get() : b.box;
float x1, x2, y1, y2, intersectX, intersectY;
// Adjust and compare bboxes to see if the glyphs might intersect
if (placementScale > b.placementScale) {
x1 = b.anchor.x + b.bbox.tl.x / placementScale;
y1 = b.anchor.y + b.bbox.tl.y / placementScale;
x2 = b.anchor.x + b.bbox.br.x / placementScale;
y2 = b.anchor.y + b.bbox.br.y / placementScale;
x1 = b.anchor.x + bbox2.tl.x / placementScale;
y1 = b.anchor.y + bbox2.tl.y / placementScale;
x2 = b.anchor.x + bbox2.br.x / placementScale;
y2 = b.anchor.y + bbox2.br.y / placementScale;
intersectX = x1 < maxPlacedX && x2 > minPlacedX;
intersectY = y1 < maxPlacedY && y2 > minPlacedY;
} else {
@@ -278,10 +239,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
@@ -301,45 +260,40 @@ 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 &bbox = glyph.bbox;
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 != 0 ? 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}};
const Box bounds = getBox(anchor, bbox, minScale, maxScale);
PlacementBox placement;
placement.anchor = anchor;
placement.box = box;
placement.bbox = bbox;
placement.rotate = horizontal;
if (glyph.hBox) {
placement.hBox = bbox;
}
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

@@ -6,6 +6,7 @@
#include <mbgl/util/pbf.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/token.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/platform/platform.hpp>
#include <uv.h>
#include <algorithm>
@@ -30,90 +31,108 @@ const std::map<uint32_t, SDFGlyph> &FontStack::getSDFs() const {
return sdfs;
}
const Shaping FontStack::getShaping(const std::u32string &string, const float &maxWidth,
const float &lineHeight, const float &alignment, const float &verticalAlignment,
const float &letterSpacing) const {
const Shaping FontStack::getShaping(const std::u32string &string, const float maxWidth,
const float lineHeight, const float horizontalAlign,
const float verticalAlign, const float justify,
const float spacing, const vec2<float> &translate) const {
std::lock_guard<std::mutex> lock(mtx);
uint32_t i = 0;
uint32_t x = 0;
Shaping shaping;
int32_t x = std::round(translate.x * 24); // one em
const int32_t y = std::round(translate.y * 24); // one em
// Loop through all characters of this label and shape.
for (uint32_t chr : string) {
shaping.emplace_back(0, chr, x, 0);
i++;
shaping.emplace_back(chr, x, y);
auto metric = metrics.find(chr);
if (metric != metrics.end()) {
x += metric->second.advance + letterSpacing;
x += metric->second.advance + spacing;
}
}
lineWrap(shaping, lineHeight, maxWidth, alignment, verticalAlignment);
if (!shaping.size())
return shaping;
lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify);
return shaping;
}
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) {
shape.y += dy;
void align(Shaping &shaping, const float justify, const float horizontalAlign,
const float verticalAlign, const uint32_t maxLineLength, const float lineHeight,
const uint32_t line) {
const float shiftX = (justify - horizontalAlign) * maxLineLength;
const float shiftY = (-verticalAlign * (line + 1) + 0.5) * lineHeight;
for (PositionedGlyph &glyph : shaping) {
glyph.x += shiftX;
glyph.y += shiftY;
}
}
void alignHorizontally(Shaping &shaping, const std::map<uint32_t, GlyphMetrics> &metrics,
const uint32_t &start, const uint32_t &end, const float &alignment) {
auto & shape = shaping.at(end);
auto metric = metrics.find(shape.glyph);
void justifyLine(Shaping &shaping, const std::map<uint32_t, GlyphMetrics> &metrics, uint32_t start,
uint32_t end, float justify) {
PositionedGlyph &glyph = shaping[end];
auto metric = metrics.find(glyph.glyph);
if (metric != metrics.end()) {
uint32_t lastAdvance = metric->second.advance;
int32_t lineIndent = (shape.x + lastAdvance) * alignment;
const uint32_t lastAdvance = metric->second.advance;
const float lineIndent = float(glyph.x + lastAdvance) * justify;
for (uint32_t j = start; j <= end; j++) {
shaping[j].x -= lineIndent;
}
}
}
void FontStack::lineWrap(Shaping &shaping, const float &lineHeight, const float &maxWidth,
const float &alignment, const float &verticalAlignment) const {
std::size_t num_shapes = shaping.size();
if (!num_shapes) {
return;
}
void FontStack::lineWrap(Shaping &shaping, const float lineHeight, const float maxWidth,
const float horizontalAlign, const float verticalAlign,
const float justify) const {
uint32_t lastSafeBreak = 0;
uint32_t lengthBeforeCurrentLine = 0;
uint32_t lineStartIndex = 0;
uint32_t line = 0;
for (uint32_t i = 0; i < shaping.size(); i++) {
GlyphPlacement &shape = shaping[i];
shape.x -= lengthBeforeCurrentLine;
shape.y += lineHeight * line;
uint32_t maxLineLength = 0;
if (shape.x > maxWidth && lastSafeBreak > 0) {
if (maxWidth) {
for (uint32_t i = 0; i < shaping.size(); i++) {
PositionedGlyph &shape = shaping[i];
uint32_t lineLength = shaping[lastSafeBreak + 1].x;
shape.x -= lengthBeforeCurrentLine;
shape.y += lineHeight * line;
for (uint32_t k = lastSafeBreak + 1; k <= i; k++) {
shaping[k].y += lineHeight;
shaping[k].x -= lineLength;
if (shape.x > maxWidth && lastSafeBreak > 0) {
uint32_t lineLength = shaping[lastSafeBreak + 1].x;
maxLineLength = util::max(lineLength, maxLineLength);
for (uint32_t k = lastSafeBreak + 1; k <= i; k++) {
shaping[k].y += lineHeight;
shaping[k].x -= lineLength;
}
if (justify) {
justifyLine(shaping, metrics, lineStartIndex, lastSafeBreak - 1, justify);
}
lineStartIndex = lastSafeBreak + 1;
lastSafeBreak = 0;
lengthBeforeCurrentLine += lineLength;
line++;
}
if (alignment) {
alignHorizontally(shaping, metrics, lineStartIndex, lastSafeBreak - 1, alignment);
if (shape.glyph == 32) {
lastSafeBreak = i;
}
lineStartIndex = lastSafeBreak + 1;
lastSafeBreak = 0;
lengthBeforeCurrentLine += lineLength;
line++;
}
if (shape.glyph == 32) {
lastSafeBreak = i;
}
}
alignHorizontally(shaping, metrics, lineStartIndex, shaping.size() - 1, alignment);
alignVertically(shaping, line + 1, lineHeight, verticalAlignment);
maxLineLength = maxLineLength || shaping.back().x;
justifyLine(shaping, metrics, lineStartIndex, shaping.size() - 1, justify);
align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line);
}
GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange)
@@ -207,12 +226,19 @@ void GlyphPBF::parse(FontStack &stack) {
data.clear();
}
GlyphStore::GlyphStore(const std::string &glyphURL)
: glyphURL(glyphURL) {}
GlyphStore::GlyphStore() {}
void GlyphStore::setURL(const std::string &url) {
glyphURL = url;
}
void GlyphStore::waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges) {
// We are implementing a blocking wait with futures: Every GlyphSet has a future that we are
// waiting for until it is loaded.
if (glyphRanges.empty()) {
return;
}
FontStack *stack = nullptr;

View File

@@ -1,75 +1,34 @@
#include <mbgl/text/placement.hpp>
#include <mbgl/map/vector_tile.hpp>
#include <mbgl/geometry/interpolate.hpp>
#include <mbgl/renderer/text_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,
int8_t direction, float maxAngleDelta) {
void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, Anchor &anchor,
float offset, const std::vector<Coordinate> &line, int segment,
int8_t direction, float maxAngle) {
const bool upsideDown = direction < 0;
if (offset < 0)
@@ -94,14 +53,14 @@ 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;
// Don't place around sharp corners
float angleDiff = std::fmod((angle - prevAngle), (2.0f * M_PI));
if (prevAngle && std::fabs(angleDiff) > maxAngleDelta) {
if (prevAngle && std::fabs(angleDiff) > maxAngle) {
anchor.scale = prevscale;
break;
}
@@ -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,152 @@ 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 */ x2 * 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 maxAngle = props.text.max_angle * 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;
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)
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,
maxAngle);
if (keepUpright)
getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, line,
anchor.segment, -1, maxAngle);
} 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 +249,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,78 +266,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,
horizontal};
// 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});
placement.shapes.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)};
}
placement.boxes.emplace_back(box, instance.anchor, glyphMinScale, instance.maxScale, padding);
}
}
}
}
void Placement::addFeature(TextBucket &bucket, const std::vector<Coordinate> &line,
const StyleBucketText &info, const GlyphPositions &face,
const Shaping &shaping) {
const bool horizontal = info.path == TextPathType::Horizontal;
const float fontScale = (tileExtent / util::tileSize) / (glyphSize / info.max_size);
// TODO avoid creating the boxes in the first place?
if (horizontal)
placement.boxes = {getMergedBoxes(placement.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 : placement.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.translate, shaping, face, fontScale, horizontal, line,
info.max_angle_delta * (M_PI/180), info.rotate);
PlacementProperty place = collision.place(boxes, anchor, anchor.scale, maxPlacementScale,
info.padding, horizontal, info.always_visible);
if (place) {
bucket.addGlyphs(glyphs, place.zoom, place.rotationRange, zoom - zOffset);
}
}
return placement;
}
}

View File

@@ -239,14 +239,14 @@ CollisionRange rotationRange(const GlyphBox &inserting,
static_cast<float>((b.anchor.y - a.anchor.y) * scale)};
// Generate a list of collision interval
if (a.rotate && b.rotate) {
if (a.hBox && b.hBox) {
collisions = rotatingRotatingCollisions(a.box, b.box, relativeAnchor);
} else if (a.rotate) {
} else if (a.hBox) {
const CollisionRect box {
b.box.tl.x + relativeAnchor.x, b.box.tl.y + relativeAnchor.y,
b.box.br.x + relativeAnchor.x, b.box.br.y + relativeAnchor.y};
collisions = rotatingFixedCollisions(a.box, box);
} else if (b.rotate) {
} else if (b.hBox) {
const CollisionRect box {
a.box.tl.x - relativeAnchor.x, a.box.tl.y - relativeAnchor.y,
a.box.br.x - relativeAnchor.x, a.box.br.y - relativeAnchor.y};

View File

@@ -13,8 +13,7 @@ TEST(Enums, StyleLayerType) {
ASSERT_EQ(StyleLayerType::Unknown, StyleLayerTypeClass("unknown"));
ASSERT_EQ(StyleLayerType::Fill, StyleLayerTypeClass("fill"));
ASSERT_EQ(StyleLayerType::Line, StyleLayerTypeClass("line"));
ASSERT_EQ(StyleLayerType::Icon, StyleLayerTypeClass("icon"));
ASSERT_EQ(StyleLayerType::Text, StyleLayerTypeClass("text"));
ASSERT_EQ(StyleLayerType::Symbol, StyleLayerTypeClass("symbol"));
ASSERT_EQ(StyleLayerType::Raster, StyleLayerTypeClass("raster"));
ASSERT_EQ(StyleLayerType::Composite, StyleLayerTypeClass("composite"));
ASSERT_EQ(StyleLayerType::Background, StyleLayerTypeClass("background"));
@@ -22,8 +21,7 @@ TEST(Enums, StyleLayerType) {
ASSERT_EQ(StyleLayerType::Unknown, StyleLayerTypeClass(StyleLayerType::Unknown));
ASSERT_EQ(StyleLayerType::Fill, StyleLayerTypeClass(StyleLayerType::Fill));
ASSERT_EQ(StyleLayerType::Line, StyleLayerTypeClass(StyleLayerType::Line));
ASSERT_EQ(StyleLayerType::Icon, StyleLayerTypeClass(StyleLayerType::Icon));
ASSERT_EQ(StyleLayerType::Text, StyleLayerTypeClass(StyleLayerType::Text));
ASSERT_EQ(StyleLayerType::Symbol, StyleLayerTypeClass(StyleLayerType::Symbol));
ASSERT_EQ(StyleLayerType::Raster, StyleLayerTypeClass(StyleLayerType::Raster));
ASSERT_EQ(StyleLayerType::Composite, StyleLayerTypeClass(StyleLayerType::Composite));
ASSERT_EQ(StyleLayerType::Background, StyleLayerTypeClass(StyleLayerType::Background));
@@ -31,8 +29,7 @@ TEST(Enums, StyleLayerType) {
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Unknown), StyleLayerTypeClass(StyleLayerType::Unknown));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Fill), StyleLayerTypeClass(StyleLayerType::Fill));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Line), StyleLayerTypeClass(StyleLayerType::Line));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Icon), StyleLayerTypeClass(StyleLayerType::Icon));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Text), StyleLayerTypeClass(StyleLayerType::Text));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Symbol), StyleLayerTypeClass(StyleLayerType::Symbol));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Raster), StyleLayerTypeClass(StyleLayerType::Raster));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Composite), StyleLayerTypeClass(StyleLayerType::Composite));
ASSERT_EQ(StyleLayerTypeClass(StyleLayerType::Background), StyleLayerTypeClass(StyleLayerType::Background));