summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/mbgl/map/camera.hpp4
-rw-r--r--platform/ios/app/MBXViewController.m77
-rw-r--r--platform/ios/src/MGLMapView.mm19
-rw-r--r--src/mbgl/layout/symbol_projection.cpp2
-rw-r--r--src/mbgl/map/transform.cpp76
-rw-r--r--src/mbgl/map/transform.hpp5
-rw-r--r--src/mbgl/map/transform_state.cpp42
-rw-r--r--src/mbgl/map/transform_state.hpp6
-rw-r--r--src/mbgl/programs/symbol_program.cpp2
-rw-r--r--src/mbgl/text/collision_index.cpp17
-rw-r--r--test/api/annotations.test.cpp67
-rw-r--r--test/fixtures/local_glyphs/no_local_with_content_insets/expected.pngbin0 -> 8428 bytes
-rw-r--r--test/fixtures/local_glyphs/no_local_with_content_insets_and_pitch/expected.pngbin0 -> 7263 bytes
-rw-r--r--test/map/transform.test.cpp45
-rw-r--r--test/text/local_glyph_rasterizer.test.cpp50
15 files changed, 311 insertions, 101 deletions
diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp
index e3d6677c0a..c8b665fc16 100644
--- a/include/mbgl/map/camera.hpp
+++ b/include/mbgl/map/camera.hpp
@@ -16,7 +16,7 @@ namespace mbgl {
*/
struct CameraOptions {
CameraOptions& withCenter(const optional<LatLng>& o) { center = o; return *this; }
- CameraOptions& withPadding(const EdgeInsets& p) { padding = p; return *this; }
+ CameraOptions& withPadding(const optional<EdgeInsets>& p) { padding = p; return *this; }
CameraOptions& withAnchor(const optional<ScreenCoordinate>& o) { anchor = o; return *this; }
CameraOptions& withZoom(const optional<double>& o) { zoom = o; return *this; }
CameraOptions& withBearing(const optional<double>& o) { bearing = o; return *this; }
@@ -27,7 +27,7 @@ struct CameraOptions {
/** Padding around the interior of the view that affects the frame of
reference for `center`. */
- EdgeInsets padding;
+ optional<EdgeInsets> padding;
/** Point of reference for `zoom` and `angle`, assuming an origin at the
top-left corner of the view. */
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 3335606f98..567ca151f7 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -105,6 +105,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
MBXSettingsMiscellaneousShowSnapshots,
MBXSettingsMiscellaneousMissingIcon,
MBXSettingsMiscellaneousShouldLimitCameraChanges,
+ MBXSettingsMiscellaneousSetContentInsets,
MBXSettingsMiscellaneousShowCustomLocationManager,
MBXSettingsMiscellaneousOrnamentsPlacement,
MBXSettingsMiscellaneousPrintLogFile,
@@ -209,6 +210,7 @@ CLLocationCoordinate2D randomWorldCoordinate() {
@property (nonatomic) BOOL shouldLimitCameraChanges;
@property (nonatomic) BOOL randomWalk;
@property (nonatomic) NSMutableArray<UIWindow *> *helperWindows;
+@property (nonatomic) NSMutableArray<UIView *> *contentInsetsOverlays;
@end
@@ -219,6 +221,8 @@ CLLocationCoordinate2D randomWorldCoordinate() {
@implementation MBXViewController
{
BOOL _isTouringWorld;
+ BOOL _contentInsetsEnabled;
+ UIEdgeInsets _originalContentInsets;
}
#pragma mark - Setup & Teardown
@@ -475,6 +479,7 @@ CLLocationCoordinate2D randomWorldCoordinate() {
@"Show Snapshots",
@"Missing Icon",
[NSString stringWithFormat:@"%@ Camera Changes", (_shouldLimitCameraChanges ? @"Unlimit" : @"Limit")],
+ [NSString stringWithFormat:@"Turn %@ Content Insets", (_contentInsetsEnabled ? @"Off" : @"On")],
@"View Route Simulation",
@"Ornaments Placement",
]];
@@ -739,6 +744,61 @@ CLLocationCoordinate2D randomWorldCoordinate() {
}
break;
}
+ case MBXSettingsMiscellaneousSetContentInsets:
+ {
+ if (!_contentInsetsEnabled) {
+ _originalContentInsets = [self.mapView contentInset];
+ }
+ _contentInsetsEnabled = !_contentInsetsEnabled;
+ self.automaticallyAdjustsScrollViewInsets = !_contentInsetsEnabled;
+ UIEdgeInsets contentInsets = self.mapView.bounds.size.width > self.mapView.bounds.size.height
+ ? UIEdgeInsetsMake(_originalContentInsets.top, 0.5 * self.mapView.bounds.size.width, _originalContentInsets.bottom, 0.0)
+ : UIEdgeInsetsMake(0.25 * self.mapView.bounds.size.height, 0.0, _originalContentInsets.bottom, 0.25 * self.mapView.bounds.size.width);
+ if (_contentInsetsEnabled) {
+ if (!self.contentInsetsOverlays)
+ self.contentInsetsOverlays = [NSMutableArray array];
+ if (![self.contentInsetsOverlays count]) {
+ UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.mapView.bounds.size.width, contentInsets.top)];
+ view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
+ [self.contentInsetsOverlays addObject:view];
+ [self.view addSubview:view];
+ view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, contentInsets.left, self.mapView.bounds.size.height)];
+ view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
+ [self.contentInsetsOverlays addObject:view];
+ [self.view addSubview:view];
+ view = [[UIView alloc]initWithFrame:CGRectMake(self.mapView.bounds.size.width - contentInsets.right, 0, contentInsets.right, self.mapView.bounds.size.height)];
+ view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
+ [self.contentInsetsOverlays addObject:view];
+ [self.view addSubview:view];
+ view = [[UIView alloc]initWithFrame:CGRectMake(0, self.mapView.bounds.size.height - contentInsets.bottom, self.mapView.bounds.size.width, self.mapView.bounds.size.height)];
+ view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
+ [self.contentInsetsOverlays addObject:view];
+ [self.view addSubview:view];
+ }
+ [self.view bringSubviewToFront:self.contentInsetsOverlays[0]];
+ [self.view bringSubviewToFront:self.contentInsetsOverlays[1]];
+ [self.view bringSubviewToFront:self.contentInsetsOverlays[2]];
+ [self.view bringSubviewToFront:self.contentInsetsOverlays[3]];
+
+ // Denver streets parallel to cardinal directions help illustrate
+ // viewport center offset when edge insets are set.
+ MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(39.72707, -104.9986)
+ acrossDistance:100
+ pitch:60
+ heading:0];
+ __weak typeof(self) weakSelf = self;
+ [self.mapView setCamera:camera withDuration:0.3 animationTimingFunction:nil completionHandler:^{
+ [weakSelf.mapView setContentInset:contentInsets animated:TRUE];
+ }];
+ } else {
+ [self.view sendSubviewToBack:self.contentInsetsOverlays[0]];
+ [self.view sendSubviewToBack:self.contentInsetsOverlays[1]];
+ [self.view sendSubviewToBack:self.contentInsetsOverlays[2]];
+ [self.view sendSubviewToBack:self.contentInsetsOverlays[3]];
+ [self.mapView setContentInset:_originalContentInsets animated:TRUE];
+ }
+ break;
+ }
case MBXSettingsMiscellaneousOrnamentsPlacement:
{
MBXOrnamentsViewController *ornamentsViewController = [[MBXOrnamentsViewController alloc] init];
@@ -2005,6 +2065,23 @@ CLLocationCoordinate2D randomWorldCoordinate() {
[sender setAccessibilityValue:nextAccessibilityValue];
}
+#pragma mark - UIViewDelegate
+
+- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
+{
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+ if (_contentInsetsEnabled)
+ {
+ _contentInsetsEnabled = NO;
+ self.automaticallyAdjustsScrollViewInsets = YES;
+ [self.mapView setContentInset:UIEdgeInsetsZero];
+ }
+ while (self.contentInsetsOverlays && [self.contentInsetsOverlays count]) {
+ [[self.contentInsetsOverlays lastObject] removeFromSuperview];
+ [self.contentInsetsOverlays removeLastObject];
+ }
+}
+
#pragma mark - MGLMapViewDelegate
- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index fa49a2f280..bc4900ace9 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -1076,20 +1076,15 @@ public:
return;
}
- // After adjusting the content inset, move the center coordinate from the
- // old frame of reference to the new one represented by the newly set
- // content inset.
- CLLocationCoordinate2D oldCenter = self.centerCoordinate;
-
- _contentInset = contentInset;
-
if (self.userTrackingMode == MGLUserTrackingModeNone)
{
// Don’t call -setCenterCoordinate:, which resets the user tracking mode.
- [self _setCenterCoordinate:oldCenter animated:animated];
+ [self _setCenterCoordinate:self.centerCoordinate edgePadding:contentInset zoomLevel:self.zoomLevel direction:self.direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL];
+ _contentInset = contentInset;
}
else
{
+ _contentInset = contentInset;
[self didUpdateLocationWithUserTrackingAnimated:animated];
}
@@ -3313,10 +3308,6 @@ public:
[self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
}
-- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated {
- [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:self.zoomLevel direction:self.direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL];
-}
-
- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePadding:(UIEdgeInsets)insets zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
if (!_mbglMap)
@@ -3356,7 +3347,7 @@ public:
}
MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions];
- if ([self.camera isEqualToMapCamera:camera])
+ if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets))
{
if (completion)
{
@@ -3893,7 +3884,7 @@ public:
return self.residualCamera;
}
- mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions();
+ mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions(cameraOptions.padding.value_or(mbgl::EdgeInsets()));
CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : *mapCamera.center);
double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel;
CLLocationDirection direction = cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction;
diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp
index b7858f8deb..48ce3a6371 100644
--- a/src/mbgl/layout/symbol_projection.cpp
+++ b/src/mbgl/layout/symbol_projection.cpp
@@ -47,7 +47,7 @@ namespace mbgl {
* For horizontal labels we want to do step 1 in the shader for performance reasons (no cpu work).
* This is what `u_label_plane_matrix` is used for.
* For labels aligned with lines we have to steps 1 and 2 on the cpu since we need access to the line geometry.
- * This is what `updateLineLabels(...)` does.
+ * This is what `updateLineLabels(...)` in JS, `reprojectLineLabels()` in gl-native, does.
* Since the conversion is handled on the cpu we just set `u_label_plane_matrix` to an identity matrix.
*
* Steps 3 and 4 are done in the shaders for all labels.
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
index e97f0da3f1..88b559592e 100644
--- a/src/mbgl/map/transform.cpp
+++ b/src/mbgl/map/transform.cpp
@@ -77,13 +77,13 @@ void Transform::jumpTo(const CameraOptions& camera) {
}
/**
- * Change any combination of center, zoom, bearing, and pitch, with a smooth animation
- * between old and new values. The map will retain the current values for any options
- * not included in `options`.
+ * Change any combination of center, zoom, bearing, pitch and edgeInsets, with a
+ * smooth animation between old and new values. The map will retain the current
+ * values for any options not included in `options`.
*/
void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& animation) {
- const EdgeInsets& padding = camera.padding;
- LatLng startLatLng = getLatLng(padding, LatLng::Unwrapped);
+ const EdgeInsets& padding = camera.padding.value_or(state.edgeInsets);
+ LatLng startLatLng = getLatLng(LatLng::Unwrapped);
const LatLng& unwrappedLatLng = camera.center.value_or(startLatLng);
const LatLng& latLng = state.bounds != LatLngBounds::unbounded() ? unwrappedLatLng : unwrappedLatLng.wrapped();
double zoom = camera.zoom.value_or(getZoom());
@@ -110,9 +110,6 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
const Point<double> startPoint = Projection::project(startLatLng, state.scale);
const Point<double> endPoint = Projection::project(latLng, state.scale);
- ScreenCoordinate center = getScreenCoordinate(padding);
- center.y = state.size.height - center.y;
-
// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
const double scale = state.zoomScale(zoom);
@@ -130,6 +127,7 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
state.panning = unwrappedLatLng != startLatLng;
state.scaling = scale != startScale;
state.rotating = bearing != startBearing;
+ const EdgeInsets startEdgeInsets = state.edgeInsets;
startTransition(camera, animation, [=](double t) {
Point<double> framePoint = util::interpolate(startPoint, endPoint, t);
@@ -143,9 +141,14 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
if (pitch != startPitch) {
state.pitch = util::interpolate(startPitch, pitch, t);
}
-
- if (!padding.isFlush()) {
- state.moveLatLng(frameLatLng, center);
+ if (padding != startEdgeInsets) {
+ // Interpolate edge insets
+ state.edgeInsets = {
+ util::interpolate(startEdgeInsets.top(), padding.top(), t),
+ util::interpolate(startEdgeInsets.left(), padding.left(), t),
+ util::interpolate(startEdgeInsets.bottom(), padding.bottom(), t),
+ util::interpolate(startEdgeInsets.right(), padding.right(), t)
+ };
}
}, duration);
}
@@ -159,8 +162,8 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
Where applicable, local variable documentation begins with the associated
variable or function in van Wijk (2003). */
void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &animation) {
- const EdgeInsets& padding = camera.padding;
- const LatLng& latLng = camera.center.value_or(getLatLng(padding, LatLng::Unwrapped)).wrapped();
+ const EdgeInsets& padding = camera.padding.value_or(state.edgeInsets);
+ const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped();
double zoom = camera.zoom.value_or(getZoom());
double bearing = camera.bearing ? -*camera.bearing * util::DEG2RAD : getBearing();
double pitch = camera.pitch ? *camera.pitch * util::DEG2RAD : getPitch();
@@ -170,15 +173,12 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
}
// Determine endpoints.
- LatLng startLatLng = getLatLng(padding, LatLng::Unwrapped).wrapped();
+ LatLng startLatLng = getLatLng(LatLng::Unwrapped).wrapped();
startLatLng.unwrapForShortestPath(latLng);
const Point<double> startPoint = Projection::project(startLatLng, state.scale);
const Point<double> endPoint = Projection::project(latLng, state.scale);
- ScreenCoordinate center = getScreenCoordinate(padding);
- center.y = state.size.height - center.y;
-
// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
pitch = util::clamp(pitch, util::PITCH_MIN, util::PITCH_MAX);
@@ -278,6 +278,7 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
state.panning = true;
state.scaling = true;
state.rotating = bearing != startBearing;
+ const EdgeInsets startEdgeInsets = state.edgeInsets;
startTransition(camera, animation, [=](double k) {
/// s: The distance traveled along the flight path, measured in
@@ -304,9 +305,14 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
if (pitch != startPitch) {
state.pitch = util::interpolate(startPitch, pitch, k);
}
-
- if (!padding.isFlush()) {
- state.moveLatLng(frameLatLng, center);
+ if (padding != startEdgeInsets) {
+ // Interpolate edge insets
+ state.edgeInsets = {
+ util::interpolate(startEdgeInsets.top(), padding.top(), us),
+ util::interpolate(startEdgeInsets.left(), padding.left(), us),
+ util::interpolate(startEdgeInsets.bottom(), padding.bottom(), us),
+ util::interpolate(startEdgeInsets.right(), padding.right(), us)
+ };
}
}, duration);
}
@@ -314,28 +320,16 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
#pragma mark - Position
void Transform::moveBy(const ScreenCoordinate& offset, const AnimationOptions& animation) {
- ScreenCoordinate centerOffset = { offset.x, -offset.y, };
- ScreenCoordinate centerPoint = getScreenCoordinate() - centerOffset;
- easeTo(CameraOptions().withCenter(state.screenCoordinateToLatLng(centerPoint)), animation);
-}
-
-LatLng Transform::getLatLng(const EdgeInsets& padding, LatLng::WrapMode wrap) const {
- if (padding.isFlush()) {
- return state.getLatLng(wrap);
- } else {
- return screenCoordinateToLatLng(padding.getCenter(state.size.width, state.size.height));
- }
+ ScreenCoordinate centerOffset = { offset.x, offset.y };
+ ScreenCoordinate pointOnScreen = state.edgeInsets.getCenter(state.size.width, state.size.height) - centerOffset;
+ // Use unwrapped LatLng to carry information about moveBy direction.
+ easeTo(CameraOptions().withCenter(screenCoordinateToLatLng(pointOnScreen, LatLng::Unwrapped)), animation);
}
-ScreenCoordinate Transform::getScreenCoordinate(const EdgeInsets& padding) const {
- if (padding.isFlush()) {
- return { state.size.width / 2., state.size.height / 2. };
- } else {
- return padding.getCenter(state.size.width, state.size.height);
- }
+LatLng Transform::getLatLng(LatLng::WrapMode wrap) const {
+ return state.getLatLng(wrap);
}
-
#pragma mark - Zoom
double Transform::getZoom() const {
@@ -364,7 +358,7 @@ void Transform::setMaxZoom(const double maxZoom) {
#pragma mark - Bearing
void Transform::rotateBy(const ScreenCoordinate& first, const ScreenCoordinate& second, const AnimationOptions& animation) {
- ScreenCoordinate center = getScreenCoordinate();
+ ScreenCoordinate center = state.edgeInsets.getCenter(state.size.width, state.size.height);
const ScreenCoordinate offset = first - center;
const double distance = std::sqrt(std::pow(2, offset.x) + std::pow(2, offset.y));
@@ -576,10 +570,10 @@ ScreenCoordinate Transform::latLngToScreenCoordinate(const LatLng& latLng) const
return point;
}
-LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point) const {
+LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng::WrapMode wrapMode) const {
ScreenCoordinate flippedPoint = point;
flippedPoint.y = state.size.height - flippedPoint.y;
- return state.screenCoordinateToLatLng(flippedPoint).wrapped();
+ return state.screenCoordinateToLatLng(flippedPoint, wrapMode);
}
} // namespace mbgl
diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp
index 29ca9bd14e..0c018a6e48 100644
--- a/src/mbgl/map/transform.hpp
+++ b/src/mbgl/map/transform.hpp
@@ -47,8 +47,7 @@ public:
@param offset The distance to pan the map by, measured in pixels from
top to bottom and from left to right. */
void moveBy(const ScreenCoordinate& offset, const AnimationOptions& = {});
- LatLng getLatLng(const EdgeInsets& = {}, LatLng::WrapMode = LatLng::Wrapped) const;
- ScreenCoordinate getScreenCoordinate(const EdgeInsets& = {}) const;
+ LatLng getLatLng(LatLng::WrapMode = LatLng::Wrapped) const;
// Bounds
@@ -105,7 +104,7 @@ public:
// Conversion and projection
ScreenCoordinate latLngToScreenCoordinate(const LatLng&) const;
- LatLng screenCoordinateToLatLng(const ScreenCoordinate&) const;
+ LatLng screenCoordinateToLatLng(const ScreenCoordinate&, LatLng::WrapMode = LatLng::Wrapped) const;
private:
MapObserver& observer;
diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp
index 596ed05195..f1d45c043c 100644
--- a/src/mbgl/map/transform_state.cpp
+++ b/src/mbgl/map/transform_state.cpp
@@ -33,24 +33,36 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
return;
}
- // Find the distance from the center point [width/2, height/2] to the
- // center top point [width/2, 0] in Z units, using the law of sines.
+ const double cameraToCenterDistance = getCameraToCenterDistance();
+ auto offset = getCenterOffset();
+
+ // Find the distance from the viewport center point
+ // [width/2 + offset.x, height/2 + offset.y] to the top edge, to point
+ // [width/2 + offset.x, 0] in Z units, using the law of sines.
// 1 Z unit is equivalent to 1 horizontal px at the center of the map
// (the distance between[width/2, height/2] and [width/2 + 1, height/2])
- const double halfFov = getFieldOfView() / 2.0;
+ const double fovAboveCenter = getFieldOfView() * (0.5 + offset.y / size.height);
const double groundAngle = M_PI / 2.0 + getPitch();
- const double topHalfSurfaceDistance = std::sin(halfFov) * getCameraToCenterDistance() / std::sin(M_PI - groundAngle - halfFov);
+ const double aboveCenterSurfaceDistance = std::sin(fovAboveCenter) * cameraToCenterDistance / std::sin(M_PI - groundAngle - fovAboveCenter);
// Calculate z distance of the farthest fragment that should be rendered.
- const double furthestDistance = std::cos(M_PI / 2 - getPitch()) * topHalfSurfaceDistance + getCameraToCenterDistance();
+ const double furthestDistance = std::cos(M_PI / 2 - getPitch()) * aboveCenterSurfaceDistance + cameraToCenterDistance;
// 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);
+ // 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, flippedY ? 1 : -1, 1);
+ matrix::scale(projMatrix, projMatrix, 1.0, flippedY ? 1 : -1, 1);
matrix::translate(projMatrix, projMatrix, 0, 0, -getCameraToCenterDistance());
@@ -134,16 +146,8 @@ ViewportMode TransformState::getViewportMode() const {
#pragma mark - Camera options
CameraOptions TransformState::getCameraOptions(const EdgeInsets& padding) const {
- LatLng center;
- if (padding.isFlush()) {
- center = getLatLng();
- } else {
- ScreenCoordinate point = padding.getCenter(size.width, size.height);
- point.y = size.height - point.y;
- center = screenCoordinateToLatLng(point).wrapped();
- }
return CameraOptions()
- .withCenter(center)
+ .withCenter(getLatLng())
.withPadding(padding)
.withZoom(getZoom())
.withBearing(-bearing * util::RAD2DEG)
@@ -222,6 +226,10 @@ double TransformState::getMaxZoom() const {
return scaleZoom(max_scale);
}
+ScreenCoordinate TransformState::getCenterOffset() const {
+ return { 0.5 * (edgeInsets.left() - edgeInsets.right()), 0.5 * (edgeInsets.top() - edgeInsets.bottom()) };
+}
+
#pragma mark - Rotation
float TransformState::getBearing() const {
@@ -240,7 +248,6 @@ float TransformState::getPitch() const {
return pitch;
}
-
#pragma mark - State
bool TransformState::isChanging() const {
@@ -328,8 +335,7 @@ LatLng TransformState::screenCoordinateToLatLng(const ScreenCoordinate& point, L
mat4 TransformState::coordinatePointMatrix() const {
mat4 proj;
getProjMatrix(proj);
- float s = util::tileSize;
- matrix::scale(proj, proj, s, s, 1);
+ matrix::scale(proj, proj, util::tileSize, util::tileSize, 1);
matrix::multiply(proj, getPixelMatrix(), proj);
return proj;
}
diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp
index f51660912c..9e501d600f 100644
--- a/src/mbgl/map/transform_state.hpp
+++ b/src/mbgl/map/transform_state.hpp
@@ -62,6 +62,10 @@ public:
void setMaxZoom(double);
double getMaxZoom() const;
+ // Viewport center offset, from [size.width / 2, size.height / 2], defined
+ // by |edgeInsets| in screen coordinates, with top left origin.
+ ScreenCoordinate getCenterOffset() const;
+
// Rotation
float getBearing() const;
float getFieldOfView() const;
@@ -137,6 +141,8 @@ private:
double ySkew = 1.0;
bool axonometric = false;
+ EdgeInsets edgeInsets;
+
// cache values for spherical mercator math
double Bc = Projection::worldSize(scale) / util::DEGREES_MAX;
double Cc = Projection::worldSize(scale) / util::M2PI;
diff --git a/src/mbgl/programs/symbol_program.cpp b/src/mbgl/programs/symbol_program.cpp
index b538a23331..8a7dccd55e 100644
--- a/src/mbgl/programs/symbol_program.cpp
+++ b/src/mbgl/programs/symbol_program.cpp
@@ -66,7 +66,7 @@ Values makeValues(const bool isText,
const bool pitchWithMap = values.pitchAlignment == style::AlignmentType::Map;
const bool rotateWithMap = values.rotationAlignment == style::AlignmentType::Map;
- // Line label rotation happens in `updateLineLabels`
+ // Line label rotation happens in `updateLineLabels`/`reprojectLineLabels``
// Pitched point labels are automatically rotated by the labelPlaneMatrix projection
// Unpitched point labels need to have their rotation applied after projection
const bool rotateInShader = rotateWithMap && !pitchWithMap && !alongLine;
diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp
index 88e59bf51c..e3d832a854 100644
--- a/src/mbgl/text/collision_index.cpp
+++ b/src/mbgl/text/collision_index.cpp
@@ -348,25 +348,28 @@ std::pair<float,float> CollisionIndex::projectAnchor(const mat4& posMatrix, cons
std::pair<Point<float>,float> CollisionIndex::projectAndGetPerspectiveRatio(const mat4& posMatrix, const Point<float>& point) const {
vec4 p = {{ point.x, point.y, 0, 1 }};
matrix::transformMat4(p, p, posMatrix);
+ auto offset = transformState.getCenterOffset();
+ auto size = transformState.getSize();
return std::make_pair(
Point<float>(
- (((p[0] / p[3] + 1) / 2) * transformState.getSize().width) + viewportPadding,
- (((-p[1] / p[3] + 1) / 2) * transformState.getSize().height) + viewportPadding
+ (((p[0] / p[3] + 1) / 2) * size.width) + viewportPadding + offset.x,
+ (((-p[1] / p[3] + 1) / 2) * size.height) + viewportPadding + offset.y
),
// See perspective ratio comment in symbol_sdf.vertex
// We're doing collision detection in viewport space so we need
// to scale down boxes in the distance
- 0.5 + 0.5 * (transformState.getCameraToCenterDistance() / p[3])
+ 0.5 + 0.5 * transformState.getCameraToCenterDistance() / p[3]
);
}
Point<float> CollisionIndex::projectPoint(const mat4& posMatrix, const Point<float>& point) const {
vec4 p = {{ point.x, point.y, 0, 1 }};
matrix::transformMat4(p, p, posMatrix);
- return Point<float>(
- (((p[0] / p[3] + 1) / 2) * transformState.getSize().width) + viewportPadding,
- (((-p[1] / p[3] + 1) / 2) * transformState.getSize().height) + viewportPadding
- );
+ auto offset = transformState.getCenterOffset();
+ auto size = transformState.getSize();
+ return Point<float> {
+ static_cast<float>((((p[0] / p[3] + 1) / 2) * size.width) + viewportPadding + offset.x),
+ static_cast<float>((((-p[1] / p[3] + 1) / 2) * size.height) + viewportPadding + offset.y) };
}
} // namespace mbgl
diff --git a/test/api/annotations.test.cpp b/test/api/annotations.test.cpp
index 2d76a3d154..d2770dc97a 100644
--- a/test/api/annotations.test.cpp
+++ b/test/api/annotations.test.cpp
@@ -434,6 +434,73 @@ TEST(Annotations, VisibleFeatures) {
EXPECT_EQ(features.size(), ids.size());
}
+TEST(Annotations, ViewFrustumCulling) {
+ // The purpose of the test is to control that annotations outside screen
+ // rectangle are not rendered for different camera setup, especially when
+ // using edge insets - viewport center is then offsetted.
+
+ // Important premise of this test is "static const float viewportPadding = 100;"
+ // as defined in collision_index.cpp: tests using edge insets are writen so that
+ // padding is 128 (half of viewSize width). If increasing viewportPadding,
+ // increase the padding in test cases below.
+ AnnotationTest test;
+
+ auto viewSize = test.frontend.getSize();
+ auto box = ScreenBox { {}, { double(viewSize.width), double(viewSize.height) } };
+
+ test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
+ test.map.addAnnotationImage(namedMarker("default_marker"));
+ const LatLng center = { 5.0, 5.0 };
+ test.map.jumpTo(CameraOptions().withCenter(center).withZoom(3.0));
+
+ LatLng tl = test.map.latLngForPixel(ScreenCoordinate(0, 0));
+ LatLng br = test.map.latLngForPixel(ScreenCoordinate(viewSize.width, viewSize.height));
+
+ std::vector<LatLng> latLngs = {
+ tl,
+ test.map.latLngForPixel(ScreenCoordinate(viewSize.width, 0)),
+ test.map.latLngForPixel(ScreenCoordinate(0, viewSize.height)),
+ br,
+ center};
+
+ std::vector<mbgl::AnnotationID> ids;
+ for (auto latLng : latLngs) {
+ ids.push_back(test.map.addAnnotation(SymbolAnnotation { { latLng.longitude(), latLng.latitude() }, "default_marker" }));
+ }
+
+ std::vector<std::pair<CameraOptions, std::vector<uint64_t>>> expectedVisibleForCamera = {
+ // Start with all markers visible.
+ { CameraOptions(), { 0, 1, 2, 3, 4 } },
+ // Move center to topLeft: only former center and top left (now center) are visible.
+ { CameraOptions().withCenter(tl), { 0, 4 } },
+ // Reset center. With pitch: only top row markers and center are visible.
+ { CameraOptions().withCenter(center).withPitch(45), { 0, 1, 4 } },
+ // Reset pitch, and use padding to move viewport center: only topleft and center are visible.
+ { CameraOptions().withPitch(0).withPadding(EdgeInsets { viewSize.height * 0.5, viewSize.width * 0.5, 0, 0 }), { 0, 4 } },
+ // Use opposite padding to move viewport center: only bottom right and center are visible.
+ { CameraOptions().withPitch(0).withPadding(EdgeInsets { 0, 0, viewSize.height * 0.5, viewSize.width * 0.5 }), { 3, 4 } },
+ // Use top padding to move viewport center: top row and center are visible.
+ { CameraOptions().withPitch(0).withPadding(EdgeInsets { viewSize.height * 0.5, 0, 0, 0 }), { 0, 1, 4 } },
+ // Use bottom padding: only bottom right and center are visible.
+ { CameraOptions().withPitch(0).withPadding(EdgeInsets { 0, 0, viewSize.height * 0.5, 0 }), { 2, 3, 4 } },
+ // Left padding and pitch: top left, bottom left and center are visible.
+ { CameraOptions().withPitch(45).withPadding(EdgeInsets { 0, viewSize.width * 0.5, 0, 0 }), { 0, 2, 4 } }
+ };
+
+ for (unsigned i = 0; i < expectedVisibleForCamera.size(); i++) {
+ auto testCase = expectedVisibleForCamera[i];
+ test.map.jumpTo(testCase.first);
+ test.frontend.render(test.map);
+ auto features = test.frontend.getRenderer()->queryRenderedFeatures(box, {});
+ for (uint64_t id : testCase.second) { // testCase.second is vector of ids expected.
+ EXPECT_NE(std::find_if(features.begin(), features.end(), [&id](auto feature) {
+ return id == feature.id.template get<uint64_t>();
+ }), features.end()) << "Point with id " << id << " is missing in test case " << i;
+ EXPECT_EQ(features.size(), testCase.second.size()) << " in test case " << i;
+ }
+ }
+}
+
TEST(Annotations, TopOffsetPixels) {
AnnotationTest test;
diff --git a/test/fixtures/local_glyphs/no_local_with_content_insets/expected.png b/test/fixtures/local_glyphs/no_local_with_content_insets/expected.png
new file mode 100644
index 0000000000..f161a95c8e
--- /dev/null
+++ b/test/fixtures/local_glyphs/no_local_with_content_insets/expected.png
Binary files differ
diff --git a/test/fixtures/local_glyphs/no_local_with_content_insets_and_pitch/expected.png b/test/fixtures/local_glyphs/no_local_with_content_insets_and_pitch/expected.png
new file mode 100644
index 0000000000..edb8d07a40
--- /dev/null
+++ b/test/fixtures/local_glyphs/no_local_with_content_insets_and_pitch/expected.png
Binary files differ
diff --git a/test/map/transform.test.cpp b/test/map/transform.test.cpp
index 33d325dfb2..3d37312b17 100644
--- a/test/map/transform.test.cpp
+++ b/test/map/transform.test.cpp
@@ -288,25 +288,48 @@ TEST(Transform, Padding) {
ASSERT_DOUBLE_EQ(0, transform.getLatLng().latitude());
ASSERT_DOUBLE_EQ(0, transform.getLatLng().longitude());
-
- transform.jumpTo(CameraOptions().withCenter(LatLng { 10, -100 }).withZoom(10.0));
+ CameraOptions nonPaddedCameraOptions = CameraOptions().withCenter(LatLng { 10, -100 }).withZoom(10.0);
+ transform.jumpTo(nonPaddedCameraOptions);
const LatLng trueCenter = transform.getLatLng();
ASSERT_DOUBLE_EQ(10, trueCenter.latitude());
ASSERT_DOUBLE_EQ(-100, trueCenter.longitude());
ASSERT_DOUBLE_EQ(10, transform.getZoom());
- const LatLng manualShiftedCenter = transform.getState().screenCoordinateToLatLng({
+ const LatLng screenCenter = transform.screenCoordinateToLatLng({
+ 1000.0 / 2.0,
1000.0 / 2.0,
- 1000.0 / 4.0,
+ });
+ const LatLng upperHalfCenter = transform.screenCoordinateToLatLng({
+ 1000.0 / 2.0,
+ 1000.0 * 0.25,
});
EdgeInsets padding(1000.0 / 2.0, 0, 0, 0);
- const LatLng shiftedCenter = transform.getLatLng(padding);
- ASSERT_NE(trueCenter.latitude(), shiftedCenter.latitude());
- ASSERT_NEAR(trueCenter.longitude(), shiftedCenter.longitude(), 1e-8);
- ASSERT_DOUBLE_EQ(manualShiftedCenter.latitude(), shiftedCenter.latitude());
- ASSERT_DOUBLE_EQ(manualShiftedCenter.longitude(), shiftedCenter.longitude());
+ // CameraOption center and zoom don't change when padding changes: center of
+ // viewport remains the same as padding defines viwport center offset in rendering.
+ CameraOptions paddedOptions = CameraOptions().withPadding(padding);
+ transform.jumpTo(paddedOptions);
+ const LatLng theSameCenter = transform.getLatLng();
+ ASSERT_DOUBLE_EQ(trueCenter.latitude(), theSameCenter.latitude());
+ ASSERT_DOUBLE_EQ(trueCenter.longitude(), theSameCenter.longitude());
+
+ // However, LatLng is now at the center of lower half - verify conversion
+ // from screen coordinate to LatLng.
+ const LatLng paddedLowerHalfScreenCenter = transform.screenCoordinateToLatLng({
+ 1000.0 / 2.0,
+ 1000.0 * 0.75,
+ });
+ ASSERT_NEAR(screenCenter.latitude(), paddedLowerHalfScreenCenter.latitude(), 1e-10);
+ ASSERT_NEAR(screenCenter.longitude(), paddedLowerHalfScreenCenter.longitude(), 1e-10);
+
+ // LatLng previously in upper half center, should now be under screen center.
+ const LatLng paddedScreenCenter = transform.screenCoordinateToLatLng({
+ 1000.0 / 2.0,
+ 1000.0 / 2.0,
+ });
+ ASSERT_NEAR(upperHalfCenter.latitude(), paddedScreenCenter.latitude(), 1e-10);
+ ASSERT_NEAR(upperHalfCenter.longitude(), paddedScreenCenter.longitude(), 1e-10);
}
TEST(Transform, MoveBy) {
@@ -440,7 +463,7 @@ TEST(Transform, Camera) {
flyOptions.transitionFrameFn = [&](double t) {
ASSERT_TRUE(t >= 0 && t <= 1);
ASSERT_LE(latLng2.latitude(), transform.getLatLng().latitude());
- ASSERT_GE(latLng2.longitude(), transform.getLatLng({}, LatLng::Unwrapped).longitude());
+ ASSERT_GE(latLng2.longitude(), transform.getLatLng(LatLng::Unwrapped).longitude());
};
flyOptions.transitionFinishFn = [&]() {
// XXX Fix precision loss in flyTo:
@@ -618,7 +641,7 @@ TEST(Transform, LatLngBounds) {
// Try crossing the antimeridian from the right.
transform.jumpTo(CameraOptions().withCenter(LatLng { 0.0, 200.0 }));
- ASSERT_DOUBLE_EQ(transform.getLatLng({}, LatLng::Unwrapped).longitude(), 180.0);
+ ASSERT_DOUBLE_EQ(transform.getLatLng(LatLng::Unwrapped).longitude(), 180.0);
ASSERT_DOUBLE_EQ(transform.getLatLng().longitude(), -180.0);
// -1 | 0 | +1
diff --git a/test/text/local_glyph_rasterizer.test.cpp b/test/text/local_glyph_rasterizer.test.cpp
index 4c0719e16c..e7c0465e45 100644
--- a/test/text/local_glyph_rasterizer.test.cpp
+++ b/test/text/local_glyph_rasterizer.test.cpp
@@ -43,9 +43,9 @@ public:
MapAdapter map { frontend, MapObserver::nullObserver(), fileSource,
MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize())};
- void checkRendering(const char * name) {
+ void checkRendering(const char * name, double imageMatchPixelsThreshold = 0.05, double pixelMatchThreshold = 0.1) {
test::checkImage(std::string("test/fixtures/local_glyphs/") + name,
- frontend.render(map), 0.05, 0.1);
+ frontend.render(map), imageMatchPixelsThreshold, pixelMatchThreshold);
}
};
@@ -85,6 +85,50 @@ TEST(LocalGlyphRasterizer, NoLocal) {
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
- test.checkRendering("no_local");
+ test.checkRendering("no_local", 0.001, 0.1);
}
+TEST(LocalGlyphRasterizer, NoLocalWithContentInsets) {
+ // Expectations: content insets imply rendering to offsetted viewport center.
+ // Rendered text should be on the same offset and keep the same size as
+ // with no offset.
+ LocalGlyphRasterizerTest test({});
+
+ test.fileSource->glyphsResponse = [&] (const Resource& resource) {
+ EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
+ Response response;
+ response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
+ return response;
+ };
+ auto viewSize = test.frontend.getSize();
+ test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
+
+ // Expected image was created using center offset as in the line below - content insets,
+ // with no pitch defined, should produce the same output.
+ // test.map.moveBy({ viewSize.width * 0.25, 0 });
+
+ test.map.jumpTo(CameraOptions().withPadding(EdgeInsets { 0, viewSize.width * 0.25 * 2, 0, 0 }));
+ test.checkRendering("no_local_with_content_insets", 0.001, 0.1);
+}
+
+TEST(LocalGlyphRasterizer, NoLocalWithContentInsetsAndPitch) {
+ // Expectations: content insets imply rendering to offsetted viewport center.
+ // Rendered text should be on the same offset and keep the same size as
+ // with no offset.
+ LocalGlyphRasterizerTest test({});
+
+ test.fileSource->glyphsResponse = [&] (const Resource& resource) {
+ EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
+ Response response;
+ response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
+ return response;
+ };
+ auto viewSize = test.frontend.getSize();
+ test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
+
+ // Expected image was verified using no-padding render, offsetted to right
+ // using bitmap editor.
+
+ test.map.jumpTo(CameraOptions().withPitch(45).withPadding(EdgeInsets { 0, viewSize.width * 0.4, 0, 0 }));
+ test.checkRendering("no_local_with_content_insets_and_pitch", 0.001, 0.1);
+}