core, android: add support for camera roll (#3011)

This commit is contained in:
Nathan Olson
2026-01-20 00:57:48 -08:00
committed by GitHub
parent a4cf4d3ffa
commit 10dbd5d68d
57 changed files with 1275 additions and 207 deletions

View File

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