diff --git a/include/llmr/map/coverage.hpp b/include/llmr/map/coverage.hpp deleted file mode 100644 index ed6317a56..000000000 --- a/include/llmr/map/coverage.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef LLMR_MAP_COVERAGE -#define LLMR_MAP_COVERAGE - -#include - -#include - -namespace llmr { - -std::forward_list 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 90cd95c01..22417bd34 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -13,10 +12,12 @@ #include #include +#include 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(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb_height); - void toggleRaster(); /* callback */ bool render(); @@ -64,13 +64,13 @@ public: void toggleDebug(); -private: - bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list& retain); - bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list& retain); - bool updateTiles(); - TileData::State addTile(const Tile::ID& id); - TileData::State hasTile(const Tile::ID& id); + box cornersToBox(uint32_t z) const; + float getPixelRatio() const; + Style& getStyle(); + GlyphAtlas& getGlyphAtlas(); +private: + bool updateTiles(); void update(); private: @@ -81,15 +81,12 @@ private: GlyphAtlas glyphAtlas; Painter painter; + std::map sources; + int32_t min_zoom; int32_t max_zoom; float pixel_ratio; - - bool use_raster = false; - - std::forward_list tiles; - std::forward_list> tile_data; }; } diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp new file mode 100644 index 000000000..e0479b525 --- /dev/null +++ b/include/llmr/map/source.hpp @@ -0,0 +1,67 @@ +#ifndef LLMR_MAP_SOURCE +#define LLMR_MAP_SOURCE + +#include +#include + +#include +#include +#include +#include + +namespace llmr { + +class Map; +class Transform; +class Painter; +class Texturepool; +class TileData; + +class Source : public std::enable_shared_from_this { +public: + enum class Type { + vector, + raster + }; + +public: + Source(Map& map, Transform& transform, Painter& painter, Texturepool& texturepool, const char *url = "", float pixel_ratio = 1.0, Type type = Type::vector, std::list zooms = {0}, uint32_t tile_size = 512, uint32_t min_zoom = 0, uint32_t max_zoom = 14, bool enabled = true); + + bool update(); + void render(); + +private: + bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list& retain); + bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list& retain); + std::forward_list covering_tiles(int32_t zoom, const box& points); + + bool updateTiles(); + + TileData::State addTile(const Tile::ID& id); + TileData::State hasTile(const Tile::ID& id); + + void renderTile(const Tile::ID& id); + +private: + Map& map; + Transform& transform; + Painter& painter; + Texturepool& texturepool; + + Type type; + std::list zooms; + const char *url; + uint32_t tile_size; + uint32_t min_zoom; + uint32_t max_zoom; + bool enabled; + + float pixel_ratio; + + std::forward_list tiles; + std::forward_list> tile_data; +}; + +} + +#endif diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index b0312b5e3..5f5d78c36 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, @@ -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; std::shared_ptr raster; @@ -91,7 +88,8 @@ public: std::map> buckets; private: - // Source data + std::string url; + bool is_raster = false; std::string data; const Style& style; GlyphAtlas& glyphAtlas; diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp index f43802ffa..32eca5f04 100644 --- a/include/llmr/map/transform.hpp +++ b/include/llmr/map/transform.hpp @@ -52,8 +52,7 @@ public: void startScaling(); void stopScaling(); - // Temporary - box mapCornersToBox(uint32_t z) const; + box cornersToBox(uint32_t z) const; private: void setScaleXY(double new_scale, double xn, double yn, double duration = 0); diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index 1724ebd35..c8bc285ea 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/ios/MBXViewController.mm b/ios/MBXViewController.mm index 9f50ab479..a9a809762 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -203,7 +203,7 @@ class MBXMapView - (void)toggleRaster { - mapView->map.toggleRaster(); +// mapView->map.toggleRaster(); } - (void)dealloc diff --git a/src/map/coverage.cpp b/src/map/coverage.cpp deleted file mode 100644 index b98518a58..000000000 --- a/src/map/coverage.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include -#include -#include - -#include -#include - -// 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 ScanLine; - -// scan-line conversion -edge _edge(const llmr::vec2 a, const llmr::vec2 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 a, const llmr::vec2 b, const llmr::vec2 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::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 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& 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 75bfdb2c9..05780b6c1 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -1,12 +1,13 @@ #include #include +#include #include #include #include -#include #include #include +#include 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() { @@ -35,6 +36,10 @@ void Map::setup(float pixelRatio) { style.sprite->load(kSpriteURL, pixel_ratio); style.loadJSON(resources::style, resources::style_size); + + sources.emplace("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", pixelRatio, Source::Type::vector, {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14}, 512, 0, 14, true)); + +// sources.emplace("raster", Source(*this, transform, painter, texturepool, "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256", pixelRatio, 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, true)); } void Map::loadStyle(const uint8_t *const data, uint32_t bytes) { @@ -60,11 +65,20 @@ void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb update(); } -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 pixel_ratio; +} + +Style& Map::getStyle() { + return style; +} + +GlyphAtlas& Map::getGlyphAtlas() { + return glyphAtlas; } void Map::moveBy(double dx, double dy, double duration) { @@ -244,197 +258,14 @@ 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& 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(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& 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& 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 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 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 pair : sources) { + Source& source = pair.second; + 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 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& tile_data) { - const std::shared_ptr 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; } @@ -455,32 +286,40 @@ bool Map::render() { // First, update all tile matrices with the new transform and render into // the stencil buffer. - uint8_t i = 1; +// uint8_t i = 1; + 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); - } - } - 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(); - } - painter.render(tile); - } +// 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); +// } +// } + + for (std::pair pair : sources) { + Source& source = pair.second; + source.render(); } +// 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(); +// } +// painter.render(tile); +// } +// } + painter.renderMatte(); return changed || transform.needsAnimation() || painter.needsAnimation(); diff --git a/src/map/source.cpp b/src/map/source.cpp new file mode 100644 index 000000000..e3611f37c --- /dev/null +++ b/src/map/source.cpp @@ -0,0 +1,386 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llmr; + +Source::Source(Map& map, Transform& transform, Painter& painter, Texturepool& texturepool, const char *url, float pixel_ratio, Source::Type type, std::list 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), + pixel_ratio(pixel_ratio), + 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::render() { + if (!enabled) { + return; + } + + uint8_t i = 1; + + 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, (type != Type::raster)); + } + } + + painter.finishClippingMask(); + + for (const Tile& tile : tiles) { + if (tile.data && tile.data->state == TileData::State::parsed) { + if (type == Type::raster && *tile.data->raster && !tile.data->raster->textured) { + tile.data->raster->setTexturepool(&texturepool); + tile.data->raster->beginFadeInAnimation(); + } + if (type == Type::raster && tile.data->raster->needsAnimation()) { + tile.data->raster->updateAnimations(); + } + painter.render(tile); + } + } + + return true; +} + +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& 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, (pixel_ratio > 1.0 ? "@2x" : "")); + } + + new_tile.data = std::make_shared(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& 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& 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 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 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 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& tile_data) { + const std::shared_ptr 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; +} + +void Source::renderTile(const Tile::ID& id) { + +// painter.render(tile); + + return; +} + +// 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 ScanLine; + +// scan-line conversion +edge _edge(const llmr::vec2 a, const llmr::vec2 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 a, const llmr::vec2 b, const llmr::vec2 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 Source::covering_tiles(int32_t zoom, const box& points) { + int32_t dim = pow(2, zoom); + std::forward_list tiles; + Type source_type = type; + uint32_t source_tile_size = tile_size; + double map_zoom = map.getZoom(); + float pixel_ratio = map.getPixelRatio(); + + auto scanLine = [&tiles, zoom, source_type, source_tile_size, map_zoom, pixel_ratio](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 (source_type == Type::raster) { + uint32_t offset = (util::tileSize / source_tile_size) - 1; + if (pixel_ratio > 1.0) { + offset++; + } + int32_t search_zoom = map_zoom + offset; + 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& 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 26ffdedb1..7e615c70e 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,14 +38,6 @@ 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 weak_tile = shared_from_this(); platform::Request *request = platform::request_http(url, [=](platform::Response& res) { @@ -78,7 +70,8 @@ bool TileData::parse() { return false; } - if (use_raster) { + if (is_raster) { + // raster tiles don't need parsed, just decoded raster = std::make_shared(); raster->load(data); state = State::parsed; diff --git a/src/map/transform.cpp b/src/map/transform.cpp index 5662e36b6..858fcc56d 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -272,7 +272,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 509dd38bd..6f3610546 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -1,8 +1,3 @@ #include -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 = "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256"; 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 6ac4e164f..092f282f9 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -162,7 +162,7 @@ void Painter::render(const Tile& tile) { matrix = tile.matrix; glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF); - if (tile.data->use_raster) { + if (tile.data->raster) { renderRaster(tile.data); } else { renderLayers(tile.data, style.layers);