Port scanline algorithm from _getCoveringTiles()

fixes #22
This commit is contained in:
Konstantin Käfer
2014-03-04 14:50:17 +01:00
parent ed5e335da1
commit ba5070bf52
7 changed files with 133 additions and 32 deletions

View File

@@ -0,0 +1,13 @@
#ifndef LLMR_MAP_COVERAGE
#define LLMR_MAP_COVERAGE
#include <llmr/util/vec.hpp>
#include <forward_list>
namespace llmr {
std::forward_list<vec3<int32_t>> covering_tiles(int32_t zoom, const box& points);
}
#endif

View File

@@ -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);

View File

@@ -55,6 +55,7 @@ struct vec4 {
struct box {
vec2<double> tl, tr, bl, br;
vec2<double> center;
};
}

98
src/map/coverage.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include <llmr/map/coverage.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::vec3<int32_t>> llmr::covering_tiles(int32_t zoom, const box& points) {
int32_t dim = pow(2, zoom);
std::forward_list<vec3<int32_t>> 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<double>& center = points.center;
tiles.sort([&center](const vec3<int32_t>& a, const vec3<int32_t>& 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;
}

View File

@@ -3,6 +3,7 @@
#include <llmr/platform/platform.hpp>
#include <llmr/style/resources.hpp>
#include <llmr/style/sprite.hpp>
#include <llmr/map/coverage.hpp>
#include <algorithm>
@@ -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<int32_t> 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<Tile::ID> 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<Tile::ID> 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

View File

@@ -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;
}

View File

@@ -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);
}