2556
bin/style.js
2556
bin/style.js
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ↦
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -51,6 +51,9 @@ protected:
|
||||
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
|
||||
std::unique_ptr<TileParser> parser;
|
||||
|
||||
public:
|
||||
const float depth;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
109
include/mbgl/renderer/symbol_bucket.hpp
Normal file
109
include/mbgl/renderer/symbol_bucket.hpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" },
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
217
src/renderer/painter_symbol.cpp
Normal file
217
src/renderer/painter_symbol.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
394
src/renderer/symbol_bucket.cpp
Normal file
394
src/renderer/symbol_bucket.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user