Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
util::tileCover optimization: three scans and no duplicates handling.
Browse files Browse the repository at this point in the history
TileCoverPitchedViewport modified to have pitch capped by targe top inset, returning 28098 tiles in zoom level 8.

TileCover.PitchOverAllowedByContentInsets test verifies pitch capping by large top inset. Expectation was calculated using previous tileCover algorithm implementation.

Related to: #15163
  • Loading branch information
astojilj committed Jul 23, 2019
1 parent 89f93a5 commit 9100032
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 36 deletions.
3 changes: 2 additions & 1 deletion benchmark/util/tilecover.benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ static void TileCoverPitchedViewport(benchmark::State& state) {
Transform transform;
transform.resize({ 512, 512 });
// slightly offset center so that tile order is better defined
transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }).withZoom(8.0).withBearing(5.0).withPitch(40.0));
transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }).withPadding(EdgeInsets { 376, 0, 0, 0 })
.withZoom(8.0).withBearing(5.0).withPitch(60.0));

std::size_t length = 0;
while (state.KeepRunning()) {
Expand Down
7 changes: 4 additions & 3 deletions src/mbgl/map/transform_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
const double cameraToCenterDistance = getCameraToCenterDistance();
auto offset = getCenterOffset();

// Find the distance from the viewport center point
// [width/2 + offset.x, height/2 + offset.y] to the top edge, to point
// [width/2 + offset.x, 0] in Z units, using the law of sines.
// Find the Z distance from the viewport center point
// [width/2 + offset.x, height/2 + offset.y] to the top edge; to point
// [width/2 + offset.x, 0] in Z units.
// 1 Z unit is equivalent to 1 horizontal px at the center of the map
// (the distance between[width/2, height/2] and [width/2 + 1, height/2])
// See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details.
// See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)).
const double tanFovAboveCenter = (size.height * 0.5 + offset.y) / (size.height * 1.5);
const double tanMultiple = tanFovAboveCenter * std::tan(getPitch());
Expand Down
95 changes: 63 additions & 32 deletions src/mbgl/util/tile_cover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,6 @@ static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine sca
}
}

// scan-line conversion
static void scanTriangle(const Point<double>& a, const Point<double>& b, const Point<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);
}

} // namespace

