core, android: add support for camera roll (#3011)
This commit is contained in:
@@ -15,6 +15,14 @@ using namespace std::numbers;
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
/*
|
||||
* The maximum angle to use for the Mercator horizon. This must be less than 90
|
||||
* to prevent errors in `MercatorTransform::_calcMatrices()`. It shouldn't be too close
|
||||
* to 90, or the distance to the horizon will become very large, unnecessarily increasing
|
||||
* the number of tiles needed to render the map.
|
||||
*/
|
||||
const double maxMercatorHorizonAngle = util::deg2rad(89.25);
|
||||
|
||||
namespace {
|
||||
LatLng latLngFromMercator(Point<double> mercatorCoordinate, LatLng::WrapMode wrapMode = LatLng::WrapMode::Unwrapped) {
|
||||
return {util::rad2deg(2 * std::atan(std::exp(pi - mercatorCoordinate.y * 2 * pi)) - (pi / 2)),
|
||||
@@ -46,15 +54,24 @@ void TransformState::setProperties(const TransformStateProperties& properties) {
|
||||
if (properties.y) {
|
||||
setY(*properties.y);
|
||||
}
|
||||
if (properties.z) {
|
||||
setZ(*properties.z);
|
||||
}
|
||||
if (properties.scale) {
|
||||
setScale(*properties.scale);
|
||||
}
|
||||
if (properties.bearing) {
|
||||
setBearing(*properties.bearing);
|
||||
}
|
||||
if (properties.fov) {
|
||||
setFieldOfView(*properties.fov);
|
||||
}
|
||||
if (properties.pitch) {
|
||||
setPitch(*properties.pitch);
|
||||
}
|
||||
if (properties.roll) {
|
||||
setRoll(*properties.roll);
|
||||
}
|
||||
if (properties.xSkew) {
|
||||
setXSkew(*properties.xSkew);
|
||||
}
|
||||
@@ -116,6 +133,9 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
|
||||
const double cameraToCenterDistance = getCameraToCenterDistance();
|
||||
const ScreenCoordinate offset = getCenterOffset();
|
||||
|
||||
const double limitedPitch = util::clamp(getPitch(), 0.0, maxMercatorHorizonAngle);
|
||||
const double cameraToSeaLevelDistance = cameraToCenterDistance + std::abs(z) / std::cos(limitedPitch);
|
||||
|
||||
// Find the Z distance from the viewport center point
|
||||
// [width/2 + offset.x, height/2 + offset.y] to the top edge; to point
|
||||
// [width/2 + offset.x, 0] in Z units.
|
||||
@@ -123,12 +143,13 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
|
||||
// (the distance between[width/2, height/2] and [width/2 + 1, height/2])
|
||||
// See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details.
|
||||
// See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)).
|
||||
const double tanFovAboveCenter =
|
||||
((size.height - frustumOffset.top()) * 0.5 + (offset.y - (frustumOffset.top() / 2))) / (size.height * 1.5);
|
||||
const double tanMultiple = tanFovAboveCenter * std::tan(getPitch());
|
||||
const double tanFovAboveCenter = (0.5 + (offset.y - frustumOffset.top()) / size.height) * 2.0 *
|
||||
std::tan(fov / 2.0) *
|
||||
(std::abs(std::cos(roll)) + std::abs(std::sin(roll)) * size.width / size.height);
|
||||
const double tanMultiple = util::clamp(tanFovAboveCenter * std::tan(limitedPitch), 0.0, 0.99);
|
||||
assert(tanMultiple < 1);
|
||||
// Calculate z distance of the farthest fragment that should be rendered.
|
||||
const double furthestDistance = cameraToCenterDistance / (1 - tanMultiple);
|
||||
const double furthestDistance = cameraToSeaLevelDistance / (1 - tanMultiple);
|
||||
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
|
||||
const double farZ = furthestDistance * 1.01;
|
||||
|
||||
@@ -208,13 +229,13 @@ void TransformState::updateCameraState() const {
|
||||
const double dy = 0.5 * worldSize - y;
|
||||
|
||||
// Set camera orientation and move it to a proper distance from the map
|
||||
camera.setOrientation(getPitch(), getBearing());
|
||||
camera.setOrientation(getRoll(), getPitch(), getBearing());
|
||||
|
||||
const vec3 forward = camera.forward();
|
||||
const vec3 orbitPosition = {{-forward[0] * cameraToCenterDistance,
|
||||
-forward[1] * cameraToCenterDistance,
|
||||
-forward[2] * cameraToCenterDistance}};
|
||||
vec3 cameraPosition = {{dx + orbitPosition[0], dy + orbitPosition[1], orbitPosition[2]}};
|
||||
vec3 cameraPosition = {{dx + orbitPosition[0], dy + orbitPosition[1], z + orbitPosition[2]}};
|
||||
|
||||
cameraPosition[0] /= worldSize;
|
||||
cameraPosition[1] /= worldSize;
|
||||
@@ -230,26 +251,39 @@ void TransformState::updateStateFromCamera() {
|
||||
const double dx = forward[0];
|
||||
const double dy = forward[1];
|
||||
const double dz = forward[2];
|
||||
assert(position[2] > 0.0 && dz < 0.0);
|
||||
|
||||
// Compute bearing and pitch
|
||||
double newBearing;
|
||||
double newPitch;
|
||||
camera.getOrientation(newPitch, newBearing);
|
||||
double newRoll;
|
||||
camera.getOrientation(newRoll, newPitch, newBearing);
|
||||
newPitch = util::clamp(newPitch, minPitch, maxPitch);
|
||||
|
||||
// Compute zoom level from the camera altitude
|
||||
const double centerDistance = getCameraToCenterDistance();
|
||||
const double zoom = util::log2(centerDistance / (position[2] / std::cos(newPitch) * util::tileSize_D));
|
||||
const double newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
double zoom;
|
||||
double newScale;
|
||||
double travel;
|
||||
if (dz < -1.0e-9 && position[2] > 1.0e-9 && newPitch <= maxMercatorHorizonAngle) {
|
||||
zoom = util::log2(centerDistance / (position[2] / std::cos(newPitch) * util::tileSize_D));
|
||||
newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
travel = -position[2] / dz;
|
||||
} else {
|
||||
zoom = 14;
|
||||
newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
travel = centerDistance / newScale / util::tileSize_D;
|
||||
}
|
||||
|
||||
// Compute center point of the map
|
||||
const double travel = -position[2] / dz;
|
||||
const Point<double> mercatorPoint = {position[0] + dx * travel, position[1] + dy * travel};
|
||||
|
||||
setLatLngZoom(latLngFromMercator(mercatorPoint), scaleZoom(newScale));
|
||||
|
||||
const double mercatorZ = position[2] + dz * travel;
|
||||
double alt_m = mercatorZ * Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), 0) * util::tileSize_D;
|
||||
setCenterAltitude(alt_m);
|
||||
setBearing(newBearing);
|
||||
setPitch(newPitch);
|
||||
setRoll(newRoll);
|
||||
}
|
||||
|
||||
FreeCameraOptions TransformState::getFreeCameraOptions() const {
|
||||
@@ -291,11 +325,6 @@ bool TransformState::setCameraOrientation(const Quaternion& orientation_) {
|
||||
const vec3 forward = unitQuat.transform({{0.0, 0.0, -1.0}});
|
||||
const vec3 up = unitQuat.transform({{0.0, -1.0, 0.0}});
|
||||
|
||||
if (up[2] < 0.0) {
|
||||
// Camera is upside down and not recoverable
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<Quaternion> updatedOrientation = util::Camera::orientationFromFrame(forward, up);
|
||||
if (!updatedOrientation) return false;
|
||||
|
||||
@@ -443,10 +472,13 @@ void TransformState::setViewportMode(ViewportMode val) {
|
||||
CameraOptions TransformState::getCameraOptions(const std::optional<EdgeInsets>& padding) const {
|
||||
return CameraOptions()
|
||||
.withCenter(getLatLng())
|
||||
.withCenterAltitude(getCenterAltitude())
|
||||
.withPadding(padding ? padding : edgeInsets)
|
||||
.withZoom(getZoom())
|
||||
.withBearing(util::rad2deg(-bearing))
|
||||
.withPitch(util::rad2deg(pitch));
|
||||
.withPitch(util::rad2deg(pitch))
|
||||
.withRoll(util::rad2deg(roll))
|
||||
.withFov(util::rad2deg(fov));
|
||||
}
|
||||
|
||||
// MARK: - EdgeInsets
|
||||
@@ -464,6 +496,10 @@ LatLng TransformState::getLatLng(LatLng::WrapMode wrapMode) const {
|
||||
return {util::rad2deg(2 * std::atan(std::exp(y / Cc)) - 0.5 * pi), -x / Bc, wrapMode};
|
||||
}
|
||||
|
||||
double TransformState::getCenterAltitude() const {
|
||||
return z * Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom());
|
||||
}
|
||||
|
||||
double TransformState::pixel_x() const {
|
||||
const double center = (size.width - Projection::worldSize(scale)) / 2;
|
||||
return center + x;
|
||||
@@ -554,6 +590,14 @@ double TransformState::getMaxPitch() const {
|
||||
return maxPitch;
|
||||
}
|
||||
|
||||
double TransformState::getMinFieldOfView() const {
|
||||
return minFov;
|
||||
}
|
||||
|
||||
double TransformState::getMaxFieldOfView() const {
|
||||
return maxFov;
|
||||
}
|
||||
|
||||
// MARK: - Scale
|
||||
double TransformState::getScale() const {
|
||||
return scale;
|
||||
@@ -590,6 +634,17 @@ void TransformState::setY(double val) {
|
||||
}
|
||||
}
|
||||
|
||||
double TransformState::getZ() const {
|
||||
return z;
|
||||
}
|
||||
|
||||
void TransformState::setZ(double val) {
|
||||
if (z != val) {
|
||||
z = val;
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rotation
|
||||
|
||||
double TransformState::getBearing() const {
|
||||
@@ -607,6 +662,24 @@ float TransformState::getFieldOfView() const {
|
||||
return static_cast<float>(fov);
|
||||
}
|
||||
|
||||
void TransformState::setFieldOfView(double val) {
|
||||
if (fov != val) {
|
||||
fov = val;
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
double TransformState::getRoll() const {
|
||||
return roll;
|
||||
}
|
||||
|
||||
void TransformState::setRoll(double val) {
|
||||
if (roll != val) {
|
||||
roll = val;
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
float TransformState::getCameraToCenterDistance() const {
|
||||
return static_cast<float>(0.5 * size.height / std::tan(fov / 2.0));
|
||||
}
|
||||
@@ -730,7 +803,7 @@ TileCoordinate TransformState::screenCoordinateToTileCoordinate(const ScreenCoor
|
||||
|
||||
double z0 = coord0[2] / w0;
|
||||
double z1 = coord1[2] / w1;
|
||||
double t = z0 == z1 ? 0 : (targetZ - z0) / (z1 - z0);
|
||||
double t = z0 <= z1 ? 0 : (targetZ - z0) / (z1 - z0);
|
||||
|
||||
Point<double> p = util::interpolate(p0, p1, t) / scale * static_cast<double>(1 << atZoom);
|
||||
return {.p = {p.x, p.y}, .z = static_cast<double>(atZoom)};
|
||||
@@ -957,6 +1030,11 @@ void TransformState::setLatLngZoom(const LatLng& latLng, double zoom) {
|
||||
setScalePoint(newScale, point);
|
||||
}
|
||||
|
||||
void TransformState::setCenterAltitude(double alt_m) {
|
||||
z = alt_m / Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom());
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
|
||||
void TransformState::setScalePoint(const double newScale, const ScreenCoordinate& point) {
|
||||
double constrainedScale = newScale;
|
||||
ScreenCoordinate constrainedPoint = point;
|
||||
|
||||
Reference in New Issue
Block a user