core, android: add support for camera roll (#3011)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -91,6 +91,8 @@ public:
|
||||
// Pitch
|
||||
|
||||
double getPitch() const;
|
||||
double getRoll() const;
|
||||
double getFieldOfView() const;
|
||||
|
||||
// North Orientation
|
||||
void setNorthOrientation(NorthOrientation);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user