Refactor TransformState to use internal 3d camera
This commit is contained in:
committed by
Mikko Pulkki
parent
9d391d7edd
commit
57b4b2829e
@@ -749,6 +749,7 @@ add_library(
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile_data.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/vector_tile_data.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/camera.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/bounding_volumes.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/bounding_volumes.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/chrono.cpp
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
#include <mbgl/util/tile_coordinate.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace {
|
||||
LatLng latLngFromMercator(Point<double> mercatorCoordinate, LatLng::WrapMode wrapMode = LatLng::WrapMode::Unwrapped) {
|
||||
return {util::RAD2DEG * (2 * std::atan(std::exp(M_PI - mercatorCoordinate.y * util::M2PI)) - M_PI_2),
|
||||
mercatorCoordinate.x * 360.0 - 180.0,
|
||||
wrapMode};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TransformState::TransformState(ConstrainMode constrainMode_, ViewportMode viewportMode_)
|
||||
: bounds(LatLngBounds()), constrainMode(constrainMode_), viewportMode(viewportMode_) {}
|
||||
|
||||
@@ -101,62 +110,52 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
|
||||
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
|
||||
const double farZ = furthestDistance * 1.01;
|
||||
|
||||
matrix::perspective(projMatrix, getFieldOfView(), double(size.width) / size.height, nearZ, farZ);
|
||||
// Make sure the camera state is up-to-date
|
||||
updateCameraState();
|
||||
|
||||
mat4 worldToCamera = camera.getWorldToCamera(scale, viewportMode == ViewportMode::FlippedY);
|
||||
mat4 cameraToClip =
|
||||
camera.getCameraToClipPerspective(getFieldOfView(), double(size.width) / size.height, nearZ, farZ);
|
||||
|
||||
// Move the center of perspective to center of specified edgeInsets.
|
||||
// Values are in range [-1, 1] where the upper and lower range values
|
||||
// position viewport center to the screen edges. This is overriden
|
||||
// if using axonometric perspective (not in public API yet, Issue #11882).
|
||||
// TODO(astojilj): Issue #11882 should take edge insets into account, too.
|
||||
projMatrix[8] = -offset.x * 2.0 / size.width;
|
||||
projMatrix[9] = offset.y * 2.0 / size.height;
|
||||
|
||||
const bool flippedY = viewportMode == ViewportMode::FlippedY;
|
||||
matrix::scale(projMatrix, projMatrix, 1.0, flippedY ? 1 : -1, 1);
|
||||
|
||||
matrix::translate(projMatrix, projMatrix, 0, 0, -cameraToCenterDistance);
|
||||
|
||||
using NO = NorthOrientation;
|
||||
switch (getNorthOrientation()) {
|
||||
case NO::Rightwards:
|
||||
matrix::rotate_y(projMatrix, projMatrix, getPitch());
|
||||
break;
|
||||
case NO::Downwards:
|
||||
matrix::rotate_x(projMatrix, projMatrix, -getPitch());
|
||||
break;
|
||||
case NO::Leftwards:
|
||||
matrix::rotate_y(projMatrix, projMatrix, -getPitch());
|
||||
break;
|
||||
default:
|
||||
matrix::rotate_x(projMatrix, projMatrix, getPitch());
|
||||
break;
|
||||
if (!axonometric) {
|
||||
cameraToClip[8] = -offset.x * 2.0 / size.width;
|
||||
cameraToClip[9] = offset.y * 2.0 / size.height;
|
||||
}
|
||||
|
||||
matrix::rotate_z(projMatrix, projMatrix, getBearing() + getNorthOrientationAngle());
|
||||
// Apply north orientation angle
|
||||
if (getNorthOrientation() != NorthOrientation::Upwards) {
|
||||
matrix::rotate_z(cameraToClip, cameraToClip, -getNorthOrientationAngle());
|
||||
}
|
||||
|
||||
const double dx = pixel_x() - size.width / 2.0f;
|
||||
const double dy = pixel_y() - size.height / 2.0f;
|
||||
matrix::translate(projMatrix, projMatrix, dx, dy, 0);
|
||||
matrix::multiply(projMatrix, cameraToClip, worldToCamera);
|
||||
|
||||
if (axonometric) {
|
||||
// mat[11] controls perspective
|
||||
projMatrix[11] = 0;
|
||||
projMatrix[11] = 0.0;
|
||||
|
||||
// mat[8], mat[9] control x-skew, y-skew
|
||||
projMatrix[8] = xSkew;
|
||||
projMatrix[9] = ySkew;
|
||||
double pixelsPerMeter = 1.0 / Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom());
|
||||
projMatrix[8] = xSkew * pixelsPerMeter;
|
||||
projMatrix[9] = ySkew * pixelsPerMeter;
|
||||
}
|
||||
|
||||
matrix::scale(projMatrix, projMatrix, 1, 1,
|
||||
1.0 / Projection::getMetersPerPixelAtLatitude(getLatLng(LatLng::Unwrapped).latitude(), getZoom()));
|
||||
|
||||
// Make a second projection matrix that is aligned to a pixel grid for rendering raster tiles.
|
||||
// We're rounding the (floating point) x/y values to achieve to avoid rendering raster images to fractional
|
||||
// coordinates. Additionally, we adjust by half a pixel in either direction in case that viewport dimension
|
||||
// is an odd integer to preserve rendering to the pixel grid. We're rotating this shift based on the angle
|
||||
// of the transformation so that 0°, 90°, 180°, and 270° rasters are crisp, and adjust the shift so that
|
||||
// it is always <= 0.5 pixels.
|
||||
|
||||
if (aligned) {
|
||||
const double worldSize = Projection::worldSize(scale);
|
||||
const double dx = x - 0.5 * worldSize;
|
||||
const double dy = y - 0.5 * worldSize;
|
||||
|
||||
const float xShift = float(size.width % 2) / 2;
|
||||
const float yShift = float(size.height % 2) / 2;
|
||||
const double bearingCos = std::cos(bearing);
|
||||
@@ -168,6 +167,65 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
|
||||
}
|
||||
}
|
||||
|
||||
void TransformState::updateCameraState() const {
|
||||
if (!valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double worldSize = Projection::worldSize(scale);
|
||||
const double cameraToCenterDistance = getCameraToCenterDistance();
|
||||
|
||||
// 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.
|
||||
const double dx = 0.5 * worldSize - x;
|
||||
const double dy = 0.5 * worldSize - y;
|
||||
|
||||
// Set camera orientation and move it to a proper distance from the map
|
||||
camera.setOrientation(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]}};
|
||||
|
||||
cameraPosition[0] /= worldSize;
|
||||
cameraPosition[1] /= worldSize;
|
||||
cameraPosition[2] /= worldSize;
|
||||
|
||||
camera.setPosition(cameraPosition);
|
||||
}
|
||||
|
||||
void TransformState::updateStateFromCamera() {
|
||||
const vec3 position = camera.getPosition();
|
||||
const vec3 forward = camera.forward();
|
||||
|
||||
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);
|
||||
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));
|
||||
const double newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
|
||||
// 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));
|
||||
setBearing(newBearing);
|
||||
setPitch(newPitch);
|
||||
}
|
||||
void TransformState::updateMatricesIfNeeded() const {
|
||||
if (!needsMatricesUpdate() || size.isEmpty()) return;
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/map/camera.hpp>
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/util/camera.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
#include <mbgl/util/geometry.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/mat4.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/util/projection.hpp>
|
||||
#include <mbgl/util/mat4.hpp>
|
||||
#include <mbgl/util/size.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -248,6 +249,9 @@ private:
|
||||
void updateMatricesIfNeeded() const;
|
||||
bool needsMatricesUpdate() const { return requestMatricesUpdate; }
|
||||
|
||||
void updateCameraState() const;
|
||||
void updateStateFromCamera();
|
||||
|
||||
const mat4& getCoordMatrix() const;
|
||||
const mat4& getInvertedMatrix() const;
|
||||
|
||||
@@ -276,6 +280,7 @@ private:
|
||||
bool axonometric = false;
|
||||
|
||||
EdgeInsets edgeInsets;
|
||||
mutable util::Camera camera;
|
||||
|
||||
// cache values for spherical mercator math
|
||||
double Bc = Projection::worldSize(scale) / util::DEGREES_MAX;
|
||||
|
||||
215
src/mbgl/util/camera.cpp
Normal file
215
src/mbgl/util/camera.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "camera.hpp"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <mbgl/map/camera.hpp>
|
||||
#include <mbgl/math/log2.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
#include <mbgl/util/projection.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace {
|
||||
double vec2Len(const vec2& v) {
|
||||
return std::sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
};
|
||||
|
||||
double vec2Dot(const vec2& a, const vec2& b) {
|
||||
return a[0] * b[0] + a[1] * b[1];
|
||||
};
|
||||
|
||||
vec2 vec2Scale(const vec2& v, double s) {
|
||||
return vec2{{v[0] * s, v[1] * s}};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace util {
|
||||
|
||||
static double mercatorXfromLng(double lng) {
|
||||
return (180.0 + lng) / 360.0;
|
||||
}
|
||||
|
||||
static double mercatorYfromLat(double lat) {
|
||||
return (180.0 - (180.0 / M_PI * std::log(std::tan(M_PI_4 + lat * M_PI / 360.0)))) / 360.0;
|
||||
}
|
||||
|
||||
static double latFromMercatorY(double y) {
|
||||
return util::RAD2DEG * (2.0 * std::atan(std::exp(M_PI - y * util::M2PI)) - M_PI_2);
|
||||
}
|
||||
|
||||
static double lngFromMercatorX(double x) {
|
||||
return x * 360.0 - 180.0;
|
||||
}
|
||||
|
||||
static double* getColumn(mat4& matrix, int col) {
|
||||
assert(col >= 0 && col < 4);
|
||||
return &matrix[col * 4];
|
||||
}
|
||||
|
||||
static const double* getColumn(const mat4& matrix, int col) {
|
||||
assert(col >= 0 && col < 4);
|
||||
return &matrix[col * 4];
|
||||
}
|
||||
|
||||
static vec3 toMercator(const LatLng& location, double altitudeMeters) {
|
||||
const double pixelsPerMeter = 1.0 / Projection::getMetersPerPixelAtLatitude(location.latitude(), 0.0);
|
||||
const double worldSize = Projection::worldSize(std::pow(2.0, 0.0));
|
||||
|
||||
return {{mercatorXfromLng(location.longitude()),
|
||||
mercatorYfromLat(location.latitude()),
|
||||
altitudeMeters * pixelsPerMeter / worldSize}};
|
||||
}
|
||||
|
||||
static Quaternion orientationFromPitchBearing(double pitch, double bearing) {
|
||||
// Both angles have to be negated to achieve CW rotation around the axis of rotation
|
||||
Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing);
|
||||
Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch);
|
||||
|
||||
return rotBearing.multiply(rotPitch);
|
||||
}
|
||||
|
||||
static void updateTransform(mat4& transform, const Quaternion& orientation) {
|
||||
// Construct rotation matrix from orientation
|
||||
mat4 m = orientation.toRotationMatrix();
|
||||
|
||||
// Apply translation to the matrix
|
||||
double* col = getColumn(m, 3);
|
||||
|
||||
col[0] = getColumn(transform, 3)[0];
|
||||
col[1] = getColumn(transform, 3)[1];
|
||||
col[2] = getColumn(transform, 3)[2];
|
||||
|
||||
transform = m;
|
||||
}
|
||||
|
||||
static void updateTransform(mat4& transform, const vec3& position) {
|
||||
getColumn(transform, 3)[0] = position[0];
|
||||
getColumn(transform, 3)[1] = position[1];
|
||||
getColumn(transform, 3)[2] = position[2];
|
||||
}
|
||||
|
||||
Camera::Camera() : orientation(Quaternion::identity) {
|
||||
matrix::identity(transform);
|
||||
}
|
||||
|
||||
vec3 Camera::getPosition() const {
|
||||
const double* p = getColumn(transform, 3);
|
||||
return {{p[0], p[1], p[2]}};
|
||||
}
|
||||
|
||||
mat4 Camera::getCameraToWorld(double scale, bool flippedY) const {
|
||||
mat4 cameraToWorld;
|
||||
matrix::invert(cameraToWorld, getWorldToCamera(scale, flippedY));
|
||||
return cameraToWorld;
|
||||
}
|
||||
|
||||
mat4 Camera::getWorldToCamera(double scale, bool flippedY) const {
|
||||
// transformation chain from world space to camera space:
|
||||
// 1. Height value (z) of renderables is in meters. Scale z coordinate by pixelsPerMeter
|
||||
// 2. Transform from pixel coordinates to camera space with cameraMatrix^-1
|
||||
// 3. flip Y if required
|
||||
|
||||
// worldToCamera: flip * cam^-1 * zScale
|
||||
// cameraToWorld: (flip * cam^-1 * zScale)^-1 => (zScale^-1 * cam * flip^-1)
|
||||
const double worldSize = Projection::worldSize(scale);
|
||||
const double latitude = latFromMercatorY(getColumn(transform, 3)[1]);
|
||||
const double pixelsPerMeter = worldSize / (std::cos(latitude * util::DEG2RAD) * util::M2PI * util::EARTH_RADIUS_M);
|
||||
|
||||
// Compute inverse of the camera matrix
|
||||
mat4 result = orientation.conjugate().toRotationMatrix();
|
||||
|
||||
matrix::translate(
|
||||
result, result, -transform[12] * worldSize, -transform[13] * worldSize, -transform[14] * worldSize);
|
||||
|
||||
if (!flippedY) {
|
||||
// Pre-multiply y
|
||||
result[1] *= -1.0;
|
||||
result[5] *= -1.0;
|
||||
result[9] *= -1.0;
|
||||
result[13] *= -1.0;
|
||||
}
|
||||
|
||||
// Post-multiply z
|
||||
result[8] *= pixelsPerMeter;
|
||||
result[9] *= pixelsPerMeter;
|
||||
result[10] *= pixelsPerMeter;
|
||||
result[11] *= pixelsPerMeter;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
mat4 Camera::getCameraToClipPerspective(double fovy, double aspectRatio, double nearZ, double farZ) const {
|
||||
mat4 projection;
|
||||
matrix::perspective(projection, fovy, aspectRatio, nearZ, farZ);
|
||||
return projection;
|
||||
}
|
||||
|
||||
vec3 Camera::forward() const {
|
||||
const double* column = getColumn(transform, 2);
|
||||
// The forward direction is towards the map, [0, 0, -1]
|
||||
return {{-column[0], -column[1], -column[2]}};
|
||||
}
|
||||
|
||||
vec3 Camera::right() const {
|
||||
const double* column = getColumn(transform, 0);
|
||||
return {{column[0], column[1], column[2]}};
|
||||
}
|
||||
|
||||
vec3 Camera::up() const {
|
||||
const double* column = getColumn(transform, 1);
|
||||
// Up direction has to be flipped due to y-axis pointing towards south
|
||||
return {{-column[0], -column[1], -column[2]}};
|
||||
}
|
||||
|
||||
void Camera::getOrientation(double& pitch, double& bearing) const {
|
||||
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]);
|
||||
}
|
||||
|
||||
void Camera::setOrientation(double pitch, double bearing) {
|
||||
orientation = orientationFromPitchBearing(pitch, bearing);
|
||||
updateTransform(transform, orientation);
|
||||
}
|
||||
|
||||
void Camera::setOrientation(const Quaternion& orientation_) {
|
||||
orientation = orientation_;
|
||||
updateTransform(transform, orientation);
|
||||
}
|
||||
|
||||
void Camera::setPosition(const vec3& position) {
|
||||
updateTransform(transform, position);
|
||||
}
|
||||
|
||||
optional<Quaternion> Camera::orientationFromFrame(const vec3& forward, const vec3& up) {
|
||||
vec3 upVector = up;
|
||||
|
||||
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 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]);
|
||||
|
||||
return util::orientationFromPitchBearing(pitch, bearing);
|
||||
}
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
42
src/mbgl/util/camera.hpp
Normal file
42
src/mbgl/util/camera.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/util/size.hpp>
|
||||
#include "quaternion.hpp"
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class LatLng;
|
||||
|
||||
namespace util {
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
Camera();
|
||||
|
||||
vec3 getPosition() const;
|
||||
mat4 getCameraToWorld(double scale, bool flippedY) const;
|
||||
mat4 getWorldToCamera(double scale, bool flippedY) const;
|
||||
mat4 getCameraToClipPerspective(double fovy, double aspectRatio, double nearZ, double farZ) const;
|
||||
|
||||
vec3 forward() const;
|
||||
vec3 right() const;
|
||||
vec3 up() const;
|
||||
|
||||
const Quaternion& getOrientation() const { return orientation; }
|
||||
void getOrientation(double& pitch, double& bearing) const;
|
||||
void setOrientation(double pitch, double bearing);
|
||||
void setOrientation(const Quaternion& orientation_);
|
||||
void setPosition(const vec3& position);
|
||||
|
||||
// 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
|
||||
static optional<Quaternion> orientationFromFrame(const vec3& forward, const vec3& up);
|
||||
|
||||
private:
|
||||
Quaternion orientation;
|
||||
mat4 transform; // Position (mercator) and orientation of the camera
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace mbgl
|
||||
Reference in New Issue
Block a user