Implement distance-based tile LOD (#4057)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
Nathan Olson
2026-02-21 03:38:10 -08:00
committed by GitHub
parent 8e0dc86956
commit 7fe58506c4
45 changed files with 650 additions and 86 deletions

View File

@@ -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)); transform.jumpTo(CameraOptions().withCenter(LatLng{0.1, -0.1}).withZoom(8.0).withBearing(5.0).withPitch(40.0));
std::size_t length = 0; std::size_t length = 0;
const Range<uint8_t> zoomRange(0, 14);
while (state.KeepRunning()) { while (state.KeepRunning()) {
auto tiles = util::tileCover({transform.getState()}, 8); auto tiles = util::tileCover({transform.getState()}, 8, zoomRange);
length += tiles.size(); length += tiles.size();
} }
benchmark::DoNotOptimize(length); benchmark::DoNotOptimize(length);

View File

@@ -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. 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 ## Instrumentation Tests
To run the instrumentation tests, choose the "Instrumentation Tests" run configuration. To run the instrumentation tests, choose the "Instrumentation Tests" run configuration.

View File

@@ -186,6 +186,14 @@ public:
/// A negative values typically improves performance but reduces quality. /// A negative values typically improves performance but reduces quality.
/// For instance, a value of -1 reduces the zoom level by 1 and this /// 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. /// 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); void setTileLodMinRadius(double radius);
double getTileLodMinRadius() const; double getTileLodMinRadius() const;
void setTileLodScale(double scale); void setTileLodScale(double scale);
@@ -194,6 +202,8 @@ public:
double getTileLodPitchThreshold() const; double getTileLodPitchThreshold() const;
void setTileLodZoomShift(double shift); void setTileLodZoomShift(double shift);
double getTileLodZoomShift() const; double getTileLodZoomShift() const;
void setTileLodMode(TileLodMode mode);
TileLodMode getTileLodMode() const;
ClientOptions getClientOptions() const; ClientOptions getClientOptions() const;

View File

@@ -35,6 +35,11 @@ enum class ViewportMode : EnumType {
FlippedY, FlippedY,
}; };
enum class TileLodMode : uint8_t {
Default, ///< Default TileLOD algorithm
Distance ///< Distance-based TileLOD algorithm
};
enum class MapDebugOptions : EnumType { enum class MapDebugOptions : EnumType {
NoDebug = 0, NoDebug = 0,
TileBorders = 1 << 1, TileBorders = 1 << 1,

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

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

View File

@@ -100,60 +100,71 @@ public:
namespace { namespace {
enum class TileLodMode { enum class TileLodProfile {
Default, // Default Tile LOD parameters Default, // Default Tile LOD parameters
NoLod, // Disable LOD NoLod, // Disable LOD
Reduced, // Reduce LOD away from camera Reduced, // Reduce LOD away from camera
Aggressive, // Aggressively reduce LOD away from camera at the detriment of quality 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) { switch (current) {
case TileLodMode::Default: case TileLodProfile::Default:
return TileLodMode::NoLod; return TileLodProfile::NoLod;
case TileLodMode::NoLod: case TileLodProfile::NoLod:
return TileLodMode::Reduced; return TileLodProfile::Reduced;
case TileLodMode::Reduced: case TileLodProfile::Reduced:
return TileLodMode::Aggressive; return TileLodProfile::Aggressive;
case TileLodMode::Aggressive: case TileLodProfile::Aggressive:
return TileLodMode::Default; return TileLodProfile::DistanceBased;
case TileLodProfile::DistanceBased:
return TileLodProfile::Default;
default: default:
return TileLodMode::Default; return TileLodProfile::Default;
} }
} }
void cycleTileLodMode(mbgl::Map &map) { void cycleTileLodProfile(mbgl::Map &map) {
// TileLodMode::Default parameters // TileLodProfile::Default parameters
static const auto defaultRadius = map.getTileLodMinRadius(); static const auto defaultRadius = map.getTileLodMinRadius();
static const auto defaultScale = map.getTileLodScale(); static const auto defaultScale = map.getTileLodScale();
static const auto defaultTilePitchThreshold = map.getTileLodPitchThreshold(); static const auto defaultTilePitchThreshold = map.getTileLodPitchThreshold();
static const auto defaultTileLodMode = map.getTileLodMode();
static TileLodMode mode = TileLodMode::Default; static TileLodProfile profile = TileLodProfile::Default;
mode = nextTileLodMode(mode); profile = nextTileLodProfile(profile);
switch (mode) { switch (profile) {
case TileLodMode::Default: case TileLodProfile::Default:
map.setTileLodMinRadius(defaultRadius); map.setTileLodMinRadius(defaultRadius);
map.setTileLodScale(defaultScale); map.setTileLodScale(defaultScale);
map.setTileLodPitchThreshold(defaultTilePitchThreshold); 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; break;
case TileLodMode::NoLod: case TileLodProfile::NoLod:
// When LOD is off we set a maximum PitchThreshold // When LOD is off we set a maximum PitchThreshold
map.setTileLodPitchThreshold(std::numbers::pi); 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; break;
case TileLodMode::Reduced: case TileLodProfile::Reduced:
map.setTileLodMinRadius(2); map.setTileLodMinRadius(2);
map.setTileLodScale(1.5); map.setTileLodScale(1.5);
map.setTileLodPitchThreshold(std::numbers::pi / 4); 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; break;
case TileLodMode::Aggressive: case TileLodProfile::Aggressive:
map.setTileLodMinRadius(1); map.setTileLodMinRadius(1);
map.setTileLodScale(2); map.setTileLodScale(2);
map.setTileLodPitchThreshold(0); 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; break;
} }
map.triggerRepaint(); map.triggerRepaint();
@@ -651,7 +662,7 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action,
view->invalidate(); view->invalidate();
} break; } break;
case GLFW_KEY_F6: { case GLFW_KEY_F6: {
cycleTileLodMode(*view->map); cycleTileLodProfile(*view->map);
} break; } break;
case GLFW_KEY_F7: { case GLFW_KEY_F7: {
tileLodZoomShift(*view->map, false); tileLodZoomShift(*view->map, false);

View File

@@ -25,6 +25,7 @@
#include <mbgl/style/style.hpp> #include <mbgl/style/style.hpp>
#include <mbgl/util/compression.hpp> #include <mbgl/util/compression.hpp>
#include <mbgl/util/io.hpp> #include <mbgl/util/io.hpp>
#include <mbgl/util/enum.hpp>
#include <mbgl/util/logging.hpp> #include <mbgl/util/logging.hpp>
#include <mbgl/util/rapidjson.hpp> #include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/run_loop.hpp> #include <mbgl/util/run_loop.hpp>
@@ -612,6 +613,10 @@ TestMetadata parseTestMetadata(const TestPaths& paths) {
return metadata; return metadata;
} }
namespace mbgl {
MBGL_DEFINE_ENUM(TileLodMode, {{TileLodMode::Default, "default"}, {TileLodMode::Distance, "distance"}});
}
namespace TestOperationNames { namespace TestOperationNames {
const std::string waitOp("wait"); const std::string waitOp("wait");
const std::string sleepOp("sleep"); const std::string sleepOp("sleep");
@@ -648,6 +653,11 @@ const std::string panGestureOp("panGesture");
const std::string gfxProbeOp("probeGFX"); const std::string gfxProbeOp("probeGFX");
const std::string gfxProbeStartOp("probeGFXStart"); const std::string gfxProbeStartOp("probeGFXStart");
const std::string gfxProbeEndOp("probeGFXEnd"); 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 } // namespace TestOperationNames
using namespace TestOperationNames; using namespace TestOperationNames;
@@ -1321,6 +1331,53 @@ TestOperations parseTestOperations(TestMetadata& metadata) {
ctx.getMetadata().metrics.gfx.insert({mark, metricProbe}); ctx.getMetadata().metrics.gfx.insert({mark, metricProbe});
return true; 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<mbgl::TileLodMode>::toEnum(operationArray[1].GetString()).value();
result.emplace_back([mode](TestContext& ctx) {
ctx.getMap().setTileLodMode(mode);
return true;
});
} else { } else {
metadata.errorMessage = std::string("Unsupported operation: ") + operationArray[0].GetString(); metadata.errorMessage = std::string("Unsupported operation: ") + operationArray[0].GetString();
return {}; return {};

View File

@@ -67,4 +67,9 @@ extern const std::string panGestureOp;
extern const std::string gfxProbeOp; extern const std::string gfxProbeOp;
extern const std::string gfxProbeStartOp; extern const std::string gfxProbeStartOp;
extern const std::string gfxProbeEndOp; 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 } // namespace TestOperationNames

View File

@@ -588,6 +588,14 @@ double Map::getTileLodZoomShift() const {
return impl->tileLodZoomShift; return impl->tileLodZoomShift;
} }
void Map::setTileLodMode(TileLodMode mode) {
impl->tileLodMode = mode;
}
TileLodMode Map::getTileLodMode() const {
return impl->tileLodMode;
}
ClientOptions Map::getClientOptions() const { ClientOptions Map::getClientOptions() const {
return impl->fileSource ? impl->fileSource->getClientOptions() : ClientOptions(); return impl->fileSource ? impl->fileSource->getClientOptions() : ClientOptions();
} }

View File

@@ -135,7 +135,8 @@ void Map::Impl::onUpdate() {
.tileLodMinRadius = tileLodMinRadius, .tileLodMinRadius = tileLodMinRadius,
.tileLodScale = tileLodScale, .tileLodScale = tileLodScale,
.tileLodPitchThreshold = tileLodPitchThreshold, .tileLodPitchThreshold = tileLodPitchThreshold,
.tileLodZoomShift = tileLodZoomShift}; .tileLodZoomShift = tileLodZoomShift,
.tileLodMode = tileLodMode};
rendererFrontend.update(std::make_shared<UpdateParameters>(std::move(params))); rendererFrontend.update(std::make_shared<UpdateParameters>(std::move(params)));
} }

View File

@@ -111,6 +111,7 @@ public:
double tileLodScale = 1; double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
double tileLodZoomShift = 0; double tileLodZoomShift = 0;
TileLodMode tileLodMode = TileLodMode::Default;
}; };
// Forward declaration of this method is required for the MapProjection class // Forward declaration of this method is required for the MapProjection class

View File

@@ -120,11 +120,14 @@ void RenderBackgroundLayer::update(gfx::ShaderRegistry& shaders,
[[maybe_unused]] UniqueChangeRequestVec& changes) { [[maybe_unused]] UniqueChangeRequestVec& changes) {
assert(updateParameters); assert(updateParameters);
const auto zoom = state.getIntegerZoom(); const auto zoom = state.getIntegerZoom();
Range<uint8_t> zoomRange(0, zoom);
const auto tileCover = util::tileCover({.transformState = state, const auto tileCover = util::tileCover({.transformState = state,
.tileLodMinRadius = updateParameters->tileLodMinRadius, .tileLodMinRadius = updateParameters->tileLodMinRadius,
.tileLodScale = updateParameters->tileLodScale, .tileLodScale = updateParameters->tileLodScale,
.tileLodPitchThreshold = updateParameters->tileLodPitchThreshold}, .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold,
zoom); .tileLodMode = updateParameters->tileLodMode},
zoom,
zoomRange);
// renderTiles is always empty, we use tileCover instead // renderTiles is always empty, we use tileCover instead
if (tileCover.empty()) { if (tileCover.empty()) {

View File

@@ -64,6 +64,7 @@ PaintParameters::PaintParameters(gfx::Context& context_,
double tileLodMinRadius_, double tileLodMinRadius_,
double tileLodScale_, double tileLodScale_,
double tileLodPitchThreshold_, double tileLodPitchThreshold_,
TileLodMode tileLodMode_,
const gfx::ScissorRect& scissorRect_) const gfx::ScissorRect& scissorRect_)
: context(context_), : context(context_),
backend(backend_), backend(backend_),
@@ -83,6 +84,7 @@ PaintParameters::PaintParameters(gfx::Context& context_,
tileLodMinRadius(tileLodMinRadius_), tileLodMinRadius(tileLodMinRadius_),
tileLodScale(tileLodScale_), tileLodScale(tileLodScale_),
tileLodPitchThreshold(tileLodPitchThreshold_), tileLodPitchThreshold(tileLodPitchThreshold_),
tileLodMode(tileLodMode_),
scissorRect(scissorRect_) { scissorRect(scissorRect_) {
pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}}; pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}};

View File

@@ -62,6 +62,7 @@ public:
double tileLodMinRadius, double tileLodMinRadius,
double tileLodScale, double tileLodScale,
double tileLodPitchThreshold, double tileLodPitchThreshold,
TileLodMode tileLodMode,
const gfx::ScissorRect&); const gfx::ScissorRect&);
~PaintParameters(); ~PaintParameters();
@@ -139,6 +140,7 @@ public:
double tileLodMinRadius; double tileLodMinRadius;
double tileLodScale; double tileLodScale;
double tileLodPitchThreshold; double tileLodPitchThreshold;
TileLodMode tileLodMode;
gfx::ScissorRect scissorRect; gfx::ScissorRect scissorRect;
}; };

