Add FreeCameraOptions to the Map class
This commit is contained in:
committed by
Mikko Pulkki
parent
57b4b2829e
commit
83c06c1f99
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/util/geo.hpp>
|
||||
#include <mbgl/util/chrono.hpp>
|
||||
#include <mbgl/util/unitbezier.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/util/unitbezier.hpp>
|
||||
#include <mbgl/util/vectors.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -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<vec3> 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<vec4> 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<LatLngAltitude> 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<vec3>& 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
|
||||
|
||||
@@ -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> impl;
|
||||
|
||||
@@ -241,4 +241,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct LatLngAltitude {
|
||||
LatLng location = {0.0, 0.0};
|
||||
double altitude = 0.0;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Quaternion> 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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -212,4 +212,51 @@ optional<Quaternion> 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<LatLngAltitude> 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<vec3>& 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
|
||||
Reference in New Issue
Block a user