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
TileCover: Replaced 4 scanSpans by 3. As the split lines (triangle, trapezoid, triangle) is horizontal, there is no need to handle duplicates.

Benchmarks (release build) on MacBookPro11,2 (Mid 2014) with Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz compared against src/mbgl/util/tile_cover.cpp from master and from the patch:

```
                            master  |   patch
                          ---------------------
TileCoverPitchedViewport    72000ns |  50300ns
TileCoverBounds              1620ns |   1400ns

```

TileCoverPitchedViewport modified to have pitch capped by targe top inset, returning 1124 tiles at 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 24, 2019
1 parent 89f93a5 commit 89fc482
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 44 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
6 changes: 4 additions & 2 deletions src/mbgl/map/transform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,11 @@ double Transform::getMaxPitchForEdgeInsets(const EdgeInsets &insets) const
double centerOffsetY = 0.5 * (insets.top() - insets.bottom()); // See TransformState::getCenterOffset.

const auto height = state.size.height;
assert(height);
// See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)).
const double fovAboveCenter = std::atan((height * 0.5 + centerOffsetY) / (height * 1.5));
return M_PI * 0.5 - fovAboveCenter - 0.001; // 0.001 prevents parallel ground to viewport clipping plane.
const double fovAboveCenter = std::atan((0.666666 + 0.02) * (0.5 + centerOffsetY / height));
return M_PI * 0.5 - fovAboveCenter; // 0.02 added ^^^^ to prevent parallel ground to viewport clipping plane.
// e.g. Maximum pitch of 60 degrees is when perspective center's offset from the top is 84% of screen height.
}

} // namespace mbgl
1 change: 0 additions & 1 deletion src/mbgl/map/transform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ class Transform : private util::noncopyable {
const Duration&);

// We don't want to show horizon: limit max pitch based on edge insets.
// e.g. for 60 deg pitch, top - bottom insets should be less than 0.732 of screen height.
double getMaxPitchForEdgeInsets(const EdgeInsets &insets) const;

TimePoint transitionStart;
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
108 changes: 71 additions & 37 deletions src/mbgl/util/tile_cover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine sca
double y1 = ::fmin(ymax, std::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)) {
double m0 = e0.dx / e0.dy;
double m1 = e1.dx / e1.dy;
double ySort = e0.y0 == e1.y0 ? ymax : std::max(e0.y0, e1.y0);
if (e0.x0 - (e0.y0 - ySort) * m0 < e1.x0 - (e1.y0 - ySort) * m1) {
std::swap(e0, e1);
std::swap(m0, m1);
}

// scan lines!
double m0 = e0.dx / e0.dy;
double m1 = e1.dx / e1.dy;
double d0 = e0.dx > 0; // use y + 1 to compute x0
double d1 = e1.dx < 0; // use y + 1 to compute x1
for (int32_t y = y0; y < y1; y++) {
Expand All @@ -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,80 @@ 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
/ ....
/ ..
d
This is an example: we handle also cases where d.y < c.y, d.y < b.y etc.
Split the scan to tree steps:
a
/ \ (1)
/ b
-----------------
/ | (2)
/ c
-----------------
/ ....
/ .. (3)
d
*/
edge ab = edge(bounds[0], bounds[1]);
edge ad = edge(bounds[0], bounds[3]);

// Scan (1).
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;
}

// Scan (2).
// yCutLower is c or d, whichever is with lower y value.
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);
}

// Scan (3) - 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
3 changes: 3 additions & 0 deletions test/map/transform.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ using namespace mbgl;

TEST(Transform, InvalidZoom) {
Transform transform;
transform.resize({1, 1});

ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude());
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude());
Expand Down Expand Up @@ -56,6 +57,7 @@ TEST(Transform, InvalidZoom) {

TEST(Transform, InvalidBearing) {
Transform transform;
transform.resize({1, 1});

ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude());
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude());
Expand All @@ -78,6 +80,7 @@ TEST(Transform, InvalidBearing) {

TEST(Transform, IntegerZoom) {
Transform transform;
transform.resize({1, 1});

auto checkIntegerZoom = [&transform](uint8_t zoomInt, double zoom) {
transform.jumpTo(CameraOptions().withZoom(zoom));
Expand Down
35 changes: 35 additions & 0 deletions test/util/tile_cover.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,41 @@ 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(45.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>{
{ 3, 4, 3 }, { 3, 3, 3 }, { 3, 4, 4 }, { 3, 3, 4 }, { 3, 4, 2 }, { 3, 5, 3 }, { 3, 5, 2 }
}),
util::tileCover(transform.getState(), 3));
}

TEST(TileCover, PitchWithLargerResultSet) {
Transform transform;
transform.resize({ 1024, 768 });

// The values used here triggered the regression with left and right edge
// selection in tile_cover.cpp scanSpans.
transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }).withPadding(EdgeInsets { 400, 0, 0, 0 })
.withZoom(5).withBearing(-142.2630000003529176).withPitch(60.0));

auto cover = util::tileCover(transform.getState(), 5);
// Returned vector has above 100 elements, we check first 16 as there is a
// plan to return lower LOD for distant tiles.
EXPECT_EQ((std::vector<UnwrappedTileID> {
{ 5, 15, 16 }, { 5, 15, 17 }, { 5, 14, 16 }, { 5, 14, 17 },
{ 5, 16, 16 }, { 5, 16, 17 }, { 5, 15, 15 }, { 5, 14, 15 },
{ 5, 15, 18 }, { 5, 14, 18 }, { 5, 16, 15 }, { 5, 13, 16 },
{ 5, 13, 17 }, { 5, 16, 18 }, { 5, 13, 18 }, { 5, 15, 19 }
}), (std::vector<UnwrappedTileID> { cover.begin(), cover.begin() + 16}) );
}

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 89fc482

Please sign in to comment.