View File

@@ -200,6 +200,7 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
.tileLodScale = updateParameters->tileLodScale, .tileLodScale = updateParameters->tileLodScale,
.tileLodPitchThreshold = updateParameters->tileLodPitchThreshold, .tileLodPitchThreshold = updateParameters->tileLodPitchThreshold,
.tileLodZoomShift = updateParameters->tileLodZoomShift, .tileLodZoomShift = updateParameters->tileLodZoomShift,
.tileLodMode = updateParameters->tileLodMode,
.dynamicTextureAtlas = dynamicTextureAtlas}; .dynamicTextureAtlas = dynamicTextureAtlas};
glyphManager->setURL(updateParameters->glyphURL); glyphManager->setURL(updateParameters->glyphURL);

View File

@@ -215,6 +215,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
updateParameters->tileLodMinRadius, updateParameters->tileLodMinRadius,
updateParameters->tileLodScale, updateParameters->tileLodScale,
updateParameters->tileLodPitchThreshold, updateParameters->tileLodPitchThreshold,
updateParameters->tileLodMode,
scissorRect, scissorRect,
}; };

View File

@@ -133,11 +133,14 @@ void RenderImageSource::update(Immutable<style::Source::Impl> baseImpl_,
bool hasVisibleTile = false; bool hasVisibleTile = false;
// Add additional wrapped tile ids if neccessary // Add additional wrapped tile ids if neccessary
Range<uint8_t> zoomRange(0, zoom);
auto idealTiles = util::tileCover({.transformState = transformState, auto idealTiles = util::tileCover({.transformState = transformState,
.tileLodMinRadius = parameters.tileLodMinRadius, .tileLodMinRadius = parameters.tileLodMinRadius,
.tileLodScale = parameters.tileLodScale, .tileLodScale = parameters.tileLodScale,
.tileLodPitchThreshold = parameters.tileLodPitchThreshold}, .tileLodPitchThreshold = parameters.tileLodPitchThreshold,
static_cast<uint8_t>(transformState.getZoom())); .tileLodMode = parameters.tileLodMode},
static_cast<uint8_t>(transformState.getZoom()),
zoomRange);
for (auto tile : idealTiles) { for (auto tile : idealTiles) {
if (tile.wrap != 0 && tileCover[0].canonical.isChildOf(tile.canonical)) { if (tile.wrap != 0 && tileCover[0].canonical.isChildOf(tile.canonical)) {
tileIds.emplace_back(tile.wrap, tileCover[0].canonical); tileIds.emplace_back(tile.wrap, tileCover[0].canonical);

View File

@@ -37,6 +37,7 @@ public:
double tileLodScale = 1; double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
double tileLodZoomShift = 0; double tileLodZoomShift = 0;
TileLodMode tileLodMode = TileLodMode::Default;
gfx::DynamicTextureAtlasPtr dynamicTextureAtlas; gfx::DynamicTextureAtlasPtr dynamicTextureAtlas;
bool isUpdateSynchronous = false; bool isUpdateSynchronous = false;
}; };

View File

@@ -113,7 +113,8 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
util::TileCoverParameters tileCoverParameters = {.transformState = parameters.transformState, util::TileCoverParameters tileCoverParameters = {.transformState = parameters.transformState,
.tileLodMinRadius = parameters.tileLodMinRadius, .tileLodMinRadius = parameters.tileLodMinRadius,
.tileLodScale = parameters.tileLodScale, .tileLodScale = parameters.tileLodScale,
.tileLodPitchThreshold = parameters.tileLodPitchThreshold}; .tileLodPitchThreshold = parameters.tileLodPitchThreshold,
.tileLodMode = parameters.tileLodMode};
if (std::cmp_greater_equal(overscaledZoom, zoomRange.min)) { if (std::cmp_greater_equal(overscaledZoom, zoomRange.min)) {
int32_t idealZoom = std::min<int32_t>(zoomRange.max, overscaledZoom); int32_t idealZoom = std::min<int32_t>(zoomRange.max, overscaledZoom);
@@ -135,11 +136,11 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
} }
if (panZoom < idealZoom) { 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 && if (parameters.mode == MapMode::Tile && type != SourceType::Raster && type != SourceType::RasterDEM &&
idealTiles.size() > 1) { idealTiles.size() > 1) {
mbgl::Log::Warning(mbgl::Event::General, mbgl::Log::Warning(mbgl::Event::General,
@@ -176,8 +177,10 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
// levels. Child tiles are used from the cache, but not created. // levels. Child tiles are used from the cache, but not created.
std::optional<util::TileRange> tileRange = std::nullopt; std::optional<util::TileRange> tileRange = std::nullopt;
if (bounds) { if (bounds) {
tileRange = util::TileRange::fromLatLngBounds( int32_t maxZoom = (parameters.tileLodMode == TileLodMode::Distance)
*bounds, zoomRange.min, std::min(tileZoom, static_cast<int32_t>(zoomRange.max))); ? zoomRange.max
: std::min(tileZoom, static_cast<int32_t>(zoomRange.max));
tileRange = util::TileRange::fromLatLngBounds(*bounds, zoomRange.min, maxZoom);
} }
auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* { auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* {
if (tileRange && !tileRange->contains(tileID.canonical)) { if (tileRange && !tileRange->contains(tileID.canonical)) {

View File

@@ -52,6 +52,7 @@ public:
double tileLodScale = 1; double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi;
double tileLodZoomShift = 0; double tileLodZoomShift = 0;
TileLodMode tileLodMode = TileLodMode::Default;
}; };
} // namespace mbgl } // namespace mbgl

View File

@@ -157,6 +157,7 @@ int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) no
std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state, std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
uint8_t z, uint8_t z,
const Range<uint8_t> zoomRange,
const std::optional<uint8_t>& overscaledZ) { const std::optional<uint8_t>& overscaledZ) {
struct Node { struct Node {
AABB aabb; AABB aabb;
@@ -171,12 +172,28 @@ std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
double sqrDist; double sqrDist;
}; };
auto childrenOf = [](const Node& node) -> std::vector<Node> {
std::vector<Node> 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 auto& transform = state.transformState;
const double numTiles = std::pow(2.0, z); const double numTiles = std::pow(2.0, z);
const double worldSize = Projection::worldSize(transform.getScale()); const double worldSize = Projection::worldSize(transform.getScale());
const uint8_t minZoom = transform.getPitch() <= state.tileLodPitchThreshold ? z : 0; const bool allowVariableZoom = transform.getPitch() > state.tileLodPitchThreshold;
const uint8_t maxZoom = z; const uint8_t minZoom = allowVariableZoom ? zoomRange.min : z;
const uint8_t overscaledZoom = std::max(overscaledZ.value_or(z), 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 bool flippedY = transform.getViewportMode() == ViewportMode::FlippedY;
const auto centerPoint = TileCoordinate::fromScreenCoordinate( const auto centerPoint = TileCoordinate::fromScreenCoordinate(
@@ -185,6 +202,12 @@ std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
const vec3 centerCoord = {{centerPoint.x, centerPoint.y, 0.0}}; 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); const Frustum frustum = Frustum::fromInvProjMatrix(transform.getInvProjectionMatrix(), worldSize, z, flippedY);
// There should always be a certain number of maximum zoom level tiles // There should always be a certain number of maximum zoom level tiles
@@ -227,48 +250,51 @@ std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
node.fullyVisible = intersection == IntersectionResult::Contains; node.fullyVisible = intersection == IntersectionResult::Contains;
} }
const vec3 distanceXyz = node.aabb.distanceXYZ(centerCoord); bool shouldSplitTile;
const double* longestDim = std::max_element(distanceXyz.data(), distanceXyz.data() + distanceXyz.size()); if (state.tileLodMode == TileLodMode::Distance) {
assert(longestDim); 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 // We're using distance based heuristics to determine if a tile should
// be split into quadrants or not. radiusOfMaxLvlLodInTiles defines that // be split into quadrants or not. radiusOfMaxLvlLodInTiles defines that
// there's always a certain number of maxLevel tiles next to the map // 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 // center. Using the fact that a parent node in quadtree is twice the
// size of its children (per dimension) we can define distance // size of its children (per dimension) we can define distance
// thresholds for each relative level: // thresholds for each relative level:
// f(k) = offset + 2 + 4 + 8 + 16 + ... + 2^k // f(k) = offset + 2 + 4 + 8 + 16 + ... + 2^k
// This is the same as: // This is the same as:
// f(k) = offset + 2^(k+1)-2 // f(k) = offset + 2^(k+1)-2
const double distToSplit = radiusOfMaxLvlLodInTiles + (1 << (maxZoom - node.zoom)) - 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? // 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. // Perform precise intersection test between the frustum and aabb.
// This will cull < 1% false positives missed by the original test // This will cull < 1% false positives missed by the original test
if (node.fullyVisible || frustum.intersectsPrecise(node.aabb, true) != IntersectionResult::Separate) { if (node.fullyVisible || frustum.intersectsPrecise(node.aabb, true) != IntersectionResult::Separate) {
const OverscaledTileID id = { const OverscaledTileID id = {
node.zoom == maxZoom ? overscaledZoom : node.zoom, node.wrap, node.zoom, node.x, node.y}; 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]; vec3 coordToLoadFirst = (state.tileLodMode == TileLodMode::Distance) ? cameraCoord : centerCoord;
const double dy = node.y + 0.5 - centerCoord[1]; 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}); result.push_back({id, dx * dx + dy * dy});
} }
continue; } else {
} std::vector<Node> children = childrenOf(node);
stack.insert(stack.end(), children.begin(), children.end());
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;
} }
} }

View File

@@ -38,12 +38,14 @@ struct TileCoverParameters {
double tileLodMinRadius = 3; double tileLodMinRadius = 3;
double tileLodScale = 1; double tileLodScale = 1;
double tileLodPitchThreshold = (60.0 / 180.0) * std::numbers::pi; 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; int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize) noexcept;
std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state, std::vector<OverscaledTileID> tileCover(const TileCoverParameters& state,
uint8_t z, uint8_t z,
const Range<uint8_t> zoomRange,
const std::optional<uint8_t>& overscaledZ = std::nullopt); const std::optional<uint8_t>& overscaledZ = std::nullopt);
std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, uint8_t z); std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, uint8_t z);
std::vector<UnwrappedTileID> tileCover(const Geometry<double>&, uint8_t z); std::vector<UnwrappedTileID> tileCover(const Geometry<double>&, uint8_t z);

