From 83c06c1f99bf82fb1bc695b8b38fe1ea1dd91dde Mon Sep 17 00:00:00 2001 From: Mikko Pulkki Date: Mon, 27 Apr 2020 13:28:46 +0300 Subject: Add FreeCameraOptions to the Map class --- include/mbgl/map/camera.hpp | 46 ++++++++++++++++++++++- include/mbgl/map/map.hpp | 8 ++++ include/mbgl/util/geo.hpp | 5 +++ src/mbgl/map/map.cpp | 10 +++++ src/mbgl/map/transform.cpp | 9 +++++ src/mbgl/map/transform.hpp | 3 ++ src/mbgl/map/transform_state.cpp | 81 +++++++++++++++++++++++++++++++++++++++- src/mbgl/map/transform_state.hpp | 5 +++ src/mbgl/util/camera.cpp | 47 +++++++++++++++++++++++ 9 files changed, 210 insertions(+), 4 deletions(-) diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 726c009231..5f723db1cc 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -1,9 +1,10 @@ #pragma once -#include #include -#include +#include #include +#include +#include #include @@ -96,4 +97,45 @@ struct AnimationOptions { : duration(d) {} }; +/** Various options for accessing physical properties of the underlying camera entity. + A direct access to these properties allows more flexible and precise controlling of the camera + while also being fully compatible and interchangeable with CameraOptions. All fields are optional. */ +struct FreeCameraOptions { + /** Position of the camera in slightly modified web mercator coordinates + - The size of 1 unit is the width of the projected world instead of the "mercator meter". + Coordinate [0, 0, 0] is the north-west corner and [1, 1, 0] is the south-east corner. + - Z coordinate is conformal and must respect minimum and maximum zoom values. + - Zoom is automatically computed from the altitude (z) + */ + optional position = nullopt; + + /** Orientation of the camera represented as a unit quaternion [x, y, z, w]. + The default pose of the camera is such that the forward vector is looking up the -Z axis and + the up vector is aligned with north orientation of the map: + forward: [0, 0, -1] + up: [0, -1, 0] + right [1, 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 */ + optional orientation = nullopt; + + /** Helper function for setting the mercator position as Lat&Lng and altitude in meters */ + void setLocation(const LatLngAltitude& location); + + /** Helper function for converting mercator position into Lat&Lng and altitude in meters. + This function fails to return a value if `position` is invalid or is not set */ + optional getLocation() const; + + /** Helper function for setting orientation of the camera by defining a focus point + on the map. Up vector is required in certain scenarios where bearing can't be deduced + from the viewing direction */ + void lookAtPoint(const LatLng& location, const optional& upVector = nullopt); + + /** 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); +}; + } // namespace mbgl diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 9aa0cb165e..3c61140eb8 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -135,6 +135,14 @@ public: bool isFullyLoaded() const; void dumpDebugLogs() const; + // FreeCameraOptions provides more direct access to the underlying camera entity. + // For backwards compatibility the state set using this API must be representable with + // `CameraOptions` as well. Parameters are clamped to a valid range or discarded as invalid + // if the conversion to the pitch and bearing presentation is ambiguous. For example orientation + // can be invalid if it leads to the camera being upside down or the quaternion has zero length. + void setFreeCameraOptions(const FreeCameraOptions& camera); + FreeCameraOptions getFreeCameraOptions() const; + protected: class Impl; const std::unique_ptr impl; diff --git a/include/mbgl/util/geo.hpp b/include/mbgl/util/geo.hpp index a653d053f5..ae6c1550fa 100644 --- a/include/mbgl/util/geo.hpp +++ b/include/mbgl/util/geo.hpp @@ -241,4 +241,9 @@ public: } }; +struct LatLngAltitude { + LatLng location = {0.0, 0.0}; + double altitude = 0.0; +}; + } // namespace mbgl diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 91a657294c..afaa1223ce 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -500,4 +500,14 @@ void Map::dumpDebugLogs() const { Log::Info(Event::General, "--------------------------------------------------------------------------------"); } +void Map::setFreeCameraOptions(const FreeCameraOptions& camera) { + impl->transform.setFreeCameraOptions(camera); + impl->cameraMutated = true; + impl->onUpdate(); +} + +FreeCameraOptions Map::getFreeCameraOptions() const { + return impl->transform.getFreeCameraOptions(); +} + } // namespace mbgl diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 3e3584e5ba..b20f9b59fd 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -650,4 +650,13 @@ double Transform::getMaxPitchForEdgeInsets(const EdgeInsets& insets) const { // e.g. Maximum pitch of 60 degrees is when perspective center's offset from the top is 84% of screen height. } +FreeCameraOptions Transform::getFreeCameraOptions() const { + return state.getFreeCameraOptions(); +} + +void Transform::setFreeCameraOptions(const FreeCameraOptions& options) { + cancelTransitions(); + state.setFreeCameraOptions(options); +} + } // namespace mbgl diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 38fb302b7b..90be7a9ef7 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -113,6 +113,9 @@ public: ScreenCoordinate latLngToScreenCoordinate(const LatLng&) const; LatLng screenCoordinateToLatLng(const ScreenCoordinate&, LatLng::WrapMode = LatLng::Wrapped) const; + FreeCameraOptions getFreeCameraOptions() const; + void setFreeCameraOptions(const FreeCameraOptions& options); + private: MapObserver& observer; TransformState state; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index a61d02963a..d9acecf888 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -177,8 +177,8 @@ void TransformState::updateCameraState() const { // x & y tracks the center of the map in pixels. However as rendering is done in pixel coordinates the rendering // origo is actually in the middle of the map (0.5 * worldSize). x&y positions have to be negated because it defines - // position of the map, not the camera. Moving map 10 units left has the same effect as moving camera 10 units to the - // right. + // position of the map, not the camera. Moving map 10 units left has the same effect as moving camera 10 units to + // the right. const double dx = 0.5 * worldSize - x; const double dy = 0.5 * worldSize - y; @@ -226,6 +226,83 @@ void TransformState::updateStateFromCamera() { setBearing(newBearing); setPitch(newPitch); } + +FreeCameraOptions TransformState::getFreeCameraOptions() const { + updateCameraState(); + + FreeCameraOptions options; + options.position = camera.getPosition(); + options.orientation = camera.getOrientation().m; + + return options; +} + +bool TransformState::setCameraPosition(const vec3& position) { + if (std::isnan(position[0]) || std::isnan(position[1]) || std::isnan(position[2])) return false; + + const double maxWorldSize = Projection::worldSize(std::pow(2.0, getMaxZoom())); + const double minWorldSize = Projection::worldSize(std::pow(2.0, getMinZoom())); + const double distToCenter = getCameraToCenterDistance(); + + const vec3 updatedPos = vec3{ + {position[0], position[1], util::clamp(position[2], distToCenter / maxWorldSize, distToCenter / minWorldSize)}}; + + camera.setPosition(updatedPos); + return true; +} + +bool TransformState::setCameraOrientation(const Quaternion& orientation_) { + const vec4& c = orientation_.m; + if (std::isnan(c[0]) || std::isnan(c[1]) || std::isnan(c[2]) || std::isnan(c[3])) { + return false; + } + + // Zero-length quaternions are not valid + if (orientation_.length() == 0.0) { + return false; + } + + Quaternion unitQuat = orientation_.normalized(); + 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 optional updatedOrientation = util::Camera::orientationFromFrame(forward, up); + if (!updatedOrientation) return false; + + camera.setOrientation(updatedOrientation.value()); + return true; +} + +void TransformState::setFreeCameraOptions(const FreeCameraOptions& options) { + if (!valid()) { + return; + } + + if (!options.position && !options.orientation) return; + + // Check if the state is dirty and camera needs to be synchronized + updateMatricesIfNeeded(); + + bool changed = false; + if (options.orientation && options.orientation.value() != camera.getOrientation().m) { + changed |= setCameraOrientation(options.orientation.value()); + } + + if (options.position && options.position.value() != camera.getPosition()) { + changed |= setCameraPosition(options.position.value()); + } + + if (changed) { + updateStateFromCamera(); + requestMatricesUpdate = true; + } +} + void TransformState::updateMatricesIfNeeded() const { if (!needsMatricesUpdate() || size.isEmpty()) return; diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index aade9be098..aa27067172 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -219,6 +219,9 @@ public: const mat4& getProjectionMatrix() const; const mat4& getInvProjectionMatrix() const; + FreeCameraOptions getFreeCameraOptions() const; + void setFreeCameraOptions(const FreeCameraOptions& options); + private: bool rotatedNorth() const; @@ -249,6 +252,8 @@ private: void updateMatricesIfNeeded() const; bool needsMatricesUpdate() const { return requestMatricesUpdate; } + bool setCameraPosition(const vec3& position); + bool setCameraOrientation(const Quaternion& orientation); void updateCameraState() const; void updateStateFromCamera(); diff --git a/src/mbgl/util/camera.cpp b/src/mbgl/util/camera.cpp index f64e24ab2d..34d27620ac 100644 --- a/src/mbgl/util/camera.cpp +++ b/src/mbgl/util/camera.cpp @@ -212,4 +212,51 @@ optional Camera::orientationFromFrame(const vec3& forward, const vec return util::orientationFromPitchBearing(pitch, bearing); } } // namespace util + +void FreeCameraOptions::setLocation(const LatLngAltitude& location) { + position = util::toMercator(location.location, location.altitude); +} + +optional FreeCameraOptions::getLocation() const { + if (!position) { + return nullopt; + } + + const vec3 positionValue = position.value(); + if (positionValue[1] < 0.0 || positionValue[1] > 1.0) { + return nullopt; + } + + const LatLng location = {util::latFromMercatorY(positionValue[1]), util::lngFromMercatorX(positionValue[0])}; + + const double metersPerPixel = Projection::getMetersPerPixelAtLatitude(location.latitude(), 0.0); + const double worldSize = Projection::worldSize(std::pow(2.0, 0.0)); + const double altitude = positionValue[2] * worldSize * metersPerPixel; + + return LatLngAltitude{location, altitude}; +} + +void FreeCameraOptions::lookAtPoint(const LatLng& location, const optional& upVector) { + orientation = nullopt; + if (!position) { + return; + } + + const vec3 target = util::toMercator(location, 0.0); + const vec3 forward = vec3Sub(target, position.value()); + vec3 up = upVector ? upVector.value() : vec3{{0.0, 0.0, 1.0}}; + + // 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; + } +} + +void FreeCameraOptions::setPitchBearing(double pitch, double bearing) { + orientation = util::orientationFromPitchBearing(pitch * util::DEG2RAD, bearing * util::DEG2RAD).m; +} + } // namespace mbgl \ No newline at end of file -- cgit v1.2.1