From 8bad15bf8785bf7d47eef9f1bf3ab86cfa393481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 13 Jan 2016 00:47:50 -0800 Subject: [PATCH] [core] Consolidated zoom and angle anchor logic Made anchor a CameraOption for easeTo(). --- include/mbgl/map/camera.hpp | 4 ++ src/mbgl/map/transform.cpp | 69 ++++++++++++++++---------------- src/mbgl/map/transform.hpp | 2 + src/mbgl/map/transform_state.cpp | 20 +++++++++ src/mbgl/map/transform_state.hpp | 5 ++- test/miscellaneous/transform.cpp | 25 ++++++++++++ 6 files changed, 90 insertions(+), 35 deletions(-) diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 95be4e817..80734b76f 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -17,6 +17,10 @@ struct CameraOptions { /** Coordinate at the center of the map. */ mapbox::util::optional center; + /** Point of reference for `zoom` and `angle`, assuming an origin at the + top-left corner of the view. */ + mapbox::util::optional anchor; + /** Zero-based zoom level. Constrained to the minimum and maximum zoom levels. */ mapbox::util::optional zoom; diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index dc541bc39..ab93b84fc 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -35,6 +35,10 @@ static double _normalizeAngle(double angle, double anchorAngle) return angle; } +inline bool _validPoint(const PrecisionPoint& point) { + return !std::isnan(point.x) && !std::isnan(point.y); +} + Transform::Transform(View &view_, ConstrainMode constrainMode) : view(view_) , state(constrainMode) @@ -72,7 +76,7 @@ void Transform::jumpTo(const CameraOptions& camera) { } void Transform::moveBy(const PrecisionPoint& offset, const Duration& duration) { - if (!offset) { + if (!_validPoint(offset)) { return; } @@ -153,26 +157,14 @@ double Transform::getScale() const { return state.scale; } -void Transform::setScale(double scale, const PrecisionPoint& flippedAnchor, const Duration& duration) { +void Transform::setScale(double scale, const PrecisionPoint& anchor, const Duration& duration) { if (std::isnan(scale)) { return; } CameraOptions camera; - if (flippedAnchor) { - const double factor = scale / state.scale; - PrecisionPoint center = { - state.width / 2.0, - state.height / 2.0, - }; - PrecisionPoint anchor = { - flippedAnchor.x, - state.height - flippedAnchor.y, - }; - PrecisionPoint offset = anchor - center; - camera.center = state.pointToLatLng(anchor - offset / factor); - } camera.zoom = state.scaleZoom(scale); + camera.anchor = anchor; easeTo(camera, duration); } @@ -202,10 +194,27 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim } } + PrecisionPoint anchor = camera.anchor ? *camera.anchor : PrecisionPoint(NAN, NAN); + LatLng anchorLatLng; + if (_validPoint(anchor)) { + anchor.y = state.getHeight() - anchor.y; + anchorLatLng = state.pointToLatLng(anchor); + } + + const PrecisionPoint startPoint = { + state.lngX(startLatLng.longitude), + state.latY(startLatLng.latitude), + }; + const PrecisionPoint endPoint = { + state.lngX(latLng.longitude), + state.latY(latLng.latitude), + }; + Update update = state.getZoom() == zoom ? Update::Repaint : Update::Zoom; // Constrain camera options. zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom()); + const double scale = state.zoomScale(zoom); pitch = util::clamp(pitch, 0., util::PITCH_MAX); // Minimize rotation by taking the shorter path around the circle. @@ -219,6 +228,9 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim state.setLatLngZoom(latLng, zoom); state.angle = angle; state.pitch = pitch; + if (_validPoint(anchor)) { + state.moveLatLng(anchorLatLng, anchor); + } if (animation.transitionFinishFn) { animation.transitionFinishFn(); @@ -231,7 +243,6 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim state.Bc = startWorldSize / 360; state.Cc = startWorldSize / util::M2PI; - const double scale = state.zoomScale(zoom); const double startScale = state.scale; const double startAngle = state.angle; const double startPitch = state.pitch; @@ -245,9 +256,10 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim return ease.solve(t, 0.001); }, [=](double t) { + PrecisionPoint framePoint = util::interpolate(startPoint, endPoint, t); LatLng frameLatLng = { - util::interpolate(startLatLng.latitude, latLng.latitude, t), - util::interpolate(startLatLng.longitude, latLng.longitude, t), + state.yLat(framePoint.y, startWorldSize), + state.xLng(framePoint.x, startWorldSize), }; double frameScale = util::interpolate(startScale, scale, t); state.setLatLngZoom(frameLatLng, state.scaleZoom(frameScale)); @@ -258,6 +270,9 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim if (pitch != startPitch) { state.pitch = util::interpolate(startPitch, pitch, t); } + if (_validPoint(anchor)) { + state.moveLatLng(anchorLatLng, anchor); + } // At t = 1.0, a DidChangeAnimated notification should be sent from finish(). if (t < 1.0) { @@ -509,29 +524,15 @@ void Transform::setAngle(double angle, const Duration& duration) { setAngle(angle, {NAN, NAN}, duration); } -void Transform::setAngle(double angle, const PrecisionPoint& flippedAnchor, const Duration& duration) { +void Transform::setAngle(double angle, const PrecisionPoint& anchor, const Duration& duration) { if (std::isnan(angle)) { return; } - - PrecisionPoint anchor = { - flippedAnchor.x, - state.height - flippedAnchor.y, - }; - LatLng anchorLatLng; - - if (flippedAnchor) { - anchorLatLng = state.pointToLatLng(anchor); - setLatLng(anchorLatLng); - } CameraOptions camera; camera.angle = angle; + camera.anchor = anchor; easeTo(camera, duration); - - if (flippedAnchor) { - setLatLng(anchorLatLng, anchor); - } } double Transform::getAngle() const { diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index b8dd808f5..69b09e5a4 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -100,6 +100,8 @@ public: bool isPanning() const { return state.isPanning(); } private: + void moveLatLng(const LatLng&, const PrecisionPoint&); + View &view; TransformState state; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 9d0165749..c9c904dab 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -369,6 +369,26 @@ void TransformState::constrain(double& scale_, double& x_, double& y_) const { y_ = std::max(-max_y, std::min(y_, max_y)); } +void TransformState::moveLatLng(const LatLng& latLng, const PrecisionPoint& anchor) { + if (!latLng || !anchor) { + return; + } + + auto coord = latLngToCoordinate(latLng); + auto coordAtPoint = pointToCoordinate(anchor); + auto coordCenter = pointToCoordinate({ width / 2.0f, height / 2.0f }); + + float columnDiff = coordAtPoint.column - coord.column; + float rowDiff = coordAtPoint.row - coord.row; + + auto newLatLng = coordinateToLatLng({ + coordCenter.column - columnDiff, + coordCenter.row - rowDiff, + coordCenter.zoom + }); + setLatLngZoom(newLatLng, coordCenter.zoom); +} + void TransformState::setLatLngZoom(const LatLng &latLng, double zoom) { double newScale = zoomScale(zoom); const double newWorldSize = newScale * util::tileSize; diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 2aa031fa1..2911d8624 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -101,8 +101,11 @@ private: mat4 coordinatePointMatrix(double z) const; mat4 getPixelMatrix() const; + /** Recenter the map so that the given coordinate is located at the given + point on screen. */ + void moveLatLng(const LatLng&, const PrecisionPoint&); void setLatLngZoom(const LatLng &latLng, double zoom); - void setScalePoint(const double scale, const PrecisionPoint &point); + void setScalePoint(const double scale, const PrecisionPoint& point); private: ConstrainMode constrainMode; diff --git a/test/miscellaneous/transform.cpp b/test/miscellaneous/transform.cpp index a5c91b84e..f06fb56f1 100644 --- a/test/miscellaneous/transform.cpp +++ b/test/miscellaneous/transform.cpp @@ -177,3 +177,28 @@ TEST(Transform, ConstrainWidthAndHeight) { ASSERT_NEAR(85.021422866378742, loc.latitude, 0.0001); ASSERT_NEAR(179.65667724609358, std::abs(loc.longitude), 0.0001); } + +TEST(Transform, Anchor) { + MockView view; + Transform transform(view, ConstrainMode::HeightOnly); + + ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude); + ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude); + ASSERT_DOUBLE_EQ(1, transform.getScale()); + + transform.setLatLngZoom({ 10, -100 }, 10); + + ASSERT_DOUBLE_EQ(10, transform.getLatLng().latitude); + ASSERT_DOUBLE_EQ(-100, transform.getLatLng().longitude); + ASSERT_DOUBLE_EQ(10, transform.getZoom()); + ASSERT_DOUBLE_EQ(0, transform.getAngle()); + + const auto size = view.getSize(); + const PrecisionPoint anchorPoint = { size[0] * 0.8, size[1] * 0.3 }; + const LatLng anchorLatLng = transform.getState().pointToLatLng(anchorPoint); + transform.setAngle(M_PI_4, anchorPoint); + + ASSERT_NEAR(M_PI_4, transform.getAngle(), 0.000001); + ASSERT_NE(anchorLatLng, transform.getLatLng()); + ASSERT_DOUBLE_EQ(anchorLatLng, transform.getState().pointToLatLng(anchorPoint)); +}