summaryrefslogtreecommitdiff
path: root/src/mbgl/map/transform.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/map/transform.cpp')
-rw-r--r--src/mbgl/map/transform.cpp472
1 files changed, 472 insertions, 0 deletions
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
new file mode 100644
index 0000000000..d05d1f7446
--- /dev/null
+++ b/src/mbgl/map/transform.cpp
@@ -0,0 +1,472 @@
+#include <mbgl/map/transform.hpp>
+#include <mbgl/map/view.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/transition.hpp>
+#include <mbgl/platform/platform.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+const double D2R = M_PI / 180.0;
+const double M2PI = 2 * M_PI;
+
+Transform::Transform(View &view_)
+ : view(view_)
+{
+}
+
+#pragma mark - Map View
+
+bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio,
+ const uint16_t fb_w, const uint16_t fb_h) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ if (final.width != w || final.height != h || final.pixelRatio != ratio ||
+ final.framebuffer[0] != fb_w || final.framebuffer[1] != fb_h) {
+
+ view.notify_map_change(MapChangeRegionWillChange);
+
+ current.width = final.width = w;
+ current.height = final.height = h;
+ current.pixelRatio = final.pixelRatio = ratio;
+ current.framebuffer[0] = final.framebuffer[0] = fb_w;
+ current.framebuffer[1] = final.framebuffer[1] = fb_h;
+ constrain(current.scale, current.y);
+
+ view.notify_map_change(MapChangeRegionDidChange);
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#pragma mark - Position
+
+void Transform::moveBy(const double dx, const double dy, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _moveBy(dx, dy, duration);
+}
+
+void Transform::_moveBy(const double dx, const double dy, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ final.x = current.x + std::cos(current.angle) * dx + std::sin(current.angle) * dy;
+ final.y = current.y + std::cos(current.angle) * dy + std::sin(-current.angle) * dx;
+
+ constrain(final.scale, final.y);
+
+ if (duration == 0) {
+ current.x = final.x;
+ current.y = final.y;
+ } else {
+ // Use a common start time for all of the transitions to avoid divergent transitions.
+ timestamp start = util::now();
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
+ }
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+void Transform::setLonLat(const double lon, const double lat, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ const double f = std::fmin(std::fmax(std::sin(D2R * lat), -0.9999), 0.9999);
+ double xn = -lon * Bc;
+ double yn = 0.5 * Cc * std::log((1 + f) / (1 - f));
+
+ _setScaleXY(current.scale, xn, yn, duration);
+}
+
+void Transform::setLonLatZoom(const double lon, const double lat, const double zoom,
+ const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double new_scale = std::pow(2.0, zoom);
+
+ const double s = new_scale * util::tileSize;
+ Bc = s / 360;
+ Cc = s / (2 * M_PI);
+
+ const double f = std::fmin(std::fmax(std::sin(D2R * lat), -0.9999), 0.9999);
+ double xn = -lon * Bc;
+ double yn = 0.5 * Cc * log((1 + f) / (1 - f));
+
+ _setScaleXY(new_scale, xn, yn, duration);
+}
+
+void Transform::getLonLat(double &lon, double &lat) const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ final.getLonLat(lon, lat);
+}
+
+void Transform::getLonLatZoom(double &lon, double &lat, double &zoom) const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ getLonLat(lon, lat);
+ zoom = getZoom();
+}
+
+void Transform::startPanning() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearPanning();
+
+ // Add a 200ms timeout for resetting this to false
+ current.panning = true;
+ timestamp start = util::now();
+ pan_timeout = std::make_shared<util::timeout<bool>>(false, current.panning, start, 200_milliseconds);
+ transitions.emplace_front(pan_timeout);
+}
+
+void Transform::stopPanning() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearPanning();
+}
+
+void Transform::_clearPanning() {
+ current.panning = false;
+ if (pan_timeout) {
+ transitions.remove(pan_timeout);
+ pan_timeout.reset();
+ }
+}
+
+#pragma mark - Zoom
+
+void Transform::scaleBy(const double ds, const double cx, const double cy, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ // clamp scale to min/max values
+ double new_scale = current.scale * ds;
+ if (new_scale < min_scale) {
+ new_scale = min_scale;
+ } else if (new_scale > max_scale) {
+ new_scale = max_scale;
+ }
+
+ _setScale(new_scale, cx, cy, duration);
+}
+
+void Transform::setScale(const double scale, const double cx, const double cy,
+ const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setScale(scale, cx, cy, duration);
+}
+
+void Transform::setZoom(const double zoom, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setScale(std::pow(2.0, zoom), -1, -1, duration);
+}
+
+double Transform::getZoom() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return std::log(final.scale) / M_LN2;
+}
+
+double Transform::getScale() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final.scale;
+}
+
+void Transform::startScaling() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearScaling();
+
+ // Add a 200ms timeout for resetting this to false
+ current.scaling = true;
+ timestamp start = util::now();
+ scale_timeout = std::make_shared<util::timeout<bool>>(false, current.scaling, start, 200_milliseconds);
+ transitions.emplace_front(scale_timeout);
+}
+
+void Transform::stopScaling() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearScaling();
+}
+
+double Transform::getMinZoom() const {
+ double test_scale = current.scale;
+ double test_y = current.y;
+ constrain(test_scale, test_y);
+
+ return std::log2(std::fmin(min_scale, test_scale));
+}
+
+double Transform::getMaxZoom() const {
+ return std::log2(max_scale);
+}
+
+void Transform::_clearScaling() {
+ // This is only called internally, so we don't need a lock here.
+
+ current.scaling = false;
+ if (scale_timeout) {
+ transitions.remove(scale_timeout);
+ scale_timeout.reset();
+ }
+}
+
+void Transform::_setScale(double new_scale, double cx, double cy, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ // Ensure that we don't zoom in further than the maximum allowed.
+ if (new_scale < min_scale) {
+ new_scale = min_scale;
+ } else if (new_scale > max_scale) {
+ new_scale = max_scale;
+ }
+
+ // Zoom in on the center if we don't have click or gesture anchor coordinates.
+ if (cx < 0 || cy < 0) {
+ cx = current.width / 2;
+ cy = current.height / 2;
+ }
+
+ // Account for the x/y offset from the center (= where the user clicked or pinched)
+ const double factor = new_scale / current.scale;
+ const double dx = (cx - current.width / 2) * (1.0 - factor);
+ const double dy = (cy - current.height / 2) * (1.0 - factor);
+
+ // Account for angle
+ const double angle_sin = std::sin(-current.angle);
+ const double angle_cos = std::cos(-current.angle);
+ const double ax = angle_cos * dx - angle_sin * dy;
+ const double ay = angle_sin * dx + angle_cos * dy;
+
+ const double xn = current.x * factor + ax;
+ const double yn = current.y * factor + ay;
+
+ _setScaleXY(new_scale, xn, yn, duration);
+}
+
+void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
+ const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ final.scale = new_scale;
+ final.x = xn;
+ final.y = yn;
+
+ constrain(final.scale, final.y);
+
+ if (duration == 0) {
+ current.scale = final.scale;
+ current.x = final.x;
+ current.y = final.y;
+ } else {
+ // Use a common start time for all of the transitions to avoid divergent transitions.
+ timestamp start = util::now();
+ transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
+ current.scale, final.scale, current.scale, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
+ }
+
+ const double s = final.scale * util::tileSize;
+ Bc = s / 360;
+ Cc = s / (2 * M_PI);
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+#pragma mark - Constraints
+
+void Transform::constrain(double& scale, double& y) const {
+ // Constrain minimum zoom to avoid zooming out far enough to show off-world areas.
+ if (scale < (current.height / util::tileSize)) scale = (current.height / util::tileSize);
+
+ // Constrain min/max vertical pan to avoid showing off-world areas.
+ double max_y = ((scale * util::tileSize) - current.height) / 2;
+
+ if (y > max_y) y = max_y;
+ if (y < -max_y) y = -max_y;
+}
+
+#pragma mark - Angle
+
+void Transform::rotateBy(const double start_x, const double start_y, const double end_x,
+ const double end_y, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double center_x = current.width / 2, center_y = current.height / 2;
+
+ const double begin_center_x = start_x - center_x;
+ const double begin_center_y = start_y - center_y;
+
+ const double beginning_center_dist =
+ std::sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
+
+ // If the first click was too close to the center, move the center of rotation by 200 pixels
+ // in the direction of the click.
+ if (beginning_center_dist < 200) {
+ const double offset_x = -200, offset_y = 0;
+ const double rotate_angle = std::atan2(begin_center_y, begin_center_x);
+ const double rotate_angle_sin = std::sin(rotate_angle);
+ const double rotate_angle_cos = std::cos(rotate_angle);
+ center_x = start_x + rotate_angle_cos * offset_x - rotate_angle_sin * offset_y;
+ center_y = start_y + rotate_angle_sin * offset_x + rotate_angle_cos * offset_y;
+ }
+
+ const double first_x = start_x - center_x, first_y = start_y - center_y;
+ const double second_x = end_x - center_x, second_y = end_y - center_y;
+
+ const double ang = current.angle + util::angle_between(first_x, first_y, second_x, second_y);
+
+ _setAngle(ang, duration);
+}
+
+void Transform::setAngle(const double new_angle, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setAngle(new_angle, duration);
+}
+
+void Transform::setAngle(const double new_angle, const double cx, const double cy) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double dx = 0, dy = 0;
+
+ if (cx >= 0 && cy >= 0) {
+ dx = (final.width / 2) - cx;
+ dy = (final.height / 2) - cy;
+ _moveBy(dx, dy, 0);
+ }
+
+ _setAngle(new_angle, 0);
+
+ if (cx >= 0 && cy >= 0) {
+ _moveBy(-dx, -dy, 0);
+ }
+}
+
+void Transform::_setAngle(double new_angle, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ while (new_angle > M_PI)
+ new_angle -= M2PI;
+ while (new_angle <= -M_PI)
+ new_angle += M2PI;
+
+ final.angle = new_angle;
+
+ if (duration == 0) {
+ current.angle = final.angle;
+ } else {
+ timestamp start = util::now();
+ transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
+ current.angle, final.angle, current.angle, start, duration));
+ }
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+double Transform::getAngle() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final.angle;
+}
+
+void Transform::startRotating() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearRotating();
+
+ // Add a 200ms timeout for resetting this to false
+ current.rotating = true;
+ timestamp start = util::now();
+ rotate_timeout = std::make_shared<util::timeout<bool>>(false, current.rotating, start, 200_milliseconds);
+ transitions.emplace_front(rotate_timeout);
+}
+
+void Transform::stopRotating() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearRotating();
+}
+
+void Transform::_clearRotating() {
+ // This is only called internally, so we don't need a lock here.
+
+ current.rotating = false;
+ if (rotate_timeout) {
+ transitions.remove(rotate_timeout);
+ rotate_timeout.reset();
+ }
+}
+
+#pragma mark - Transition
+
+bool Transform::needsTransition() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return !transitions.empty();
+}
+
+void Transform::updateTransitions(const timestamp now) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ transitions.remove_if([now](const util::ptr<util::transition> &transition) {
+ return transition->update(now) == util::transition::complete;
+ });
+}
+
+void Transform::cancelTransitions() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ transitions.clear();
+}
+
+#pragma mark - Transform state
+
+const TransformState Transform::currentState() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return current;
+}
+
+const TransformState Transform::finalState() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final;
+}