diff --git a/benchmark/util/tilecover.benchmark.cpp b/benchmark/util/tilecover.benchmark.cpp index e966875a655..cd9da548557 100644 --- a/benchmark/util/tilecover.benchmark.cpp +++ b/benchmark/util/tilecover.benchmark.cpp @@ -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()) { diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 9d082d150ca..77309a2a55b 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -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()); diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 5189b79f267..6b57a854d6f 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -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& a, const Point& b, const Point& 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 { @@ -85,7 +69,7 @@ std::vector tileCover(const Point& tl, const Point& bl, const Point& c, int32_t z) { - const int32_t tiles = 1 << z; + const int32_t tiles = (1 << z) + 1; struct ID { int32_t x, y; @@ -96,30 +80,77 @@ std::vector tileCover(const Point& 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> 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 result; for (const auto& id : t) { diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index 3fc7681520d..06cb1457c5f 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -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{ + { 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{ { 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 },