namespace util {
Expand All @@ -85,7 +69,7 @@ std::vector<UnwrappedTileID> tileCover(const Point<double>& tl,
const Point<double>& bl,
const Point<double>& c,
int32_t z) {
const int32_t tiles = 1 << z;
const int32_t tiles = (1 << z) + 1;

struct ID {
int32_t x, y;
Expand All @@ -96,30 +80,77 @@ std::vector<UnwrappedTileID> tileCover(const Point<double>& tl,

auto scanLine = [&](int32_t x0, int32_t x1, int32_t y) {
int32_t x;
if (y >= 0 && y <= tiles) {
for (x = x0; x < x1; ++x) {
const auto dx = x + 0.5 - c.x, dy = y + 0.5 - c.y;
t.emplace_back(ID{ x, y, dx * dx + dy * dy });
}
for (x = x0; x < x1; ++x) {
const auto dx = x + 0.5 - c.x, dy = y + 0.5 - c.y;
t.emplace_back(ID { x, y, dx * dx + dy * dy });
}
};

// Divide the screen up in two triangles and scan each of them:
// \---+
// | \ |
// +---\.
scanTriangle(tl, tr, br, 0, tiles, scanLine);
scanTriangle(br, bl, tl, 0, tiles, scanLine);
std::vector<Point<double>> bounds = {tl, tr, br, bl};
while (bounds[0].y > min(min(bounds[1].y, bounds[2].y), bounds[3].y)) {
std::rotate(bounds.begin(), bounds.begin() + 1, bounds.end());
}
// Keeping the clockwise winding order (abcd), we rotated convex quadrilateral
// angles in such way that angle a (bounds[0]) is on top):
// a
// / \
// / b
// / |
// / c
// / .... // there is edge between c and d :)
// / ..
// d
// This is an example: we also handle also cases where d.y < c.y, d.y < b.y etc.
// Split this to tree scans:
// a
// / \
// / b
// -----------------
// / |
// / c
// -----------------
// / .... // there is edge between c and d :)
// / ..
// d
// polygon abcd (bounds[0..3]) cannot be concave. Point a is one on the top
// (lowest y value) after previous rotation.
edge ab = edge(bounds[0], bounds[1]);
edge ad = edge(bounds[0], bounds[3]);

int32_t ymin = std::floor(bounds[0].y);
if (bounds[3].y < bounds[1].y) { std::swap(ab, ad); }
int32_t ymax = std::ceil(ab.y1);
if (ab.dy) {
scanSpans(ad, ab, std::max(0, ymin), std::min(tiles, ymax), scanLine);
ymin = ymax;
}

float yCutLower = min(bounds[2].y, ad.y1);
ymax = std::ceil(yCutLower);

// bc is edge opposite of ad
edge bc = bounds[3].y < bounds[1].y ? edge(bounds[3], bounds[2]) : edge(bounds[1], bounds[2]);
if (bc.dy) {
scanSpans(ad, bc, std::max(0, ymin), std::min(tiles, ymax), scanLine);
ymin = ymax;
} else {
ymin = std::floor(yCutLower);
}

// The triangle at the bottom
if (ad.y1 < bc.y1) { std::swap(ad, bc); }
ymax = std::ceil(ad.y1);
bc = edge({ bc.x1, bc.y1 }, { ad.x1, ad.y1 });
if (bc.dy) { scanSpans(ad, bc, std::max(0, ymin), std::min(tiles, ymax), scanLine); }

// Sort first by distance, then by x/y.
std::sort(t.begin(), t.end(), [](const ID& a, const ID& b) {
return std::tie(a.sqDist, a.x, a.y) < std::tie(b.sqDist, b.x, b.y);
});

// Erase duplicate tile IDs (they typically occur at the common side of both triangles).
t.erase(std::unique(t.begin(), t.end(), [](const ID& a, const ID& b) {
return a.x == b.x && a.y == b.y;
}), t.end());
assert(t.end() == std::unique(t.begin(), t.end(), [](const ID& a, const ID& b) {
return a.x == b.x && a.y == b.y;
})); // no duplicates.

std::vector<UnwrappedTileID> result;
for (const auto& id : t) {
Expand Down
17 changes: 17 additions & 0 deletions test/util/tile_cover.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ TEST(TileCover, Pitch) {
util::tileCover(transform.getState(), 2));
}

TEST(TileCover, PitchOverAllowedByContentInsets) {
Transform transform;
transform.resize({ 512, 512 });

transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }).withPadding(EdgeInsets { 376, 0, 0, 0 })
.withZoom(8.0).withBearing(5.0).withPitch(60.0));
// Top padding of 376 leads to capped pitch. See Transform::getMaxPitchForEdgeInsets.
EXPECT_LE(transform.getPitch() + 0.001, util::DEG2RAD * 60);

EXPECT_EQ((std::vector<UnwrappedTileID>{
{ 0, { 2, 2, 1 } }, { 0, { 2, 1, 1 } }, { 0, { 2, 2, 2 } }, { 0, { 2, 1, 2 } }, { 0, { 2, 2, 0 } },
{ 0, { 2, 1, 0 } }, { 0, { 2, 3, 1 } }, { 0, { 2, 0, 1 } }, { 0, { 2, 3, 0 } }, { 0, { 2, 0, 0 } },
{ 1, { 2, 0, 1 } }, { 1, { 2, 0, 0 } }, {-1, { 2, 3, 0 } }, { 1, { 2, 1, 0 } }, { 1, { 2, 2, 0 } }
}),
util::tileCover(transform.getState(), 2));
}

TEST(TileCover, WorldZ1) {
EXPECT_EQ((std::vector<UnwrappedTileID>{
{ 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 },
Expand Down

0 comments on commit 9100032

Please sign in to comment.