[core] Cleaned up Transform

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.
This commit is contained in:
Minh Nguyễn
2016-01-10 17:28:21 -08:00
committed by Minh Nguyễn
parent 27b308a234
commit d26b956a48
7 changed files with 155 additions and 181 deletions

View File

@@ -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<void()> 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

View File

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

View File

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

View File

@@ -62,64 +62,29 @@ bool Transform::resize(const std::array<uint16_t, 2> 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 {

View File

@@ -25,10 +25,10 @@ public:
bool resize(std::array<uint16_t, 2> 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;

View File

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

View File

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