[core] Consolidated zoom and angle anchor logic

Made anchor a CameraOption for easeTo().
This commit is contained in:
Minh Nguyễn
2016-01-13 00:47:50 -08:00
committed by Minh Nguyễn
parent a3d4aeefec
commit 8bad15bf87
6 changed files with 90 additions and 35 deletions

View File

@@ -17,6 +17,10 @@ struct CameraOptions {
/** Coordinate at the center of the map. */
mapbox::util::optional<LatLng> center;
/** Point of reference for `zoom` and `angle`, assuming an origin at the
top-left corner of the view. */
mapbox::util::optional<PrecisionPoint> anchor;
/** Zero-based zoom level. Constrained to the minimum and maximum zoom
levels. */
mapbox::util::optional<double> zoom;

View File

@@ -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 {

View File

@@ -100,6 +100,8 @@ public:
bool isPanning() const { return state.isPanning(); }
private:
void moveLatLng(const LatLng&, const PrecisionPoint&);
View &view;
TransformState state;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}