[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:
committed by
Minh Nguyễn
parent
27b308a234
commit
d26b956a48
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user