1 #include <mbgl/map/transform_state.hpp>
2 #include <mbgl/tile/tile_id.hpp>
3 #include <mbgl/util/constants.hpp>
4 #include <mbgl/util/interpolate.hpp>
5 #include <mbgl/util/projection.hpp>
6 #include <mbgl/math/log2.hpp>
7 #include <mbgl/math/clamp.hpp>
8 
9 namespace mbgl {
10 
TransformState(ConstrainMode constrainMode_,ViewportMode viewportMode_)11 TransformState::TransformState(ConstrainMode constrainMode_, ViewportMode viewportMode_)
12     : constrainMode(constrainMode_)
13     , viewportMode(viewportMode_)
14 {
15 }
16 
17 #pragma mark - Matrix
18 
matrixFor(mat4 & matrix,const UnwrappedTileID & tileID) const19 void TransformState::matrixFor(mat4& matrix, const UnwrappedTileID& tileID) const {
20     const uint64_t tileScale = 1ull << tileID.canonical.z;
21     const double s = Projection::worldSize(scale) / tileScale;
22 
23     matrix::identity(matrix);
24     matrix::translate(matrix, matrix,
25                       int64_t(tileID.canonical.x + tileID.wrap * tileScale) * s,
26                       int64_t(tileID.canonical.y) * s, 0);
27     matrix::scale(matrix, matrix, s / util::EXTENT, s / util::EXTENT, 1);
28 }
29 
getProjMatrix(mat4 & projMatrix,uint16_t nearZ,bool aligned) const30 void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligned) const {
31     if (size.isEmpty()) {
32         return;
33     }
34 
35      // Find the distance from the center point [width/2, height/2] to the
36     // center top point [width/2, 0] in Z units, using the law of sines.
37     // 1 Z unit is equivalent to 1 horizontal px at the center of the map
38     // (the distance between[width/2, height/2] and [width/2 + 1, height/2])
39     const double halfFov = getFieldOfView() / 2.0;
40     const double groundAngle = M_PI / 2.0 + getPitch();
41     const double topHalfSurfaceDistance = std::sin(halfFov) * getCameraToCenterDistance() / std::sin(M_PI - groundAngle - halfFov);
42 
43 
44     // Calculate z distance of the farthest fragment that should be rendered.
45     const double furthestDistance = std::cos(M_PI / 2 - getPitch()) * topHalfSurfaceDistance + getCameraToCenterDistance();
46     // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
47     const double farZ = furthestDistance * 1.01;
48 
49     matrix::perspective(projMatrix, getFieldOfView(), double(size.width) / size.height, nearZ, farZ);
50 
51     const bool flippedY = viewportMode == ViewportMode::FlippedY;
52     matrix::scale(projMatrix, projMatrix, 1, flippedY ? 1 : -1, 1);
53 
54     matrix::translate(projMatrix, projMatrix, 0, 0, -getCameraToCenterDistance());
55 
56     using NO = NorthOrientation;
57     switch (getNorthOrientation()) {
58         case NO::Rightwards: matrix::rotate_y(projMatrix, projMatrix, getPitch()); break;
59         case NO::Downwards: matrix::rotate_x(projMatrix, projMatrix, -getPitch()); break;
60         case NO::Leftwards: matrix::rotate_y(projMatrix, projMatrix, -getPitch()); break;
61         default: matrix::rotate_x(projMatrix, projMatrix, getPitch()); break;
62     }
63 
64     matrix::rotate_z(projMatrix, projMatrix, getAngle() + getNorthOrientationAngle());
65 
66     const double dx = pixel_x() - size.width / 2.0f, dy = pixel_y() - size.height / 2.0f;
67     matrix::translate(projMatrix, projMatrix, dx, dy, 0);
68 
69     if (axonometric) {
70         // mat[11] controls perspective
71         projMatrix[11] = 0;
72 
73         // mat[8], mat[9] control x-skew, y-skew
74         projMatrix[8] = xSkew;
75         projMatrix[9] = ySkew;
76     }
77 
78     matrix::scale(projMatrix, projMatrix, 1, 1,
79                   1.0 / Projection::getMetersPerPixelAtLatitude(getLatLng(LatLng::Unwrapped).latitude(), getZoom()));
80 
81     // Make a second projection matrix that is aligned to a pixel grid for rendering raster tiles.
82     // We're rounding the (floating point) x/y values to achieve to avoid rendering raster images to fractional
83     // coordinates. Additionally, we adjust by half a pixel in either direction in case that viewport dimension
84     // is an odd integer to preserve rendering to the pixel grid. We're rotating this shift based on the angle
85     // of the transformation so that 0°, 90°, 180°, and 270° rasters are crisp, and adjust the shift so that
86     // it is always <= 0.5 pixels.
87     if (aligned) {
88         const float xShift = float(size.width % 2) / 2, yShift = float(size.height % 2) / 2;
89         const double angleCos = std::cos(angle), angleSin = std::sin(angle);
90         double devNull;
91         const float dxa = -std::modf(dx, &devNull) + angleCos * xShift + angleSin * yShift;
92         const float dya = -std::modf(dy, &devNull) + angleCos * yShift + angleSin * xShift;
93         matrix::translate(projMatrix, projMatrix, dxa > 0.5 ? dxa - 1 : dxa, dya > 0.5 ? dya - 1 : dya, 0);
94     }
95 }
96 
97 #pragma mark - Dimensions
98 
getSize() const99 Size TransformState::getSize() const {
100     return size;
101 }
102 
103 #pragma mark - North Orientation
104 
getNorthOrientation() const105 NorthOrientation TransformState::getNorthOrientation() const {
106     return orientation;
107 }
108 
getNorthOrientationAngle() const109 double TransformState::getNorthOrientationAngle() const {
110     double angleOrientation = 0;
111     if (orientation == NorthOrientation::Rightwards) {
112         angleOrientation += M_PI / 2.0f;
113     } else if (orientation == NorthOrientation::Downwards) {
114         angleOrientation += M_PI;
115     } else if (orientation == NorthOrientation::Leftwards) {
116         angleOrientation -= M_PI / 2.0f;
117     }
118     return angleOrientation;
119 }
120 
121 #pragma mark - Constrain mode
122 
getConstrainMode() const123 ConstrainMode TransformState::getConstrainMode() const {
124     return constrainMode;
125 }
126 
127 #pragma mark - ViewportMode
128 
getViewportMode() const129 ViewportMode TransformState::getViewportMode() const {
130     return viewportMode;
131 }
132 
133 #pragma mark - Position
134 
getLatLng(LatLng::WrapMode wrapMode) const135 LatLng TransformState::getLatLng(LatLng::WrapMode wrapMode) const {
136     return {
137         util::RAD2DEG * (2 * std::atan(std::exp(y / Cc)) - 0.5 * M_PI),
138         -x / Bc,
139         wrapMode
140     };
141 }
142 
pixel_x() const143 double TransformState::pixel_x() const {
144     const double center = (size.width - Projection::worldSize(scale)) / 2;
145     return center + x;
146 }
147 
pixel_y() const148 double TransformState::pixel_y() const {
149     const double center = (size.height - Projection::worldSize(scale)) / 2;
150     return center + y;
151 }
152 
153 #pragma mark - Zoom
154 
getZoom() const155 double TransformState::getZoom() const {
156     return scaleZoom(scale);
157 }
158 
getIntegerZoom() const159 uint8_t TransformState::getIntegerZoom() const {
160     return getZoom();
161 }
162 
getZoomFraction() const163 double TransformState::getZoomFraction() const {
164     return getZoom() - getIntegerZoom();
165 }
166 
167 #pragma mark - Bounds
168 
setLatLngBounds(optional<LatLngBounds> bounds_)169 void TransformState::setLatLngBounds(optional<LatLngBounds> bounds_) {
170     if (bounds_ != bounds) {
171         bounds = bounds_;
172         setLatLngZoom(getLatLng(LatLng::Unwrapped), getZoom());
173     }
174 }
175 
getLatLngBounds() const176 optional<LatLngBounds> TransformState::getLatLngBounds() const {
177     return bounds;
178 }
179 
setMinZoom(const double minZoom)180 void TransformState::setMinZoom(const double minZoom) {
181     if (minZoom <= getMaxZoom()) {
182         min_scale = zoomScale(util::clamp(minZoom, util::MIN_ZOOM, util::MAX_ZOOM));
183     }
184 }
185 
getMinZoom() const186 double TransformState::getMinZoom() const {
187     double test_scale = min_scale;
188     double unused_x = x;
189     double unused_y = y;
190     constrain(test_scale, unused_x, unused_y);
191 
192     return scaleZoom(test_scale);
193 }
194 
setMaxZoom(const double maxZoom)195 void TransformState::setMaxZoom(const double maxZoom) {
196     if (maxZoom >= getMinZoom()) {
197         max_scale = zoomScale(util::clamp(maxZoom, util::MIN_ZOOM, util::MAX_ZOOM));
198     }
199 }
200 
getMaxZoom() const201 double TransformState::getMaxZoom() const {
202     return scaleZoom(max_scale);
203 }
204 
setMinPitch(double minPitch)205 void TransformState::setMinPitch(double minPitch) {
206     if (minPitch <= getMaxPitch()) {
207         min_pitch = minPitch;
208     }
209 }
210 
getMinPitch() const211 double TransformState::getMinPitch() const {
212     return min_pitch;
213 }
214 
setMaxPitch(double maxPitch)215 void TransformState::setMaxPitch(double maxPitch) {
216     if (maxPitch >= getMinPitch()) {
217         max_pitch = maxPitch;
218     }
219 }
220 
getMaxPitch() const221 double TransformState::getMaxPitch() const {
222     return max_pitch;
223 }
224 
225 #pragma mark - Rotation
226 
getAngle() const227 float TransformState::getAngle() const {
228     return angle;
229 }
230 
getFieldOfView() const231 float TransformState::getFieldOfView() const {
232     return fov;
233 }
234 
getCameraToCenterDistance() const235 float TransformState::getCameraToCenterDistance() const {
236     return 0.5 * size.height / std::tan(fov / 2.0);
237 }
238 
getPitch() const239 float TransformState::getPitch() const {
240     return pitch;
241 }
242 
243 
244 #pragma mark - State
245 
isChanging() const246 bool TransformState::isChanging() const {
247     return rotating || scaling || panning || gestureInProgress;
248 }
249 
isRotating() const250 bool TransformState::isRotating() const {
251     return rotating;
252 }
253 
isScaling() const254 bool TransformState::isScaling() const {
255     return scaling;
256 }
257 
isPanning() const258 bool TransformState::isPanning() const {
259     return panning;
260 }
261 
isGestureInProgress() const262 bool TransformState::isGestureInProgress() const {
263     return gestureInProgress;
264 }
265 
266 #pragma mark - Projection
267 
zoomScale(double zoom) const268 double TransformState::zoomScale(double zoom) const {
269     return std::pow(2.0, zoom);
270 }
271 
scaleZoom(double s) const272 double TransformState::scaleZoom(double s) const {
273     return ::log2(s);
274 }
275 
latLngToScreenCoordinate(const LatLng & latLng) const276 ScreenCoordinate TransformState::latLngToScreenCoordinate(const LatLng& latLng) const {
277     if (size.isEmpty()) {
278         return {};
279     }
280 
281     mat4 mat = coordinatePointMatrix(getZoom());
282     vec4 p;
283     Point<double> pt = Projection::project(latLng, scale) / util::tileSize;
284     vec4 c = {{ pt.x, pt.y, 0, 1 }};
285     matrix::transformMat4(p, c, mat);
286     return { p[0] / p[3], size.height - p[1] / p[3] };
287 }
288 
screenCoordinateToLatLng(const ScreenCoordinate & point,LatLng::WrapMode wrapMode) const289 LatLng TransformState::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng::WrapMode wrapMode) const {
290     if (size.isEmpty()) {
291         return {};
292     }
293 
294     float targetZ = 0;
295     mat4 mat = coordinatePointMatrix(getZoom());
296 
297     mat4 inverted;
298     bool err = matrix::invert(inverted, mat);
299 
300     if (err) throw std::runtime_error("failed to invert coordinatePointMatrix");
301 
302     double flippedY = size.height - point.y;
303 
304     // since we don't know the correct projected z value for the point,
305     // unproject two points to get a line and then find the point on that
306     // line with z=0
307 
308     vec4 coord0;
309     vec4 coord1;
310     vec4 point0 = {{ point.x, flippedY, 0, 1 }};
311     vec4 point1 = {{ point.x, flippedY, 1, 1 }};
312     matrix::transformMat4(coord0, point0, inverted);
313     matrix::transformMat4(coord1, point1, inverted);
314 
315     double w0 = coord0[3];
316     double w1 = coord1[3];
317 
318     Point<double> p0 = Point<double>(coord0[0], coord0[1]) / w0;
319     Point<double> p1 = Point<double>(coord1[0], coord1[1]) / w1;
320 
321     double z0 = coord0[2] / w0;
322     double z1 = coord1[2] / w1;
323     double t = z0 == z1 ? 0 : (targetZ - z0) / (z1 - z0);
324 
325     return Projection::unproject(util::interpolate(p0, p1, t), scale / util::tileSize, wrapMode);
326 }
327 
coordinatePointMatrix(double z) const328 mat4 TransformState::coordinatePointMatrix(double z) const {
329     mat4 proj;
330     getProjMatrix(proj);
331     float s = Projection::worldSize(scale) / std::pow(2, z);
332     matrix::scale(proj, proj, s, s, 1);
333     matrix::multiply(proj, getPixelMatrix(), proj);
334     return proj;
335 }
336 
getPixelMatrix() const337 mat4 TransformState::getPixelMatrix() const {
338     mat4 m;
339     matrix::identity(m);
340     matrix::scale(m, m,
341                   static_cast<double>(size.width) / 2, -static_cast<double>(size.height) / 2, 1);
342     matrix::translate(m, m, 1, -1, 0);
343     return m;
344 }
345 
346 
347 #pragma mark - (private helper functions)
348 
rotatedNorth() const349 bool TransformState::rotatedNorth() const {
350     using NO = NorthOrientation;
351     return (orientation == NO::Leftwards || orientation == NO::Rightwards);
352 }
353 
constrain(double & scale_,double & x_,double & y_) const354 void TransformState::constrain(double& scale_, double& x_, double& y_) const {
355     // Constrain minimum scale to avoid zooming out far enough to show off-world areas.
356     scale_ = util::max(scale_,
357                        static_cast<double>(rotatedNorth() ? size.height : size.width) / util::tileSize,
358                        static_cast<double>(rotatedNorth() ? size.width : size.height) / util::tileSize);
359 
360     // Constrain min/max pan to avoid showing off-world areas.
361     if (constrainMode == ConstrainMode::WidthAndHeight) {
362         double max_x = (scale_ * util::tileSize - (rotatedNorth() ? size.height : size.width)) / 2;
363         x_ = std::max(-max_x, std::min(x_, max_x));
364     }
365 
366     if (constrainMode != ConstrainMode::None) {
367         double max_y = (scale_ * util::tileSize - (rotatedNorth() ? size.width : size.height)) / 2;
368         y_ = std::max(-max_y, std::min(y_, max_y));
369     }
370 }
371 
moveLatLng(const LatLng & latLng,const ScreenCoordinate & anchor)372 void TransformState::moveLatLng(const LatLng& latLng, const ScreenCoordinate& anchor) {
373     auto centerCoord = Projection::project(getLatLng(LatLng::Unwrapped), scale);
374     auto latLngCoord = Projection::project(latLng, scale);
375     auto anchorCoord = Projection::project(screenCoordinateToLatLng(anchor), scale);
376     setLatLngZoom(Projection::unproject(centerCoord + latLngCoord - anchorCoord, scale), getZoom());
377 }
378 
setLatLngZoom(const LatLng & latLng,double zoom)379 void TransformState::setLatLngZoom(const LatLng& latLng, double zoom) {
380     LatLng constrained = latLng;
381     if (bounds) {
382         constrained = bounds->constrain(latLng);
383     }
384 
385     double newScale = util::clamp(zoomScale(zoom), min_scale, max_scale);
386     const double newWorldSize = newScale * util::tileSize;
387     Bc = newWorldSize / util::DEGREES_MAX;
388     Cc = newWorldSize / util::M2PI;
389 
390     const double m = 1 - 1e-15;
391     const double f = util::clamp(std::sin(util::DEG2RAD * constrained.latitude()), -m, m);
392 
393     ScreenCoordinate point = {
394         -constrained.longitude() * Bc,
395         0.5 * Cc * std::log((1 + f) / (1 - f)),
396     };
397     setScalePoint(newScale, point);
398 }
399 
setScalePoint(const double newScale,const ScreenCoordinate & point)400 void TransformState::setScalePoint(const double newScale, const ScreenCoordinate &point) {
401     double constrainedScale = newScale;
402     ScreenCoordinate constrainedPoint = point;
403     constrain(constrainedScale, constrainedPoint.x, constrainedPoint.y);
404 
405     scale = constrainedScale;
406     x = constrainedPoint.x;
407     y = constrainedPoint.y;
408     Bc = Projection::worldSize(scale) / util::DEGREES_MAX;
409     Cc = Projection::worldSize(scale) / util::M2PI;
410 }
411 
getCameraToTileDistance(const UnwrappedTileID & tileID) const412 float TransformState::getCameraToTileDistance(const UnwrappedTileID& tileID) const {
413     mat4 projectionMatrix;
414     getProjMatrix(projectionMatrix);
415     mat4 tileProjectionMatrix;
416     matrixFor(tileProjectionMatrix, tileID);
417     matrix::multiply(tileProjectionMatrix, projectionMatrix, tileProjectionMatrix);
418     vec4 tileCenter = {{util::tileSize / 2, util::tileSize / 2, 0, 1}};
419     vec4 projectedCenter;
420     matrix::transformMat4(projectedCenter, tileCenter, tileProjectionMatrix);
421     return projectedCenter[3];
422 }
423 
maxPitchScaleFactor() const424 float TransformState::maxPitchScaleFactor() const {
425     if (size.isEmpty()) {
426         return {};
427     }
428     auto latLng = screenCoordinateToLatLng({ 0, static_cast<float>(getSize().height) });
429     mat4 mat = coordinatePointMatrix(getZoom());
430     Point<double> pt = Projection::project(latLng, scale) / util::tileSize;
431     vec4 p = {{ pt.x, pt.y, 0, 1 }};
432     vec4 topPoint;
433     matrix::transformMat4(topPoint, p, mat);
434     return topPoint[3] / getCameraToCenterDistance();
435 }
436 
437 } // namespace mbgl
438