diff --git a/benchmark/util/tilecover.benchmark.cpp b/benchmark/util/tilecover.benchmark.cpp index c541323ed..524fe3a38 100644 --- a/benchmark/util/tilecover.benchmark.cpp +++ b/benchmark/util/tilecover.benchmark.cpp @@ -25,8 +25,9 @@ static void TileCoverPitchedViewport(benchmark::State& state) { transform.jumpTo(CameraOptions().withCenter(LatLng{0.1, -0.1}).withZoom(8.0).withBearing(5.0).withPitch(40.0)); std::size_t length = 0; + const Range zoomRange(0, 14); while (state.KeepRunning()) { - auto tiles = util::tileCover({transform.getState()}, 8); + auto tiles = util::tileCover({transform.getState()}, 8, zoomRange); length += tiles.size(); } benchmark::DoNotOptimize(length); diff --git a/docs/mdbook/src/platforms/android/android-tests.md b/docs/mdbook/src/platforms/android/android-tests.md index 8f497a393..23d94c6b6 100644 --- a/docs/mdbook/src/platforms/android/android-tests.md +++ b/docs/mdbook/src/platforms/android/android-tests.md @@ -69,6 +69,17 @@ Now download the `actual.png` in `metrics/integration/render-tests/background-co Of we don't want to commit this change. But know you can add and debug (Android) render tests. +### Adding Alternate Expected Images + +You can add alternate expected images by coping the `actual.png` to a file that starts with `expected`. You can use the following command: + +``` +render_test_name=tile-lod/zoom-shift +adb exec-out run-as org.maplibre.render_test_runner cat files/metrics/integration/render-tests/$render_test_name/actual.png > metrics/integration/render-tests/$render_test_name/expected-android.png +``` + +Which will pull `tile-lod/zoom-shift/actual.png` and save it as `tile-lod/zoom-shift/expected-android.png`. + ## Instrumentation Tests To run the instrumentation tests, choose the "Instrumentation Tests" run configuration. diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index a614b7bd7..464b36f25 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -186,6 +186,14 @@ public: /// A negative values typically improves performance but reduces quality. /// For instance, a value of -1 reduces the zoom level by 1 and this /// reduces the number of tiles by a factor of 4 for the same camera view. + /// - `TileLodMode` selects the tile cover algorithm. The possible algorithms + /// are "default" and "distance". When "distance" is selected, the tile LOD is + /// based on the distance from camera to tile. This algorithm causes tiles + /// closer to the camera to be rendered at higher LOD, in contrast with the + /// default algorithm, which renders the highest LOD at the center of the + /// screen. The distance based algorithm observes `TileLodScale` and + /// `TileLodPitchThreshold` and ignores `TileLodMinRadius` and + /// `TileLodZoomShift`. void setTileLodMinRadius(double radius); double getTileLodMinRadius() const; void setTileLodScale(double scale); @@ -194,6 +202,8 @@ public: double getTileLodPitchThreshold() const; void setTileLodZoomShift(double shift); double getTileLodZoomShift() const; + void setTileLodMode(TileLodMode mode); + TileLodMode getTileLodMode() const; ClientOptions getClientOptions() const; diff --git a/include/mbgl/map/mode.hpp b/include/mbgl/map/mode.hpp index 7e21fab72..3804f5117 100644 --- a/include/mbgl/map/mode.hpp +++ b/include/mbgl/map/mode.hpp @@ -35,6 +35,11 @@ enum class ViewportMode : EnumType { FlippedY, }; +enum class TileLodMode : uint8_t { + Default, ///< Default TileLOD algorithm + Distance ///< Distance-based TileLOD algorithm +}; + enum class MapDebugOptions : EnumType { NoDebug = 0, TileBorders = 1 << 1, diff --git a/metrics/cache-style.db b/metrics/cache-style.db index 11658f275..daad7c6a6 100644 Binary files a/metrics/cache-style.db and b/metrics/cache-style.db differ diff --git a/metrics/integration/render-tests/tile-lod/default/expected-android.png b/metrics/integration/render-tests/tile-lod/default/expected-android.png new file mode 100644 index 000000000..5c0a0eede Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/default/expected-android.png differ diff --git a/metrics/integration/render-tests/tile-lod/default/expected.png b/metrics/integration/render-tests/tile-lod/default/expected.png new file mode 100644 index 000000000..4f457764d Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/default/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/default/style.json b/metrics/integration/render-tests/tile-lod/default/style.json new file mode 100644 index 000000000..8bf16ccb2 --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/default/style.json @@ -0,0 +1,38 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 72, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/distance-based-pitch-threshold/expected.png b/metrics/integration/render-tests/tile-lod/distance-based-pitch-threshold/expected.png new file mode 100644 index 000000000..fc9d7d70b Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/distance-based-pitch-threshold/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/distance-based-pitch-threshold/style.json b/metrics/integration/render-tests/tile-lod/distance-based-pitch-threshold/style.json new file mode 100644 index 000000000..106dcd509 --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/distance-based-pitch-threshold/style.json @@ -0,0 +1,40 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodMode", "distance"], + ["setTileLodPitchThreshold", 62], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 61, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/distance-based-scale/expected-android.png b/metrics/integration/render-tests/tile-lod/distance-based-scale/expected-android.png new file mode 100644 index 000000000..1e48d5e6e Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/distance-based-scale/expected-android.png differ diff --git a/metrics/integration/render-tests/tile-lod/distance-based-scale/expected.png b/metrics/integration/render-tests/tile-lod/distance-based-scale/expected.png new file mode 100644 index 000000000..a1acb50a1 Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/distance-based-scale/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/distance-based-scale/style.json b/metrics/integration/render-tests/tile-lod/distance-based-scale/style.json new file mode 100644 index 000000000..2170607ff --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/distance-based-scale/style.json @@ -0,0 +1,40 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodMode", "distance"], + ["setTileLodScale", 3], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 72, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/distance-based/expected-android.png b/metrics/integration/render-tests/tile-lod/distance-based/expected-android.png new file mode 100644 index 000000000..a0a8cfebc Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/distance-based/expected-android.png differ diff --git a/metrics/integration/render-tests/tile-lod/distance-based/expected.png b/metrics/integration/render-tests/tile-lod/distance-based/expected.png new file mode 100644 index 000000000..cd5108814 Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/distance-based/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/distance-based/style.json b/metrics/integration/render-tests/tile-lod/distance-based/style.json new file mode 100644 index 000000000..12d5f6547 --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/distance-based/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodMode", "distance"], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 72, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/min-radius/expected-android.png b/metrics/integration/render-tests/tile-lod/min-radius/expected-android.png new file mode 100644 index 000000000..abb15313c Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/min-radius/expected-android.png differ diff --git a/metrics/integration/render-tests/tile-lod/min-radius/expected.png b/metrics/integration/render-tests/tile-lod/min-radius/expected.png new file mode 100644 index 000000000..763397e07 Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/min-radius/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/min-radius/style.json b/metrics/integration/render-tests/tile-lod/min-radius/style.json new file mode 100644 index 000000000..f4268525a --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/min-radius/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodMinRadius", 1], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 72, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/pitch-threshold/expected.png b/metrics/integration/render-tests/tile-lod/pitch-threshold/expected.png new file mode 100644 index 000000000..fc9d7d70b Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/pitch-threshold/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/pitch-threshold/style.json b/metrics/integration/render-tests/tile-lod/pitch-threshold/style.json new file mode 100644 index 000000000..abcdab1f2 --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/pitch-threshold/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodPitchThreshold", 62], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 61, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/scale/expected-android.png b/metrics/integration/render-tests/tile-lod/scale/expected-android.png new file mode 100644 index 000000000..4a77d8a9a Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/scale/expected-android.png differ diff --git a/metrics/integration/render-tests/tile-lod/scale/expected.png b/metrics/integration/render-tests/tile-lod/scale/expected.png new file mode 100644 index 000000000..d0602da28 Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/scale/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/scale/style.json b/metrics/integration/render-tests/tile-lod/scale/style.json new file mode 100644 index 000000000..49e70e84d --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/scale/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodScale", 3], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 72, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/tile-lod/zoom-shift/expected-android.png b/metrics/integration/render-tests/tile-lod/zoom-shift/expected-android.png new file mode 100644 index 000000000..981c1ad25 Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/zoom-shift/expected-android.png differ diff --git a/metrics/integration/render-tests/tile-lod/zoom-shift/expected.png b/metrics/integration/render-tests/tile-lod/zoom-shift/expected.png new file mode 100644 index 000000000..6a1554f83 Binary files /dev/null and b/metrics/integration/render-tests/tile-lod/zoom-shift/expected.png differ diff --git a/metrics/integration/render-tests/tile-lod/zoom-shift/style.json b/metrics/integration/render-tests/tile-lod/zoom-shift/style.json new file mode 100644 index 000000000..add120f78 --- /dev/null +++ b/metrics/integration/render-tests/tile-lod/zoom-shift/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "maxPitch": 85, + "operations": [ + ["setTileLodZoomShift", 2], + ["wait"] + ] + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "bearing": 30, + "pitch": 72, + "zoom": 16, + "sources": { + "satellite": { + "type": "raster", + "tiles": [ + "https://demotiles.maplibre.org/debug-tiles/number/{z}.png" + ], + "maxzoom": 22, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "raster", + "type": "raster", + "source": "satellite", + "paint": { + "raster-fade-duration": 0 + } + } + ] +} diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index 6cc585296..e7880722d 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -100,60 +100,71 @@ public: namespace { -enum class TileLodMode { - Default, // Default Tile LOD parameters - NoLod, // Disable LOD - Reduced, // Reduce LOD away from camera - Aggressive, // Aggressively reduce LOD away from camera at the detriment of quality +enum class TileLodProfile { + Default, // Default Tile LOD parameters + NoLod, // Disable LOD + Reduced, // Reduce LOD away from camera + Aggressive, // Aggressively reduce LOD away from camera at the detriment of quality + DistanceBased, // Use distance-based algorithm }; -constexpr TileLodMode nextTileLodMode(TileLodMode current) { +constexpr TileLodProfile nextTileLodProfile(TileLodProfile current) { switch (current) { - case TileLodMode::Default: - return TileLodMode::NoLod; - case TileLodMode::NoLod: - return TileLodMode::Reduced; - case TileLodMode::Reduced: - return TileLodMode::Aggressive; - case TileLodMode::Aggressive: - return TileLodMode::Default; + case TileLodProfile::Default: + return TileLodProfile::NoLod; + case TileLodProfile::NoLod: + return TileLodProfile::Reduced; + case TileLodProfile::Reduced: + return TileLodProfile::Aggressive; + case TileLodProfile::Aggressive: + return TileLodProfile::DistanceBased; + case TileLodProfile::DistanceBased: + return TileLodProfile::Default; default: - return TileLodMode::Default; + return TileLodProfile::Default; } } -void cycleTileLodMode(mbgl::Map &map) { - // TileLodMode::Default parameters +void cycleTileLodProfile(mbgl::Map &map) { + // TileLodProfile::Default parameters static const auto defaultRadius = map.getTileLodMinRadius(); static const auto defaultScale = map.getTileLodScale(); static const auto defaultTilePitchThreshold = map.getTileLodPitchThreshold(); + static const auto defaultTileLodMode = map.getTileLodMode(); - static TileLodMode mode = TileLodMode::Default; - mode = nextTileLodMode(mode); + static TileLodProfile profile = TileLodProfile::Default; + profile = nextTileLodProfile(profile); - switch (mode) { - case TileLodMode::Default: + switch (profile) { + case TileLodProfile::Default: map.setTileLodMinRadius(defaultRadius); map.setTileLodScale(defaultScale); map.setTileLodPitchThreshold(defaultTilePitchThreshold); - mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: default"); + map.setTileLodMode(defaultTileLodMode); + mbgl::Log::Info(mbgl::Event::General, "Tile LOD profile: default"); break; - case TileLodMode::NoLod: + case TileLodProfile::NoLod: // When LOD is off we set a maximum PitchThreshold map.setTileLodPitchThreshold(std::numbers::pi); - mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: disabled"); + mbgl::Log::Info(mbgl::Event::General, "Tile LOD profile: disabled"); break; - case TileLodMode::Reduced: + case TileLodProfile::Reduced: map.setTileLodMinRadius(2); map.setTileLodScale(1.5); map.setTileLodPitchThreshold(std::numbers::pi / 4); - mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: reduced"); + mbgl::Log::Info(mbgl::Event::General, "Tile LOD profile: reduced"); break; - case TileLodMode::Aggressive: + case TileLodProfile::Aggressive: map.setTileLodMinRadius(1); map.setTileLodScale(2); map.setTileLodPitchThreshold(0); - mbgl::Log::Info(mbgl::Event::General, "Tile LOD mode: aggressive"); + mbgl::Log::Info(mbgl::Event::General, "Tile LOD profile: aggressive"); + break; + case TileLodProfile::DistanceBased: + map.setTileLodScale(1); + map.setTileLodPitchThreshold(0); + map.setTileLodMode(mbgl::TileLodMode::Distance); + mbgl::Log::Info(mbgl::Event::General, "Tile LOD profile: distance-based"); break; } map.triggerRepaint(); @@ -651,7 +662,7 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, view->invalidate(); } break; case GLFW_KEY_F6: { - cycleTileLodMode(*view->map); + cycleTileLodProfile(*view->map); } break; case GLFW_KEY_F7: { tileLodZoomShift(*view->map, false); diff --git a/render-test/parser.cpp b/render-test/parser.cpp index ca07021d8..a5788e3d9 100644 --- a/render-test/parser.cpp +++ b/render-test/parser.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -612,6 +613,10 @@ TestMetadata parseTestMetadata(const TestPaths& paths) { return metadata; } +namespace mbgl { +MBGL_DEFINE_ENUM(TileLodMode, {{TileLodMode::Default, "default"}, {TileLodMode::Distance, "distance"}}); +} + namespace TestOperationNames { const std::string waitOp("wait"); const std::string sleepOp("sleep"); @@ -648,6 +653,11 @@ const std::string panGestureOp("panGesture"); const std::string gfxProbeOp("probeGFX"); const std::string gfxProbeStartOp("probeGFXStart"); const std::string gfxProbeEndOp("probeGFXEnd"); +const std::string setTileLodMinRadiusOp("setTileLodMinRadius"); +const std::string setTileLodScaleOp("setTileLodScale"); +const std::string setTileLodPitchThresholdOp("setTileLodPitchThreshold"); +const std::string setTileLodZoomShiftOp("setTileLodZoomShift"); +const std::string setTileLodModeOp("setTileLodMode"); } // namespace TestOperationNames using namespace TestOperationNames; @@ -1321,6 +1331,53 @@ TestOperations parseTestOperations(TestMetadata& metadata) { ctx.getMetadata().metrics.gfx.insert({mark, metricProbe}); return true; }); + + } else if (operationArray[0].GetString() == setTileLodMinRadiusOp) { + // setTileLodMinRadius + assert(operationArray.Size() == 2u); + assert(operationArray[1].IsNumber()); + double minRadius = operationArray[1].GetDouble(); + result.emplace_back([minRadius](TestContext& ctx) { + ctx.getMap().setTileLodMinRadius(minRadius); + return true; + }); + + } else if (operationArray[0].GetString() == setTileLodScaleOp) { + // setTileLodScale + assert(operationArray.Size() == 2u); + assert(operationArray[1].IsNumber()); + double scale = operationArray[1].GetDouble(); + result.emplace_back([scale](TestContext& ctx) { + ctx.getMap().setTileLodScale(scale); + return true; + }); + } else if (operationArray[0].GetString() == setTileLodPitchThresholdOp) { + // setTileLodPitchThreshold + assert(operationArray.Size() == 2u); + assert(operationArray[1].IsNumber()); + double pitchThreshold = operationArray[1].GetDouble(); + result.emplace_back([pitchThreshold](TestContext& ctx) { + ctx.getMap().setTileLodPitchThreshold(pitchThreshold); + return true; + }); + } else if (operationArray[0].GetString() == setTileLodZoomShiftOp) { + // setTileLodZoomShift + assert(operationArray.Size() == 2u); + assert(operationArray[1].IsNumber()); + double zoomShift = operationArray[1].GetDouble(); + result.emplace_back([zoomShift](TestContext& ctx) { + ctx.getMap().setTileLodZoomShift(zoomShift); + return true; + }); + } else if (operationArray[0].GetString() == setTileLodModeOp) { + // setTileLodMode + assert(operationArray.Size() == 2u); + assert(operationArray[1].IsString()); + mbgl::TileLodMode mode = mbgl::Enum::toEnum(operationArray[1].GetString()).value(); + result.emplace_back([mode](TestContext& ctx) { + ctx.getMap().setTileLodMode(mode); + return true; + }); } else { metadata.errorMessage = std::string("Unsupported operation: ") + operationArray[0].GetString(); return {}; diff --git a/render-test/parser.hpp b/render-test/parser.hpp index 61a6ba847..ec55c2d53 100644 --- a/render-test/parser.hpp +++ b/render-test/parser.hpp @@ -67,4 +67,9 @@ extern const std::string panGestureOp; extern const std::string gfxProbeOp; extern const std::string gfxProbeStartOp; extern const std::string gfxProbeEndOp; +extern const std::string setTileLodMinRadiusOp; +extern const std::string setTileLodScaleOp; +extern const std::string setTileLodPitchThresholdOp; +extern const std::string setTileLodZoomShiftOp; +extern const std::string setTileLodModeOp; } // namespace TestOperationNames diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 52aad7977..dc2fd989a 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -588,6 +588,14 @@ double Map::getTileLodZoomShift() const { return impl->tileLodZoomShift; } +void Map::setTileLodMode(TileLodMode mode) { + impl->tileLodMode = mode; +} + +TileLodMode Map::getTileLodMode() const { + return impl->tileLodMode; +} + ClientOptions Map::getClientOptions() const { return impl->fileSource ? impl->fileSource->getClientOptions() : ClientOptions(); } diff --git a/src/mbgl/map/map_impl.cpp b/src/mbgl/map/map_impl.cpp index 52d7460cd..6bb533f10 100644 --- a/src/mbgl/map/map_impl.cpp +++ b/src/mbgl/map/map_impl.cpp @@ -135,7 +135,8 @@ void Map::Impl::onUpdate() { .tileLodMinRadius = tileLodMinRadius, .tileLodScale = tileLodScale, .tileLodPitchThreshold = tileLodPitchThreshold, - .tileLodZoomShift = tileLodZoomShift}; + .tileLodZoomShift = tileLodZoomShift, + .tileLodMode = tileLodMode}; rendererFrontend.update(std::make_shared(std::move(params))); } diff --git a/src/mbgl/map/map_impl.hpp b/src/mbgl/map/map_impl.hpp index 1207b510a..e231b3182 100644 --- a/src/mbgl/map/map_impl.hpp +++ b/src/mbgl/map/map_impl.hpp @@ -111,6 +111,7 @@ public: double tileLodScale = 1; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodZoomShift = 0; + TileLodMode tileLodMode = TileLodMode::Default; }; // Forward declaration of this method is required for the MapProjection class diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index 39dda3ab4..cbed3fd7e 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -120,11 +120,14 @@ void RenderBackgroundLayer::update(gfx::ShaderRegistry& shaders, [[maybe_unused]] UniqueChangeRequestVec& changes) { assert(updateParameters); const auto zoom = state.getIntegerZoom(); + Range zoomRange(0, zoom); const auto tileCover = util::tileCover({.transformState = state, .tileLodMinRadius = updateParameters->tileLodMinRadius, .tileLodScale = updateParameters->tileLodScale, - .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold}, - zoom); + .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold, + .tileLodMode = updateParameters->tileLodMode}, + zoom, + zoomRange); // renderTiles is always empty, we use tileCover instead if (tileCover.empty()) { diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index ecd082611..c5a6eb5bb 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -64,6 +64,7 @@ PaintParameters::PaintParameters(gfx::Context& context_, double tileLodMinRadius_, double tileLodScale_, double tileLodPitchThreshold_, + TileLodMode tileLodMode_, const gfx::ScissorRect& scissorRect_) : context(context_), backend(backend_), @@ -83,6 +84,7 @@ PaintParameters::PaintParameters(gfx::Context& context_, tileLodMinRadius(tileLodMinRadius_), tileLodScale(tileLodScale_), tileLodPitchThreshold(tileLodPitchThreshold_), + tileLodMode(tileLodMode_), scissorRect(scissorRect_) { pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}}; diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index a985ef557..abcac59d8 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -62,6 +62,7 @@ public: double tileLodMinRadius, double tileLodScale, double tileLodPitchThreshold, + TileLodMode tileLodMode, const gfx::ScissorRect&); ~PaintParameters(); @@ -139,6 +140,7 @@ public: double tileLodMinRadius; double tileLodScale; double tileLodPitchThreshold; + TileLodMode tileLodMode; gfx::ScissorRect scissorRect; }; diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp index c6a468658..3c3419bf8 100644 --- a/src/mbgl/renderer/render_orchestrator.cpp +++ b/src/mbgl/renderer/render_orchestrator.cpp @@ -200,6 +200,7 @@ std::unique_ptr RenderOrchestrator::createRenderTree( .tileLodScale = updateParameters->tileLodScale, .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold, .tileLodZoomShift = updateParameters->tileLodZoomShift, + .tileLodMode = updateParameters->tileLodMode, .dynamicTextureAtlas = dynamicTextureAtlas}; glyphManager->setURL(updateParameters->glyphURL); diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 814b6a29b..3d53cc8e4 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -215,6 +215,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr< updateParameters->tileLodMinRadius, updateParameters->tileLodScale, updateParameters->tileLodPitchThreshold, + updateParameters->tileLodMode, scissorRect, }; diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index 1f36d1e26..ef4c5f776 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -133,11 +133,14 @@ void RenderImageSource::update(Immutable baseImpl_, bool hasVisibleTile = false; // Add additional wrapped tile ids if neccessary + Range zoomRange(0, zoom); auto idealTiles = util::tileCover({.transformState = transformState, .tileLodMinRadius = parameters.tileLodMinRadius, .tileLodScale = parameters.tileLodScale, - .tileLodPitchThreshold = parameters.tileLodPitchThreshold}, - static_cast(transformState.getZoom())); + .tileLodPitchThreshold = parameters.tileLodPitchThreshold, + .tileLodMode = parameters.tileLodMode}, + static_cast(transformState.getZoom()), + zoomRange); for (auto tile : idealTiles) { if (tile.wrap != 0 && tileCover[0].canonical.isChildOf(tile.canonical)) { tileIds.emplace_back(tile.wrap, tileCover[0].canonical); diff --git a/src/mbgl/renderer/tile_parameters.hpp b/src/mbgl/renderer/tile_parameters.hpp index 4434076b4..f0c2b373c 100644 --- a/src/mbgl/renderer/tile_parameters.hpp +++ b/src/mbgl/renderer/tile_parameters.hpp @@ -37,6 +37,7 @@ public: double tileLodScale = 1; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodZoomShift = 0; + TileLodMode tileLodMode = TileLodMode::Default; gfx::DynamicTextureAtlasPtr dynamicTextureAtlas; bool isUpdateSynchronous = false; }; diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index a3c2e2726..6dfe81340 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -113,7 +113,8 @@ void TilePyramid::update(const std::vector>& l util::TileCoverParameters tileCoverParameters = {.transformState = parameters.transformState, .tileLodMinRadius = parameters.tileLodMinRadius, .tileLodScale = parameters.tileLodScale, - .tileLodPitchThreshold = parameters.tileLodPitchThreshold}; + .tileLodPitchThreshold = parameters.tileLodPitchThreshold, + .tileLodMode = parameters.tileLodMode}; if (std::cmp_greater_equal(overscaledZoom, zoomRange.min)) { int32_t idealZoom = std::min(zoomRange.max, overscaledZoom); @@ -135,11 +136,11 @@ void TilePyramid::update(const std::vector>& l } if (panZoom < idealZoom) { - panTiles = util::tileCover(tileCoverParameters, panZoom); + panTiles = util::tileCover(tileCoverParameters, panZoom, zoomRange); } } - idealTiles = util::tileCover(tileCoverParameters, idealZoom, tileZoom); + idealTiles = util::tileCover(tileCoverParameters, idealZoom, zoomRange, tileZoom); if (parameters.mode == MapMode::Tile && type != SourceType::Raster && type != SourceType::RasterDEM && idealTiles.size() > 1) { mbgl::Log::Warning(mbgl::Event::General, @@ -176,8 +177,10 @@ void TilePyramid::update(const std::vector>& l // levels. Child tiles are used from the cache, but not created. std::optional tileRange = std::nullopt; if (bounds) { - tileRange = util::TileRange::fromLatLngBounds( - *bounds, zoomRange.min, std::min(tileZoom, static_cast(zoomRange.max))); + int32_t maxZoom = (parameters.tileLodMode == TileLodMode::Distance) + ? zoomRange.max + : std::min(tileZoom, static_cast(zoomRange.max)); + tileRange = util::TileRange::fromLatLngBounds(*bounds, zoomRange.min, maxZoom); } auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* { if (tileRange && !tileRange->contains(tileID.canonical)) { diff --git a/src/mbgl/renderer/update_parameters.hpp b/src/mbgl/renderer/update_parameters.hpp index 0e25678ed..7e7520b4d 100644 --- a/src/mbgl/renderer/update_parameters.hpp +++ b/src/mbgl/renderer/update_parameters.hpp @@ -52,6 +52,7 @@ public: double tileLodScale = 1; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodZoomShift = 0; + TileLodMode tileLodMode = TileLodMode::Default; }; } // namespace mbgl diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 7b84fc60d..0a5edd5a7 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -157,6 +157,7 @@ int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) no std::vector tileCover(const TileCoverParameters& state, uint8_t z, + const Range zoomRange, const std::optional& overscaledZ) { struct Node { AABB aabb; @@ -171,12 +172,28 @@ std::vector tileCover(const TileCoverParameters& state, double sqrDist; }; + auto childrenOf = [](const Node& node) -> std::vector { + std::vector children(4); + for (int i = 0; i < 4; i++) { + const uint32_t childX = (node.x << 1) + (i % 2); + const uint32_t childY = (node.y << 1) + (i >> 1); + + children[i] = node; + children[i].aabb = node.aabb.quadrant(i); + children[i].zoom = node.zoom + 1; + children[i].x = childX; + children[i].y = childY; + } + return children; + }; + const auto& transform = state.transformState; const double numTiles = std::pow(2.0, z); const double worldSize = Projection::worldSize(transform.getScale()); - const uint8_t minZoom = transform.getPitch() <= state.tileLodPitchThreshold ? z : 0; - const uint8_t maxZoom = z; - const uint8_t overscaledZoom = std::max(overscaledZ.value_or(z), z); + const bool allowVariableZoom = transform.getPitch() > state.tileLodPitchThreshold; + const uint8_t minZoom = allowVariableZoom ? zoomRange.min : z; + const uint8_t maxZoom = ((state.tileLodMode == TileLodMode::Distance) && allowVariableZoom) ? zoomRange.max : z; + const uint8_t overscaledZoom = std::max(overscaledZ.value_or(z), maxZoom); const bool flippedY = transform.getViewportMode() == ViewportMode::FlippedY; const auto centerPoint = TileCoordinate::fromScreenCoordinate( @@ -185,6 +202,12 @@ std::vector tileCover(const TileCoverParameters& state, const vec3 centerCoord = {{centerPoint.x, centerPoint.y, 0.0}}; + assert(transform.getFreeCameraOptions().position); + const vec3 cameraPositionMercator = *transform.getFreeCameraOptions().position; + const double nominalScale = std::pow(2.0, z); + const vec3 cameraCoord = vec3Scale(cameraPositionMercator, nominalScale); + const double cameraToCenterDistanceMercator = vec3Length(vec3Sub(cameraCoord, centerCoord)) / worldSize; + const Frustum frustum = Frustum::fromInvProjMatrix(transform.getInvProjectionMatrix(), worldSize, z, flippedY); // There should always be a certain number of maximum zoom level tiles @@ -227,48 +250,51 @@ std::vector tileCover(const TileCoverParameters& state, node.fullyVisible = intersection == IntersectionResult::Contains; } - const vec3 distanceXyz = node.aabb.distanceXYZ(centerCoord); - const double* longestDim = std::max_element(distanceXyz.data(), distanceXyz.data() + distanceXyz.size()); - assert(longestDim); + bool shouldSplitTile; + if (state.tileLodMode == TileLodMode::Distance) { + const vec3 camToTileMercator = vec3Scale(node.aabb.distanceXYZ(cameraCoord), 1.0 / worldSize); + const double distanceToTileMercator = vec3Length(camToTileMercator); + const double cosPitchToTile = std::max(0.0, camToTileMercator[2] / distanceToTileMercator); + const double pitchExponent = + 0.5; // 0: constant screen width, 1/2: constant screen area, 1: constant screen height + double tileScale = std::pow(2.0, node.zoom); + shouldSplitTile = distanceToTileMercator * tileScale < std::pow(cosPitchToTile, pitchExponent) * + cameraToCenterDistanceMercator / + state.tileLodScale * nominalScale; + } else { + const vec3 distanceXyz = node.aabb.distanceXYZ(centerCoord); + const double* longestDim = std::max_element(distanceXyz.data(), distanceXyz.data() + distanceXyz.size()); + assert(longestDim); - // We're using distance based heuristics to determine if a tile should - // be split into quadrants or not. radiusOfMaxLvlLodInTiles defines that - // there's always a certain number of maxLevel tiles next to the map - // center. Using the fact that a parent node in quadtree is twice the - // size of its children (per dimension) we can define distance - // thresholds for each relative level: - // f(k) = offset + 2 + 4 + 8 + 16 + ... + 2^k - // This is the same as: - // f(k) = offset + 2^(k+1)-2 - const double distToSplit = radiusOfMaxLvlLodInTiles + (1 << (maxZoom - node.zoom)) - 2; + // We're using distance based heuristics to determine if a tile should + // be split into quadrants or not. radiusOfMaxLvlLodInTiles defines that + // there's always a certain number of maxLevel tiles next to the map + // center. Using the fact that a parent node in quadtree is twice the + // size of its children (per dimension) we can define distance + // thresholds for each relative level: + // f(k) = offset + 2 + 4 + 8 + 16 + ... + 2^k + // This is the same as: + // f(k) = offset + 2^(k+1)-2 + const double distToSplit = radiusOfMaxLvlLodInTiles + (1 << (maxZoom - node.zoom)) - 2; + shouldSplitTile = *longestDim * state.tileLodScale < distToSplit; + } // Have we reached the target depth or is the tile too far away to be any split further? - if (node.zoom == maxZoom || (*longestDim * state.tileLodScale > distToSplit && node.zoom >= minZoom)) { + if (node.zoom == maxZoom || (!shouldSplitTile && node.zoom >= minZoom)) { // Perform precise intersection test between the frustum and aabb. // This will cull < 1% false positives missed by the original test if (node.fullyVisible || frustum.intersectsPrecise(node.aabb, true) != IntersectionResult::Separate) { const OverscaledTileID id = { node.zoom == maxZoom ? overscaledZoom : node.zoom, node.wrap, node.zoom, node.x, node.y}; - const double dx = node.wrap * numTiles + node.x + 0.5 - centerCoord[0]; - const double dy = node.y + 0.5 - centerCoord[1]; + vec3 coordToLoadFirst = (state.tileLodMode == TileLodMode::Distance) ? cameraCoord : centerCoord; + const double dx = node.wrap * numTiles + node.x + 0.5 - coordToLoadFirst[0]; + const double dy = node.y + 0.5 - coordToLoadFirst[1]; result.push_back({id, dx * dx + dy * dy}); } - continue; - } - - for (int i = 0; i < 4; i++) { - const uint32_t childX = (node.x << 1) + (i % 2); - const uint32_t childY = (node.y << 1) + (i >> 1); - - // Create child node and push to the stack for traversal - stack.emplace_back(node); - Node& child = stack.back(); - - child.aabb = node.aabb.quadrant(i); - child.zoom = node.zoom + 1; - child.x = childX; - child.y = childY; + } else { + std::vector children = childrenOf(node); + stack.insert(stack.end(), children.begin(), children.end()); } } diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index a935b2d3a..2975df712 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -38,12 +38,14 @@ struct TileCoverParameters { double tileLodMinRadius = 3; double tileLodScale = 1; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; + TileLodMode tileLodMode = TileLodMode::Default; }; int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize) noexcept; std::vector tileCover(const TileCoverParameters& state, uint8_t z, + const Range zoomRange, const std::optional& overscaledZ = std::nullopt); std::vector tileCover(const LatLngBounds&, uint8_t z); std::vector tileCover(const Geometry&, uint8_t z); diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index bf6fda78f..c98dac448 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -10,6 +10,8 @@ using namespace mbgl; +const Range zoomRange(0, 14); + TEST(TileCover, Empty) { EXPECT_EQ((std::vector{}), util::tileCover(LatLngBounds::empty(), 0)); } @@ -44,7 +46,12 @@ TEST(TileCover, Pitch) { .withPitch(40.0)); EXPECT_EQ((std::vector{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}), - util::tileCover({transform.getState()}, 2)); + util::tileCover({transform.getState()}, 2, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{2, 1, 2}, {2, 2, 2}, {2, 1, 1}, {2, 2, 1}}), + util::tileCover(params, 2, zoomRange)); } TEST(TileCover, PitchIssue15442) { @@ -62,7 +69,47 @@ TEST(TileCover, PitchIssue15442) { EXPECT_EQ((std::vector{ {2, 3, 1}, {2, 2, 1}, {2, 3, 0}, {2, 2, 0}, {2, 1, {2, 0, 0}}, {2, 1, {2, 1, 0}}}), - util::tileCover({transform.getState()}, 2)); + util::tileCover({transform.getState()}, 2, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{ + {2, 2, 1}, {2, 3, 1}, {2, 2, 0}, {2, 3, 0}, {2, 1, {2, 0, 0}}, {2, 1, {2, 1, 0}}}), + util::tileCover(params, 2, zoomRange)); +} + +TEST(TileCover, HighPitch) { + 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(2.0) + .withBearing(5.0) + .withPitch(60.0)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodPitchThreshold = 0.0; + params.tileLodMinRadius = 1.0; + EXPECT_EQ( + (std::vector{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}, {2, 1, 0}, {2, 2, 0}, {2, 3, 0}}), + util::tileCover(params, 2, zoomRange)); + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{2, 1, 1}, + {2, 2, 1}, + {3, 3, 4}, + {3, 3, 5}, + {2, 1, 0}, + {2, 2, 0}, + {3, 4, 4}, + {2, 3, 0}, + {3, 4, 5}, + {3, 5, 4}}), + util::tileCover(params, 2, zoomRange)); } TEST(TileCover, PitchOverAllowedByContentInsets) { @@ -79,7 +126,12 @@ TEST(TileCover, PitchOverAllowedByContentInsets) { EXPECT_LE(transform.getPitch() + 0.001, util::deg2rad(60)); EXPECT_EQ((std::vector{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}}), - util::tileCover({transform.getState()}, 3)); + util::tileCover({transform.getState()}, 3, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{3, 3, 4}, {3, 3, 3}, {3, 4, 4}, {3, 4, 3}}), + util::tileCover(params, 3, zoomRange)); } TEST(TileCover, PitchWithLargerResultSet) { @@ -95,7 +147,7 @@ TEST(TileCover, PitchWithLargerResultSet) { .withBearing(-142.2630000003529176) .withPitch(60.0)); - auto cover = util::tileCover({.transformState = transform.getState()}, 5); + auto cover = util::tileCover({.transformState = transform.getState()}, 5, zoomRange); // 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{{5, 15, 16}, @@ -147,7 +199,13 @@ TEST(TileCover, CoordinatesAreUnwrapped) { EXPECT_EQ( (std::vector{{1, 0, {1, 1, 0}}, {1, 1, {1, 0, 0}}, {1, 0, {1, 1, 1}}, {1, 1, {1, 0, 1}}}), - util::tileCover({transform.getState()}, 1)); + util::tileCover({transform.getState()}, 1, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ( + (std::vector{{1, 0, {1, 1, 0}}, {1, 1, {1, 0, 0}}, {1, 0, {1, 1, 1}}, {1, 1, {1, 0, 1}}}), + util::tileCover(params, 1, zoomRange)); } TEST(TileCover, DifferentOverscaledZ) { @@ -164,7 +222,13 @@ TEST(TileCover, DifferentOverscaledZ) { EXPECT_EQ( (std::vector{{3, 0, {2, 1, 1}}, {3, 0, {2, 2, 1}}, {3, 0, {2, 1, 2}}, {3, 0, {2, 2, 2}}}), - util::tileCover({transform.getState()}, 2, 3)); + util::tileCover({transform.getState()}, 2, zoomRange, 3)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ( + (std::vector{{3, 0, {2, 1, 1}}, {3, 0, {2, 2, 1}}, {3, 0, {2, 1, 2}}, {3, 0, {2, 2, 2}}}), + util::tileCover(params, 2, zoomRange, 3)); } TEST(TileCover, DifferentOverscaledZWithPitch) { @@ -176,7 +240,7 @@ TEST(TileCover, DifferentOverscaledZWithPitch) { -10.0, }) .withZoom(3.5) - .withPitch(60)); + .withPitch(61)); EXPECT_EQ((std::vector{{5, 0, {3, 3, 3}}, {5, 0, {3, 4, 3}}, @@ -185,7 +249,18 @@ TEST(TileCover, DifferentOverscaledZWithPitch) { {5, 0, {3, 3, 1}}, {5, 0, {3, 4, 1}}, {5, 0, {3, 2, 1}}}), - util::tileCover({transform.getState()}, 3, 5)); + util::tileCover({transform.getState()}, 3, zoomRange, 5)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{5, 0, {3, 3, 3}}, + {5, 0, {3, 4, 3}}, + {5, 0, {3, 3, 2}}, + {5, 0, {3, 4, 2}}, + {5, 0, {3, 3, 1}}, + {5, 0, {3, 4, 1}}, + {5, 0, {3, 2, 1}}}), + util::tileCover(params, 3, zoomRange, 5)); } TEST(TileCover, DifferentOverscaledZWrapped) { @@ -200,7 +275,13 @@ TEST(TileCover, DifferentOverscaledZWrapped) { EXPECT_EQ( (std::vector{{2, 0, {1, 1, 0}}, {2, 1, {1, 0, 0}}, {2, 0, {1, 1, 1}}, {2, 1, {1, 0, 1}}}), - util::tileCover({transform.getState()}, 1, 2)); + util::tileCover({transform.getState()}, 1, zoomRange, 2)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ( + (std::vector{{2, 0, {1, 1, 0}}, {2, 1, {1, 0, 0}}, {2, 0, {1, 1, 1}}, {2, 1, {1, 0, 1}}}), + util::tileCover(params, 1, zoomRange, 2)); } TEST(TileCover, FlippedY) { @@ -215,7 +296,12 @@ TEST(TileCover, FlippedY) { .withZoom(1.0)); EXPECT_EQ((std::vector{{1, 0, 0}, {1, 1, 0}, {1, 0, 1}, {1, 1, 1}}), - util::tileCover({transform.getState()}, 1)); + util::tileCover({transform.getState()}, 1, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{1, 0, 0}, {1, 1, 0}, {1, 0, 1}, {1, 1, 1}}), + util::tileCover(params, 1, zoomRange)); } TEST(TileCover, FlippedYPitch) { @@ -232,7 +318,12 @@ TEST(TileCover, FlippedYPitch) { .withPitch(40.0)); EXPECT_EQ((std::vector{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}), - util::tileCover({transform.getState()}, 2)); + util::tileCover({transform.getState()}, 2, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{2, 1, 2}, {2, 2, 2}, {2, 1, 1}, {2, 2, 1}}), + util::tileCover(params, 2, zoomRange)); } TEST(TileCover, FlippedYHelsinki) { @@ -247,7 +338,12 @@ TEST(TileCover, FlippedYHelsinki) { .withZoom(11.447425)); EXPECT_EQ((std::vector{{11, 1165, 592}, {11, 1166, 592}, {11, 1165, 593}, {11, 1166, 593}}), - util::tileCover({transform.getState()}, 11)); + util::tileCover({transform.getState()}, 11, zoomRange)); + + util::TileCoverParameters params{transform.getState()}; + params.tileLodMode = TileLodMode::Distance; + EXPECT_EQ((std::vector{{11, 1165, 592}, {11, 1166, 592}, {11, 1165, 593}, {11, 1166, 593}}), + util::tileCover(params, 11, zoomRange)); } TEST(TileCoverStream, Arctic) {