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;