From d26b956a48dc2da9333b40d8d2c4371c63b372a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Sun, 10 Jan 2016 17:28:21 -0800 Subject: [PATCH] [core] Cleaned up Transform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminated many redundant methods on Transform. Minimized usage of TransformState::x and TransformState::y. Added convenient constructors for AnimationOptions. When scaling, passing in view’s origin as the anchor no longer anchors the scaling operation at the view’s center. To specify no anchor, use NaN. Increased precision of worldSize(), now that it’s used much more heavily in transform methods. --- include/mbgl/map/camera.hpp | 7 + include/mbgl/map/map.hpp | 4 +- .../mapbox/mapboxsdk/views/NativeMapView.java | 4 +- src/mbgl/map/transform.cpp | 300 ++++++++---------- src/mbgl/map/transform.hpp | 15 +- src/mbgl/map/transform_state.cpp | 4 +- src/mbgl/map/transform_state.hpp | 2 +- 7 files changed, 155 insertions(+), 181 deletions(-) diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 87adc4691..95be4e817 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -59,6 +59,13 @@ struct AnimationOptions { /** A function that is called once on the last frame of the transition, just before the corresponding screen update. */ std::function transitionFinishFn; + + /** Creates an animation with no options specified. */ + inline AnimationOptions() {} + + /** Creates an animation with the specified duration. */ + inline AnimationOptions(Duration d) + : duration(d) {} }; } // namespace mbgl diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 4a6b85b28..3e52d57d1 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -111,8 +111,8 @@ public: void resetPosition(); // Scale - void scaleBy(double ds, const PrecisionPoint& = { 0, 0 }, const Duration& = Duration::zero()); - void setScale(double scale, const PrecisionPoint& = { 0, 0 }, const Duration& = Duration::zero()); + void scaleBy(double ds, const PrecisionPoint& = { NAN, NAN }, const Duration& = Duration::zero()); + void setScale(double scale, const PrecisionPoint& = { NAN, NAN }, const Duration& = Duration::zero()); double getScale() const; void setZoom(double zoom, const Duration& = Duration::zero()); double getZoom() const; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java index 6df0defe3..c90b925f3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java @@ -264,7 +264,7 @@ final class NativeMapView { } public void scaleBy(double ds) { - scaleBy(ds, -1.0, -1.0); + scaleBy(ds, Double.NaN, Double.NaN); } public void scaleBy(double ds, double cx, double cy) { @@ -276,7 +276,7 @@ final class NativeMapView { } public void setScale(double scale) { - setScale(scale, -1.0, -1.0); + setScale(scale, Double.NaN, Double.NaN); } public void setScale(double scale, double cx, double cy) { diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 5dae77770..da99d64fc 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -62,64 +62,29 @@ bool Transform::resize(const std::array size) { #pragma mark - Position -/* +/** * Change any combination of center, zoom, bearing, and pitch, without * a transition. The map will retain the current values for any options * not included in `options`. */ void Transform::jumpTo(const CameraOptions& camera) { - easeTo(camera, {}); + easeTo(camera); } -/* - * Change any combination of center, zoom, bearing, and pitch, with a smooth animation - * between old and new values. The map will retain the current values for any options - * not included in `options`. - */ -void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& animation) { - CameraOptions easeCamera(camera); - LatLng latLng = easeCamera.center ? *easeCamera.center : getLatLng(); - double zoom = easeCamera.zoom ? *easeCamera.zoom : getZoom(); - double angle = easeCamera.angle ? *easeCamera.angle : getAngle(); - if (!latLng || std::isnan(zoom) || std::isnan(angle)) { +void Transform::moveBy(const PrecisionPoint& offset, const Duration& duration) { + if (!offset) { return; } - double new_scale = state.zoomScale(zoom); + PrecisionPoint centerOffset = { + offset.x, + -offset.y, + }; + PrecisionPoint centerPoint = state.latLngToPoint(state.getLatLng()) - centerOffset; - const double s = new_scale * util::tileSize; - state.Bc = s / 360; - state.Cc = s / util::M2PI; - - const double m = 1 - 1e-15; - const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m); - - double xn = -latLng.longitude * state.Bc; - double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f)); - - easeCamera.center.reset(); - easeCamera.zoom.reset(); - easeCamera.angle.reset(); - _easeTo(easeCamera, animation, new_scale, angle, xn, yn); -} - -void Transform::moveBy(const PrecisionPoint& point, const Duration& duration) { - if (!point) { - return; - } - - _moveBy(point, duration); -} - -void Transform::_moveBy(const PrecisionPoint& point, const Duration& duration) { - double x = state.x + std::cos(state.angle) * point.x + std::sin( state.angle) * point.y; - double y = state.y + std::cos(state.angle) * point.y + std::sin(-state.angle) * point.x; - - state.constrain(state.scale, x, y); - - AnimationOptions animation; - animation.duration = duration; - _easeTo({}, animation, state.scale, state.angle, x, y); + CameraOptions camera; + camera.center = state.pointToLatLng(centerPoint); + easeTo(camera, duration); } void Transform::setLatLng(const LatLng& latLng, const Duration& duration) { @@ -129,9 +94,7 @@ void Transform::setLatLng(const LatLng& latLng, const Duration& duration) { CameraOptions camera; camera.center = latLng; - AnimationOptions animation; - animation.duration = duration; - easeTo(camera, animation); + easeTo(camera, duration); } void Transform::setLatLng(const LatLng& latLng, const PrecisionPoint& point, const Duration& duration) { @@ -163,44 +126,23 @@ void Transform::setLatLngZoom(const LatLng& latLng, double zoom, const Duration& CameraOptions camera; camera.center = latLng; camera.zoom = zoom; - AnimationOptions animation; - animation.duration = duration; - easeTo(camera, animation); + easeTo(camera, duration); } #pragma mark - Zoom void Transform::scaleBy(double ds, const PrecisionPoint& center, const Duration& duration) { - if (std::isnan(ds) || !center) { + if (std::isnan(ds)) { return; } - // clamp scale to min/max values - double new_scale = state.scale * ds; - if (new_scale < state.min_scale) { - new_scale = state.min_scale; - } else if (new_scale > state.max_scale) { - new_scale = state.max_scale; - } - - _setScale(new_scale, center, duration); -} - -void Transform::setScale(double scale, const PrecisionPoint& center, const Duration& duration) { - if (std::isnan(scale) || !center) { - return; - } - - _setScale(scale, center, duration); + double scale = util::clamp(state.scale * ds, state.min_scale, state.max_scale); + setScale(scale, center, duration); } void Transform::setZoom(double zoom, const Duration& duration) { - if (std::isnan(zoom)) { - return; - } - - _setScale(std::pow(2.0, zoom), { 0, 0 }, duration); + setScale(state.zoomScale(zoom), {NAN, NAN}, duration); } double Transform::getZoom() const { @@ -211,52 +153,80 @@ double Transform::getScale() const { return state.scale; } -void Transform::_setScale(double new_scale, const PrecisionPoint& center, const Duration& duration) { - const double factor = new_scale / state.scale; - double dx = 0; - double dy = 0; - - if (center.x > 0 || center.y > 0) { - auto coord = state.pointToCoordinate({ center.x, state.getHeight() - center.y }).zoomTo(state.getZoom()); - auto centerCoord = state.pointToCoordinate({ state.width / 2.0f, state.height / 2.0f }).zoomTo(state.getZoom()); - auto coordDiff = centerCoord - coord; - dx = coordDiff.column * util::tileSize * (1.0 - factor); - dy = coordDiff.row * util::tileSize * (1.0 - factor); +void Transform::setScale(double scale, const PrecisionPoint& anchor, const Duration& duration) { + if (std::isnan(scale)) { + return; + } + + const double factor = scale / state.scale; + PrecisionPoint offset = { 0, 0 }; + PrecisionPoint center = { + state.width / 2.0f, + state.height / 2.0f, + }; + if (anchor) { + offset = { + anchor.x - center.x, + center.y - anchor.y, + }; } - const double xn = state.x * factor - dx; - const double yn = state.y * factor - dy; - - AnimationOptions animation; - animation.duration = duration; - _easeTo({}, animation, new_scale, state.angle, xn, yn); + CameraOptions camera; + camera.center = state.pointToLatLng(center + offset / factor); + camera.zoom = state.scaleZoom(scale); + easeTo(camera, duration); } -void Transform::_easeTo(const CameraOptions& camera, const AnimationOptions& animation, - double new_scale, double new_angle, double xn, double yn) { - Update update = state.scale == new_scale ? Update::Repaint : Update::Zoom; - double scale = new_scale; - double x = xn; - double y = yn; - - state.constrain(scale, x, y); +/** + * Change any combination of center, zoom, bearing, and pitch, with a smooth animation + * between old and new values. The map will retain the current values for any options + * not included in `options`. + */ +void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& animation) { + LatLng latLng = camera.center ? *camera.center : getLatLng(); + double zoom = camera.zoom ? *camera.zoom : getZoom(); + double angle = camera.angle ? *camera.angle : getAngle(); + double pitch = camera.pitch ? *camera.pitch : getPitch(); - double angle = _normalizeAngle(new_angle, state.angle); - state.angle = _normalizeAngle(state.angle, angle); - double pitch = camera.pitch ? *camera.pitch : state.pitch; + if (!latLng || std::isnan(zoom)) { + return; + } + + // If a path crossing the antemeridian would be shorter, extend the final + // coordinate so that interpolating between the two endpoints will cross it. + LatLng startLatLng = getLatLng(); + if (std::abs(startLatLng.longitude) + std::abs(latLng.longitude) > 180) { + if (startLatLng.longitude > 0 && latLng.longitude < 0) { + latLng.longitude += 360; + } else if (startLatLng.longitude < 0 && latLng.longitude > 0) { + latLng.longitude -= 360; + } + } + + 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()); pitch = util::clamp(pitch, 0., util::PITCH_MAX); + + // Minimize rotation by taking the shorter path around the circle. + angle = _normalizeAngle(angle, state.angle); + state.angle = _normalizeAngle(state.angle, angle); Duration duration = animation.duration ? *animation.duration : Duration::zero(); if (duration == Duration::zero()) { view.notifyMapChange(MapChangeRegionWillChange); - state.scale = scale; - state.x = x; - state.y = y; - const double s = state.scale * util::tileSize; - state.Bc = s / 360; - state.Cc = s / util::M2PI; - + state.setLatLngZoom(latLng, zoom); state.angle = angle; state.pitch = pitch; @@ -266,15 +236,18 @@ void Transform::_easeTo(const CameraOptions& camera, const AnimationOptions& ani view.notifyMapChange(MapChangeRegionDidChange); } else { view.notifyMapChange(MapChangeRegionWillChangeAnimated); - - const double startS = state.scale; - const double startA = state.angle; - const double startP = state.pitch; - const double startX = state.x; - const double startY = state.y; - state.panning = true; - state.scaling = true; - state.rotating = true; + + const double startWorldSize = state.worldSize(); + 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; + state.panning = latLng != startLatLng; + state.scaling = scale != startScale; + state.rotating = angle != startAngle; startTransition( [=](double t) { @@ -282,14 +255,24 @@ void Transform::_easeTo(const CameraOptions& camera, const AnimationOptions& ani return ease.solve(t, 0.001); }, [=](double t) { - state.scale = util::interpolate(startS, scale, t); - state.x = util::interpolate(startX, x, t); - state.y = util::interpolate(startY, y, t); - const double s = state.scale * util::tileSize; - state.Bc = s / 360; - state.Cc = s / util::M2PI; - state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI); - state.pitch = util::interpolate(startP, pitch, t); + // Calculate the current point and zoom level along the flight path. + PrecisionPoint framePoint = util::interpolate(startPoint, endPoint, t); + double frameScale = util::interpolate(startScale, scale, t); + + // Convert to geographic coordinates and set the new viewpoint. + LatLng frameLatLng = { + state.yLat(framePoint.y, startWorldSize), + state.xLng(framePoint.x, startWorldSize), + }; + state.setLatLngZoom(frameLatLng, state.scaleZoom(frameScale)); + + if (angle != startAngle) { + state.angle = util::wrap(util::interpolate(startAngle, angle, t), -M_PI, M_PI); + } + if (pitch != startPitch) { + state.pitch = util::interpolate(startPitch, pitch, t); + } + // At t = 1.0, a DidChangeAnimated notification should be sent from finish(). if (t < 1.0) { if (animation.transitionFrameFn) { @@ -321,17 +304,17 @@ void Transform::_easeTo(const CameraOptions& camera, const AnimationOptions& ani variable or function in van Wijk (2003). */ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &animation) { LatLng latLng = camera.center ? *camera.center : getLatLng(); - LatLng startLatLng = getLatLng(); double zoom = camera.zoom ? *camera.zoom : getZoom(); double angle = camera.angle ? *camera.angle : getAngle(); double pitch = camera.pitch ? *camera.pitch : getPitch(); - if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) { + if (!latLng || std::isnan(zoom)) { return; } // If a path crossing the antemeridian would be shorter, extend the final // coordinate so that interpolating between the two endpoints will cross it. + LatLng startLatLng = getLatLng(); if (std::abs(startLatLng.longitude) + std::abs(latLng.longitude) > 180) { if (startLatLng.longitude > 0 && latLng.longitude < 0) { latLng.longitude += 360; @@ -350,12 +333,11 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima }; zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom()); + pitch = util::clamp(pitch, 0., util::PITCH_MAX); // Minimize rotation by taking the shorter path around the circle. - double normalizedAngle = _normalizeAngle(angle, state.angle); - state.angle = _normalizeAngle(state.angle, normalizedAngle); - - pitch = util::clamp(pitch, 0., util::PITCH_MAX); + angle = _normalizeAngle(angle, state.angle); + state.angle = _normalizeAngle(state.angle, angle); const double startZoom = state.scaleZoom(state.scale); const double startAngle = state.angle; @@ -469,7 +451,7 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima double us = u(s); // Calculate the current point and zoom level along the flight path. - PrecisionPoint framePoint = startPoint + (endPoint - startPoint) * us; + PrecisionPoint framePoint = util::interpolate(startPoint, endPoint, us); double frameZoom = startZoom + state.scaleZoom(1 / w(s)); // Convert to geographic coordinates and set the new viewpoint. @@ -480,7 +462,7 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima state.setLatLngZoom(frameLatLng, frameZoom); if (angle != startAngle) { - state.angle = util::wrap(util::interpolate(startAngle, normalizedAngle, k), -M_PI, M_PI); + state.angle = util::wrap(util::interpolate(startAngle, angle, k), -M_PI, M_PI); } if (pitch != startPitch) { state.pitch = util::interpolate(startPitch, pitch, k); @@ -531,43 +513,35 @@ void Transform::rotateBy(const PrecisionPoint& first, const PrecisionPoint& seco const PrecisionPoint newFirst = first - center; const PrecisionPoint newSecond = second - center; const double ang = state.angle + util::angle_between(newFirst.x, newFirst.y, newSecond.x, newSecond.y); - - _setAngle(ang, duration); + + CameraOptions camera; + camera.angle = ang; + easeTo(camera, duration); } -void Transform::setAngle(double new_angle, const Duration& duration) { - if (std::isnan(new_angle)) { - return; - } - - _setAngle(new_angle, duration); +void Transform::setAngle(double angle, const Duration& duration) { + setAngle(angle, {NAN, NAN}, duration); } -void Transform::setAngle(double new_angle, const PrecisionPoint& center) { - if (std::isnan(new_angle) || !center) { +void Transform::setAngle(double angle, const PrecisionPoint& center, const Duration& duration) { + if (std::isnan(angle)) { return; } LatLng rotationCenter; - if (center.x > 0 || center.y > 0) { + if (center) { rotationCenter = state.pointToLatLng(center); - setLatLng(rotationCenter, Duration::zero()); + setLatLng(rotationCenter); } - - _setAngle(new_angle); - - if (center.x > 0 && center.y > 0) { - setLatLng(rotationCenter, center, Duration::zero()); - } -} - -void Transform::_setAngle(double new_angle, const Duration& duration) { + CameraOptions camera; - camera.angle = new_angle; - AnimationOptions animation; - animation.duration = duration; - easeTo(camera, animation); + camera.angle = angle; + easeTo(camera, duration); + + if (center) { + setLatLng(rotationCenter, center); + } } double Transform::getAngle() const { @@ -583,9 +557,7 @@ void Transform::setPitch(double pitch, const Duration& duration) { CameraOptions camera; camera.pitch = pitch; - AnimationOptions animation; - animation.duration = duration; - easeTo(camera, animation); + easeTo(camera, duration); } double Transform::getPitch() const { diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 7e0e85c2b..52e3b3e62 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -25,10 +25,10 @@ public: bool resize(std::array size); void jumpTo(const CameraOptions&); - void easeTo(const CameraOptions&, const AnimationOptions&); + void easeTo(const CameraOptions&, const AnimationOptions& = {}); /** Smoothly zoom out, pan, and zoom back into the given camera along a great circle, as though the viewer is aboard a supersonic jetcopter. */ - void flyTo(const CameraOptions&, const AnimationOptions&); + void flyTo(const CameraOptions&, const AnimationOptions& = {}); // Position void moveBy(const PrecisionPoint&, const Duration& = Duration::zero()); @@ -38,8 +38,8 @@ public: LatLng getLatLng() const { return state.getLatLng(); } // Zoom - void scaleBy(double ds, const PrecisionPoint& center = { 0, 0 }, const Duration& = Duration::zero()); - void setScale(double scale, const PrecisionPoint& center = { 0, 0 }, const Duration& = Duration::zero()); + void scaleBy(double ds, const PrecisionPoint& center = {NAN, NAN}, const Duration& = Duration::zero()); + void setScale(double scale, const PrecisionPoint& center = {NAN, NAN}, const Duration& = Duration::zero()); void setZoom(double zoom, const Duration& = Duration::zero()); double getZoom() const; double getScale() const; @@ -47,7 +47,7 @@ public: // Angle void rotateBy(const PrecisionPoint& first, const PrecisionPoint& second, const Duration& = Duration::zero()); void setAngle(double angle, const Duration& = Duration::zero()); - void setAngle(double angle, const PrecisionPoint& center); + void setAngle(double angle, const PrecisionPoint& center, const Duration& = Duration::zero()); double getAngle() const; // Pitch @@ -74,11 +74,6 @@ public: bool isPanning() const { return state.isPanning(); } private: - void _moveBy(const PrecisionPoint&, const Duration& = Duration::zero()); - void _setScale(double scale, const PrecisionPoint& center, const Duration& = Duration::zero()); - void _easeTo(const CameraOptions&, const AnimationOptions&, double new_scale, double new_angle, double xn, double yn); - void _setAngle(double angle, const Duration& = Duration::zero()); - View &view; TransformState state; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 2a594cef1..376424c74 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -248,8 +248,8 @@ double TransformState::scaleZoom(double s) const { return ::log2(s); } -float TransformState::worldSize() const { - return util::tileSize * scale; +double TransformState::worldSize() const { + return scale * util::tileSize; } PrecisionPoint TransformState::latLngToPoint(const LatLng& latLng) const { diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 9ae2f62a4..2aa031fa1 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -96,7 +96,7 @@ private: double latY(double lat) const; double zoomScale(double zoom) const; double scaleZoom(double scale) const; - float worldSize() const; + double worldSize() const; mat4 coordinatePointMatrix(double z) const; mat4 getPixelMatrix() const;