diff --git a/bin/render.cpp b/bin/render.cpp index 26e1fa11ba0..ab8fb586338 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -33,8 +33,11 @@ int main(int argc, char* argv[]) { args::ValueFlag lonValue(argumentParser, "degrees", "Longitude", {'x', "lon"}); args::ValueFlag latValue(argumentParser, "degrees", "Latitude", {'y', "lat"}); + args::ValueFlag altValue(argumentParser, "degrees", "Altitude", {'A', "alt"}); + args::ValueFlag fovValue(argumentParser, "degrees", "FOV", {'f', "fov"}); args::ValueFlag bearingValue(argumentParser, "degrees", "Bearing", {'b', "bearing"}); args::ValueFlag pitchValue(argumentParser, "degrees", "Pitch", {'p', "pitch"}); + args::ValueFlag rollValue(argumentParser, "degrees", "Roll", {'R', "roll"}); args::ValueFlag widthValue(argumentParser, "pixels", "Image width", {'w', "width"}); args::ValueFlag heightValue(argumentParser, "pixels", "Image height", {'h', "height"}); @@ -55,9 +58,12 @@ int main(int argc, char* argv[]) { const double lat = latValue ? args::get(latValue) : 0; const double lon = lonValue ? args::get(lonValue) : 0; + const double alt = altValue ? args::get(altValue) : 0; const double zoom = zoomValue ? args::get(zoomValue) : 0; + const double fov = fovValue ? args::get(fovValue) : 37; const double bearing = bearingValue ? args::get(bearingValue) : 0; const double pitch = pitchValue ? args::get(pitchValue) : 0; + const double roll = rollValue ? args::get(rollValue) : 0; const double pixelRatio = pixelRatioValue ? args::get(pixelRatioValue) : 1; const uint32_t width = widthValue ? args::get(widthValue) : 512; @@ -97,7 +103,14 @@ int main(int argc, char* argv[]) { } map.getStyle().loadURL(style); - map.jumpTo(CameraOptions().withCenter(LatLng{lat, lon}).withZoom(zoom).withBearing(bearing).withPitch(pitch)); + map.jumpTo(CameraOptions() + .withCenter(LatLng{lat, lon}) + .withCenterAltitude(alt) + .withZoom(zoom) + .withBearing(bearing) + .withPitch(pitch) + .withRoll(roll) + .withFov(fov)); if (debug) { map.setDebug(debug ? mbgl::MapDebugOptions::TileBorders | mbgl::MapDebugOptions::ParseStatus diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 3bdacf5e8be..5357647b3bc 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -21,6 +21,10 @@ struct CameraOptions { center = o; return *this; } + CameraOptions& withCenterAltitude(const std::optional& o) { + centerAltitude = o; + return *this; + } CameraOptions& withPadding(const std::optional& p) { padding = p; return *this; @@ -41,10 +45,21 @@ struct CameraOptions { pitch = o; return *this; } + CameraOptions& withRoll(const std::optional& o) { + roll = o; + return *this; + } + CameraOptions& withFov(const std::optional& o) { + fov = o; + return *this; + } /** Coordinate at the center of the map. */ std::optional center; + /** Altitude of the center of the map, in meters above sea level. */ + std::optional centerAltitude; + /** Padding around the interior of the view that affects the frame of reference for `center`. */ std::optional padding; @@ -63,11 +78,18 @@ struct CameraOptions { /** Pitch toward the horizon measured in degrees , with 0 deg resulting in a two-dimensional map. */ std::optional pitch; + + /** Camera roll, measured in degrees. */ + std::optional roll; + + /** Camera vertical field of view, measured in degrees. */ + std::optional fov; }; constexpr bool operator==(const CameraOptions& a, const CameraOptions& b) { return a.center == b.center && a.padding == b.padding && a.anchor == b.anchor && a.zoom == b.zoom && - a.bearing == b.bearing && a.pitch == b.pitch; + a.bearing == b.bearing && a.pitch == b.pitch && a.roll == b.roll && a.fov == b.fov && + a.centerAltitude == b.centerAltitude; } constexpr bool operator!=(const CameraOptions& a, const CameraOptions& b) { @@ -134,7 +156,6 @@ struct FreeCameraOptions { 0, 0] Orientation can be set freely but certain constraints still apply - - Orientation must be representable with only pitch and bearing. - Pitch has an upper limit */ std::optional orientation = std::nullopt; @@ -151,9 +172,9 @@ struct FreeCameraOptions { bearing can't be deduced from the viewing direction */ void lookAtPoint(const LatLng& location, const std::optional& upVector = std::nullopt) noexcept; - /** Helper function for setting the orientation of the camera as a pitch and - a bearing. Both values are in degrees */ - void setPitchBearing(double pitch, double bearing) noexcept; + /** Helper function for setting the orientation of the camera as a roll, pitch, and + a bearing. All values are in degrees */ + void setRollPitchBearing(double roll, double pitch, double bearing) noexcept; }; } // namespace mbgl diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index 51900f93ed1..33f91892956 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -32,7 +32,7 @@ constexpr double LATITUDE_MAX = 85.051128779806604; constexpr double LONGITUDE_MAX = 180; constexpr double DEGREES_MAX = 360; constexpr double PITCH_MIN = 0.0; -constexpr double PITCH_MAX = M_PI / 3; +constexpr double PITCH_MAX = M_PI; constexpr double MIN_ZOOM = 0.0; constexpr double MAX_ZOOM = 25.5; constexpr float MIN_ZOOM_F = MIN_ZOOM; diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 5f7fcfcf876..732c18667f1 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -60,8 +60,9 @@ void Transform::resize(const Size size) { 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)); observer.onCameraDidChange(MapObserver::CameraChangeMode::Immediate); } @@ -99,6 +100,9 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim double zoom = camera.zoom.value_or(getZoom()); 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 (animation.transitionFinishFn) { @@ -120,6 +124,7 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim startLatLng.unwrapForShortestPath(latLng); } } + const double startCenterAlt = state.getCenterAltitude(); const Point startPoint = Projection::project(startLatLng, state.getScale()); const Point endPoint = Projection::project(latLng, state.getScale()); @@ -127,6 +132,7 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim // 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()); @@ -135,6 +141,8 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim 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) @@ -149,6 +157,7 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim 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)); } @@ -160,9 +169,14 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim util::interpolate(startEdgeInsets.bottom(), padding.bottom(), t), util::interpolate(startEdgeInsets.right(), padding.right(), t)}); } - double maxPitch = getMaxPitchForEdgeInsets(state.getEdgeInsets()); - if (pitch != startPitch || maxPitch < startPitch) { - state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, t))); + if (pitch != startPitch) { + state.setPitch(util::interpolate(startPitch, pitch, t)); + } + if (roll != startRoll) { + state.setPitch(util::interpolate(startRoll, roll, t)); + } + if (fov != startFov) { + state.setFieldOfView(util::interpolate(startFov, fov, t)); } }, duration); @@ -179,9 +193,12 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& animation, bool linearZoomInterpolation) { 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 zoom = camera.zoom.value_or(getZoom()); 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 (animation.transitionFinishFn) { @@ -193,6 +210,7 @@ void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& anima // Determine endpoints. LatLng startLatLng = getLatLng(LatLng::Unwrapped).wrapped(); startLatLng.unwrapForShortestPath(latLng); + const double startCenterAlt = state.getCenterAltitude(); const Point startPoint = Projection::project(startLatLng, state.getScale()); const Point endPoint = Projection::project(latLng, state.getScale()); @@ -200,6 +218,7 @@ void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& anima // 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()); @@ -207,6 +226,8 @@ void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& anima 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 screenful. @@ -320,6 +341,7 @@ void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& anima // 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)); } @@ -331,10 +353,15 @@ void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& anima 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); @@ -431,6 +458,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) { @@ -438,8 +473,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 { @@ -453,8 +489,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 { @@ -646,7 +683,8 @@ double Transform::getMaxPitchForEdgeInsets(const EdgeInsets& insets) const { // 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); + const double tangentOfFovAboveCenterAngle = 1.03 * (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 diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index eac0e8fb99d..a8b6acd2835 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -77,6 +77,8 @@ class Transform : private util::noncopyable { // Pitch double getPitch() const; + double getRoll() const; + double getFieldOfView() const; // North Orientation void setNorthOrientation(NorthOrientation); diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index d00ef81fec6..a0ad723d5e1 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -46,15 +46,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); } @@ -120,7 +129,8 @@ 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 * 0.5 + offset.y) / (size.height * 1.5); + const double tanFovAboveCenter = (0.5 + offset.y / 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 = tanFovAboveCenter * std::tan(getPitch()); assert(tanMultiple < 1); // Calculate z distance of the farthest fragment that should be rendered. @@ -204,13 +214,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; @@ -231,7 +241,8 @@ void TransformState::updateStateFromCamera() { // 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 @@ -246,6 +257,7 @@ void TransformState::updateStateFromCamera() { setLatLngZoom(latLngFromMercator(mercatorPoint), scaleZoom(newScale)); setBearing(newBearing); setPitch(newPitch); + setRoll(newRoll); } FreeCameraOptions TransformState::getFreeCameraOptions() const { @@ -428,10 +440,13 @@ void TransformState::setViewportMode(ViewportMode val) { CameraOptions TransformState::getCameraOptions(const std::optional& 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 @@ -449,6 +464,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; @@ -539,6 +558,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; @@ -575,6 +602,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 { @@ -592,6 +630,24 @@ float TransformState::getFieldOfView() const { return static_cast(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(0.5 * size.height / std::tan(fov / 2.0)); } @@ -715,7 +771,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 = (z1 / z0 > 0.5) ? 0 : (targetZ - z0) / (z1 - z0); Point p = util::interpolate(p0, p1, t) / scale * static_cast(1 << atZoom); return {{p.x, p.y}, static_cast(atZoom)}; @@ -798,6 +854,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; diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 28384eb0ba5..be81f13e7ac 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -29,10 +29,18 @@ struct TransformStateProperties { y = val; return *this; } + TransformStateProperties& withZ(const std::optional& val) { + z = val; + return *this; + } TransformStateProperties& withScale(const std::optional& val) { scale = val; return *this; } + TransformStateProperties& withFov(const std::optional& val) { + fov = val; + return *this; + } TransformStateProperties& withBearing(const std::optional& val) { bearing = val; return *this; @@ -41,6 +49,10 @@ struct TransformStateProperties { pitch = val; return *this; } + TransformStateProperties& withRoll(const std::optional& val) { + roll = val; + return *this; + } TransformStateProperties& withXSkew(const std::optional& val) { xSkew = val; return *this; @@ -88,9 +100,12 @@ struct TransformStateProperties { std::optional x; std::optional y; - std::optional bearing; + std::optional z; + std::optional fov; std::optional scale; + std::optional bearing; std::optional pitch; + std::optional roll; std::optional xSkew; std::optional ySkew; std::optional axonometric; @@ -139,6 +154,7 @@ class TransformState { // Position LatLng getLatLng(LatLng::WrapMode = LatLng::Unwrapped) const; + double getCenterAltitude() const; double pixel_x() const; double pixel_y() const; @@ -156,6 +172,8 @@ class TransformState { void setX(double); double getY() const; void setY(double); + double getZ() const; + void setZ(double); // Bounds void setLatLngBounds(LatLngBounds); @@ -168,14 +186,19 @@ class TransformState { 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); @@ -214,6 +237,7 @@ class TransformState { 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; const mat4& getProjectionMatrix() const; @@ -238,6 +262,8 @@ class TransformState { // Limit the amount of pitch double minPitch = util::PITCH_MIN; double maxPitch = util::PITCH_MAX; + double minFov = util::deg2rad(0.1); + double maxFov = util::deg2rad(150.0); NorthOrientation orientation = NorthOrientation::Upwards; @@ -271,7 +297,7 @@ class TransformState { 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 @@ -280,6 +306,7 @@ class TransformState { // with: `fov = 2 * arctan((height / 2) / (height * 1.5))` double fov = 0.6435011087932844; double pitch = 0.0; + double roll = 0.0; double xSkew = 0.0; double ySkew = 1.0; bool axonometric = false; diff --git a/src/mbgl/util/camera.cpp b/src/mbgl/util/camera.cpp index e25c5004cba..fa3a0edd390 100644 --- a/src/mbgl/util/camera.cpp +++ b/src/mbgl/util/camera.cpp @@ -15,7 +15,7 @@ using namespace std::numbers; namespace mbgl { namespace { -double vec2Len(const vec2& v) noexcept { +/*double vec2Len(const vec2& v) noexcept { return std::sqrt(v[0] * v[0] + v[1] * v[1]); }; @@ -25,7 +25,7 @@ double vec2Dot(const vec2& a, const vec2& b) noexcept { vec2 vec2Scale(const vec2& v, double s) noexcept { return vec2{{v[0] * s, v[1] * s}}; -}; +};*/ } // namespace namespace util { @@ -65,12 +65,13 @@ static vec3 toMercator(const LatLng& location, double altitudeMeters) noexcept { altitudeMeters * pixelsPerMeter / worldSize}}; } -static Quaternion orientationFromPitchBearing(double pitch, double bearing) noexcept { +static 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); } static void updateTransform(mat4& transform, const Quaternion& orientation) noexcept { @@ -167,16 +168,17 @@ 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(); bearing = std::atan2(-r[1], r[0]); pitch = std::atan2(std::sqrt(f[0] * f[0] + f[1] * f[1]), -f[2]); + roll = 0; // TODO } -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); } @@ -190,32 +192,13 @@ void Camera::setPosition(const vec3& position) noexcept { } std::optional 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); - - if (vec3Length(right) < epsilon) { - return std::nullopt; - } + const vec3 right = vec3Cross(up, forward); 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 roll = 0; // TODO - return util::orientationFromPitchBearing(pitch, bearing); + return util::orientationFromRollPitchBearing(pitch, bearing, roll); } } // namespace util @@ -255,14 +238,12 @@ 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; - } + orientation = util::Camera::orientationFromFrame(forward, up)->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 diff --git a/src/mbgl/util/camera.hpp b/src/mbgl/util/camera.hpp index d17e95bcfc7..d5119dc480b 100644 --- a/src/mbgl/util/camera.hpp +++ b/src/mbgl/util/camera.hpp @@ -25,14 +25,13 @@ class Camera { 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 orientationFromFrame(const vec3& forward, const vec3& up) noexcept; private: diff --git a/test/util/camera.test.cpp b/test/util/camera.test.cpp index de93f297a80..b29f7cf8990 100644 --- a/test/util/camera.test.cpp +++ b/test/util/camera.test.cpp @@ -226,18 +226,18 @@ TEST(FreeCameraOptions, LookAtPointInvalidInput) { ASSERT_FALSE(options.orientation); } -TEST(FreeCameraOptions, SetPitchBearing) { +TEST(FreeCameraOptions, SetRollPitchBearing) { FreeCameraOptions options; vec3 right, up, forward; - options.setPitchBearing(0.0, 0.0); + options.setRollPitchBearing(0.0, 0.0, 0.0); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}})); ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -1.0, 0.0}})); ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 0.0, -1.0}})); - options.setPitchBearing(0.0, 180.0); + options.setRollPitchBearing(0.0, 0.0, 180.0); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{-1.0, 0.0, 0.0}})); @@ -247,14 +247,14 @@ TEST(FreeCameraOptions, SetPitchBearing) { const double cos60 = std::cos(util::deg2rad(60.0)); const double sin60 = std::sin(util::deg2rad(60.0)); - options.setPitchBearing(60.0, 0.0); + options.setRollPitchBearing(0.0, 60.0, 0.0); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}})); ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -cos60, sin60}})); ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, -sin60, -cos60}})); - options.setPitchBearing(60.0, -450); + options.setRollPitchBearing(0.0, 60.0, -450); ASSERT_TRUE(options.orientation); std::tie(right, up, forward) = rotatedFrame(options.orientation.value()); ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}}));