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

@@ -65,6 +65,44 @@ namespace mbgl {
* Steps 3 and 4 are done in the shaders for all labels.
*/
void getTileSkewVectors(const TransformState& state, vec2& vecEast, vec2& vecSouth) {
const double cosRoll = cos(state.getRoll());
const double sinRoll = sin(state.getRoll());
const double cosPitch = cos(state.getPitch());
const double cosBearing = cos(-state.getBearing());
const double sinBearing = sin(-state.getBearing());
vecSouth[0] = -cosBearing * cosPitch * sinRoll - sinBearing * cosRoll;
vecSouth[1] = -sinBearing * cosPitch * sinRoll + cosBearing * cosRoll;
const double vecSouthLen = std::hypot(vecSouth[0], vecSouth[1]);
if (vecSouthLen < 1.0e-9) {
vecSouth = {0, 0};
} else {
vecSouth[0] /= vecSouthLen;
vecSouth[1] /= vecSouthLen;
}
vecEast[0] = cosBearing * cosPitch * cosRoll - sinBearing * sinRoll;
vecEast[1] = sinBearing * cosPitch * cosRoll + cosBearing * sinRoll;
const double vecEastLen = std::hypot(vecEast[0], vecEast[1]);
if (vecEastLen < 1.0e-9) {
vecEast = {0, 0};
} else {
vecEast[0] /= vecEastLen;
vecEast[1] /= vecEastLen;
}
}
mat4 getTileSkewMatrix(const TransformState& state) {
vec2 vecEast, vecSouth;
getTileSkewVectors(state, vecEast, vecSouth);
mat4 m;
matrix::identity(m);
m[0] = vecEast[0];
m[1] = vecEast[1];
m[4] = vecSouth[0];
m[5] = vecSouth[1];
return m;
}
/*
* Returns a matrix for converting from tile units to the correct label coordinate space.
*/
@@ -78,7 +116,9 @@ mat4 getLabelPlaneMatrix(const mat4& posMatrix,
if (pitchWithMap) {
matrix::scale(m, m, 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1);
if (!rotateWithMap) {
matrix::rotate_z(m, m, state.getBearing());
mat4 skew = getTileSkewMatrix(state);
matrix::invert(skew, skew);
matrix::multiply(m, m, skew);
}
} else {
matrix::scale(m, m, state.getSize().width / 2.0, -(state.getSize().height / 2.0), 1.0);
@@ -102,7 +142,7 @@ mat4 getGlCoordMatrix(const mat4& posMatrix,
matrix::multiply(m, m, posMatrix);
matrix::scale(m, m, pixelsToTileUnits, pixelsToTileUnits, 1);
if (!rotateWithMap) {
matrix::rotate_z(m, m, -state.getBearing());
matrix::multiply(m, m, getTileSkewMatrix(state));
}
} else {
matrix::scale(m, m, 1, -1, 1);

View File

@@ -62,6 +62,7 @@ void Transform::resize(const Size size) {
double scale{state.getScale()};
double x{state.getX()};
double y{state.getY()};
double z{state.getZ()};
double lat;
double lon;
@@ -80,7 +81,7 @@ void Transform::resize(const Size size) {
return;
}
state.constrain(scale, x, y);
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z));
observer.onCameraDidChange(MapObserver::CameraChangeMode::Immediate);
}
@@ -125,8 +126,11 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
double fov = camera.fov ? util::deg2rad(*camera.fov) : getFieldOfView();
double centerAlt = camera.centerAltitude.value_or(state.getCenterAltitude());
double roll = camera.roll ? util::deg2rad(*camera.roll) : getRoll();
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch)) {
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || std::isnan(roll) || std::isnan(fov)) {
if (animation.transitionFinishFn) {
animation.transitionFinishFn();
}
@@ -146,6 +150,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
startLatLng.unwrapForShortestPath(latLng);
}
}
const double startCenterAlt = state.getCenterAltitude();
const Point<double> startPoint = Projection::project(startLatLng, state.getScale());
const Point<double> endPoint = Projection::project(latLng, state.getScale());
@@ -153,6 +158,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
pitch = util::clamp(pitch, state.getMinPitch(), state.getMaxPitch());
fov = util::clamp(fov, state.getMinFieldOfView(), state.getMaxFieldOfView());
// Minimize rotation by taking the shorter path around the circle.
bearing = _normalizeAngle(bearing, state.getBearing());
@@ -161,6 +167,8 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
const double startZoom = state.getZoom();
const double startBearing = state.getBearing();
const double startPitch = state.getPitch();
const double startRoll = state.getRoll();
const double startFov = state.getFieldOfView();
state.setProperties(TransformStateProperties()
.withPanningInProgress(unwrappedLatLng != startLatLng)
.withScalingInProgress(zoom != startZoom)
@@ -175,6 +183,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
LatLng frameLatLng = Projection::unproject(framePoint, state.zoomScale(startZoom));
double frameZoom = util::interpolate(startZoom, zoom, t);
state.setLatLngZoom(frameLatLng, frameZoom);
state.setCenterAltitude(util::interpolate(startCenterAlt, centerAlt, t));
if (bearing != startBearing) {
state.setBearing(util::wrap(util::interpolate(startBearing, bearing, t), -pi, pi));
}
@@ -190,6 +199,12 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
if (pitch != startPitch || maxPitch < startPitch) {
state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, t)));
}
if (roll != startRoll) {
state.setRoll(util::interpolate(startRoll, roll, t));
}
if (fov != startFov) {
state.setFieldOfView(util::interpolate(startFov, fov, t));
}
},
duration);
}
@@ -212,11 +227,14 @@ void Transform::flyTo(const CameraOptions& inputCamera,
const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets());
const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped();
const double centerAlt = camera.centerAltitude.value_or(state.getCenterAltitude());
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
double roll = camera.roll ? util::deg2rad(*camera.roll) : getRoll();
double fov = camera.fov ? util::deg2rad(*camera.fov) : getFieldOfView();
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || state.getSize().isEmpty()) {
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || std::isnan(roll) || std::isnan(fov) ||
state.getSize().isEmpty()) {
if (animation.transitionFinishFn) {
animation.transitionFinishFn();
}
@@ -226,6 +244,7 @@ void Transform::flyTo(const CameraOptions& inputCamera,
// Determine endpoints.
LatLng startLatLng = getLatLng(LatLng::Unwrapped).wrapped();
startLatLng.unwrapForShortestPath(latLng);
const double startCenterAlt = state.getCenterAltitude();
const Point<double> startPoint = Projection::project(startLatLng, state.getScale());
const Point<double> endPoint = Projection::project(latLng, state.getScale());
@@ -233,6 +252,7 @@ void Transform::flyTo(const CameraOptions& inputCamera,
// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
pitch = util::clamp(pitch, state.getMinPitch(), state.getMaxPitch());
fov = util::clamp(fov, state.getMinFieldOfView(), state.getMaxFieldOfView());
// Minimize rotation by taking the shorter path around the circle.
bearing = _normalizeAngle(bearing, state.getBearing());
@@ -240,6 +260,8 @@ void Transform::flyTo(const CameraOptions& inputCamera,
const double startZoom = state.scaleZoom(state.getScale());
const double startBearing = state.getBearing();
const double startPitch = state.getPitch();
const double startRoll = state.getRoll();
const double startFov = state.getFieldOfView();
/// w₀: Initial visible span, measured in pixels at the initial scale.
/// Known henceforth as a <i>screenful</i>.
@@ -353,6 +375,7 @@ void Transform::flyTo(const CameraOptions& inputCamera,
// Convert to geographic coordinates and set the new viewpoint.
LatLng frameLatLng = Projection::unproject(framePoint, startScale);
state.setLatLngZoom(frameLatLng, frameZoom);
state.setCenterAltitude(util::interpolate(startCenterAlt, centerAlt, us));
if (bearing != startBearing) {
state.setBearing(util::wrap(util::interpolate(startBearing, bearing, k), -pi, pi));
}
@@ -364,10 +387,15 @@ void Transform::flyTo(const CameraOptions& inputCamera,
util::interpolate(startEdgeInsets.bottom(), padding.bottom(), k),
util::interpolate(startEdgeInsets.right(), padding.right(), k)});
}
double maxPitch = getMaxPitchForEdgeInsets(state.getEdgeInsets());
if (pitch != startPitch || maxPitch < startPitch) {
state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, k)));
if (pitch != startPitch) {
state.setPitch(util::interpolate(startPitch, pitch, k));
}
if (roll != startRoll) {
state.setPitch(util::interpolate(startRoll, roll, k));
}
if (fov != startFov) {
state.setFieldOfView(util::interpolate(startFov, fov, k));
}
},
duration);
@@ -472,6 +500,14 @@ double Transform::getPitch() const {
return state.getPitch();
}
double Transform::getRoll() const {
return state.getRoll();
}
double Transform::getFieldOfView() const {
return state.getFieldOfView();
}
// MARK: - North Orientation
void Transform::setNorthOrientation(NorthOrientation orientation) {
@@ -479,8 +515,9 @@ void Transform::setNorthOrientation(NorthOrientation orientation) {
double scale{state.getScale()};
double x{state.getX()};
double y{state.getY()};
double z{state.getZ()};
state.constrain(scale, x, y);
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z));
}
NorthOrientation Transform::getNorthOrientation() const {
@@ -494,8 +531,9 @@ void Transform::setConstrainMode(mbgl::ConstrainMode mode) {
double scale{state.getScale()};
double x{state.getX()};
double y{state.getY()};
double z{state.getZ()};
state.constrain(scale, x, y);
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z));
}
ConstrainMode Transform::getConstrainMode() const {
@@ -676,22 +714,16 @@ LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng
double Transform::getMaxPitchForEdgeInsets(const EdgeInsets& insets) const {
double centerOffsetY = 0.5 * (insets.top() - insets.bottom()); // See TransformState::getCenterOffset.
if (centerOffsetY == 0.0) {
return state.getMaxPitch();
}
const auto height = state.getSize().height;
assert(height);
// For details, see description at
// https://github.com/mapbox/mapbox-gl-native/pull/15195 The definition of
// half of TransformState::fov with no inset, is: fov = arctan((height / 2)
// / (height * 1.5)). We use half of fov, as it is field of view above
// perspective center. With inset, this angle changes and
// tangentOfFovAboveCenterAngle = (h/2 + centerOffsetY) / (height
// * 1.5). 1.03 is a bit extra added to prevent parallel ground to viewport
// clipping plane.
const double tangentOfFovAboveCenterAngle = 1.03 * (height / 2.0 + centerOffsetY) / (1.5 * height);
// Half of fov is the field of view above perspective center.
const double tangentOfFovAboveCenterAngle = (0.5 + centerOffsetY / height) * 2.0 * tan(getFieldOfView() / 2.0);
const double fovAboveCenter = std::atan(tangentOfFovAboveCenterAngle);
return pi * 0.5 - fovAboveCenter;
// e.g. Maximum pitch of 60 degrees is when perspective center's offset from
// the top is 84% of screen height.
return state.getMaxPitch() + getFieldOfView() / 2.0 - fovAboveCenter;
}
FreeCameraOptions Transform::getFreeCameraOptions() const {

View File

@@ -91,6 +91,8 @@ public:
// Pitch
double getPitch() const;
double getRoll() const;
double getFieldOfView() const;
// North Orientation
void setNorthOrientation(NorthOrientation);

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;

View File

@@ -29,10 +29,18 @@ struct TransformStateProperties {
y = val;
return *this;
}
TransformStateProperties& withZ(const std::optional<double>& val) {
z = val;
return *this;
}
TransformStateProperties& withScale(const std::optional<double>& val) {
scale = val;
return *this;
}
TransformStateProperties& withFov(const std::optional<double>& val) {
fov = val;
return *this;
}
TransformStateProperties& withBearing(const std::optional<double>& val) {
bearing = val;
return *this;
@@ -41,6 +49,10 @@ struct TransformStateProperties {
pitch = val;
return *this;
}
TransformStateProperties& withRoll(const std::optional<double>& val) {
roll = val;
return *this;
}
TransformStateProperties& withXSkew(const std::optional<double>& val) {
xSkew = val;
return *this;
@@ -92,9 +104,12 @@ struct TransformStateProperties {
std::optional<double> x;
std::optional<double> y;
std::optional<double> bearing;
std::optional<double> z;
std::optional<double> fov;
std::optional<double> scale;
std::optional<double> bearing;
std::optional<double> pitch;
std::optional<double> roll;
std::optional<double> xSkew;
std::optional<double> ySkew;
std::optional<bool> axonometric;
@@ -147,6 +162,7 @@ public:
// Position
LatLng getLatLng(LatLng::WrapMode = LatLng::Unwrapped) const;
double getCenterAltitude() const;
double pixel_x() const;
double pixel_y() const;
@@ -164,6 +180,8 @@ public:
void setX(double);
double getY() const;
void setY(double);
double getZ() const;
void setZ(double);
// Bounds
void setLatLngBounds(LatLngBounds);
@@ -176,14 +194,19 @@ public:
double getMinPitch() const;
void setMaxPitch(double);
double getMaxPitch() const;
double getMinFieldOfView() const;
double getMaxFieldOfView() const;
// Rotation
double getBearing() const;
void setBearing(double);
float getFieldOfView() const;
void setFieldOfView(double);
float getCameraToCenterDistance() const;
double getPitch() const;
void setPitch(double);
double getRoll() const;
void setRoll(double);
double getXSkew() const;
void setXSkew(double);
@@ -222,6 +245,7 @@ public:
point on screen. */
void moveLatLng(const LatLng&, const ScreenCoordinate&);
void setLatLngZoom(const LatLng& latLng, double zoom);
void setCenterAltitude(double alt_m);
void constrain(double& scale, double& x, double& y) const;
bool constrainScreen(double& scale_, double& x_, double& y_) const;
@@ -248,7 +272,9 @@ private:
// Limit the amount of pitch
double minPitch = util::PITCH_MIN;
double maxPitch = util::PITCH_MAX;
double maxPitch = util::DEFAULT_PITCH_MAX;
double minFov = util::deg2rad(0.1);
double maxFov = util::deg2rad(150.0);
NorthOrientation orientation = NorthOrientation::Upwards;
@@ -283,15 +309,12 @@ private:
bool gestureInProgress = false;
// map position
double x = 0, y = 0;
double x = 0, y = 0, z = 0;
double bearing = 0;
double scale = 1;
// This fov value is somewhat arbitrary. The altitude of the camera used
// to be defined as 1.5 screen heights above the ground, which was an
// arbitrary choice. This is the fov equivalent to that value calculated
// with: `fov = 2 * arctan((height / 2) / (height * 1.5))`
double fov = 0.6435011087932844;
double fov = util::DEFAULT_FOV;
double pitch = 0.0;
double roll = 0.0;
double xSkew = 0.0;
double ySkew = 1.0;
bool axonometric = false;

View File

@@ -69,6 +69,13 @@ StyleParseResult Parser::parse(const std::string& json) {
}
}
if (document.HasMember("centerAltitude")) {
const JSValue& value = document["centerAltitude"];
if (value.IsNumber()) {
centerAltitude = value.GetDouble();
}
}
if (document.HasMember("zoom")) {
const JSValue& value = document["zoom"];
if (value.IsNumber()) {
@@ -90,6 +97,13 @@ StyleParseResult Parser::parse(const std::string& json) {
}
}
if (document.HasMember("roll")) {
const JSValue& value = document["roll"];
if (value.IsNumber()) {
roll = value.GetDouble();
}
}
if (document.HasMember("transition")) {
parseTransition(document["transition"]);
}

View File

@@ -45,6 +45,8 @@ public:
double zoom = 0;
double bearing = 0;
double pitch = 0;
double roll = 0;
double centerAltitude = 0;
// Statically evaluate layer properties to determine what font stacks are used.
std::set<FontStack> fontStacks() const;

View File

@@ -115,9 +115,11 @@ void Style::Impl::parse(const std::string& json_) {
name = parser.name;
defaultCamera.center = parser.latLng;
defaultCamera.centerAltitude = parser.centerAltitude;
defaultCamera.zoom = parser.zoom;
defaultCamera.bearing = parser.bearing;
defaultCamera.pitch = parser.pitch;
defaultCamera.roll = parser.roll;
setLight(std::make_unique<Light>(parser.light));

View File

@@ -14,20 +14,6 @@ using namespace std::numbers;
namespace mbgl {
namespace {
double vec2Len(const vec2& v) noexcept {
return std::sqrt(v[0] * v[0] + v[1] * v[1]);
};
double vec2Dot(const vec2& a, const vec2& b) noexcept {
return a[0] * b[0] + a[1] * b[1];
};
vec2 vec2Scale(const vec2& v, double s) noexcept {
return vec2{{v[0] * s, v[1] * s}};
};
} // namespace
namespace util {
namespace {
@@ -66,12 +52,13 @@ vec3 toMercator(const LatLng& location, double altitudeMeters) noexcept {
altitudeMeters * pixelsPerMeter / worldSize}};
}
Quaternion orientationFromPitchBearing(double pitch, double bearing) noexcept {
Quaternion orientationFromRollPitchBearing(double roll, double pitch, double bearing) noexcept {
// Both angles have to be negated to achieve CW rotation around the axis of rotation
const Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing);
const Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch);
Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing);
Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch);
Quaternion rotRoll = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, roll);
return rotBearing.multiply(rotPitch);
return rotBearing.multiply(rotPitch).multiply(rotRoll);
}
void updateTransform(mat4& transform, const Quaternion& orientation) noexcept {
@@ -170,16 +157,24 @@ vec3 Camera::up() const noexcept {
return {{-column[0], -column[1], -column[2]}};
}
void Camera::getOrientation(double& pitch, double& bearing) const noexcept {
void Camera::getOrientation(double& roll, double& pitch, double& bearing) const noexcept {
const vec3 f = forward();
const vec3 r = right();
const vec3 u = up();
bearing = std::atan2(-r[1], r[0]);
pitch = std::atan2(std::sqrt(f[0] * f[0] + f[1] * f[1]), -f[2]);
const double f01 = std::sqrt(f[0] * f[0] + f[1] * f[1]);
pitch = std::atan2(f01, -f[2]);
if (f01 > 1.0e-3) {
bearing = -std::atan2(f[0], -f[1]);
roll = -std::atan2(r[2], u[2]);
} else {
bearing = std::atan2(-r[1], r[0]);
roll = 0.0;
}
}
void Camera::setOrientation(double pitch, double bearing) noexcept {
orientation = orientationFromPitchBearing(pitch, bearing);
void Camera::setOrientation(double roll, double pitch, double bearing) noexcept {
orientation = orientationFromRollPitchBearing(roll, pitch, bearing);
updateTransform(transform, orientation);
}
@@ -193,32 +188,27 @@ void Camera::setPosition(const vec3& position) noexcept {
}
std::optional<Quaternion> Camera::orientationFromFrame(const vec3& forward, const vec3& up) noexcept {
vec3 upVector = up;
const vec2 xyForward = {{forward[0], forward[1]}};
vec2 xyUp = {{up[0], up[1]}};
const double epsilon = 1e-15;
// Remove roll-component of the resulting orientation by projecting
// the up-vector to the forward vector on xy-plane
if (vec2Len(xyForward) >= epsilon) {
const vec2 xyDir = vec2Scale(xyForward, 1.0 / vec2Len(xyForward));
xyUp = vec2Scale(xyDir, vec2Dot(xyUp, xyDir));
upVector[0] = xyUp[0];
upVector[1] = xyUp[1];
}
const vec3 right = vec3Cross(upVector, forward);
const vec3 right = vec3Cross(up, forward);
if (vec3Length(right) < epsilon) {
return std::nullopt;
}
const double bearing = std::atan2(-right[1], right[0]);
const double pitch = std::atan2(std::sqrt(forward[0] * forward[0] + forward[1] * forward[1]), -forward[2]);
const double f01 = std::sqrt(forward[0] * forward[0] + forward[1] * forward[1]);
const double pitch = std::atan2(f01, -forward[2]);
double bearing;
double roll;
if (f01 > 1.0e-3) {
bearing = -std::atan2(forward[0], -forward[1]);
roll = -std::atan2(right[2], up[2]);
} else {
bearing = std::atan2(-right[1], right[0]);
roll = 0.0;
}
return util::orientationFromPitchBearing(pitch, bearing);
return util::orientationFromRollPitchBearing(roll, pitch, bearing);
}
} // namespace util
@@ -258,14 +248,16 @@ void FreeCameraOptions::lookAtPoint(const LatLng& location, const std::optional<
// Flip z-component of the up vector if it's pointing downwards
up[2] = std::abs(up[2]);
const auto newOrientation = util::Camera::orientationFromFrame(forward, up);
if (newOrientation) {
orientation = newOrientation.value().m;
auto q = util::Camera::orientationFromFrame(forward, up);
if (q) {
orientation = q->m;
}
}
void FreeCameraOptions::setPitchBearing(double pitch, double bearing) noexcept {
orientation = util::orientationFromPitchBearing(util::deg2rad(pitch), util::deg2rad(bearing)).m;
void FreeCameraOptions::setRollPitchBearing(double roll, double pitch, double bearing) noexcept {
orientation =
util::orientationFromRollPitchBearing(util::deg2rad(roll), util::deg2rad(pitch), util::deg2rad(bearing)).m;
}
} // namespace mbgl

View File

@@ -25,14 +25,13 @@ public:
vec3 up() const noexcept;
const Quaternion& getOrientation() const noexcept { return orientation; }
void getOrientation(double& pitch, double& bearing) const noexcept;
void setOrientation(double pitch, double bearing) noexcept;
void getOrientation(double& roll, double& pitch, double& bearing) const noexcept;
void setOrientation(double roll, double pitch, double bearing) noexcept;
void setOrientation(const Quaternion& orientation_) noexcept;
void setPosition(const vec3& position) noexcept;
// Computes orientation using forward and up vectors of the provided
// coordinate frame. Only bearing and pitch components will be used. Does
// not return a value if input is invalid
// coordinate frame. Does not return a value if input is invalid
static std::optional<Quaternion> orientationFromFrame(const vec3& forward, const vec3& up) noexcept;
private: