[core] Added ConstrainMode::{HeightOnly,WidthAndHeight}

ConstrainMode gives flexibility to our engine to choose between
constraining both vertically and horizontally, or just vertically
(default behavior).

Constrain in both axis means we can no longer pan the map beyond the map
boundaries. This fixes an issue where e.g. annotations disappear upon
crossing the map boundaries.
This commit is contained in:
Bruno de Oliveira Abinader
2015-11-17 17:19:54 +02:00
parent cb9b397985
commit adfac6f061
9 changed files with 93 additions and 33 deletions

View File

@@ -45,7 +45,8 @@ class Map : private util::noncopyable {
public:
explicit Map(View&, FileSource&,
MapMode mapMode = MapMode::Continuous,
GLContextMode contextMode = GLContextMode::Unique);
GLContextMode contextMode = GLContextMode::Unique,
ConstrainMode constrainMode = ConstrainMode::HeightOnly);
~Map();
// Pauses the render thread. The render thread will stop running but will not be terminated and will not lose state until resumed.

View File

@@ -19,6 +19,13 @@ enum class GLContextMode : uint8_t {
Shared,
};
// We can choose to constrain the map both horizontally or vertically, or only
// vertically e.g. while panning.
enum class ConstrainMode : uint8_t {
HeightOnly,
WidthAndHeight,
};
} // namespace mbgl
#endif // MBGL_MAP_MODE

View File

@@ -14,9 +14,9 @@
namespace mbgl {
Map::Map(View& view_, FileSource& fileSource, MapMode mapMode, GLContextMode contextMode)
Map::Map(View& view_, FileSource& fileSource, MapMode mapMode, GLContextMode contextMode, ConstrainMode constrainMode)
: view(view_),
transform(std::make_unique<Transform>(view)),
transform(std::make_unique<Transform>(view, constrainMode)),
data(std::make_unique<MapData>(mapMode, contextMode, view.getPixelRatio())),
context(std::make_unique<util::Thread<MapContext>>(util::ThreadContext{"Map", util::ThreadType::Map, util::ThreadPriority::Regular}, view, fileSource, *data))
{

View File

@@ -34,8 +34,9 @@ static double _normalizeAngle(double angle, double anchorAngle)
return angle;
}
Transform::Transform(View &view_)
Transform::Transform(View &view_, ConstrainMode constrainMode)
: view(view_)
, state(constrainMode)
{
}
@@ -48,7 +49,7 @@ bool Transform::resize(const std::array<uint16_t, 2> size) {
state.width = size[0];
state.height = size[1];
state.constrain(state.scale, state.y);
state.constrain(state.scale, state.x, state.y);
view.notifyMapChange(MapChangeRegionDidChange);
@@ -105,7 +106,7 @@ void Transform::_moveBy(const PrecisionPoint& point, const Duration& duration) {
double x = state.x + std::cos(state.angle) * point.x + std::sin( state.angle) * point.y;
double y = state.y + std::cos(state.angle) * point.y + std::sin(-state.angle) * point.x;
state.constrain(state.scale, y);
state.constrain(state.scale, x, y);
CameraOptions options;
options.duration = duration;
@@ -239,7 +240,7 @@ void Transform::_easeTo(const CameraOptions& options, double new_scale, double n
double x = xn;
double y = yn;
state.constrain(scale, y);
state.constrain(scale, x, y);
double angle = _normalizeAngle(new_angle, state.angle);
state.angle = _normalizeAngle(state.angle, angle);

View File

@@ -1,10 +1,11 @@
#ifndef MBGL_MAP_TRANSFORM
#define MBGL_MAP_TRANSFORM
#include <mbgl/map/transform_state.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/noncopyable.hpp>
@@ -18,7 +19,7 @@ class View;
class Transform : private util::noncopyable {
public:
Transform(View&);
Transform(View&, ConstrainMode);
// Map view
bool resize(std::array<uint16_t, 2> size);

View File

@@ -7,7 +7,8 @@
using namespace mbgl;
TransformState::TransformState()
TransformState::TransformState(ConstrainMode constrainMode_)
: constrainMode(constrainMode_)
{
}
@@ -143,8 +144,9 @@ double TransformState::getScale() const {
double TransformState::getMinZoom() const {
double test_scale = scale;
double test_y = y;
constrain(test_scale, test_y);
double unused_x = x;
double unused_y = y;
constrain(test_scale, unused_x, unused_y);
return ::log2(::fmin(min_scale, test_scale));
}
@@ -316,17 +318,22 @@ mat4 TransformState::getPixelMatrix() const {
#pragma mark - (private helper functions)
void TransformState::constrain(double& scale_, double& y_) const {
// Constrain minimum zoom to avoid zooming out far enough to show off-world areas.
if (scale_ < height / util::tileSize) {
scale_ = height / util::tileSize;
void TransformState::constrain(double& scale_, double& x_, double& y_) const {
// Constrain minimum scale to avoid zooming out far enough to show off-world areas.
if (constrainMode == ConstrainMode::WidthAndHeight) {
scale_ = std::max(scale_, static_cast<double>(width / util::tileSize));
}
// Constrain min/max vertical pan to avoid showing off-world areas.
double max_y = ((scale_ * util::tileSize) - height) / 2;
scale_ = std::max(scale_, static_cast<double>(height / util::tileSize));
if (y_ > max_y) y_ = max_y;
if (y_ < -max_y) y_ = -max_y;
// Constrain min/max pan to avoid showing off-world areas.
if (constrainMode == ConstrainMode::WidthAndHeight) {
double max_x = (scale_ * util::tileSize - width) / 2;
x_ = std::max(-max_x, std::min(x_, max_x));
}
double max_y = (scale_ * util::tileSize - height) / 2;
y_ = std::max(-max_y, std::min(y_, max_y));
}

View File

@@ -1,6 +1,7 @@
#ifndef MBGL_MAP_TRANSFORM_STATE
#define MBGL_MAP_TRANSFORM_STATE
#include <mbgl/map/mode.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/vec.hpp>
@@ -21,7 +22,7 @@ class TransformState {
friend class Transform;
public:
TransformState();
TransformState(ConstrainMode = ConstrainMode::HeightOnly);
// Matrix
void matrixFor(mat4& matrix, const TileID& id, const int8_t z) const;
@@ -73,7 +74,7 @@ public:
TileCoordinate pointToCoordinate(const PrecisionPoint&) const;
private:
void constrain(double& scale, double& y) const;
void constrain(double& scale, double& x, double& y) const;
// Limit the amount of zooming possible on the map.
double min_scale = std::pow(2, 0);
@@ -93,6 +94,8 @@ private:
mat4 getPixelMatrix() const;
private:
ConstrainMode constrainMode;
// animation state
bool rotating = false;
bool scaling = false;

View File

@@ -7,13 +7,13 @@ using namespace mbgl;
TEST(Transform, InvalidScale) {
MockView view;
Transform transform(view);
Transform transform(view, ConstrainMode::HeightOnly);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude);
ASSERT_DOUBLE_EQ(1, transform.getScale());
transform.setScale(2);
transform.setScale(2 << 0);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude);
@@ -47,13 +47,13 @@ TEST(Transform, InvalidScale) {
TEST(Transform, InvalidLatLng) {
MockView view;
Transform transform(view);
Transform transform(view, ConstrainMode::HeightOnly);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude);
ASSERT_DOUBLE_EQ(1, transform.getScale());
transform.setScale(2);
transform.setScale(2 << 0);
transform.setLatLng({ 8, 10 });
ASSERT_DOUBLE_EQ(8, transform.getLatLng().latitude);
@@ -83,13 +83,13 @@ TEST(Transform, InvalidLatLng) {
TEST(Transform, InvalidBearing) {
MockView view;
Transform transform(view);
Transform transform(view, ConstrainMode::HeightOnly);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude);
ASSERT_DOUBLE_EQ(1, transform.getScale());
transform.setScale(2);
transform.setScale(2 << 0);
transform.setAngle(2);
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude);
@@ -108,10 +108,10 @@ TEST(Transform, InvalidBearing) {
TEST(Transform, PerspectiveProjection) {
MockView view;
Transform transform(view);
Transform transform(view, ConstrainMode::HeightOnly);
transform.resize({{ 1000, 1000 }});
transform.setScale(1024);
transform.setScale(2 << 9);
transform.setPitch(0.9);
transform.setLatLng(LatLng(38, -77));
@@ -129,7 +129,7 @@ TEST(Transform, PerspectiveProjection) {
ASSERT_NEAR(-76.75823239205641, loc.longitude, 0.0001);
ASSERT_NEAR(37.692872969426375, loc.latitude, 0.0001);
vec2<double> point = transform.getState().latLngToPoint({38.74661326302018, -77.59198961199148});
PrecisionPoint point = transform.getState().latLngToPoint({38.74661326302018, -77.59198961199148});
ASSERT_NEAR(point.x, 0, 0.01);
ASSERT_NEAR(point.y, 1000, 0.01);
@@ -137,3 +137,43 @@ TEST(Transform, PerspectiveProjection) {
ASSERT_NEAR(point.x, 1000, 0.02);
ASSERT_NEAR(point.y, 0, 0.02);
}
TEST(Transform, ConstrainHeightOnly) {
MockView view;
LatLng loc;
LatLngBounds bounds;
Transform transform(view, ConstrainMode::HeightOnly);
transform.resize({{ 1000, 1000 }});
transform.setScale(1024);
transform.setLatLng(bounds.sw);
loc = transform.getState().pointToLatLng({ 500, 500 });
ASSERT_NEAR(-85.021422866378714, loc.latitude, 0.0001);
ASSERT_NEAR(180, std::abs(loc.longitude), 0.0001);
transform.setLatLng(bounds.ne);
loc = transform.getState().pointToLatLng({ 500, 500 });
ASSERT_NEAR(85.021422866378742, loc.latitude, 0.0001);
ASSERT_NEAR(180, std::abs(loc.longitude), 0.0001);
}
TEST(Transform, ConstrainWidthAndHeight) {
MockView view;
LatLng loc;
LatLngBounds bounds;
Transform transform(view, ConstrainMode::WidthAndHeight);
transform.resize({{ 1000, 1000 }});
transform.setScale(1024);
transform.setLatLng(bounds.sw);
loc = transform.getState().pointToLatLng({ 500, 500 });
ASSERT_NEAR(-85.021422866378714, loc.latitude, 0.0001);
ASSERT_NEAR(-179.65667724609375, loc.longitude, 0.0001);
transform.setLatLng(bounds.ne);
loc = transform.getState().pointToLatLng({ 500, 500 });
ASSERT_NEAR(85.021422866378742, loc.latitude, 0.0001);
ASSERT_NEAR(179.65667724609358, std::abs(loc.longitude), 0.0001);
}

View File

@@ -24,7 +24,7 @@ public:
FileSource& fileSource,
const std::function<void(std::exception_ptr error)>& callback)
: data_(MapMode::Still, GLContextMode::Unique, view.getPixelRatio()),
transform_(view),
transform_(view, ConstrainMode::HeightOnly),
callback_(callback) {
util::ThreadContext::setFileSource(&fileSource);