diff --git a/include/llmr/map/coverage.hpp b/include/llmr/map/coverage.hpp new file mode 100644 index 000000000..0d50cd9f7 --- /dev/null +++ b/include/llmr/map/coverage.hpp @@ -0,0 +1,13 @@ +#ifndef LLMR_MAP_COVERAGE +#define LLMR_MAP_COVERAGE + +#include +#include + +namespace llmr { + +std::forward_list> covering_tiles(int32_t zoom, const box& points); + +} + +#endif diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp index a628bf33d..4a07c98f9 100644 --- a/include/llmr/map/transform.hpp +++ b/include/llmr/map/transform.hpp @@ -51,7 +51,7 @@ public: void stopScaling(); // Temporary - void mapCornersToBox(uint32_t z, box& b) const; + box mapCornersToBox(uint32_t z) const; private: void setScaleXY(double new_scale, double xn, double yn, double duration = 0); diff --git a/include/llmr/util/vec.hpp b/include/llmr/util/vec.hpp index 70aa11134..dbc31187b 100644 --- a/include/llmr/util/vec.hpp +++ b/include/llmr/util/vec.hpp @@ -55,6 +55,7 @@ struct vec4 { struct box { vec2 tl, tr, bl, br; + vec2 center; }; } diff --git a/src/map/coverage.cpp b/src/map/coverage.cpp new file mode 100644 index 000000000..e65e3469d --- /dev/null +++ b/src/map/coverage.cpp @@ -0,0 +1,98 @@ +#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) { + int32_t dim = pow(2, zoom); + std::forward_list> tiles; + + auto scanLine = [&tiles, zoom](int32_t x0, int32_t x1, int32_t y, int32_t ymax) { + int32_t x, wx; + if (y >= 0 && y <= ymax) { + for (x = x0; x < x1; x++) { + wx = (x + ymax) % ymax; + tiles.emplace_front(wx, y, zoom); + } + } + }; + + // 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 vec3& a, const vec3& 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 dc6780951..68360db03 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -329,27 +330,12 @@ bool Map::updateTiles() { 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, box); + box box = transform.mapCornersToBox(zoom); - vec2 tl, br; - tl.x = fmax(0, floor(fmin(box.tl.x, box.bl.x))); - tl.y = fmax(0, floor(fmin(box.tl.y, box.tr.y))); - br.x = fmin(max_dim, ceil(fmax(box.tr.x, box.br.x))); - br.y = fmin(max_dim, ceil(fmax(box.bl.y, box.br.y))); - - - // TODO: Discard tiles that are outside the viewport - std::forward_list required; - for (int32_t y = tl.y; y < br.y; ++y) { - for (int32_t x = tl.x; x < br.x; ++x) { - required.emplace_front(x, y, 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); // 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 diff --git a/src/map/transform.cpp b/src/map/transform.cpp index 12c55e1f9..f8f8380a7 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -271,33 +271,37 @@ double Transform::getAngle() const { return angle; } -void Transform::mapCornersToBox(uint32_t z, box& b) const { +box Transform::mapCornersToBox(uint32_t z) const { const double ref_scale = pow(2, z); - // Keep the map as upright as possible. - double local_angle = angle; - if (local_angle >= M_PI_2) local_angle -= M_PI; - if (local_angle < -M_PI_2) local_angle += M_PI; - - const double angle_sin = sin(-local_angle); - const double angle_cos = cos(-local_angle); + const double angle_sin = sin(-angle); + const double angle_cos = cos(-angle); const double w_2 = width / 2; const double h_2 = height / 2; - const double ss_1 = ref_scale / (scale * util::tileSize); - const double ss_2 = (scale * util::tileSize) / 2; + const double ss_0 = scale * util::tileSize; + const double ss_1 = ref_scale / ss_0; + const double ss_2 = ss_0 / 2.0; // Calculate the corners of the map view. The resulting coordinates will be // in fractional tile coordinates. + box b; + b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1; b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1; + b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1; b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1; + b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1; b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1; + b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1; b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1; - // Quick hack: use the entire non-rotated bounding box. + b.center.x = (ss_2 - x) * ss_1; + b.center.y = (ss_2 - y) * ss_1; + + return b; } diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 6cc6eb009..90ba77631 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -119,7 +119,6 @@ void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) { void Painter::finishClippingMask() { glColorMask(true, true, true, true); - glEnable(GL_STENCIL_TEST); glEnable(GL_DEPTH_TEST); glStencilMask(0x0); }