From 73d9f26d03810854c36a96b252885353f8ba81ce Mon Sep 17 00:00:00 2001 From: Aleksandar Stojiljkovic Date: Tue, 23 Jul 2019 17:19:52 +0300 Subject: [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 --- src/mbgl/map/transform.cpp | 31 +++++++++++++++++++++++++------ src/mbgl/map/transform.hpp | 3 +++ src/mbgl/map/transform_state.cpp | 20 ++++++++++---------- 3 files changed, 38 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index a51d4dd4ff..e360150cc2 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -138,9 +138,6 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim if (bearing != startBearing) { state.bearing = util::wrap(util::interpolate(startBearing, bearing, t), -M_PI, M_PI); } - if (pitch != startPitch) { - state.pitch = util::interpolate(startPitch, pitch, t); - } if (padding != startEdgeInsets) { // Interpolate edge insets state.edgeInsets = { @@ -150,6 +147,10 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim util::interpolate(startEdgeInsets.right(), padding.right(), t) }; } + auto maxPitch = getMaxPitchForEdgeInsets(state.edgeInsets); + if (pitch != startPitch || maxPitch < startPitch) { + state.pitch = std::min(maxPitch, util::interpolate(startPitch, pitch, t)); + } }, duration); } @@ -302,9 +303,6 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima if (bearing != startBearing) { state.bearing = util::wrap(util::interpolate(startBearing, bearing, k), -M_PI, M_PI); } - if (pitch != startPitch) { - state.pitch = util::interpolate(startPitch, pitch, k); - } if (padding != startEdgeInsets) { // Interpolate edge insets state.edgeInsets = { @@ -314,6 +312,10 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima util::interpolate(startEdgeInsets.right(), padding.right(), us) }; } + auto maxPitch = getMaxPitchForEdgeInsets(state.edgeInsets); + if (pitch != startPitch || maxPitch < startPitch) { + state.pitch = std::min(maxPitch, util::interpolate(startPitch, pitch, k)); + } }, duration); } @@ -576,4 +578,21 @@ LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng return state.screenCoordinateToLatLng(flippedPoint, wrapMode); } +double Transform::getMaxPitchForEdgeInsets(const EdgeInsets &insets) const +{ + double centerOffsetY = 0.5 * (insets.top() - insets.bottom()); // See TransformState::getCenterOffset. + + const auto height = state.size.height; + assert(height); + // For details, see description at https://github.com/mapbox/mapbox-gl-native/pull/15195 + // The definition of half of TransformState::fov with no inset, is: fov = arctan((height / 2) / (height * 1.5)). + // We use half of fov, as it is field of view above perspective center. + // With inset, this angle changes and tangentOfFovAboveCenterAngle = (h/2 + centerOffsetY) / (height * 1.5). + // 1.03 is a bit extra added to prevent parallel ground to viewport clipping plane. + const double tangentOfFovAboveCenterAngle = 1.03 * (height / 2.0 + centerOffsetY) / (1.5 * height); + const double fovAboveCenter = std::atan(tangentOfFovAboveCenterAngle); + return M_PI * 0.5 - fovAboveCenter; + // e.g. Maximum pitch of 60 degrees is when perspective center's offset from the top is 84% of screen height. +} + } // namespace mbgl diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 0c018a6e48..75dfeff645 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -115,6 +115,9 @@ private: std::function, const Duration&); + // We don't want to show horizon: limit max pitch based on edge insets. + double getMaxPitchForEdgeInsets(const EdgeInsets &insets) const; + TimePoint transitionStart; Duration transitionDuration; std::function transitionFrameFn; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 92c02d0bc7..77309a2a55 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -36,18 +36,18 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne const double cameraToCenterDistance = getCameraToCenterDistance(); auto offset = getCenterOffset(); - // Find the distance from the viewport center point - // [width/2 + offset.x, height/2 + offset.y] to the top edge, to point - // [width/2 + offset.x, 0] in Z units, using the law of sines. + // Find the Z distance from the viewport center point + // [width/2 + offset.x, height/2 + offset.y] to the top edge; to point + // [width/2 + offset.x, 0] in Z units. // 1 Z unit is equivalent to 1 horizontal px at the center of the map // (the distance between[width/2, height/2] and [width/2 + 1, height/2]) - const double fovAboveCenter = getFieldOfView() * (0.5 + offset.y / size.height); - const double groundAngle = M_PI / 2.0 + getPitch(); - const double aboveCenterSurfaceDistance = std::sin(fovAboveCenter) * cameraToCenterDistance / std::sin(M_PI - groundAngle - fovAboveCenter); - - + // See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details. + // See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)). + const double tanFovAboveCenter = (size.height * 0.5 + offset.y) / (size.height * 1.5); + const double tanMultiple = tanFovAboveCenter * std::tan(getPitch()); + assert(tanMultiple < 1); // Calculate z distance of the farthest fragment that should be rendered. - const double furthestDistance = std::cos(M_PI / 2 - getPitch()) * aboveCenterSurfaceDistance + cameraToCenterDistance; + const double furthestDistance = cameraToCenterDistance / (1 - tanMultiple); // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance` const double farZ = furthestDistance * 1.01; @@ -64,7 +64,7 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne const bool flippedY = viewportMode == ViewportMode::FlippedY; matrix::scale(projMatrix, projMatrix, 1.0, flippedY ? 1 : -1, 1); - matrix::translate(projMatrix, projMatrix, 0, 0, -getCameraToCenterDistance()); + matrix::translate(projMatrix, projMatrix, 0, 0, -cameraToCenterDistance); using NO = NorthOrientation; switch (getNorthOrientation()) { -- cgit v1.2.1