diff options
author | Aleksandar Stojiljkovic <aleksandar.stojiljkovic@mapbox.com> | 2019-04-15 15:03:47 +0300 |
---|---|---|
committer | Aleksandar Stojiljkovic <aleksandar.stojiljkovic@mapbox.com> | 2019-05-13 11:07:12 +0300 |
commit | cff558252b1b86afb6c390f903b3a128dfac33ae (patch) | |
tree | 1fd441a495604672c4b98744e4f67253a0990049 | |
parent | 615c5387fccf28c0d54fb71475f7a878318e1413 (diff) | |
download | qtlocation-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"
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 Binary files differnew file mode 100644 index 0000000000..f161a95c8e --- /dev/null +++ b/test/fixtures/local_glyphs/no_local_with_content_insets/expected.png 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 Binary files differnew file mode 100644 index 0000000000..c61c15840d --- /dev/null +++ b/test/fixtures/local_glyphs/no_local_with_content_insets_and_pitch/expected.png 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); +} |