diff options
31 files changed, 874 insertions, 467 deletions
diff --git a/bin/style.js b/bin/style.js index c262dac0ef..b9d404a9e9 100644 --- a/bin/style.js +++ b/bin/style.js @@ -2,93 +2,97 @@ module.exports = { "buckets": { + "satellite": { + "source": "satellite", + "type": "raster" + }, "water": { - "source": "streets", + "source": "mapbox streets", "layer": "water", "type": "fill" }, "road_large": { - "source": "streets", + "source": "mapbox streets", "layer": "road", "field": "class", "value": ["motorway", "main"], "type": "line", "cap": "round", "join": "round" }, "road_regular": { - "source": "streets", + "source": "mapbox streets", "layer": "road", "field": "class", "value": "street", "type": "line", "cap": "round", "join": "round" }, "road_limited": { - "source": "streets", + "source": "mapbox streets", "layer": "road", "field": "class", "value": "street_limited", "type": "line", "cap": "round", "join": "round" }, "park": { - "source": "streets", + "source": "mapbox streets", "layer": "landuse", "field": "class", "value": "park", "type": "fill" }, "wood": { - "source": "streets", + "source": "mapbox streets", "layer": "landuse", "field": "class", "value": "wood", "type": "fill" }, "school": { - "source": "streets", + "source": "mapbox streets", "layer": "landuse", "field": "class", "value": "school", "type": "fill" }, "cemetery": { - "source": "streets", + "source": "mapbox streets", "layer": "landuse", "field": "class", "value": "cemetery", "type": "fill" }, "industrial": { - "source": "streets", + "source": "mapbox streets", "layer": "landuse", "field": "class", "value": "industrial", "type": "fill" }, "building": { - "source": "streets", + "source": "mapbox streets", "layer": "building", "type": "fill" }, "alcohol_poi": { - "source": "streets", + "source": "mapbox streets", "layer": "poi_label", "field": "type", "value": ["Alcohol"], "type": "point" }, "cafe_poi": { - "source": "streets", + "source": "mapbox streets", "layer": "poi_label", "field": "type", "value": ["Cafe"], "type": "point" }, "embassy_poi": { - "source": "streets", + "source": "mapbox streets", "layer": "poi_label", "field": "type", "value": ["Embassy"], "type": "point" }, "park_poi": { - "source": "streets", + "source": "mapbox streets", "layer": "poi_label", "field": "type", "value": ["Park"], "type": "point" }, "restaurant_poi": { - "source": "streets", + "source": "mapbox streets", "layer": "poi_label", "field": "type", "value": ["Restaurant"], "type": "point" }, "country_label": { - "source": "streets", + "source": "mapbox streets", "type": "text", "layer": "country_label", "feature_type": "point", @@ -99,7 +103,7 @@ module.exports = { "fontSize": 16 }, "place_label": { - "source": "streets", + "source": "mapbox streets", "type": "text", "layer": "place_label", "feature_type": "point", @@ -110,7 +114,7 @@ module.exports = { "alwaysVisible": true }, "road_label": { - "source": "streets", + "source": "mapbox streets", "type": "text", "layer": "road_label", "feature_type": "line", @@ -128,6 +132,7 @@ module.exports = { "text": "#000000", }, "structure": [ + { "name": "satellite", "bucket": "satellite" }, { "name": "park", "bucket": "park" }, { "name": "wood", "bucket": "wood" }, { "name": "water", "bucket": "water" }, @@ -265,6 +270,123 @@ module.exports = { "size": ["exponential", 14, 8, 1, 8, 12] }, } + }, + { + "name": "satellite", + "layers": { + "background": { + "type": "background", + "color": "#333333", + "opacity": 1.0 + }, + "park": { + "type": "fill", + "opacity": 0, + }, + "wood": { + "type": "fill", + "opacity": 0, + }, + "water": { + "type": "fill", + "opacity": 0, + }, + "building": { + "type": "fill", + "opacity": 0, + }, + "road_limited": { + "type": "line", + "color": "#BBBBBB", + "width": [ + "stops", + { z: 0, val: 1 }, + { z: 30, val: 1 } + ] + }, + "road_regular": { + "type": "line", + "color": "#999999", + "width": [ + "stops", + { z: 0, val: 0.5 }, + { z: 13, val: 0.5 }, + { z: 16, val: 2 }, + { z: 20, val: 16 }, + { z: 30, val: 16 } + ], + }, + "road_large": { + "type": "line", + "color": "#666666", + "width": [ + "stops", + { z: 0, val: 0.5 }, + { z: 11, val: 0.5 }, + { z: 13, val: 1 }, + { z: 16, val: 4 }, + { z: 20, val: 32 }, + { z: 30, val: 32 } + ], + }, + "alcohol_poi": { + "type": "point", + "color": "#cccccc", + "size": 18, + "image": "alcohol-shop", + "enabled": [ "min", 17 ] + }, + "cafe_poi": { + "type": "point", + "color": "#cccccc", + "size": 18, + "image": "cafe", + "enabled": [ "min", 17 ] + }, + "embassy_poi": { + "type": "point", + "color": "#cccccc", + "size": 18, + "image": "embassy", + "enabled": [ "min", 17 ] + }, + "park_poi": { + "type": "point", + "color": "#cccccc", + "size": 18, + "image": "park", + "enabled": [ "min", 17 ] + }, + "restaurant_poi": { + "type": "point", + "color": "#cccccc", + "size": 18, + "image": "restaurant", + "enabled": [ "min", 17 ] + }, + "country_label": { + "type": "text", + "stroke": [ 1, 1, 1, 0.7 ], + "color": "text", + "size": 16 + }, + "place_label": { + "type": "text", + "stroke": [1,1,1,0.7], + "color": "text", + "size": 18 + }, + "road_label": { + "type": "text", + "color": "text", + "stroke": [1,1,1,0.7], + "size": ["exponential", 14, 8, 1, 8, 12] + }, + "satellite": { + "type": "raster", + "opacity": 0.25 + }, + } } ] }; diff --git a/include/llmr/map/coverage.hpp b/include/llmr/map/coverage.hpp deleted file mode 100644 index ed6317a560..0000000000 --- a/include/llmr/map/coverage.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef LLMR_MAP_COVERAGE -#define LLMR_MAP_COVERAGE - -#include <llmr/map/tile.hpp> - -#include <forward_list> - -namespace llmr { - -std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points, const bool use_raster = false, const bool use_retina = false); - -} - -#endif diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index 640786e105..fef3673860 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -5,7 +5,6 @@ #include <llmr/map/tile_data.hpp> #include <llmr/map/transform.hpp> #include <llmr/style/style.hpp> -#include <llmr/style/style.hpp> #include <llmr/geometry/glyph_atlas.hpp> #include <llmr/renderer/painter.hpp> #include <llmr/util/noncopyable.hpp> @@ -13,10 +12,12 @@ #include <cstdint> #include <string> +#include <map> namespace llmr { class Settings; +class Source; class Map : private util::noncopyable { public: @@ -28,7 +29,6 @@ public: void loadStyle(const uint8_t *const data, uint32_t bytes); void loadSettings(); void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height); - void toggleRaster(); /* callback */ void update(); @@ -64,14 +64,15 @@ public: void stopRotating(); void toggleDebug(); + void toggleRaster(); + + box cornersToBox(uint32_t z) const; + float getPixelRatio() const; + Style& getStyle(); + GlyphAtlas& getGlyphAtlas(); private: - bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain); - bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain); bool updateTiles(); - TileData::State addTile(const Tile::ID& id); - TileData::State hasTile(const Tile::ID& id); - private: Settings& settings; @@ -81,16 +82,12 @@ private: GlyphAtlas glyphAtlas; Painter painter; - bool use_raster = false; + std::map<std::string, Source> sources; int32_t min_zoom; int32_t max_zoom; - float pixel_ratio; - - - std::forward_list<Tile> tiles; - std::forward_list<std::weak_ptr<TileData>> tile_data; + // float pixel_ratio; }; } diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp new file mode 100644 index 0000000000..3ddebb0ee4 --- /dev/null +++ b/include/llmr/map/source.hpp @@ -0,0 +1,67 @@ +#ifndef LLMR_MAP_SOURCE +#define LLMR_MAP_SOURCE + +#include <llmr/map/tile.hpp> +#include <llmr/map/tile_data.hpp> + +#include <forward_list> +#include <memory> +#include <list> +#include <string> + +namespace llmr { + +class Map; +class Transform; +class Painter; +class Texturepool; + +class Source : public std::enable_shared_from_this<Source> { +public: + enum class Type { + vector, + raster + }; + +public: + Source(Map& map, Transform& transform, Painter& painter, Texturepool& texturepool, const char *url = "", Type type = Type::vector, std::list<uint32_t> zooms = {0}, uint32_t tile_size = 512, uint32_t min_zoom = 0, uint32_t max_zoom = 14, bool enabled = true); + + bool update(); + void prepare_render(bool is_baselayer = false); + void render(double animationTime, bool is_baselayer = false); + +public: + bool enabled; + +private: + bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain); + bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain); + std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points); + + bool updateTiles(); + + TileData::State addTile(const Tile::ID& id); + TileData::State hasTile(const Tile::ID& id); + + double getZoom() const; + +private: + Map& map; + Transform& transform; + Painter& painter; + Texturepool& texturepool; + + Type type; + std::list<uint32_t> zooms; + const char *url; + uint32_t tile_size; + uint32_t min_zoom; + uint32_t max_zoom; + + std::forward_list<Tile> tiles; + std::forward_list<std::weak_ptr<TileData>> tile_data; +}; + +} + +#endif diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index 955c1ffbc8..c6283e3706 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -35,7 +35,6 @@ class Raster; class LayerDescription; class BucketDescription; - class PlainShader; class TileData : public std::enable_shared_from_this<TileData>, @@ -57,7 +56,7 @@ public: }; public: - TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster = false, const bool use_retina = false); + TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const std::string url, const bool is_raster); ~TileData(); void request(); @@ -68,8 +67,6 @@ public: public: const Tile::ID id; - const bool use_raster; - const bool use_retina; std::atomic<State> state; std::shared_ptr<Raster> raster; @@ -91,7 +88,8 @@ public: std::map<std::string, std::unique_ptr<Bucket>> buckets; private: - // Source data + const std::string url; + const bool is_raster = false; std::string data; const Style& style; GlyphAtlas& glyphAtlas; diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index b99f9aaf94..ee623f1ff9 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -8,7 +8,7 @@ namespace llmr { class TileParser { public: - TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas); + TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas, bool is_raster = false); private: bool obsolete() const; @@ -19,10 +19,12 @@ private: std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr<Bucket> createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr<Bucket> createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); + std::unique_ptr<Bucket> createRasterBucket(const BucketDescription& bucket_desc); template <class Bucket> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc); private: - const VectorTile data; + const VectorTile vector_data; + const std::string raster_data; TileData& tile; const Style& style; GlyphAtlas& glyphAtlas; diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp index 7b4d59ee17..e81b9e4121 100644 --- a/include/llmr/map/transform.hpp +++ b/include/llmr/map/transform.hpp @@ -56,8 +56,7 @@ public: void startScaling(); void stopScaling(); - // Temporary - box mapCornersToBox(uint32_t z) const; + box cornersToBox(uint32_t z) const; // More getters inline uint16_t getWidth() const { return width; } diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index 58292fc07f..d66a8f7746 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -7,11 +7,7 @@ namespace llmr { -extern const char *kTileVectorURL; -extern const char *kTileRasterURL; extern const char *kSpriteURL; -extern const int32_t kTileVectorMaxZoom; -extern const int32_t kTileRasterMaxZoom; namespace platform { diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index 3a96f81001..5cca820ecc 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -28,6 +28,7 @@ class FillBucket; class LineBucket; class PointBucket; class TextBucket; +class RasterBucket; class Painter : private util::noncopyable { public: @@ -42,17 +43,17 @@ public: void renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& id); void renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& id); void renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id); + void renderRaster(const std::string& layer_name, const std::shared_ptr<TileData>& tile_data); void resize(); void prepareClippingMask(); - void drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque = true); + void drawClippingMask(const mat4& matrix, uint8_t clip_id); void finishClippingMask(); bool needsAnimation() const; private: void setupShaders(); - void renderRaster(const std::shared_ptr<TileData>& tile); void renderLayers(const std::shared_ptr<TileData>& tile, const std::vector<LayerDescription>& layers); void renderLayer(const std::shared_ptr<TileData>& tile_data, const LayerDescription& layer_desc); void renderDebug(const std::shared_ptr<TileData>& tile); diff --git a/include/llmr/renderer/raster_bucket.hpp b/include/llmr/renderer/raster_bucket.hpp new file mode 100644 index 0000000000..88bb3bd846 --- /dev/null +++ b/include/llmr/renderer/raster_bucket.hpp @@ -0,0 +1,20 @@ +#ifndef LLMR_RENDERER_RASTERBUCKET +#define LLMR_RENDERER_RASTERBUCKET + +#include <llmr/renderer/bucket.hpp> +#include <llmr/style/bucket_description.hpp> + +namespace llmr { + +class BucketDescription; + +class RasterBucket : public Bucket { +public: + RasterBucket(const BucketDescription& bucket_desc); + + virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); +}; + +} + +#endif diff --git a/include/llmr/shader/plain_shader.hpp b/include/llmr/shader/plain_shader.hpp index bf768f8845..d833375eb5 100644 --- a/include/llmr/shader/plain_shader.hpp +++ b/include/llmr/shader/plain_shader.hpp @@ -13,12 +13,15 @@ public: void setColor(float r, float g, float b, float a); void setColor(const std::array<float, 4>& color); + void setOpacity(float opacity); private: int32_t a_pos = -1; std::array<float, 4> color = {{}}; int32_t u_color = -1; + float opacity = 0.0f; + float u_opacity = 0.0f; }; } diff --git a/include/llmr/style/bucket_description.hpp b/include/llmr/style/bucket_description.hpp index 498e935f2d..e7cde3db8c 100644 --- a/include/llmr/style/bucket_description.hpp +++ b/include/llmr/style/bucket_description.hpp @@ -14,7 +14,8 @@ enum class BucketType { Fill = 1, Line = 2, Point = 3, - Text = 4 + Text = 4, + Raster = 5 }; enum class CapType { @@ -42,6 +43,7 @@ inline BucketType bucketType(const std::string& type) { else if (type == "line") return BucketType::Line; else if (type == "point") return BucketType::Point; else if (type == "text") return BucketType::Text; + else if (type == "raster") return BucketType::Raster; else return BucketType::None; } diff --git a/include/llmr/style/class_description.hpp b/include/llmr/style/class_description.hpp index e98aee0c56..dc8c5faa25 100644 --- a/include/llmr/style/class_description.hpp +++ b/include/llmr/style/class_description.hpp @@ -15,6 +15,7 @@ public: std::map<std::string, LineClass> line; std::map<std::string, PointClass> point; std::map<std::string, TextClass> text; + std::map<std::string, RasterClass> raster; }; diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index 47b6ff195c..8baa6cfef4 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -128,13 +128,23 @@ struct TextProperties { struct BackgroundClass { Color color = {{ 1, 1, 1, 1 }}; + FunctionProperty opacity = 1; }; - struct BackgroundProperties { Color color = {{ 1, 1, 1, 1 }}; + float opacity = 1.0; +}; + +struct RasterClass { + FunctionProperty enabled = true; + FunctionProperty opacity = 1; }; +struct RasterProperties { + bool enabled = true; + float opacity = 1.0; +}; } diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index a004df438a..2f1e42c4fd 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -47,6 +47,7 @@ public: std::map<std::string, LineProperties> lines; std::map<std::string, PointProperties> points; std::map<std::string, TextProperties> texts; + std::map<std::string, RasterProperties> rasters; } computed; }; diff --git a/include/llmr/style/style_parser.hpp b/include/llmr/style/style_parser.hpp index 60bb9a0504..a096a81a8b 100644 --- a/include/llmr/style/style_parser.hpp +++ b/include/llmr/style/style_parser.hpp @@ -25,6 +25,7 @@ private: PointClass parsePointClass(JSVal value); TextClass parseTextClass(JSVal value); BackgroundClass parseBackgroundClass(JSVal value); + RasterClass parseRasterClass(JSVal value); bool parseBoolean(JSVal value); std::string parseString(JSVal value); diff --git a/src/map/coverage.cpp b/src/map/coverage.cpp deleted file mode 100644 index b98518a588..0000000000 --- a/src/map/coverage.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include <llmr/map/coverage.hpp> -#include <llmr/util/constants.hpp> -#include <llmr/util/vec.hpp> - -#include <functional> -#include <cmath> - -// Taken from polymaps src/Layer.js -// https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383 - -struct edge { - double x0 = 0, y0 = 0; - double x1 = 0, y1 = 0; - double dx = 0, dy = 0; - edge(double x0, double y0, double x1, double y1, double dx, double dy) - : x0(x0), y0(y0), x1(x1), y1(y1), dx(dx), dy(dy) {} -}; - -typedef const std::function<void(int32_t, int32_t, int32_t, int32_t)> ScanLine; - -// scan-line conversion -edge _edge(const llmr::vec2<double> a, const llmr::vec2<double> b) { - if (a.y > b.y) { - return { b.x, b.y, a.x, a.y, a.x - b.x, a.y - b.y }; - } else { - return { a.x, a.y, b.x, b.y, b.x - a.x, b.y - a.y }; - } -} - -// scan-line conversion -void _scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) { - double y0 = fmax(ymin, floor(e1.y0)), - y1 = fmin(ymax, ceil(e1.y1)); - - // sort edges by x-coordinate - if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ? - (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) : - (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) { - std::swap(e0, e1); - } - - // scan lines! - double m0 = e0.dx / e0.dy, - m1 = e1.dx / e1.dy, - d0 = e0.dx > 0, // use y + 1 to compute x0 - d1 = e1.dx < 0; // use y + 1 to compute x1 - for (int32_t y = y0; y < y1; y++) { - double x0 = m0 * fmax(0, fmin(e0.dy, y + d0 - e0.y0)) + e0.x0, - x1 = m1 * fmax(0, fmin(e1.dy, y + d1 - e1.y0)) + e1.x0; - scanLine(floor(x1), ceil(x0), y, ymax); - } -} - -// scan-line conversion -void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const llmr::vec2<double> c, int32_t ymin, int32_t ymax, ScanLine& scanLine) { - edge ab = _edge(a, b); - edge bc = _edge(b, c); - edge ca = _edge(c, a); - - // sort edges by y-length - if (ab.dy > bc.dy) { std::swap(ab, bc); } - if (ab.dy > ca.dy) { std::swap(ab, ca); } - if (bc.dy > ca.dy) { std::swap(bc, ca); } - - // scan span! scan span! - if (ab.dy) _scanSpans(ca, ab, ymin, ymax, scanLine); - if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine); -} - -std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points, const bool use_raster, const bool use_retina) { - int32_t dim = pow(2, zoom); - std::forward_list<llmr::Tile::ID> tiles; - - auto scanLine = [&tiles, zoom, use_raster, use_retina](int32_t x0, int32_t x1, int32_t y, int32_t ymax) { - int32_t x; - if (y >= 0 && y <= ymax) { - for (x = x0; x < x1; x++) { - if (use_raster) { - uint32_t search_zoom = zoom; - search_zoom += (uint32_t)((llmr::util::tileSize / 256.0f) - 1.0f); - if (use_retina) { - search_zoom += 1; - } - Tile::ID id = Tile::ID(zoom, x, y); - auto ids = id.children(search_zoom); - for (const Tile::ID& child_id : ids) { - tiles.emplace_front(child_id.z, child_id.x, child_id.y); - } - } else { - tiles.emplace_front(zoom, x, y); - } - } - } - }; - - // Divide the screen up in two triangles and scan each of them: - // \---+ - // | \ | - // +---\. - _scanTriangle(points.tl, points.tr, points.br, 0, dim, scanLine); - _scanTriangle(points.br, points.bl, points.tl, 0, dim, scanLine); - - const vec2<double>& center = points.center; - tiles.sort([¢er](const Tile::ID& a, const Tile::ID& b) { - // Sorts by distance from the box center - return fabs(a.x - center.x) + fabs(a.y - center.y) < - fabs(b.x - center.x) + fabs(b.y - center.y); - }); - - tiles.unique(); - - return tiles; -} diff --git a/src/map/map.cpp b/src/map/map.cpp index 5685102244..48d3df0419 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -1,12 +1,13 @@ #include <llmr/map/map.hpp> #include <llmr/map/settings.hpp> +#include <llmr/map/source.hpp> #include <llmr/platform/platform.hpp> #include <llmr/style/resources.hpp> #include <llmr/style/sprite.hpp> -#include <llmr/map/coverage.hpp> #include <llmr/util/animation.hpp> #include <algorithm> +#include <memory> using namespace llmr; @@ -18,7 +19,7 @@ Map::Map(Settings& settings) glyphAtlas(1024, 1024), painter(transform, settings, style, glyphAtlas), min_zoom(0), - max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) { + max_zoom(21) { } Map::~Map() { @@ -29,6 +30,32 @@ void Map::setup() { painter.setup(); style.loadJSON(resources::style, resources::style_size); + + sources.emplace("mapbox streets", + Source(*this, + transform, + painter, + texturepool, + "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf", + Source::Type::vector, + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14}, + 512, + 0, + 14, + true)); + + sources.emplace("satellite", + Source(*this, + transform, + painter, + texturepool, + "https://a.tiles.mapbox.com/v3/justin.hh0gkdfm/%d/%d/%d%s.png256", + Source::Type::raster, + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21}, + 256, + 0, + 21, + false)); } void Map::loadStyle(const uint8_t *const data, uint32_t bytes) { @@ -53,11 +80,20 @@ void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width } } -void Map::toggleRaster() { - use_raster = ! use_raster; - max_zoom = (use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom); - tiles.clear(); - update(); +box Map::cornersToBox(uint32_t z) const { + return transform.cornersToBox(z); +} + +float Map::getPixelRatio() const { + return transform.getPixelRatio(); +} + +Style& Map::getStyle() { + return style; +} + +GlyphAtlas& Map::getGlyphAtlas() { + return glyphAtlas; } void Map::moveBy(double dx, double dy, double duration) { @@ -223,6 +259,24 @@ void Map::toggleDebug() { settings.persist(); } +void Map::toggleRaster() { + auto it = sources.find("satellite"); + std::pair<std::string, Source&> pair = *it; + Source& satellite_source = pair.second; + + if (satellite_source.enabled) { + satellite_source.enabled = false; + style.appliedClasses.erase(style.appliedClasses.find("satellite")); + } else { + satellite_source.enabled = true; + style.appliedClasses.insert("satellite"); + } + + style.cascade(transform.getNormalizedZoom()); + + update(); +} + void Map::cancelAnimations() { transform.cancelAnimations(); } @@ -238,197 +292,16 @@ void Map::update() { platform::restart(); } - -TileData::State Map::hasTile(const Tile::ID& id) { - for (const Tile& tile : tiles) { - if (tile.id == id && tile.data) { - return tile.data->state; - } - } - - return TileData::State::invalid; -} - -TileData::State Map::addTile(const Tile::ID& id) { - const TileData::State state = hasTile(id); - - if (state != TileData::State::invalid) { - return state; - } - - tiles.emplace_front(id); - Tile& new_tile = tiles.front(); - - // We couldn't find the tile in the list. Create a new one. - // Try to find the associated TileData object. - const Tile::ID normalized_id = id.normalized(); - - auto it = std::find_if(tile_data.begin(), tile_data.end(), [&normalized_id](const std::weak_ptr<TileData>& tile_data) { - return !tile_data.expired() && tile_data.lock()->id == normalized_id; - }); - - if (it != tile_data.end()) { - // Create a shared_ptr handle. Note that this might be empty! - new_tile.data = it->lock(); - } - - if (new_tile.data && new_tile.data->state == TileData::State::obsolete) { - // Do not consider the tile if it's already obsolete. - new_tile.data.reset(); - } - - if (!new_tile.data) { - // If we don't find working tile data, we're just going to load it. - new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas, use_raster, (pixel_ratio > 1.0)); - new_tile.data->request(); - tile_data.push_front(new_tile.data); - } - - return new_tile.data->state; -} - -/** - * Recursively find children of the given tile that are already loaded. - * - * @param id The tile ID that we should find children for. - * @param maxCoveringZoom The maximum zoom level of children to look for. - * @param retain An object that we add the found tiles to. - * - * @return boolean Whether the children found completely cover the tile. - */ -bool Map::findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain) { - bool complete = true; - int32_t z = id.z; - - - auto ids = id.children(z + 1); - for (const Tile::ID& child_id : ids) { - const TileData::State state = hasTile(child_id); - if (state == TileData::State::parsed) { - retain.emplace_front(child_id); - } else { - complete = false; - if (z < maxCoveringZoom) { - // Go further down the hierarchy to find more unloaded children. - findLoadedChildren(child_id, maxCoveringZoom, retain); - } - } - } - return complete; -} - -/** - * Find a loaded parent of the given tile. - * - * @param id The tile ID that we should find children for. - * @param minCoveringZoom The minimum zoom level of parents to look for. - * @param retain An object that we add the found tiles to. - * - * @return boolean Whether a parent was found. - */ -bool Map::findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain) { - for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) { - const Tile::ID parent_id = id.parent(z); - const TileData::State state = hasTile(parent_id); - if (state == TileData::State::parsed) { - retain.emplace_front(parent_id); - return true; - } - } - return false; -} - - bool Map::updateTiles() { bool changed = false; - // Figure out what tiles we need to load - int32_t zoom = transform.getZoom(); - if (zoom > max_zoom) zoom = max_zoom; - if (zoom < min_zoom) zoom = min_zoom; - - int32_t max_covering_zoom = zoom + 1; - if (max_covering_zoom > max_zoom) max_covering_zoom = max_zoom; - - int32_t min_covering_zoom = zoom - 10; - if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom; - - int32_t max_dim = pow(2, zoom); - - // Map four viewport corners to pixel coordinates - box box = transform.mapCornersToBox(zoom); - - // Performs a scanline algorithm search that covers the rectangle of the box - // and sorts them by proximity to the center. - std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box, use_raster, (pixel_ratio > 1.0)); - - // Retain is a list of tiles that we shouldn't delete, even if they are not - // the most ideal tile for the current viewport. This may include tiles like - // parent or child tiles that are *already* loaded. - std::forward_list<Tile::ID> retain(required); - - // Add existing child/parent tiles if the actual tile is not yet loaded - for (const Tile::ID& id : required) { - const TileData::State state = addTile(id); - - if (state != TileData::State::parsed) { - if (use_raster && (transform.rotating || transform.scaling || transform.panning)) - break; - - // The tile we require is not yet loaded. Try to find a parent or - // child tile that we already have. - - // First, try to find existing child tiles that completely cover the - // missing tile. - bool complete = findLoadedChildren(id, max_covering_zoom, retain); - - // Then, if there are no complete child tiles, try to find existing - // parent tiles that completely cover the missing tile. - if (!complete) { - findLoadedParent(id, min_covering_zoom, retain); - } - } - - if (state == TileData::State::initial) { - changed = true; + for (std::pair<std::string, Source&> pair : sources) { + Source& source = pair.second; + if (source.enabled) { + changed = source.update() || changed; } } - // Remove tiles that we definitely don't need, i.e. tiles that are not on - // the required list. - std::forward_list<Tile::ID> retain_data; - tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) { - bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end(); - if (obsolete) { - changed = true; - } else { - retain_data.push_front(tile.data->id); - } - return obsolete; - }); - - // Sort tiles by zoom level, front to back. - // We're painting front-to-back, so we want to draw more detailed tiles first - // before filling in other parts with lower zoom levels. - tiles.sort([](const Tile & a, const Tile & b) { - return a.id.z > b.id.z; - }); - - // Remove all the expired pointers from the list. - tile_data.remove_if([&retain_data](const std::weak_ptr<TileData>& tile_data) { - const std::shared_ptr<TileData> tile = tile_data.lock(); - if (!tile) { - return true; - } - bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end(); - if (obsolete) { - tile->cancel(); - return true; - } else { - return false; - } - }); - return changed; } @@ -452,32 +325,31 @@ bool Map::render() { painter.changeMatrix(); - // First, update all tile matrices with the new transform and render into - // the stencil buffer. - uint8_t i = 1; + // First, update the sources' tile matrices with the new + // transform and render into the stencil buffer, including + // drawing the background. + painter.prepareClippingMask(); - for (Tile& tile : tiles) { - if (tile.data && tile.data->state == TileData::State::parsed) { - // The position matrix. - transform.matrixFor(tile.matrix, tile.id); - matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); - tile.clip_id = i++; - painter.drawClippingMask(tile.matrix, tile.clip_id, !tile.data->use_raster); - } + + bool is_baselayer = true; + + for (std::pair<std::string, Source&> pair : sources) { + Source& source = pair.second; + source.prepare_render(is_baselayer); + is_baselayer = source.enabled ? false : (true && is_baselayer); } + painter.finishClippingMask(); - for (const Tile& tile : tiles) { - if (tile.data && tile.data->state == TileData::State::parsed) { - if (tile.data->use_raster && *tile.data->raster && !tile.data->raster->textured) { - tile.data->raster->setTexturepool(&texturepool); - tile.data->raster->beginFadeInAnimation(); - } - if (tile.data->use_raster && tile.data->raster->needsAnimation()) { - tile.data->raster->updateAnimations(animationTime); - } - painter.render(tile); - } + // Then, render each source's tiles. TODO: handle more than one + // vector source. + + is_baselayer = true; + + for (std::pair<std::string, Source&> pair : sources) { + Source& source = pair.second; + source.render(animationTime, is_baselayer); + is_baselayer = source.enabled ? false : (true && is_baselayer); } painter.renderMatte(); diff --git a/src/map/source.cpp b/src/map/source.cpp new file mode 100644 index 0000000000..87e6a021b1 --- /dev/null +++ b/src/map/source.cpp @@ -0,0 +1,379 @@ +#include <llmr/map/source.hpp> +#include <llmr/map/map.hpp> +#include <llmr/map/transform.hpp> +#include <llmr/renderer/painter.hpp> +#include <llmr/util/constants.hpp> +#include <llmr/util/raster.hpp> +#include <llmr/util/string.hpp> +#include <llmr/util/texturepool.hpp> +#include <llmr/util/vec.hpp> +#include <llmr/geometry/glyph_atlas.hpp> + +using namespace llmr; + +Source::Source(Map& map, Transform& transform, Painter& painter, Texturepool& texturepool, const char *url, Source::Type type, std::list<uint32_t> zooms, uint32_t tile_size, uint32_t min_zoom, uint32_t max_zoom, bool enabled) + : map(map), + transform(transform), + painter(painter), + texturepool(texturepool), + url(url), + type(type), + zooms(zooms), + tile_size(tile_size), + min_zoom(min_zoom), + max_zoom(max_zoom), + enabled(enabled) { +} + +bool Source::update() { + return updateTiles(); +} + +void Source::prepare_render(bool is_baselayer) { + if (!enabled) return; + + uint8_t i = 1; + + for (Tile& tile : tiles) { + if (tile.data && tile.data->state == TileData::State::parsed) { + transform.matrixFor(tile.matrix, tile.id); + matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); + tile.clip_id = i++; + if (is_baselayer) { + painter.drawClippingMask(tile.matrix, tile.clip_id); + } + } + } +} + +void Source::render(double animationTime, bool is_baselayer) { + if (!enabled) return; + + for (const Tile& tile : tiles) { + if (tile.data && tile.data->state == TileData::State::parsed) { + if (type == Type::raster) { + std::shared_ptr<Raster> raster = tile.data->raster; + if (raster && !raster->textured) { + raster->setTexturepool(&texturepool); + raster->beginFadeInAnimation(); + } + if (raster && raster->needsAnimation()) { + raster->updateAnimations(animationTime); + } + } + painter.render(tile); + } + } +} + +TileData::State Source::hasTile(const Tile::ID& id) { + for (const Tile& tile : tiles) { + if (tile.id == id && tile.data) { + return tile.data->state; + } + } + + return TileData::State::invalid; +} + +TileData::State Source::addTile(const Tile::ID& id) { + const TileData::State state = hasTile(id); + + if (state != TileData::State::invalid) { + return state; + } + + tiles.emplace_front(id); + Tile& new_tile = tiles.front(); + + // We couldn't find the tile in the list. Create a new one. + // Try to find the associated TileData object. + const Tile::ID normalized_id = id.normalized(); + + auto it = std::find_if(tile_data.begin(), tile_data.end(), [normalized_id](const std::weak_ptr<TileData>& tile_data) { + return !tile_data.expired() && tile_data.lock()->id == normalized_id; + }); + + if (it != tile_data.end()) { + // Create a shared_ptr handle. Note that this might be empty! + new_tile.data = it->lock(); + } + + if (new_tile.data && new_tile.data->state == TileData::State::obsolete) { + // Do not consider the tile if it's already obsolete. + new_tile.data.reset(); + } + + if (!new_tile.data) { + // If we don't find working tile data, we're just going to load it. + + std::string formed_url; + + if (type == Source::Type::vector) { + formed_url = util::sprintf(url, id.z, id.x, id.y); + } else { + formed_url = util::sprintf(url, id.z, id.x, id.y, (map.getPixelRatio() > 1.0 ? "@2x" : "")); + } + + new_tile.data = std::make_shared<TileData>(normalized_id, map.getStyle(), map.getGlyphAtlas(), formed_url, (type == Source::Type::raster)); + new_tile.data->request(); + tile_data.push_front(new_tile.data); + } + + return new_tile.data->state; +} + +/** + * Recursively find children of the given tile that are already loaded. + * + * @param id The tile ID that we should find children for. + * @param maxCoveringZoom The maximum zoom level of children to look for. + * @param retain An object that we add the found tiles to. + * + * @return boolean Whether the children found completely cover the tile. + */ +bool Source::findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain) { + bool complete = true; + int32_t z = id.z; + + + auto ids = id.children(z + 1); + for (const Tile::ID& child_id : ids) { + const TileData::State state = hasTile(child_id); + if (state == TileData::State::parsed) { + retain.emplace_front(child_id); + } else { + complete = false; + if (z < maxCoveringZoom) { + // Go further down the hierarchy to find more unloaded children. + findLoadedChildren(child_id, maxCoveringZoom, retain); + } + } + } + return complete; +} + +/** + * Find a loaded parent of the given tile. + * + * @param id The tile ID that we should find children for. + * @param minCoveringZoom The minimum zoom level of parents to look for. + * @param retain An object that we add the found tiles to. + * + * @return boolean Whether a parent was found. + */ +bool Source::findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain) { + for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) { + const Tile::ID parent_id = id.parent(z); + const TileData::State state = hasTile(parent_id); + if (state == TileData::State::parsed) { + retain.emplace_front(parent_id); + return true; + } + } + return false; +} + +bool Source::updateTiles() { + bool changed = false; + + // Figure out what tiles we need to load + int32_t zoom = map.getZoom(); + if (zoom > max_zoom) zoom = max_zoom; + if (zoom < min_zoom) zoom = min_zoom; + + int32_t max_covering_zoom = zoom + 1; + if (max_covering_zoom > max_zoom) max_covering_zoom = max_zoom; + + int32_t min_covering_zoom = zoom - 10; + if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom; + + int32_t max_dim = pow(2, zoom); + + // Map four viewport corners to pixel coordinates + box box = map.cornersToBox(zoom); + + // Performs a scanline algorithm search that covers the rectangle of the box + // and sorts them by proximity to the center. + + std::forward_list<Tile::ID> required = covering_tiles(zoom, box); + + // Retain is a list of tiles that we shouldn't delete, even if they are not + // the most ideal tile for the current viewport. This may include tiles like + // parent or child tiles that are *already* loaded. + std::forward_list<Tile::ID> retain(required); + + // Add existing child/parent tiles if the actual tile is not yet loaded + for (const Tile::ID& id : required) { + const TileData::State state = addTile(id); + + if (state != TileData::State::parsed) { +// if (use_raster && (transform.rotating || transform.scaling || transform.panning)) +// break; + + // The tile we require is not yet loaded. Try to find a parent or + // child tile that we already have. + + // First, try to find existing child tiles that completely cover the + // missing tile. + bool complete = findLoadedChildren(id, max_covering_zoom, retain); + + // Then, if there are no complete child tiles, try to find existing + // parent tiles that completely cover the missing tile. + if (!complete) { + findLoadedParent(id, min_covering_zoom, retain); + } + } + + if (state == TileData::State::initial) { + changed = true; + } + } + + // Remove tiles that we definitely don't need, i.e. tiles that are not on + // the required list. + std::forward_list<Tile::ID> retain_data; + tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) { + bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end(); + if (obsolete) { + changed = true; + } else { + retain_data.push_front(tile.data->id); + } + return obsolete; + }); + + // Sort tiles by zoom level, front to back. + // We're painting front-to-back, so we want to draw more detailed tiles first + // before filling in other parts with lower zoom levels. + tiles.sort([](const Tile & a, const Tile & b) { + return a.id.z > b.id.z; + }); + + // Remove all the expired pointers from the list. + tile_data.remove_if([&retain_data](const std::weak_ptr<TileData>& tile_data) { + const std::shared_ptr<TileData> tile = tile_data.lock(); + if (!tile) { + return true; + } + bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end(); + if (obsolete) { + tile->cancel(); + return true; + } else { + return false; + } + }); + + return changed; +} + +// Taken from polymaps src/Layer.js +// https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383 + +struct edge { + double x0 = 0, y0 = 0; + double x1 = 0, y1 = 0; + double dx = 0, dy = 0; + edge(double x0, double y0, double x1, double y1, double dx, double dy) + : x0(x0), y0(y0), x1(x1), y1(y1), dx(dx), dy(dy) {} +}; + +typedef const std::function<void(int32_t, int32_t, int32_t, int32_t)> ScanLine; + +// scan-line conversion +edge _edge(const llmr::vec2<double> a, const llmr::vec2<double> b) { + if (a.y > b.y) { + return { b.x, b.y, a.x, a.y, a.x - b.x, a.y - b.y }; + } else { + return { a.x, a.y, b.x, b.y, b.x - a.x, b.y - a.y }; + } +} + +// scan-line conversion +void _scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) { + double y0 = fmax(ymin, floor(e1.y0)), + y1 = fmin(ymax, ceil(e1.y1)); + + // sort edges by x-coordinate + if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ? + (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) : + (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) { + std::swap(e0, e1); + } + + // scan lines! + double m0 = e0.dx / e0.dy, + m1 = e1.dx / e1.dy, + d0 = e0.dx > 0, // use y + 1 to compute x0 + d1 = e1.dx < 0; // use y + 1 to compute x1 + for (int32_t y = y0; y < y1; y++) { + double x0 = m0 * fmax(0, fmin(e0.dy, y + d0 - e0.y0)) + e0.x0, + x1 = m1 * fmax(0, fmin(e1.dy, y + d1 - e1.y0)) + e1.x0; + scanLine(floor(x1), ceil(x0), y, ymax); + } +} + +// scan-line conversion +void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const llmr::vec2<double> c, int32_t ymin, int32_t ymax, ScanLine& scanLine) { + edge ab = _edge(a, b); + edge bc = _edge(b, c); + edge ca = _edge(c, a); + + // sort edges by y-length + if (ab.dy > bc.dy) { std::swap(ab, bc); } + if (ab.dy > ca.dy) { std::swap(ab, ca); } + if (bc.dy > ca.dy) { std::swap(bc, ca); } + + // scan span! scan span! + if (ab.dy) _scanSpans(ca, ab, ymin, ymax, scanLine); + if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine); +} + +double Source::getZoom() const { + double offset = log(util::tileSize / tile_size) / log(2); + offset += (map.getPixelRatio() > 1.0 ? 1 :0); + return map.getZoom() + offset; +} + +std::forward_list<llmr::Tile::ID> Source::covering_tiles(int32_t zoom, const box& points) { + int32_t dim = pow(2, zoom); + std::forward_list<llmr::Tile::ID> tiles; + bool is_raster = (type == Type::raster); + double search_zoom = getZoom(); + + auto scanLine = [&tiles, zoom, is_raster, search_zoom](int32_t x0, int32_t x1, int32_t y, int32_t ymax) { + int32_t x; + if (y >= 0 && y <= ymax) { + for (x = x0; x < x1; x++) { + if (is_raster) { + Tile::ID id = Tile::ID(zoom, x, y); + auto ids = id.children(search_zoom); + for (const Tile::ID& child_id : ids) { + tiles.emplace_front(child_id.z, child_id.x, child_id.y); + } + } else { + tiles.emplace_front(zoom, x, y); + } + } + } + }; + + // Divide the screen up in two triangles and scan each of them: + // \---+ + // | \ | + // +---\. + _scanTriangle(points.tl, points.tr, points.br, 0, dim, scanLine); + _scanTriangle(points.br, points.bl, points.tl, 0, dim, scanLine); + + const vec2<double>& center = points.center; + tiles.sort([¢er](const Tile::ID& a, const Tile::ID& b) { + // Sorts by distance from the box center + return fabs(a.x - center.x) + fabs(a.y - center.y) < + fabs(b.x - center.x) + fabs(b.y - center.y); + }); + + tiles.unique(); + + return tiles; +} diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 4df01778d1..203b147c89 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -11,11 +11,11 @@ using namespace llmr; -TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster, const bool use_retina) +TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const std::string url, const bool is_raster) : id(id), - use_raster(use_raster), - use_retina(use_retina), + url(url), state(State::initial), + is_raster(is_raster), raster(), style(style), glyphAtlas(glyphAtlas) { @@ -38,17 +38,9 @@ const std::string TileData::toString() const { void TileData::request() { state = State::loading; - std::string url; - - if (use_raster) { - url = util::sprintf(kTileRasterURL, id.z, id.x, id.y, (use_retina ? "@2x" : "")); - } else { - url = util::sprintf(kTileVectorURL, id.z, id.x, id.y); - } - // Note: Somehow this feels slower than the change to request_http() std::weak_ptr<TileData> weak_tile = shared_from_this(); - req = platform::request_http(url, [weak_tile, url](platform::Response *res) { + req = platform::request_http(url, [weak_tile](platform::Response *res) { std::shared_ptr<TileData> tile = weak_tile.lock(); if (!tile || tile->state == State::obsolete) { // noop. Tile is obsolete and we're now just waiting for the refcount @@ -58,7 +50,7 @@ void TileData::request() { tile->data.swap(res->body); tile->parse(); } else { - fprintf(stderr, "[%s] tile loading failed: %d, %s\n", url.c_str(), res->code, res->error_message.c_str()); + fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str()); } }, []() { platform::restart(); @@ -77,18 +69,11 @@ bool TileData::parse() { return false; } - if (use_raster) { - raster = std::make_shared<Raster>(); - raster->load(data); - state = State::parsed; - return true; - } - try { // 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, style, glyphAtlas); + TileParser parser(data, *this, style, glyphAtlas, is_raster); } 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(); diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index c21b6b961b..9e75fe3f70 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -5,19 +5,22 @@ #include <llmr/renderer/line_bucket.hpp> #include <llmr/renderer/point_bucket.hpp> #include <llmr/renderer/text_bucket.hpp> +#include <llmr/renderer/raster_bucket.hpp> +#include <llmr/util/raster.hpp> #include <llmr/util/std.hpp> using namespace llmr; -TileParser::TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas) - : data(pbf((const uint8_t *)data.data(), data.size())), +TileParser::TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas, bool is_raster) + : vector_data(is_raster ? pbf(0, 0) : pbf((const uint8_t *)data.data(), data.size())), + raster_data(is_raster ? data : ""), tile(tile), style(style), glyphAtlas(glyphAtlas), - placement(tile.id.z) { - parseGlyphs(); + placement(is_raster ? 0 : tile.id.z) { + if (!is_raster) parseGlyphs(); parseStyleLayers(style.layers); } @@ -26,7 +29,7 @@ bool TileParser::obsolete() const { } void TileParser::parseGlyphs() { - for (const std::pair<std::string, const VectorTileFace> pair : data.faces) { + for (const std::pair<std::string, const VectorTileFace> pair : vector_data.faces) { const std::string &name = pair.first; const VectorTileFace &face = pair.second; @@ -77,22 +80,30 @@ void TileParser::parseStyleLayers(const std::vector<LayerDescription>& layers) { } std::unique_ptr<Bucket> TileParser::createBucket(const BucketDescription& bucket_desc) { - auto layer_it = data.layers.find(bucket_desc.source_layer); - if (layer_it != data.layers.end()) { - const VectorTileLayer& layer = layer_it->second; - if (bucket_desc.type == BucketType::Fill) { - return createFillBucket(layer, bucket_desc); - } else if (bucket_desc.type == BucketType::Line) { - return createLineBucket(layer, bucket_desc); - } else if (bucket_desc.type == BucketType::Point) { - return createPointBucket(layer, bucket_desc); - } else if (bucket_desc.type == BucketType::Text) { - return createTextBucket(layer, bucket_desc); - } else { - throw std::runtime_error("unknown bucket type"); + if (bucket_desc.type == BucketType::Raster) { + if (raster_data.length()) { + tile.raster = std::make_shared<Raster>(); + tile.raster->load(raster_data); } + return createRasterBucket(bucket_desc); } else { - // The layer specified in the bucket does not exist. Do nothing. + auto layer_it = vector_data.layers.find(bucket_desc.source_layer); + if (layer_it != vector_data.layers.end()) { + const VectorTileLayer& layer = layer_it->second; + if (bucket_desc.type == BucketType::Fill) { + return createFillBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Line) { + return createLineBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Point) { + return createPointBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Text) { + return createTextBucket(layer, bucket_desc); + } else { + throw std::runtime_error("unknown bucket type"); + } + } else { + // The layer specified in the bucket does not exist. Do nothing. + } } return nullptr; @@ -166,3 +177,10 @@ std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer& laye return std::move(bucket); } + +std::unique_ptr<Bucket> TileParser::createRasterBucket(const BucketDescription& bucket_desc) { + std::unique_ptr<RasterBucket> bucket = std::make_unique<RasterBucket>(bucket_desc); + // Raster buckets are just empty dummies so that they behave + // similarly to vector buckets in styling configurations. + return obsolete() ? nullptr : std::move(bucket); +} diff --git a/src/map/transform.cpp b/src/map/transform.cpp index 3c9246adaa..545de6e805 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -298,7 +298,7 @@ double Transform::getAngle() const { return angle; } -box Transform::mapCornersToBox(uint32_t z) const { +box Transform::cornersToBox(uint32_t z) const { const double ref_scale = pow(2, z); const double angle_sin = sin(-angle); diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index 78381b8d81..6f36105462 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -1,8 +1,3 @@ #include <llmr/platform/platform.hpp> -const char *llmr::kTileVectorURL = "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf"; -const char *llmr::kTileRasterURL = "http://a.tiles.mapbox.com/v3/kkaefer.holbci77/%d/%d/%d.png"; const char *llmr::kSpriteURL = "http://mapbox-kkaefer.s3.amazonaws.com/static/sprite"; - -const int32_t llmr::kTileVectorMaxZoom = 14; -const int32_t llmr::kTileRasterMaxZoom = 21; diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 6afa474a8c..fb2cd0e342 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -9,6 +9,7 @@ #include <llmr/renderer/line_bucket.hpp> #include <llmr/renderer/point_bucket.hpp> #include <llmr/renderer/text_bucket.hpp> +#include <llmr/renderer/raster_bucket.hpp> #include <llmr/map/transform.hpp> #include <llmr/map/settings.hpp> @@ -123,13 +124,13 @@ void Painter::prepareClippingMask() { coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); } -void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque) { +void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) { plainShader->setMatrix(matrix); - if (opaque) { - plainShader->setColor(style.computed.background.color); - } - glStencilFunc(GL_ALWAYS, clip_id, 0xFF); + plainShader->setColor(style.computed.background.color); + plainShader->setOpacity(style.computed.background.opacity); + + glStencilFunc(GL_ALWAYS, 1, 0xFF); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); } @@ -160,31 +161,14 @@ void Painter::render(const Tile& tile) { frameHistory.record(transform.getNormalizedZoom()); matrix = tile.matrix; - glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF); - if (tile.data->use_raster) { - renderRaster(tile.data); - } else { - renderLayers(tile.data, style.layers); - } + renderLayers(tile.data, style.layers); if (settings.debug) { renderDebug(tile.data); } } -void Painter::renderRaster(const std::shared_ptr<TileData>& tile_data) { - useProgram(rasterShader->program); - rasterShader->setMatrix(matrix); - rasterShader->setImage(0); - rasterShader->setOpacity(tile_data->raster->opacity); - tile_data->raster->bind(true); - - coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0)); - glDepthRange(strata, 1.0f); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); -} - void Painter::renderLayers(const std::shared_ptr<TileData>& tile_data, const std::vector<LayerDescription>& layers) { float strata_thickness = 1.0f / (layers.size() + 1); @@ -226,14 +210,37 @@ void Painter::renderLayer(const std::shared_ptr<TileData>& tile_data, const Laye } else { // This is a singular layer. Try to find the bucket associated with // this layer and render it. - auto bucket_it = tile_data->buckets.find(layer_desc.bucket_name); - if (bucket_it != tile_data->buckets.end()) { - assert(bucket_it->second); - bucket_it->second->render(*this, layer_desc.name, tile_data->id); + if (style.buckets[layer_desc.bucket_name].type == BucketType::Raster) { + if (tile_data && tile_data->raster) { + renderRaster(layer_desc.name, tile_data); + } + } else { + auto bucket_it = tile_data->buckets.find(layer_desc.bucket_name); + if (bucket_it != tile_data->buckets.end()) { + assert(bucket_it->second); + bucket_it->second->render(*this, layer_desc.name, tile_data->id); + } } } } +void Painter::renderRaster(const std::string& layer_name, const std::shared_ptr<TileData>& tile_data) { + if (pass == Opaque) return; + + const RasterProperties& properties = style.computed.rasters[layer_name]; + if (!properties.enabled) return; + + useProgram(rasterShader->program); + rasterShader->setMatrix(matrix); + rasterShader->setImage(0); + rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); + tile_data->raster->bind(true); + + coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0)); + glDepthRange(strata, 1.0f); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); +} + void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { // Abort early. if (bucket.empty()) return; diff --git a/src/renderer/raster_bucket.cpp b/src/renderer/raster_bucket.cpp new file mode 100644 index 0000000000..54f7dc6398 --- /dev/null +++ b/src/renderer/raster_bucket.cpp @@ -0,0 +1,14 @@ +#include <llmr/renderer/raster_bucket.hpp> +#include <llmr/renderer/painter.hpp> + +using namespace llmr; + +RasterBucket::RasterBucket(const BucketDescription& bucket_desc) { +} + +void RasterBucket::render(Painter& painter, const std::string& layer_name, const Tile::ID& id) { + // The painter renders rasters directly. This function is a no-op + // and exists just to satisfy the abstract superclass requirements. + // That way, we can avoid having raster buckets pass around + // references to tile data and/or rasters directly. +} diff --git a/src/shader/plain.fragment.glsl b/src/shader/plain.fragment.glsl index 8df552c171..0761ea5e65 100644 --- a/src/shader/plain.fragment.glsl +++ b/src/shader/plain.fragment.glsl @@ -1,5 +1,6 @@ uniform vec4 u_color; +uniform float u_opacity; void main() { - gl_FragColor = u_color; + gl_FragColor = u_color * u_opacity; } diff --git a/src/shader/plain_shader.cpp b/src/shader/plain_shader.cpp index ac4b7edeb0..c9bb3e59d6 100644 --- a/src/shader/plain_shader.cpp +++ b/src/shader/plain_shader.cpp @@ -20,11 +20,13 @@ PlainShader::PlainShader() u_matrix = glGetUniformLocation(program, "u_matrix"); u_color = glGetUniformLocation(program, "u_color"); + u_opacity = glGetUniformLocation(program, "u_opacity"); // fprintf(stderr, "PlainShader:\n"); // fprintf(stderr, " - a_pos: %d\n", a_pos); // fprintf(stderr, " - u_matrix: %d\n", u_matrix); // fprintf(stderr, " - u_color: %d\n", u_color); + // fprintf(stderr, " - u_opacity: %f\n", u_opacity); } void PlainShader::bind(char *offset) { @@ -42,3 +44,10 @@ void PlainShader::setColor(const std::array<float, 4>& new_color) { void PlainShader::setColor(float r, float g, float b, float a) { setColor({{ r, g, b, a }}); } + +void PlainShader::setOpacity(float new_opacity) { + if (opacity != new_opacity) { + glUniform1f(u_opacity, new_opacity); + opacity = new_opacity; + } +} diff --git a/src/shader/shaders_gl.cpp b/src/shader/shaders_gl.cpp index 5bc95082d3..bde4fa65c6 100644 --- a/src/shader/shaders_gl.cpp +++ b/src/shader/shaders_gl.cpp @@ -24,7 +24,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = { }, { "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\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}\n\n", - "#version 120\nuniform vec4 u_color;\nvoid main ()\n{\n gl_FragColor = u_color;\n}\n\n", + "#version 120\nuniform vec4 u_color;\nuniform float u_opacity;\nvoid main ()\n{\n gl_FragColor = (u_color * u_opacity);\n}\n\n", }, { "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\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}\n\n", diff --git a/src/shader/shaders_gles2.cpp b/src/shader/shaders_gles2.cpp index e30b650bd1..e6a174da59 100644 --- a/src/shader/shaders_gles2.cpp +++ b/src/shader/shaders_gles2.cpp @@ -24,7 +24,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = { }, { "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\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}\n\n", - "precision highp float;\nuniform vec4 u_color;\nvoid main ()\n{\n gl_FragColor = u_color;\n}\n\n", + "precision highp float;\nuniform vec4 u_color;\nuniform float u_opacity;\nvoid main ()\n{\n gl_FragColor = (u_color * u_opacity);\n}\n\n", }, { "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\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}\n\n", diff --git a/src/style/style.cpp b/src/style/style.cpp index 7e72623f9f..ad85c7f1a7 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -76,11 +76,12 @@ void Style::cascade(float z) { point.image = layer.image; } + // Cascade text classes for (const auto& text_pair : sheetClass.text) { const std::string& layer_name = text_pair.first; const llmr::TextClass& layer = text_pair.second; - // TODO: This should be restricted to point styles that have actual + // TODO: This should be restricted to text styles that have actual // values so as to not override with default values. llmr::TextProperties& text = computed.texts[layer_name]; text.enabled = layer.enabled.evaluate<bool>(z); @@ -92,8 +93,21 @@ void Style::cascade(float z) { text.alwaysVisible = layer.alwaysVisible.evaluate<bool>(z); } + // Cascade raster classes + for (const auto& raster_pair : sheetClass.raster) { + const std::string& layer_name = raster_pair.first; + const llmr::RasterClass& layer = raster_pair.second; + + // TODO: This should be restricted to raster styles that have actual + // values so as to not override with default values. + llmr::RasterProperties& raster = computed.rasters[layer_name]; + raster.enabled = layer.enabled.evaluate<bool>(z); + raster.opacity = layer.opacity.evaluate<float>(z); + } + // Cascade background computed.background.color = sheetClass.background.color; + computed.background.opacity = sheetClass.background.opacity.evaluate<float>(z); } } diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index 7c452f94da..9064b3ac69 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -254,10 +254,12 @@ void StyleParser::parseClass(const std::string& name, JSVal value, ClassDescript class_desc.point.insert({ name, std::forward<PointClass>(parsePointClass(value)) }); } else if (type_name == "text") { class_desc.text.insert({ name, std::forward<TextClass>(parseTextClass(value)) }); + } else if (type_name == "raster") { + class_desc.raster.insert({ name, std::forward<RasterClass>(parseRasterClass(value)) }); } else if (type_name == "background") { class_desc.background = parseBackgroundClass(value); } else { - throw Style::exception("unkonwn class type name"); + throw Style::exception("unknown class type name"); } } else { throw Style::exception("style class type must be a string"); @@ -502,6 +504,20 @@ TextClass StyleParser::parseTextClass(JSVal value) { return klass; } +RasterClass StyleParser::parseRasterClass(JSVal value) { + RasterClass klass; + + if (value.HasMember("enabled")) { + klass.enabled = parseFunction(value["enabled"]); + } + + if (value.HasMember("opacity")) { + klass.opacity = parseFunction(value["opacity"]); + } + + return klass; +} + BackgroundClass StyleParser::parseBackgroundClass(JSVal value) { BackgroundClass klass; @@ -509,6 +525,10 @@ BackgroundClass StyleParser::parseBackgroundClass(JSVal value) { klass.color = parseColor(value["color"]); } + if (value.HasMember("opacity")) { + klass.opacity = parseFunction(value["opacity"]); + } + return klass; } |