summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksandar Stojiljkovic <aleksandar.stojiljkovic@mapbox.com>2019-04-15 15:03:47 +0300
committerAleksandar Stojiljkovic <aleksandar.stojiljkovic@mapbox.com>2019-05-13 11:07:12 +0300
commitcff558252b1b86afb6c390f903b3a128dfac33ae (patch)
tree1fd441a495604672c4b98744e4f67253a0990049
parent615c5387fccf28c0d54fb71475f7a878318e1413 (diff)
downloadqtlocation-mapboxgl-upstream/astojilj-viewport-center-offset.tar.gz
WIP: Viewport center offsetupstream/astojilj-viewport-center-offset
* Extend glViewport to have the center of viewport out of the screen center * iOS app changes to verify vanishing point change: "Turn On Content Insets"
-rw-r--r--include/mbgl/map/camera.hpp4
-rw-r--r--platform/ios/app/MBXViewController.m70
-rw-r--r--platform/ios/src/MGLMapView.mm20
-rw-r--r--src/mbgl/gfx/command_encoder.hpp2
-rw-r--r--src/mbgl/gl/command_encoder.cpp5
-rw-r--r--src/mbgl/gl/command_encoder.hpp2
-rw-r--r--src/mbgl/layout/symbol_projection.cpp6
-rw-r--r--src/mbgl/map/transform.cpp92
-rw-r--r--src/mbgl/map/transform.hpp6
-rw-r--r--src/mbgl/map/transform_state.cpp53
-rw-r--r--src/mbgl/map/transform_state.hpp19
-rw-r--r--src/mbgl/programs/symbol_program.cpp13
-rw-r--r--src/mbgl/renderer/renderer_impl.cpp6
-rw-r--r--src/mbgl/renderer/renderer_state.cpp4
-rw-r--r--src/mbgl/text/collision_index.cpp13
-rw-r--r--test/api/annotations.test.cpp68
-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 -> 8999 bytes
-rw-r--r--test/map/transform.test.cpp33
-rw-r--r--test/text/local_glyph_rasterizer.test.cpp50
20 files changed, 349 insertions, 117 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..87c9bd0864 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,55 @@ CLLocationCoordinate2D randomWorldCoordinate() {
}
break;
}
+ case MBXSettingsMiscellaneousSetContentInsets:
+ {
+ _contentInsetsEnabled = !_contentInsetsEnabled;
+ UIEdgeInsets c = self.mapView.bounds.size.width > self.mapView.bounds.size.height ?
+ UIEdgeInsetsMake(0.0, 0.5, 0.0, 0.0) : UIEdgeInsetsMake(0.25, 0.0, 0.0, 0.25);
+ 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, c.top * 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, 0, c.left * 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];
+ view = [[UIView alloc]initWithFrame:CGRectMake((1 - c.right) * self.mapView.bounds.size.width, 0, c.right * 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];
+ view = [[UIView alloc]initWithFrame:CGRectMake(0, (1 - c.bottom) * self.mapView.bounds.size.height, 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]];
+
+ _originalContentInsets = [self.mapView contentInset];
+ 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 animationTimingFunction:nil completionHandler:^{
+ [weakSelf.mapView setContentInset:UIEdgeInsetsMake(self.mapView.bounds.size.height * c.top,
+ self.mapView.bounds.size.width * c.left,
+ self.mapView.bounds.size.height * c.bottom,
+ self.mapView.bounds.size.width * c.right) 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 +2059,22 @@ 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.mapView setContentInset:_originalContentInsets];
+ }
+ 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..534638ce57 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,12 +3308,9 @@ 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
{
+ BOOL contentInsetsAreEqual = UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets);
if (!_mbglMap)
{
if (completion)
@@ -3356,7 +3348,7 @@ public:
}
MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions];
- if ([self.camera isEqualToMapCamera:camera])
+ if ([self.camera isEqualToMapCamera:camera] && contentInsetsAreEqual)
{
if (completion)
{
@@ -3893,7 +3885,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/gfx/command_encoder.hpp b/src/mbgl/gfx/command_encoder.hpp
index 145c2bef82..a76b5e81b3 100644
--- a/src/mbgl/gfx/command_encoder.hpp
+++ b/src/mbgl/gfx/command_encoder.hpp
@@ -28,6 +28,8 @@ public:
}
virtual std::unique_ptr<RenderPass> createRenderPass(const char* name, const RenderPassDescriptor&) = 0;
+
+ virtual void setViewport(double originX, double originY, double width, double height) = 0;
};
} // namespace gfx
diff --git a/src/mbgl/gl/command_encoder.cpp b/src/mbgl/gl/command_encoder.cpp
index 26d1008789..f823f1ec0c 100644
--- a/src/mbgl/gl/command_encoder.cpp
+++ b/src/mbgl/gl/command_encoder.cpp
@@ -19,6 +19,11 @@ CommandEncoder::createRenderPass(const char* name, const gfx::RenderPassDescript
return std::make_unique<gl::RenderPass>(*this, name, descriptor);
}
+void CommandEncoder::setViewport(double originX, double originY, double width, double height) {
+ context.viewport = { static_cast<int32_t>(originX), static_cast<int32_t>(originY),
+ { static_cast<uint32_t>(width), static_cast<uint32_t>(height) } };
+}
+
void CommandEncoder::pushDebugGroup(const char* name) {
(void)name;
#ifndef NDEBUG
diff --git a/src/mbgl/gl/command_encoder.hpp b/src/mbgl/gl/command_encoder.hpp
index 8074000034..b28683912b 100644
--- a/src/mbgl/gl/command_encoder.hpp
+++ b/src/mbgl/gl/command_encoder.hpp
@@ -19,6 +19,8 @@ public:
std::unique_ptr<gfx::RenderPass> createRenderPass(const char* name, const gfx::RenderPassDescriptor&) override;
+ void setViewport(double originX, double originY, double width, double height) override;
+
private:
void pushDebugGroup(const char* name) override;
void popDebugGroup() override;
diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp
index b7858f8deb..8f8d6cebb5 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.
@@ -401,7 +401,9 @@ namespace mbgl {
continue;
}
- const float cameraToAnchorDistance = anchorPos[3];
+ const double viewportZoomCorrection = pitchWithMap ? 1.0 / state.getViewport().scale : 1.0;
+
+ const float cameraToAnchorDistance = anchorPos[3] * viewportZoomCorrection; // TODO(astojilj): explanation
const float perspectiveRatio = 0.5 + 0.5 * (cameraToAnchorDistance / state.getCameraToCenterDistance());
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol);
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
index e97f0da3f1..aa103c0208 100644
--- a/src/mbgl/map/transform.cpp
+++ b/src/mbgl/map/transform.cpp
@@ -57,6 +57,7 @@ void Transform::resize(const Size size) {
state.size = size;
state.constrain(state.scale, state.x, state.y);
+ recalculateViewport();
observer.onCameraDidChange(MapObserver::CameraChangeMode::Immediate);
}
@@ -77,13 +78,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 +111,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 +128,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 +142,15 @@ 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)
+ };
+ recalculateViewport();
}
}, duration);
}
@@ -159,8 +164,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,14 +175,14 @@ 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;
+ // ScreenCoordinate center = getScreenCoordinate(padding);
+ // center.y = state.size.height - center.y;
// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
@@ -278,6 +283,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 +310,15 @@ 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)
+ };
+ recalculateViewport();
}
}, duration);
}
@@ -314,28 +326,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);
+ 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);
}
-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));
- }
+LatLng Transform::getLatLng(LatLng::WrapMode wrap) const {
+ return state.getLatLng(wrap);
}
-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);
- }
-}
-
-
#pragma mark - Zoom
double Transform::getZoom() const {
@@ -364,7 +364,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));
@@ -438,6 +438,18 @@ ProjectionMode Transform::getProjectionMode() const {
.withYSkew(state.ySkew);
}
+void Transform::recalculateViewport() {
+ auto size = state.size;
+ auto edgeInsets = state.edgeInsets;
+ ScreenCoordinate center = {0.5 * (size.width + edgeInsets.left() - edgeInsets.right()),
+ 0.5 * (size.height + edgeInsets.top() - edgeInsets.bottom())};
+ double s = std::max(std::max(center.x, size.width - center.x) / size.width,
+ std::max(center.y, size.height - center.y) / size.height) * 2.0;
+ double width = s * size.width;
+ double height = s * size.height;
+ state.viewport = { center.x - 0.5 * width, size.height - height - (size.height - center.y - 0.5 * height), width, height, s };
+}
+
#pragma mark - Transition
void Transform::startTransition(const CameraOptions& camera,
@@ -576,10 +588,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..a30d2aefd2 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;
@@ -115,6 +114,7 @@ private:
const AnimationOptions&,
std::function<void(double)>,
const Duration&);
+ void recalculateViewport();
TimePoint transitionStart;
Duration transitionDuration;
diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp
index 45bee7ac7f..3d19f822d5 100644
--- a/src/mbgl/map/transform_state.cpp
+++ b/src/mbgl/map/transform_state.cpp
@@ -12,7 +12,7 @@ TransformState::TransformState(ConstrainMode constrainMode_, ViewportMode viewpo
: bounds(LatLngBounds::unbounded())
, constrainMode(constrainMode_)
, viewportMode(viewportMode_)
-{
+ {
}
#pragma mark - Matrix
@@ -33,24 +33,32 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
return;
}
+ // To offset viewport center, we render to offsetted and enlarged viewport.
+ // Enlarged viewport implies additional zoom - to compensate it, and get the
+ // same as with no insets specified, we flippedY ? 1.0 : -1.0, viewportScale:
+ // z axis, near and far value, by the same scale the viewport got.
+ const double cameraToCenterDistanceAdjusted = getCameraToCenterDistance() * getViewport().scale;
+
+
// 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.
// 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 groundAngle = M_PI / 2.0 + getPitch();
- const double topHalfSurfaceDistance = std::sin(halfFov) * getCameraToCenterDistance() / std::sin(M_PI - groundAngle - halfFov);
+ const double topHalfSurfaceDistance = std::sin(halfFov) * cameraToCenterDistanceAdjusted / std::sin(M_PI - groundAngle - halfFov);
// 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()) * topHalfSurfaceDistance + cameraToCenterDistanceAdjusted;
// 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);
const bool flippedY = viewportMode == ViewportMode::FlippedY;
- matrix::scale(projMatrix, projMatrix, 1, flippedY ? 1 : -1, 1);
+ // Viewport zoom applied to z axis as described above.
+ matrix::scale(projMatrix, projMatrix, 1.0, flippedY ? 1.0 : -1.0, viewport.scale);
matrix::translate(projMatrix, projMatrix, 0, 0, -getCameraToCenterDistance());
@@ -134,16 +142,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)
@@ -180,10 +180,6 @@ uint8_t TransformState::getIntegerZoom() const {
return getZoom();
}
-double TransformState::getZoomFraction() const {
- return getZoom() - getIntegerZoom();
-}
-
#pragma mark - Bounds
void TransformState::setLatLngBounds(LatLngBounds bounds_) {
@@ -240,7 +236,6 @@ float TransformState::getPitch() const {
return pitch;
}
-
#pragma mark - State
bool TransformState::isChanging() const {
@@ -278,7 +273,7 @@ ScreenCoordinate TransformState::latLngToScreenCoordinate(const LatLng& latLng)
return {};
}
- mat4 mat = coordinatePointMatrix(getZoom());
+ mat4 mat = coordinatePointMatrix();
vec4 p;
Point<double> pt = Projection::project(latLng, scale) / util::tileSize;
vec4 c = {{ pt.x, pt.y, 0, 1 }};
@@ -292,7 +287,7 @@ LatLng TransformState::screenCoordinateToLatLng(const ScreenCoordinate& point, L
}
float targetZ = 0;
- mat4 mat = coordinatePointMatrix(getZoom());
+ mat4 mat = coordinatePointMatrix();
mat4 inverted;
bool err = matrix::invert(inverted, mat);
@@ -325,10 +320,11 @@ LatLng TransformState::screenCoordinateToLatLng(const ScreenCoordinate& point, L
return Projection::unproject(util::interpolate(p0, p1, t), scale / util::tileSize, wrapMode);
}
-mat4 TransformState::coordinatePointMatrix(double z) const {
+mat4 TransformState::coordinatePointMatrix() const {
mat4 proj;
getProjMatrix(proj);
- float s = Projection::worldSize(scale) / std::pow(2, z);
+ // Projection::worldSize(scale) / zoomScale(getZoom()) == util::tileSize
+ float s = util::tileSize;
matrix::scale(proj, proj, s, s, 1);
matrix::multiply(proj, getPixelMatrix(), proj);
return proj;
@@ -337,9 +333,10 @@ mat4 TransformState::coordinatePointMatrix(double z) const {
mat4 TransformState::getPixelMatrix() const {
mat4 m;
matrix::identity(m);
- matrix::scale(m, m,
- static_cast<double>(size.width) / 2, -static_cast<double>(size.height) / 2, 1);
+ matrix::scale(m, m, viewport.width / 2, -viewport.height / 2, 1);
matrix::translate(m, m, 1, -1, 0);
+ m[12] += viewport.x;
+ m[13] += viewport.y;
return m;
}
@@ -409,6 +406,10 @@ void TransformState::setScalePoint(const double newScale, const ScreenCoordinate
Cc = Projection::worldSize(scale) / util::M2PI;
}
+const TransformState::Viewport& TransformState::getViewport() const {
+ return viewport;
+}
+
float TransformState::getCameraToTileDistance(const UnwrappedTileID& tileID) const {
mat4 projectionMatrix;
getProjMatrix(projectionMatrix);
@@ -426,12 +427,12 @@ float TransformState::maxPitchScaleFactor() const {
return {};
}
auto latLng = screenCoordinateToLatLng({ 0, static_cast<float>(getSize().height) });
- mat4 mat = coordinatePointMatrix(getZoom());
+ mat4 mat = coordinatePointMatrix();
Point<double> pt = Projection::project(latLng, scale) / util::tileSize;
vec4 p = {{ pt.x, pt.y, 0, 1 }};
vec4 topPoint;
matrix::transformMat4(topPoint, p, mat);
- return topPoint[3] / getCameraToCenterDistance();
+ return topPoint[3] / (getCameraToCenterDistance() * getViewport().scale);
}
} // namespace mbgl
diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp
index 811fa253af..44febac584 100644
--- a/src/mbgl/map/transform_state.hpp
+++ b/src/mbgl/map/transform_state.hpp
@@ -20,7 +20,6 @@ class UnwrappedTileID;
class TransformState {
friend class Transform;
- friend class RendererState;
public:
TransformState(ConstrainMode = ConstrainMode::HeightOnly, ViewportMode = ViewportMode::Default);
@@ -52,7 +51,6 @@ public:
// Zoom
double getZoom() const;
uint8_t getIntegerZoom() const;
- double getZoomFraction() const;
// Bounds
void setLatLngBounds(LatLngBounds);
@@ -62,6 +60,18 @@ public:
void setMaxZoom(double);
double getMaxZoom() const;
+ // Enlarged and offsetted viewport's left, top, width and height in logical
+ // coordinates.
+ struct Viewport {
+ double x;
+ double y;
+ double width;
+ double height;
+ double scale;
+ };
+
+ const Viewport& getViewport() const;
+
// Rotation
float getBearing() const;
float getFieldOfView() const;
@@ -104,7 +114,7 @@ private:
// logical dimensions
Size size;
- mat4 coordinatePointMatrix(double z) const;
+ mat4 coordinatePointMatrix() const;
mat4 getPixelMatrix() const;
/** Recenter the map so that the given coordinate is located at the given
@@ -137,6 +147,9 @@ private:
double ySkew = 1.0;
bool axonometric = false;
+ EdgeInsets edgeInsets;
+ Viewport viewport;
+
// 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..1cac46a322 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;
@@ -82,6 +82,8 @@ Values makeValues(const bool isText,
mat4 glCoordMatrix = getGlCoordMatrix(tile.matrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits);
+ const double viewportZoomCorrection = pitchWithMap ? 1.0 / state.getViewport().scale
+ : state.getViewport().scale;
return Values {
uniforms::matrix::Value( tile.translatedMatrix(values.translate,
values.translateAnchor,
@@ -96,7 +98,8 @@ Values makeValues(const bool isText,
uniforms::texsize::Value( texsize ),
uniforms::fade_change::Value( symbolFadeChange ),
uniforms::is_text::Value( isText ),
- uniforms::camera_to_center_distance::Value( state.getCameraToCenterDistance() ),
+ // uniforms::camera_to_center_distance::Value( state.getCameraToCenterDistance() ),
+ uniforms::camera_to_center_distance::Value( state.getCameraToCenterDistance() / viewportZoomCorrection ),
uniforms::pitch::Value( state.getPitch() ),
uniforms::pitch_with_map::Value( pitchWithMap ),
uniforms::rotate_symbol::Value( rotateInShader ),
@@ -141,9 +144,9 @@ SymbolSDFProgram<Name, PaintProperties>::layoutUniformValues(const bool isText,
const TransformState& state,
const float symbolFadeChange,
const SymbolSDFPart part) {
- const float gammaScale = (values.pitchAlignment == AlignmentType::Map
- ? std::cos(state.getPitch()) * state.getCameraToCenterDistance()
- : 1.0);
+ const float gammaScale = values.pitchAlignment == AlignmentType::Map
+ ? std::cos(state.getPitch()) * state.getCameraToCenterDistance() * state.getViewport().scale
+ : 1;
return makeValues<SymbolSDFProgram<Name, PaintProperties>::LayoutUniformValues>(
isText,
diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp
index 3db9439b5c..3a9bd2eacb 100644
--- a/src/mbgl/renderer/renderer_impl.cpp
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -418,6 +418,12 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
color = backgroundColor;
}
parameters.renderPass = parameters.encoder->createRenderPass("main buffer", { parameters.backend.getDefaultRenderable(), color, 1, 0 });
+
+ auto viewport = parameters.state.getViewport();
+ // TransformState::Viewport is with top left origin: transform it to bottom left.
+ double bottomLeftOriginY = parameters.state.getSize().height - viewport.height - viewport.y;
+ parameters.encoder->setViewport(viewport.x * pixelRatio, bottomLeftOriginY * pixelRatio,
+ viewport.width * pixelRatio, viewport.height * pixelRatio);
}
// Actually render the layers
diff --git a/src/mbgl/renderer/renderer_state.cpp b/src/mbgl/renderer/renderer_state.cpp
index 33f6eb27dd..492c8b5d4a 100644
--- a/src/mbgl/renderer/renderer_state.cpp
+++ b/src/mbgl/renderer/renderer_state.cpp
@@ -45,12 +45,12 @@ ScreenCoordinate RendererState::pixelForLatLng(const UpdateParameters& updatePar
LatLng unwrappedLatLng = latLng.wrapped();
unwrappedLatLng.unwrapForShortestPath(updateParameters.transformState.getLatLng());
const ScreenCoordinate point = updateParameters.transformState.latLngToScreenCoordinate(latLng);
- return ScreenCoordinate { point.x, updateParameters.transformState.size.height - point.y };
+ return ScreenCoordinate { point.x, updateParameters.transformState.getSize().height - point.y };
}
LatLng RendererState::latLngForPixel(const UpdateParameters& updateParameters, const ScreenCoordinate& point) {
ScreenCoordinate flippedPoint = point;
- flippedPoint.y = updateParameters.transformState.size.height - flippedPoint.y;
+ flippedPoint.y = updateParameters.transformState.getSize().height - flippedPoint.y;
return updateParameters.transformState.screenCoordinateToLatLng(flippedPoint);
}
diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp
index 88e59bf51c..32d2d99e31 100644
--- a/src/mbgl/text/collision_index.cpp
+++ b/src/mbgl/text/collision_index.cpp
@@ -348,24 +348,27 @@ 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 viewport = transformState.getViewport();
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) * viewport.width) + viewportPadding + viewport.x,
+ (((-p[1] / p[3] + 1) / 2) * viewport.height) + viewportPadding + viewport.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() * viewport.scale / 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);
+ auto viewport = transformState.getViewport();
+
return 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) * viewport.width) + viewport.x,
+ (((-p[1] / p[3] + 1) / 2) * viewport.height) + viewport.y
);
}
diff --git a/test/api/annotations.test.cpp b/test/api/annotations.test.cpp
index 2d76a3d154..0d311a203a 100644
--- a/test/api/annotations.test.cpp
+++ b/test/api/annotations.test.cpp
@@ -434,6 +434,74 @@ 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 - to offset viewport center, viewport is enlarged and
+ // offset and screen content is subrectangle of viewport rectangle.
+
+ // 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.
+ { {}, { 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, static_cast<double>(viewSize.width), 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..c61c15840d
--- /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..fac0d6aec7 100644
--- a/test/map/transform.test.cpp
+++ b/test/map/transform.test.cpp
@@ -83,7 +83,6 @@ TEST(Transform, IntegerZoom) {
transform.jumpTo(CameraOptions().withZoom(zoom));
ASSERT_NEAR(transform.getZoom(), zoom, 1e-8);
ASSERT_EQ(transform.getState().getIntegerZoom(), zoomInt);
- ASSERT_NEAR(transform.getState().getZoomFraction(), zoom - zoomInt, 1e-8);
};
for (uint8_t zoomInt = 0; zoomInt < 20; ++zoomInt) {
@@ -288,25 +287,35 @@ 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,
});
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());
+
+ 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);
}
TEST(Transform, MoveBy) {
@@ -440,7 +449,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 +627,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..343889a82d 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 enlarged and offsetted viewport.
+ // Rendered fonts should have the same size but center of viewport should be
+ // offsetted.
+ 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 enlarged and offsetted viewport.
+ // Rendered fonts should have the same size but center of viewport should be
+ // offsetted.
+ 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 no padding and then 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);
+}