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

Commit bd3f137

Browse files
committed
[core] Limit pitch based on edge insets. Fix max Z calculation in getProjMatrix.
Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon: Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch. Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation: furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch())); TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation. Related to: #15163
1 parent f529380 commit bd3f137

File tree

7 files changed

+78
-18
lines changed

7 files changed

+78
-18
lines changed

include/mbgl/util/projection.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class Projection {
9898
return Point<double> {
9999
util::LONGITUDE_MAX + latLng.longitude(),
100100
util::LONGITUDE_MAX - util::RAD2DEG * std::log(std::tan(M_PI / 4 + latitude * M_PI / util::DEGREES_MAX))
101-
} * worldSize / util::DEGREES_MAX;
101+
} * (worldSize / util::DEGREES_MAX);
102102
}
103103
};
104104

src/mbgl/map/transform.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,6 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
138138
if (bearing != startBearing) {
139139
state.bearing = util::wrap(util::interpolate(startBearing, bearing, t), -M_PI, M_PI);
140140
}
141-
if (pitch != startPitch) {
142-
state.pitch = util::interpolate(startPitch, pitch, t);
143-
}
144141
if (padding != startEdgeInsets) {
145142
// Interpolate edge insets
146143
state.edgeInsets = {
@@ -150,6 +147,10 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
150147
util::interpolate(startEdgeInsets.right(), padding.right(), t)
151148
};
152149
}
150+
auto maxPitch = getMaxPitchForEdgeInsets(state.edgeInsets);
151+
if (pitch != startPitch || maxPitch < startPitch) {
152+
state.pitch = std::min(maxPitch, util::interpolate(startPitch, pitch, t));
153+
}
153154
}, duration);
154155
}
155156

@@ -302,9 +303,6 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
302303
if (bearing != startBearing) {
303304
state.bearing = util::wrap(util::interpolate(startBearing, bearing, k), -M_PI, M_PI);
304305
}
305-
if (pitch != startPitch) {
306-
state.pitch = util::interpolate(startPitch, pitch, k);
307-
}
308306
if (padding != startEdgeInsets) {
309307
// Interpolate edge insets
310308
state.edgeInsets = {
@@ -314,6 +312,10 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
314312
util::interpolate(startEdgeInsets.right(), padding.right(), us)
315313
};
316314
}
315+
auto maxPitch = getMaxPitchForEdgeInsets(state.edgeInsets);
316+
if (pitch != startPitch || maxPitch < startPitch) {
317+
state.pitch = std::min(maxPitch, util::interpolate(startPitch, pitch, k));
318+
}
317319
}, duration);
318320
}
319321

@@ -576,4 +578,21 @@ LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng
576578
return state.screenCoordinateToLatLng(flippedPoint, wrapMode);
577579
}
578580

581+
double Transform::getMaxPitchForEdgeInsets(const EdgeInsets &insets) const
582+
{
583+
double centerOffsetY = 0.5 * (insets.top() - insets.bottom()); // See TransformState::getCenterOffset.
584+
585+
const auto height = state.size.height;
586+
assert(height);
587+
// For details, see description at https://github.com/mapbox/mapbox-gl-native/pull/15195
588+
// The definition of half of TransformState::fov with no inset, is: fov = arctan((height / 2) / (height * 1.5)).
589+
// We use half of fov, as it is field of view above perspective center.
590+
// With inset, this angle changes and tangentOfFovAboveCenterAngle = (h/2 + centerOffsetY) / (height * 1.5).
591+
// 1.03 is a bit extra added to prevent parallel ground to viewport clipping plane.
592+
const double tangentOfFovAboveCenterAngle = 1.03 * (height / 2.0 + centerOffsetY) / (1.5 * height);
593+
const double fovAboveCenter = std::atan(tangentOfFovAboveCenterAngle);
594+
return M_PI * 0.5 - fovAboveCenter;
595+
// e.g. Maximum pitch of 60 degrees is when perspective center's offset from the top is 84% of screen height.
596+
}
597+
579598
} // namespace mbgl

src/mbgl/map/transform.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ class Transform : private util::noncopyable {
115115
std::function<void(double)>,
116116
const Duration&);
117117

118+
// We don't want to show horizon: limit max pitch based on edge insets.
119+
double getMaxPitchForEdgeInsets(const EdgeInsets &insets) const;
120+
118121
TimePoint transitionStart;
119122
Duration transitionDuration;
120123
std::function<bool(const TimePoint)> transitionFrameFn;

src/mbgl/map/transform_state.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,18 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
3636
const double cameraToCenterDistance = getCameraToCenterDistance();
3737
auto offset = getCenterOffset();
3838

39-
// Find the distance from the viewport center point
40-
// [width/2 + offset.x, height/2 + offset.y] to the top edge, to point
41-
// [width/2 + offset.x, 0] in Z units, using the law of sines.
39+
// Find the Z distance from the viewport center point
40+
// [width/2 + offset.x, height/2 + offset.y] to the top edge; to point
41+
// [width/2 + offset.x, 0] in Z units.
4242
// 1 Z unit is equivalent to 1 horizontal px at the center of the map
4343
// (the distance between[width/2, height/2] and [width/2 + 1, height/2])
44-
const double fovAboveCenter = getFieldOfView() * (0.5 + offset.y / size.height);
45-
const double groundAngle = M_PI / 2.0 + getPitch();
46-
const double aboveCenterSurfaceDistance = std::sin(fovAboveCenter) * cameraToCenterDistance / std::sin(M_PI - groundAngle - fovAboveCenter);
47-
48-
44+
// See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details.
45+
// See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)).
46+
const double tanFovAboveCenter = (size.height * 0.5 + offset.y) / (size.height * 1.5);
47+
const double tanMultiple = tanFovAboveCenter * std::tan(getPitch());
48+
assert(tanMultiple < 1);
4949
// Calculate z distance of the farthest fragment that should be rendered.
50-
const double furthestDistance = std::cos(M_PI / 2 - getPitch()) * aboveCenterSurfaceDistance + cameraToCenterDistance;
50+
const double furthestDistance = cameraToCenterDistance / (1 - tanMultiple);
5151
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
5252
const double farZ = furthestDistance * 1.01;
5353

@@ -64,7 +64,7 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
6464
const bool flippedY = viewportMode == ViewportMode::FlippedY;
6565
matrix::scale(projMatrix, projMatrix, 1.0, flippedY ? 1 : -1, 1);
6666

67-
matrix::translate(projMatrix, projMatrix, 0, 0, -getCameraToCenterDistance());
67+
matrix::translate(projMatrix, projMatrix, 0, 0, -cameraToCenterDistance);
6868

6969
using NO = NorthOrientation;
7070
switch (getNorthOrientation()) {

test/map/transform.test.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ using namespace mbgl;
99

1010
TEST(Transform, InvalidZoom) {
1111
Transform transform;
12+
transform.resize({1, 1});
1213

1314
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude());
1415
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude());
@@ -56,6 +57,7 @@ TEST(Transform, InvalidZoom) {
5657

5758
TEST(Transform, InvalidBearing) {
5859
Transform transform;
60+
transform.resize({1, 1});
5961

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

7981
TEST(Transform, IntegerZoom) {
8082
Transform transform;
83+
transform.resize({1, 1});
8184

8285
auto checkIntegerZoom = [&transform](uint8_t zoomInt, double zoom) {
8386
transform.jumpTo(CameraOptions().withZoom(zoom));

test/util/tile_cover.test.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,41 @@ TEST(TileCover, Pitch) {
4343
util::tileCover(transform.getState(), 2));
4444
}
4545

46+
TEST(TileCover, PitchOverAllowedByContentInsets) {
47+
Transform transform;
48+
transform.resize({ 512, 512 });
49+
50+
transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }).withPadding(EdgeInsets { 376, 0, 0, 0 })
51+
.withZoom(8.0).withBearing(45.0).withPitch(60.0));
52+
// Top padding of 376 leads to capped pitch. See Transform::getMaxPitchForEdgeInsets.
53+
EXPECT_LE(transform.getPitch() + 0.001, util::DEG2RAD * 60);
54+
55+
EXPECT_EQ((std::vector<UnwrappedTileID>{
56+
{ 3, 4, 3 }, { 3, 3, 3 }, { 3, 4, 4 }, { 3, 3, 4 }, { 3, 4, 2 }, { 3, 5, 3 }, { 3, 5, 2 }
57+
}),
58+
util::tileCover(transform.getState(), 3));
59+
}
60+
61+
TEST(TileCover, PitchWithLargerResultSet) {
62+
Transform transform;
63+
transform.resize({ 1024, 768 });
64+
65+
// The values used here triggered the regression with left and right edge
66+
// selection in tile_cover.cpp scanSpans.
67+
transform.jumpTo(CameraOptions().withCenter(LatLng { 0.1, -0.1 }).withPadding(EdgeInsets { 400, 0, 0, 0 })
68+
.withZoom(5).withBearing(-142.2630000003529176).withPitch(60.0));
69+
70+
auto cover = util::tileCover(transform.getState(), 5);
71+
// Returned vector has above 100 elements, we check first 16 as there is a
72+
// plan to return lower LOD for distant tiles.
73+
EXPECT_EQ((std::vector<UnwrappedTileID> {
74+
{ 5, 15, 16 }, { 5, 15, 17 }, { 5, 14, 16 }, { 5, 14, 17 },
75+
{ 5, 16, 16 }, { 5, 16, 17 }, { 5, 15, 15 }, { 5, 14, 15 },
76+
{ 5, 15, 18 }, { 5, 14, 18 }, { 5, 16, 15 }, { 5, 13, 16 },
77+
{ 5, 13, 17 }, { 5, 16, 18 }, { 5, 13, 18 }, { 5, 15, 19 }
78+
}), (std::vector<UnwrappedTileID> { cover.begin(), cover.begin() + 16}) );
79+
}
80+
4681
TEST(TileCover, WorldZ1) {
4782
EXPECT_EQ((std::vector<UnwrappedTileID>{
4883
{ 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 },

0 commit comments

Comments
 (0)