View File

@@ -10,6 +10,8 @@
using namespace mbgl; using namespace mbgl;
const Range<uint8_t> zoomRange(0, 14);
TEST(TileCover, Empty) { TEST(TileCover, Empty) {
EXPECT_EQ((std::vector<UnwrappedTileID>{}), util::tileCover(LatLngBounds::empty(), 0)); EXPECT_EQ((std::vector<UnwrappedTileID>{}), util::tileCover(LatLngBounds::empty(), 0));
} }
@@ -44,7 +46,12 @@ TEST(TileCover, Pitch) {
.withPitch(40.0)); .withPitch(40.0));
EXPECT_EQ((std::vector<OverscaledTileID>{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}), EXPECT_EQ((std::vector<OverscaledTileID>{{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<OverscaledTileID>{{2, 1, 2}, {2, 2, 2}, {2, 1, 1}, {2, 2, 1}}),
util::tileCover(params, 2, zoomRange));
} }
TEST(TileCover, PitchIssue15442) { TEST(TileCover, PitchIssue15442) {
@@ -62,7 +69,47 @@ TEST(TileCover, PitchIssue15442) {
EXPECT_EQ((std::vector<OverscaledTileID>{ EXPECT_EQ((std::vector<OverscaledTileID>{
{2, 3, 1}, {2, 2, 1}, {2, 3, 0}, {2, 2, 0}, {2, 1, {2, 0, 0}}, {2, 1, {2, 1, 0}}}), {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<OverscaledTileID>{
{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<OverscaledTileID>{{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<OverscaledTileID>{{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) { TEST(TileCover, PitchOverAllowedByContentInsets) {
@@ -79,7 +126,12 @@ TEST(TileCover, PitchOverAllowedByContentInsets) {
EXPECT_LE(transform.getPitch() + 0.001, util::deg2rad(60)); EXPECT_LE(transform.getPitch() + 0.001, util::deg2rad(60));
EXPECT_EQ((std::vector<OverscaledTileID>{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}}), EXPECT_EQ((std::vector<OverscaledTileID>{{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<OverscaledTileID>{{3, 3, 4}, {3, 3, 3}, {3, 4, 4}, {3, 4, 3}}),
util::tileCover(params, 3, zoomRange));
} }
TEST(TileCover, PitchWithLargerResultSet) { TEST(TileCover, PitchWithLargerResultSet) {
@@ -95,7 +147,7 @@ TEST(TileCover, PitchWithLargerResultSet) {
.withBearing(-142.2630000003529176) .withBearing(-142.2630000003529176)
.withPitch(60.0)); .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 // Returned vector has above 100 elements, we check first 16 as there is a
// plan to return lower LOD for distant tiles. // plan to return lower LOD for distant tiles.
EXPECT_EQ((std::vector<OverscaledTileID>{{5, 15, 16}, EXPECT_EQ((std::vector<OverscaledTileID>{{5, 15, 16},
@@ -147,7 +199,13 @@ TEST(TileCover, CoordinatesAreUnwrapped) {
EXPECT_EQ( EXPECT_EQ(
(std::vector<OverscaledTileID>{{1, 0, {1, 1, 0}}, {1, 1, {1, 0, 0}}, {1, 0, {1, 1, 1}}, {1, 1, {1, 0, 1}}}), (std::vector<OverscaledTileID>{{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<OverscaledTileID>{{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) { TEST(TileCover, DifferentOverscaledZ) {
@@ -164,7 +222,13 @@ TEST(TileCover, DifferentOverscaledZ) {
EXPECT_EQ( EXPECT_EQ(
(std::vector<OverscaledTileID>{{3, 0, {2, 1, 1}}, {3, 0, {2, 2, 1}}, {3, 0, {2, 1, 2}}, {3, 0, {2, 2, 2}}}), (std::vector<OverscaledTileID>{{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<OverscaledTileID>{{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) { TEST(TileCover, DifferentOverscaledZWithPitch) {
@@ -176,7 +240,7 @@ TEST(TileCover, DifferentOverscaledZWithPitch) {
-10.0, -10.0,
}) })
.withZoom(3.5) .withZoom(3.5)
.withPitch(60)); .withPitch(61));
EXPECT_EQ((std::vector<OverscaledTileID>{{5, 0, {3, 3, 3}}, EXPECT_EQ((std::vector<OverscaledTileID>{{5, 0, {3, 3, 3}},
{5, 0, {3, 4, 3}}, {5, 0, {3, 4, 3}},
@@ -185,7 +249,18 @@ TEST(TileCover, DifferentOverscaledZWithPitch) {
{5, 0, {3, 3, 1}}, {5, 0, {3, 3, 1}},
{5, 0, {3, 4, 1}}, {5, 0, {3, 4, 1}},
{5, 0, {3, 2, 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<OverscaledTileID>{{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) { TEST(TileCover, DifferentOverscaledZWrapped) {
@@ -200,7 +275,13 @@ TEST(TileCover, DifferentOverscaledZWrapped) {
EXPECT_EQ( EXPECT_EQ(
(std::vector<OverscaledTileID>{{2, 0, {1, 1, 0}}, {2, 1, {1, 0, 0}}, {2, 0, {1, 1, 1}}, {2, 1, {1, 0, 1}}}), (std::vector<OverscaledTileID>{{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<OverscaledTileID>{{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) { TEST(TileCover, FlippedY) {
@@ -215,7 +296,12 @@ TEST(TileCover, FlippedY) {
.withZoom(1.0)); .withZoom(1.0));
EXPECT_EQ((std::vector<OverscaledTileID>{{1, 0, 0}, {1, 1, 0}, {1, 0, 1}, {1, 1, 1}}), EXPECT_EQ((std::vector<OverscaledTileID>{{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<OverscaledTileID>{{1, 0, 0}, {1, 1, 0}, {1, 0, 1}, {1, 1, 1}}),
util::tileCover(params, 1, zoomRange));
} }
TEST(TileCover, FlippedYPitch) { TEST(TileCover, FlippedYPitch) {
@@ -232,7 +318,12 @@ TEST(TileCover, FlippedYPitch) {
.withPitch(40.0)); .withPitch(40.0));
EXPECT_EQ((std::vector<OverscaledTileID>{{2, 1, 1}, {2, 2, 1}, {2, 1, 2}, {2, 2, 2}}), EXPECT_EQ((std::vector<OverscaledTileID>{{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<OverscaledTileID>{{2, 1, 2}, {2, 2, 2}, {2, 1, 1}, {2, 2, 1}}),
util::tileCover(params, 2, zoomRange));
} }
TEST(TileCover, FlippedYHelsinki) { TEST(TileCover, FlippedYHelsinki) {
@@ -247,7 +338,12 @@ TEST(TileCover, FlippedYHelsinki) {
.withZoom(11.447425)); .withZoom(11.447425));
EXPECT_EQ((std::vector<OverscaledTileID>{{11, 1165, 592}, {11, 1166, 592}, {11, 1165, 593}, {11, 1166, 593}}), EXPECT_EQ((std::vector<OverscaledTileID>{{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<OverscaledTileID>{{11, 1165, 592}, {11, 1166, 592}, {11, 1165, 593}, {11, 1166, 593}}),
util::tileCover(params, 11, zoomRange));
} }
TEST(TileCoverStream, Arctic) { TEST(TileCoverStream, Arctic) {