use sprite atlas
This commit is contained in:
@@ -231,5 +231,34 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h
|
||||
glfwMakeContextCurrent(current_window);
|
||||
}
|
||||
|
||||
|
||||
void show_color_debug_image(std::string name, const char *data, size_t width, size_t height) {
|
||||
static GLFWwindow *debug_window = nullptr;
|
||||
if (!debug_window) {
|
||||
debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr);
|
||||
if (!debug_window) {
|
||||
glfwTerminate();
|
||||
fprintf(stderr, "Failed to initialize window\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
GLFWwindow *current_window = glfwGetCurrentContext();
|
||||
|
||||
glfwSetWindowSize(debug_window, width, height);
|
||||
glfwMakeContextCurrent(debug_window);
|
||||
|
||||
int fb_width, fb_height;
|
||||
glfwGetFramebufferSize(debug_window, &fb_width, &fb_height);
|
||||
float scale = (float)fb_width / (float)width;
|
||||
|
||||
glPixelZoom(scale, -scale);
|
||||
glRasterPos2f(-1.0f, 1.0f);
|
||||
glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
|
||||
glfwSwapBuffers(debug_window);
|
||||
|
||||
glfwMakeContextCurrent(current_window);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
68
include/llmr/geometry/sprite_atlas.hpp
Normal file
68
include/llmr/geometry/sprite_atlas.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef LLMR_GEOMETRY_SPRITE_ATLAS
|
||||
#define LLMR_GEOMETRY_SPRITE_ATLAS
|
||||
|
||||
#include <llmr/geometry/binpack.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Sprite;
|
||||
|
||||
class SpriteAtlas {
|
||||
public:
|
||||
typedef uint16_t dimension;
|
||||
|
||||
public:
|
||||
// Add way to construct this from another SpriteAtlas (e.g. with another pixelRatio)
|
||||
SpriteAtlas(dimension width, dimension height);
|
||||
~SpriteAtlas();
|
||||
|
||||
// Changes the pixel ratio.
|
||||
bool resize(float newRatio);
|
||||
|
||||
// Update uninitialized sprites in this atlas from the given sprite.
|
||||
void update(const Sprite &sprite);
|
||||
|
||||
// Returns the coordinates of the icon. The getter also *creates* new 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);
|
||||
|
||||
// Updates or creates an icon with a given size and name. The data must be a
|
||||
// a (pixelRatio * size)^2 * 4 byte long RGBA image buffer.
|
||||
Rect<dimension> setIcon(int size, const std::string &name, const std::string &icon);
|
||||
|
||||
// Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out
|
||||
// of date.
|
||||
void bind(bool linear = false);
|
||||
|
||||
inline float getTextureWidth() const { return width * pixelRatio; }
|
||||
inline float getTextureHeight() const { return height * pixelRatio; }
|
||||
|
||||
private:
|
||||
void allocate();
|
||||
|
||||
public:
|
||||
const dimension width = 0;
|
||||
const dimension height = 0;
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
float pixelRatio = 1.0f;
|
||||
BinPack<dimension> bin;
|
||||
std::map<int, std::map<std::string, Rect<dimension>>> index;
|
||||
std::set<std::pair<int, std::string>> uninitialized;
|
||||
char *data = nullptr;
|
||||
std::atomic<bool> dirty;
|
||||
uint32_t texture = 0;
|
||||
uint32_t filter = 0;
|
||||
static const int buffer = 1;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
inline const TransformState &getState() const { return state; }
|
||||
inline const Style &getStyle() const { return style; }
|
||||
inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; }
|
||||
inline SpriteAtlas &getSpriteAtlas() { return spriteAtlas; }
|
||||
inline uv_loop_t *getLoop() { return loop; }
|
||||
inline time getAnimationTime() const { return animationTime; }
|
||||
inline Texturepool &getTexturepool() { return texturepool; }
|
||||
@@ -143,6 +144,7 @@ private:
|
||||
Texturepool texturepool;
|
||||
Style style;
|
||||
GlyphAtlas glyphAtlas;
|
||||
SpriteAtlas spriteAtlas;
|
||||
Painter painter;
|
||||
|
||||
std::map<std::string, const std::unique_ptr<Source>> sources;
|
||||
|
||||
@@ -9,13 +9,14 @@ namespace llmr {
|
||||
|
||||
class Style;
|
||||
class GlyphAtlas;
|
||||
class SpriteAtlas;
|
||||
|
||||
class LayerDescription;
|
||||
class Bucket;
|
||||
|
||||
class TileParser {
|
||||
public:
|
||||
TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas);
|
||||
TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas);
|
||||
|
||||
private:
|
||||
bool obsolete() const;
|
||||
@@ -27,13 +28,14 @@ private:
|
||||
std::unique_ptr<Bucket> createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
std::unique_ptr<Bucket> createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
template <class Bucket> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
template <class Bucket, typename... Args> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args... args);
|
||||
template <class Bucket, typename ...Args> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args);
|
||||
|
||||
private:
|
||||
const VectorTile vector_data;
|
||||
VectorTileData& tile;
|
||||
const Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
SpriteAtlas &spriteAtlas;
|
||||
Faces faces;
|
||||
Placement placement;
|
||||
};
|
||||
|
||||
@@ -32,8 +32,11 @@ std::shared_ptr<Request> request_http(const std::string &url,
|
||||
// Cancels an HTTP request.
|
||||
void cancel_request_http(const std::shared_ptr<Request> &req);
|
||||
|
||||
// Shows an RGBA image with the specified dimensions in a named window.
|
||||
// Shows an alpha image with the specified dimensions in a named window.
|
||||
void show_debug_image(std::string name, const char *data, size_t width, size_t height);
|
||||
|
||||
// Shows an alpha image with the specified dimensions in a named window.
|
||||
void show_color_debug_image(std::string name, const char *data, size_t width, size_t height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class IconVertexBuffer;
|
||||
class BucketDescription;
|
||||
class IconShader;
|
||||
class DotShader;
|
||||
class Sprite;
|
||||
class SpriteAtlas;
|
||||
class VectorTileFeature;
|
||||
|
||||
class IconBucket : public Bucket {
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
virtual bool hasData() const;
|
||||
|
||||
void addFeature(const VectorTileFeature &feature, const std::shared_ptr<Sprite> &sprite);
|
||||
void addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas);
|
||||
|
||||
void drawIcons(IconShader& shader);
|
||||
void drawIcons(DotShader& shader);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <llmr/style/bucket_description.hpp>
|
||||
#include <llmr/style/layer_description.hpp>
|
||||
#include <llmr/style/class_description.hpp>
|
||||
#include <llmr/geometry/sprite_atlas.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@@ -87,12 +87,28 @@ inline T dist(const S1& a, const S2& b) {
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T length(T a, T b) {
|
||||
return std::sqrt(a * a + b * b);
|
||||
}
|
||||
|
||||
// Take the magnitude of vector a.
|
||||
template <typename T = double, typename S>
|
||||
inline T mag(const S& a) {
|
||||
return std::sqrt(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T clamp(T value, T min, T max) {
|
||||
return value < min ? min : (value > max ? max : value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T smoothstep(T edge0, T edge1, T x) {
|
||||
T t = clamp((x - edge0) / (edge1 - edge0), T(0), T(1));
|
||||
return t * t * (T(3) - T(2) * t);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ public:
|
||||
bool needsTransition() const;
|
||||
void updateTransitions(time now);
|
||||
|
||||
inline const char *getData() const { return img; }
|
||||
|
||||
public:
|
||||
// loaded image dimensions
|
||||
uint32_t width = 0, height = 0;
|
||||
|
||||
@@ -9,6 +9,11 @@ struct Rect {
|
||||
T x = 0, y = 0;
|
||||
T w = 0, h = 0;
|
||||
|
||||
template <typename Number>
|
||||
Rect operator *(Number value) const {
|
||||
return Rect(x * value, y * value, w * value, h * value);
|
||||
}
|
||||
|
||||
operator bool() const { return w == 0 || h == 0; }
|
||||
};
|
||||
}
|
||||
|
||||
235
src/geometry/sprite_atlas.cpp
Normal file
235
src/geometry/sprite_atlas.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#include <llmr/geometry/sprite_atlas.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
|
||||
#include <llmr/style/sprite.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
SpriteAtlas::SpriteAtlas(dimension width, dimension height)
|
||||
: width(width),
|
||||
height(height),
|
||||
bin(width, height),
|
||||
dirty(true) {
|
||||
}
|
||||
|
||||
bool SpriteAtlas::resize(const float newRatio) {
|
||||
if (pixelRatio == newRatio) return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
const float oldRatio = pixelRatio;
|
||||
pixelRatio = newRatio;
|
||||
|
||||
if (data) {
|
||||
char *old_data = data;
|
||||
|
||||
data = nullptr;
|
||||
allocate();
|
||||
|
||||
dimension w = static_cast<dimension>(width * newRatio);
|
||||
dimension h = static_cast<dimension>(height * newRatio);
|
||||
float s = std::pow(oldRatio / newRatio, 2);
|
||||
|
||||
// Basic image scaling. TODO: Replace this with better image scaling.
|
||||
uint32_t *img_new = reinterpret_cast<uint32_t *>(data);
|
||||
uint32_t *img_old = reinterpret_cast<uint32_t *>(old_data);
|
||||
for (size_t i = 0, length = w * h; i < length; i++) {
|
||||
img_new[i] = img_old[static_cast<size_t>(s * i)];
|
||||
}
|
||||
|
||||
free(old_data);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
Rect<SpriteAtlas::dimension> SpriteAtlas::getIcon(const int size, const std::string &name) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
std::map<std::string, Rect<dimension>> &size_index = index[size];
|
||||
|
||||
auto rect_it = size_index.find(name);
|
||||
if (rect_it != size_index.end()) {
|
||||
return rect_it->second;
|
||||
}
|
||||
|
||||
// We have to allocate a new area in the bin, and store an empty image in it.
|
||||
// Add a 1px border around every image.
|
||||
const dimension pack_size = size + 2 * buffer;
|
||||
|
||||
Rect<dimension> rect = bin.allocate(pack_size, pack_size);
|
||||
if (rect.w == 0) {
|
||||
fprintf(stderr, "sprite atlas bitmap overflow");
|
||||
return rect;
|
||||
}
|
||||
|
||||
size_index.emplace(name, rect);
|
||||
|
||||
allocate();
|
||||
|
||||
|
||||
// Draw an antialiased circle.
|
||||
const int img_size = size * pixelRatio;
|
||||
const int img_offset_x = (rect.x + buffer) * pixelRatio;
|
||||
const int img_offset_y = (rect.y + buffer) * pixelRatio;
|
||||
|
||||
uint32_t *sprite_img = reinterpret_cast<uint32_t *>(data);
|
||||
const float blur = 1.5f / size;
|
||||
|
||||
const uint8_t r = 0x7F;
|
||||
const uint8_t g = 0x7F;
|
||||
const uint8_t b = 0x7F;
|
||||
|
||||
const int sprite_stride = width * pixelRatio;
|
||||
for (int y = 0; y < img_size; y++) {
|
||||
const int img_y = (img_offset_y + y) * sprite_stride + img_offset_x;
|
||||
for (int x = 0; x < img_size; x++) {
|
||||
|
||||
const float dist = util::length(float(x) / img_size - 0.5f, float(y) / img_size - 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);
|
||||
sprite_img[img_y + x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
uninitialized.emplace(size, name);
|
||||
|
||||
dirty = true;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void SpriteAtlas::allocate() {
|
||||
if (!data) {
|
||||
dimension w = static_cast<dimension>(width * pixelRatio);
|
||||
dimension h = static_cast<dimension>(height * pixelRatio);
|
||||
data = (char *)calloc(w * h, sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
Rect<SpriteAtlas::dimension> SpriteAtlas::setIcon(const int size, const std::string &name, const std::string &icon) {
|
||||
Rect<dimension> rect = getIcon(size, name);
|
||||
|
||||
// Copy the bitmap
|
||||
const int img_size = size * pixelRatio;
|
||||
const int img_offset_x = (rect.x + buffer) * pixelRatio;
|
||||
const int img_offset_y = (rect.y + buffer) * pixelRatio;
|
||||
|
||||
if (std::pow(size * pixelRatio, 2) * 4 /* rgba */ != icon.size()) { fprintf(stderr, "mismatched icon buffer size!"); }
|
||||
|
||||
const uint32_t *icon_img = reinterpret_cast<const uint32_t *>(icon.data());
|
||||
uint32_t *sprite_img = reinterpret_cast<uint32_t *>(data);
|
||||
|
||||
const int sprite_stride = width * pixelRatio;
|
||||
const int icon_stride = size * pixelRatio;
|
||||
for (size_t y = 0; y < img_size; y++) {
|
||||
const int img_y = (img_offset_y + y) * sprite_stride + img_offset_x;
|
||||
const int icon_y = y * icon_stride;
|
||||
for (size_t x = 0; x < img_size; x++) {
|
||||
sprite_img[img_y + x] = icon_img[icon_y + x];
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void SpriteAtlas::update(const Sprite &sprite) {
|
||||
if (!sprite.isLoaded()) return;
|
||||
|
||||
SpriteAtlas &atlas = *this;
|
||||
std::erase_if(uninitialized, [&sprite, &atlas](const std::pair<int, std::string> &pair) {
|
||||
const int &size = pair.first;
|
||||
const std::string &name = pair.second;
|
||||
const SpritePosition &src = sprite.getSpritePosition(name + "-" + std::to_string(size));
|
||||
if (src.width == size && src.height == size && src.pixelRatio == atlas.pixelRatio) {
|
||||
const uint32_t *src_img = reinterpret_cast<const uint32_t *>(sprite.raster.getData());
|
||||
|
||||
uint32_t *dst_img = reinterpret_cast<uint32_t *>(atlas.data);
|
||||
Rect<dimension> dst = atlas.getIcon(size, name);
|
||||
dst.x = (dst.x + buffer) * atlas.pixelRatio;
|
||||
dst.y = (dst.y + buffer) * atlas.pixelRatio;
|
||||
dst.w = (dst.w - 2 * buffer) * atlas.pixelRatio;
|
||||
dst.h = (dst.h - 2 * buffer) * atlas.pixelRatio;
|
||||
|
||||
|
||||
const int src_image_stride = sprite.raster.width;
|
||||
const int dst_image_stride = atlas.width * atlas.pixelRatio;
|
||||
const int src_stride = src.width * src.pixelRatio;
|
||||
|
||||
for (int y = 0; y < dst.h; y++) {
|
||||
const int src_pos = (src.y * src.pixelRatio + y) * src_image_stride + src.x * src.pixelRatio;
|
||||
const int dst_pos = (dst.y + y) * dst_image_stride + dst.x;
|
||||
|
||||
// TODO: this is crashing
|
||||
// memcpy(dst_img + dst_pos, src_img + src_pos, src_stride * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteAtlas::bind(bool linear) {
|
||||
if (!texture) {
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
GLuint filter_val = linear ? GL_LINEAR : GL_NEAREST;
|
||||
if (filter_val != filter) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_val);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_val);
|
||||
filter = filter_val;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
allocate();
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D, // GLenum target
|
||||
0, // GLint level
|
||||
GL_RGBA, // GLint internalformat
|
||||
width * pixelRatio, // GLsizei width
|
||||
height * pixelRatio, // GLsizei height
|
||||
0, // GLint border
|
||||
GL_RGBA, // GLenum format
|
||||
GL_UNSIGNED_BYTE, // GLenum type
|
||||
data // const GLvoid * data
|
||||
);
|
||||
|
||||
platform::show_color_debug_image("Sprite Atlas", data, width * pixelRatio, height * pixelRatio);
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
};
|
||||
|
||||
SpriteAtlas::~SpriteAtlas() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (data) {
|
||||
free(data);
|
||||
data = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ Map::Map(View& view)
|
||||
texturepool(),
|
||||
style(),
|
||||
glyphAtlas(1024, 1024),
|
||||
spriteAtlas(512, 512),
|
||||
painter(*this),
|
||||
loop(uv_loop_new()) {
|
||||
|
||||
@@ -175,7 +176,16 @@ void Map::resize(uint16_t width, uint16_t height, float ratio) {
|
||||
}
|
||||
|
||||
void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height) {
|
||||
bool changed = false;
|
||||
|
||||
if (transform.resize(width, height, ratio, fb_width, fb_height)) {
|
||||
changed = true;
|
||||
}
|
||||
if (spriteAtlas.resize(ratio)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -393,6 +403,11 @@ void Map::prepare() {
|
||||
style.sprite->load(kSpriteURL);
|
||||
}
|
||||
|
||||
// Allow the sprite atlas to potentially pull new sprite images if needed.
|
||||
if (style.sprite && style.sprite->isLoaded()) {
|
||||
spriteAtlas.update(*style.sprite);
|
||||
}
|
||||
|
||||
updateTiles();
|
||||
|
||||
updateClippingIDs();
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
using namespace llmr;
|
||||
|
||||
|
||||
TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas)
|
||||
TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas)
|
||||
: vector_data(pbf((const uint8_t *)data.data(), data.size())),
|
||||
tile(tile),
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas),
|
||||
spriteAtlas(spriteAtlas),
|
||||
placement(tile.id.z) {
|
||||
parseGlyphs();
|
||||
parseStyleLayers(style.layers);
|
||||
@@ -120,12 +121,12 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer,
|
||||
}
|
||||
}
|
||||
|
||||
template <class Bucket, typename... Args>
|
||||
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args... args) {
|
||||
template <class Bucket, typename ...Args>
|
||||
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args) {
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (const pbf& feature_pbf : filtered_layer) {
|
||||
if (obsolete()) return;
|
||||
bucket->addFeature({ feature_pbf, layer }, args...);
|
||||
bucket->addFeature({ feature_pbf, layer }, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +147,7 @@ std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& laye
|
||||
std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<IconBucket> bucket = std::make_unique<IconBucket>(
|
||||
tile.iconVertexBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc, style.sprite);
|
||||
addBucketFeatures(bucket, layer, bucket_desc, spriteAtlas);
|
||||
return obsolete() ? nullptr : std::move(bucket);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ void VectorTileData::parse() {
|
||||
// Parsing creates state that is encapsulated in TileParser. While parsing,
|
||||
// the TileParser object writes results into this objects. All other state
|
||||
// is going to be discarded afterwards.
|
||||
TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas());
|
||||
TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getSpriteAtlas());
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
|
||||
cancel();
|
||||
|
||||
@@ -23,27 +23,18 @@ IconBucket::IconBucket(IconVertexBuffer& vertexBuffer,
|
||||
vertex_start(vertexBuffer.index()) {
|
||||
}
|
||||
|
||||
void IconBucket::addFeature(const VectorTileFeature &feature, const std::shared_ptr<Sprite> &sprite) {
|
||||
// TODO: We somehow need to reparse the stylesheet, or maintain a mapping of id => sprite name
|
||||
// For now, we're just not showing points if the sprite was not yet loaded at *parse time* of
|
||||
// the tile.
|
||||
if (!sprite || !sprite->isLoaded()) return;
|
||||
|
||||
void IconBucket::addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas) {
|
||||
auto field_it = feature.properties.find(geometry.field);
|
||||
if (field_it == feature.properties.end()) {
|
||||
fprintf(stderr, "feature doesn't contain field '%s'\n", geometry.field.c_str());
|
||||
// fprintf(stderr, "feature doesn't contain field '%s'\n", geometry.field.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string field = toString(field_it->second);
|
||||
if (geometry.size) {
|
||||
field.append("-");
|
||||
field.append(std::to_string(static_cast<int>(std::round(geometry.size))));
|
||||
}
|
||||
|
||||
const SpritePosition &pos = sprite->getSpritePosition(field);
|
||||
const uint16_t tx = pos.x + pos.width / 2;
|
||||
const uint16_t ty = pos.y + pos.height / 2;
|
||||
const Rect<uint16_t> rect = sprite_atlas.getIcon(geometry.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;
|
||||
|
||||
@@ -26,44 +26,27 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
|
||||
|
||||
const std::shared_ptr<Sprite> &sprite = map.getStyle().sprite;
|
||||
SpriteAtlas &spriteAtlas = map.getSpriteAtlas();
|
||||
|
||||
if (!sprite || !sprite->isLoaded()) {
|
||||
useProgram(dotShader->program);
|
||||
dotShader->setMatrix(vtxMatrix);
|
||||
dotShader->setColor(color);
|
||||
useProgram(iconShader->program);
|
||||
iconShader->setMatrix(vtxMatrix);
|
||||
iconShader->setColor(color);
|
||||
iconShader->setImage(0);
|
||||
|
||||
const float iconSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio();
|
||||
dotShader->setSize(iconSize);
|
||||
iconShader->setDimension({{
|
||||
spriteAtlas.getTextureWidth(),
|
||||
spriteAtlas.getTextureHeight(),
|
||||
}});
|
||||
|
||||
spriteAtlas.bind(map.getState().isChanging());
|
||||
|
||||
const float iconSize = bucket.geometry.size * map.getState().getPixelRatio();
|
||||
iconShader->setSize(iconSize);
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
glPointSize(iconSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
dotShader->setBlur((properties.blur ? properties.blur : 1.5) / iconSize);
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawIcons(*dotShader);
|
||||
} else {
|
||||
useProgram(iconShader->program);
|
||||
iconShader->setMatrix(vtxMatrix);
|
||||
iconShader->setColor(color);
|
||||
iconShader->setImage(0);
|
||||
|
||||
iconShader->setDimension({{
|
||||
static_cast<float>(sprite->raster.width),
|
||||
static_cast<float>(sprite->raster.height)
|
||||
}});
|
||||
|
||||
sprite->raster.bind(map.getState().isChanging());
|
||||
|
||||
const float iconSize = bucket.geometry.size * map.getState().getPixelRatio();
|
||||
iconShader->setSize(iconSize);
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
glPointSize(iconSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
glPointSize(iconSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawIcons(*iconShader);
|
||||
}
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawIcons(*iconShader);
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ varying vec2 v_tex;
|
||||
|
||||
void main() {
|
||||
vec2 pos = (v_tex + (gl_PointCoord - 0.5) * u_size) / u_dimension;
|
||||
gl_FragColor = texture2D(u_image, pos) * u_color;
|
||||
gl_FragColor = texture2D(u_image, pos);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = {
|
||||
},
|
||||
{
|
||||
"#version 120\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n",
|
||||
"#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = (texture2D (u_image, ((v_tex + \n ((gl_PointCoord - 0.5) * u_size)\n ) / u_dimension)) * u_color);\n}\n\n",
|
||||
"#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = texture2D (u_image, ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension));\n}\n\n",
|
||||
},
|
||||
{
|
||||
"#version 120\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n",
|
||||
|
||||
@@ -12,7 +12,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = {
|
||||
},
|
||||
{
|
||||
"precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n",
|
||||
"precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = (texture2D (u_image, tmpvar_1) * u_color);\n gl_FragColor = tmpvar_2;\n}\n\n",
|
||||
"precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = texture2D (u_image, tmpvar_1);\n gl_FragColor = tmpvar_2;\n}\n\n",
|
||||
},
|
||||
{
|
||||
"precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n",
|
||||
|
||||
@@ -31,35 +31,35 @@ Sprite::Sprite(Map &map, float pixelRatio)
|
||||
}
|
||||
|
||||
void Sprite::load(const std::string& base_url) {
|
||||
std::shared_ptr<Sprite> sprite = shared_from_this();
|
||||
std::shared_ptr<Sprite> sprite = shared_from_this();
|
||||
|
||||
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
|
||||
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
|
||||
|
||||
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->body.swap(res->body);
|
||||
sprite->asyncParseJSON();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->body.swap(res->body);
|
||||
sprite->asyncParseJSON();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
|
||||
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->image.swap(res->body);
|
||||
sprite->asyncParseImage();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite image\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->image.swap(res->body);
|
||||
sprite->asyncParseImage();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite image\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
}
|
||||
|
||||
void Sprite::complete(std::shared_ptr<Sprite> &sprite) {
|
||||
if (sprite->raster.isLoaded() && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.update();
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
}
|
||||
if (sprite->raster.isLoaded() && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.update();
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool Sprite::isLoaded() const {
|
||||
|
||||
Reference in New Issue
Block a user