1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd. |
4 | ** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com> |
5 | ** Contact: http://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the QtLocation module of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:LGPL3$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see http://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at http://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU Lesser General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
22 | ** packaging of this file. Please review the following information to |
23 | ** ensure the GNU Lesser General Public License version 3 requirements |
24 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
25 | ** |
26 | ** GNU General Public License Usage |
27 | ** Alternatively, this file may be used under the terms of the GNU |
28 | ** General Public License version 2.0 or later as published by the Free |
29 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
30 | ** the packaging of this file. Please review the following information to |
31 | ** ensure the GNU General Public License version 2.0 requirements will be |
32 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
33 | ** |
34 | ** $QT_END_LICENSE$ |
35 | ** |
36 | ****************************************************************************/ |
37 | #include "qgeotiledmapscene_p.h" |
38 | #include "qgeotiledmapscene_p_p.h" |
39 | #include "qgeocameradata_p.h" |
40 | #include "qabstractgeotilecache_p.h" |
41 | #include "qgeotilespec_p.h" |
42 | #include <QtPositioning/private/qdoublevector3d_p.h> |
43 | #include <QtPositioning/private/qwebmercator_p.h> |
44 | #include <QtCore/private/qobject_p.h> |
45 | #include <QtQuick/QQuickWindow> |
46 | #include <QtGui/QVector3D> |
47 | #include <cmath> |
48 | #include <QtPositioning/private/qlocationutils_p.h> |
49 | #include <QtPositioning/private/qdoublematrix4x4_p.h> |
50 | #include <QtPositioning/private/qwebmercator_p.h> |
51 | |
52 | static QVector3D toVector3D(const QDoubleVector3D& in) |
53 | { |
54 | return QVector3D(in.x(), in.y(), in.z()); |
55 | } |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | QGeoTiledMapScene::QGeoTiledMapScene(QObject *parent) |
60 | : QObject(*new QGeoTiledMapScenePrivate(),parent) |
61 | { |
62 | } |
63 | |
64 | QGeoTiledMapScene::~QGeoTiledMapScene() |
65 | { |
66 | } |
67 | |
68 | void QGeoTiledMapScene::setScreenSize(const QSize &size) |
69 | { |
70 | Q_D(QGeoTiledMapScene); |
71 | d->m_screenSize = size; |
72 | } |
73 | |
74 | void QGeoTiledMapScene::updateSceneParameters() |
75 | { |
76 | Q_D(QGeoTiledMapScene); |
77 | d->m_intZoomLevel = static_cast<int>(std::floor(x: d->m_cameraData.zoomLevel())); |
78 | const float delta = d->m_cameraData.zoomLevel() - d->m_intZoomLevel; |
79 | d->m_linearScaling = qAbs(t: delta) > 0.05 || d->isTiltedOrRotated(); |
80 | d->m_sideLength = 1 << d->m_intZoomLevel; |
81 | d->m_mapEdgeSize = std::pow(x: 2.0, y: d->m_cameraData.zoomLevel()) * d->m_tileSize; |
82 | } |
83 | |
84 | void QGeoTiledMapScene::setTileSize(int tileSize) |
85 | { |
86 | Q_D(QGeoTiledMapScene); |
87 | if (d->m_tileSize == tileSize) |
88 | return; |
89 | |
90 | d->m_tileSize = tileSize; |
91 | updateSceneParameters(); |
92 | } |
93 | |
94 | void QGeoTiledMapScene::setCameraData(const QGeoCameraData &cameraData) |
95 | { |
96 | Q_D(QGeoTiledMapScene); |
97 | d->m_cameraData = cameraData; |
98 | updateSceneParameters(); |
99 | } |
100 | |
101 | void QGeoTiledMapScene::setVisibleArea(const QRectF &visibleArea) |
102 | { |
103 | Q_D(QGeoTiledMapScene); |
104 | if (d->m_visibleArea == visibleArea) |
105 | return; |
106 | d->m_visibleArea = visibleArea; |
107 | updateSceneParameters(); |
108 | } |
109 | |
110 | void QGeoTiledMapScene::setVisibleTiles(const QSet<QGeoTileSpec> &tiles) |
111 | { |
112 | Q_D(QGeoTiledMapScene); |
113 | d->setVisibleTiles(tiles); |
114 | } |
115 | |
116 | const QSet<QGeoTileSpec> &QGeoTiledMapScene::visibleTiles() const |
117 | { |
118 | Q_D(const QGeoTiledMapScene); |
119 | return d->m_visibleTiles; |
120 | } |
121 | |
122 | void QGeoTiledMapScene::addTile(const QGeoTileSpec &spec, QSharedPointer<QGeoTileTexture> texture) |
123 | { |
124 | Q_D(QGeoTiledMapScene); |
125 | d->addTile(spec, texture); |
126 | } |
127 | |
128 | QSet<QGeoTileSpec> QGeoTiledMapScene::texturedTiles() |
129 | { |
130 | Q_D(QGeoTiledMapScene); |
131 | QSet<QGeoTileSpec> textured; |
132 | for (auto it = d->m_textures.cbegin(); it != d->m_textures.cend(); ++it) |
133 | textured += it.value()->spec; |
134 | |
135 | return textured; |
136 | } |
137 | |
138 | void QGeoTiledMapScene::clearTexturedTiles() |
139 | { |
140 | Q_D(QGeoTiledMapScene); |
141 | d->m_textures.clear(); |
142 | d->m_dropTextures = true; |
143 | } |
144 | |
145 | QGeoTiledMapScenePrivate::QGeoTiledMapScenePrivate() |
146 | : QObjectPrivate(), |
147 | m_tileSize(0), |
148 | #ifdef QT_LOCATION_DEBUG |
149 | m_scaleFactor(1.0), |
150 | #else |
151 | m_scaleFactor(10.0), |
152 | #endif |
153 | m_intZoomLevel(0), |
154 | m_sideLength(0), |
155 | m_minTileX(-1), |
156 | m_minTileY(-1), |
157 | m_maxTileX(-1), |
158 | m_maxTileY(-1), |
159 | m_tileXWrapsBelow(0), |
160 | m_linearScaling(false), |
161 | m_dropTextures(false) |
162 | { |
163 | } |
164 | |
165 | QGeoTiledMapScenePrivate::~QGeoTiledMapScenePrivate() |
166 | { |
167 | } |
168 | |
169 | bool QGeoTiledMapScenePrivate::buildGeometry(const QGeoTileSpec &spec, QSGImageNode *imageNode, bool &overzooming) |
170 | { |
171 | overzooming = false; |
172 | int x = spec.x(); |
173 | |
174 | if (x < m_tileXWrapsBelow) |
175 | x += m_sideLength; |
176 | |
177 | if ((x < m_minTileX) |
178 | || (m_maxTileX < x) |
179 | || (spec.y() < m_minTileY) |
180 | || (m_maxTileY < spec.y()) |
181 | || (spec.zoom() != m_intZoomLevel)) { |
182 | return false; |
183 | } |
184 | |
185 | double edge = m_scaleFactor * m_tileSize; |
186 | |
187 | double x1 = (x - m_minTileX); |
188 | double x2 = x1 + 1.0; |
189 | |
190 | double y1 = (m_minTileY - spec.y()); |
191 | double y2 = y1 - 1.0; |
192 | |
193 | x1 *= edge; |
194 | x2 *= edge; |
195 | y1 *= edge; |
196 | y2 *= edge; |
197 | |
198 | imageNode->setRect(QRectF(QPointF(x1, y2), QPointF(x2, y1))); |
199 | imageNode->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically); |
200 | |
201 | // Calculate the texture mapping, in case we are magnifying some lower ZL tile |
202 | const auto it = m_textures.find(akey: spec); // This should be always found, but apparently sometimes it isn't, possibly due to memory shortage |
203 | if (it != m_textures.end()) { |
204 | if (it.value()->spec.zoom() < spec.zoom()) { |
205 | // Currently only using lower ZL tiles for the overzoom. |
206 | const int tilesPerTexture = 1 << (spec.zoom() - it.value()->spec.zoom()); |
207 | const int mappedSize = imageNode->texture()->textureSize().width() / tilesPerTexture; |
208 | const int x = (spec.x() % tilesPerTexture) * mappedSize; |
209 | const int y = (spec.y() % tilesPerTexture) * mappedSize; |
210 | imageNode->setSourceRect(QRectF(x, y, mappedSize, mappedSize)); |
211 | overzooming = true; |
212 | } else { |
213 | imageNode->setSourceRect(QRectF(QPointF(0,0), imageNode->texture()->textureSize())); |
214 | } |
215 | } else { |
216 | qWarning() << "!! buildGeometry: tileSpec not present in m_textures !!" ; |
217 | imageNode->setSourceRect(QRectF(QPointF(0,0), imageNode->texture()->textureSize())); |
218 | } |
219 | |
220 | return true; |
221 | } |
222 | |
223 | void QGeoTiledMapScenePrivate::addTile(const QGeoTileSpec &spec, QSharedPointer<QGeoTileTexture> texture) |
224 | { |
225 | if (!m_visibleTiles.contains(value: spec)) // Don't add the geometry if it isn't visible |
226 | return; |
227 | |
228 | if (m_textures.contains(akey: spec)) |
229 | m_updatedTextures.append(t: spec); |
230 | m_textures.insert(akey: spec, avalue: texture); |
231 | } |
232 | |
233 | void QGeoTiledMapScenePrivate::setVisibleTiles(const QSet<QGeoTileSpec> &visibleTiles) |
234 | { |
235 | // work out the tile bounds for the new scene |
236 | updateTileBounds(tiles: visibleTiles); |
237 | |
238 | // set up the gl camera for the new scene |
239 | setupCamera(); |
240 | |
241 | QSet<QGeoTileSpec> toRemove = m_visibleTiles - visibleTiles; |
242 | if (!toRemove.isEmpty()) |
243 | removeTiles(oldTiles: toRemove); |
244 | |
245 | m_visibleTiles = visibleTiles; |
246 | } |
247 | |
248 | void QGeoTiledMapScenePrivate::removeTiles(const QSet<QGeoTileSpec> &oldTiles) |
249 | { |
250 | typedef QSet<QGeoTileSpec>::const_iterator iter; |
251 | iter i = oldTiles.constBegin(); |
252 | iter end = oldTiles.constEnd(); |
253 | |
254 | for (; i != end; ++i) { |
255 | QGeoTileSpec tile = *i; |
256 | m_textures.remove(akey: tile); |
257 | } |
258 | } |
259 | |
260 | void QGeoTiledMapScenePrivate::updateTileBounds(const QSet<QGeoTileSpec> &tiles) |
261 | { |
262 | if (tiles.isEmpty()) { |
263 | m_minTileX = -1; |
264 | m_minTileY = -1; |
265 | m_maxTileX = -1; |
266 | m_maxTileY = -1; |
267 | return; |
268 | } |
269 | |
270 | typedef QSet<QGeoTileSpec>::const_iterator iter; |
271 | iter i = tiles.constBegin(); |
272 | iter end = tiles.constEnd(); |
273 | |
274 | // determine whether the set of map tiles crosses the dateline. |
275 | // A gap in the tiles indicates dateline crossing |
276 | bool hasFarLeft = false; |
277 | bool hasFarRight = false; |
278 | bool hasMidLeft = false; |
279 | bool hasMidRight = false; |
280 | |
281 | for (; i != end; ++i) { |
282 | if ((*i).zoom() != m_intZoomLevel) |
283 | continue; |
284 | int x = (*i).x(); |
285 | if (x == 0) |
286 | hasFarLeft = true; |
287 | else if (x == (m_sideLength - 1)) |
288 | hasFarRight = true; |
289 | else if (x == ((m_sideLength / 2) - 1)) { |
290 | hasMidLeft = true; |
291 | } else if (x == (m_sideLength / 2)) { |
292 | hasMidRight = true; |
293 | } |
294 | } |
295 | |
296 | // if dateline crossing is detected we wrap all x pos of tiles |
297 | // that are in the left half of the map. |
298 | m_tileXWrapsBelow = 0; |
299 | |
300 | if (hasFarLeft && hasFarRight) { |
301 | if (!hasMidRight) { |
302 | m_tileXWrapsBelow = m_sideLength / 2; |
303 | } else if (!hasMidLeft) { |
304 | m_tileXWrapsBelow = (m_sideLength / 2) - 1; |
305 | } |
306 | } |
307 | |
308 | // finally, determine the min and max bounds |
309 | i = tiles.constBegin(); |
310 | |
311 | QGeoTileSpec tile = *i; |
312 | |
313 | int x = tile.x(); |
314 | if (tile.x() < m_tileXWrapsBelow) |
315 | x += m_sideLength; |
316 | |
317 | m_minTileX = x; |
318 | m_maxTileX = x; |
319 | m_minTileY = tile.y(); |
320 | m_maxTileY = tile.y(); |
321 | |
322 | ++i; |
323 | |
324 | for (; i != end; ++i) { |
325 | tile = *i; |
326 | if (tile.zoom() != m_intZoomLevel) |
327 | continue; |
328 | |
329 | int x = tile.x(); |
330 | if (tile.x() < m_tileXWrapsBelow) |
331 | x += m_sideLength; |
332 | |
333 | m_minTileX = qMin(a: m_minTileX, b: x); |
334 | m_maxTileX = qMax(a: m_maxTileX, b: x); |
335 | m_minTileY = qMin(a: m_minTileY, b: tile.y()); |
336 | m_maxTileY = qMax(a: m_maxTileY, b: tile.y()); |
337 | } |
338 | } |
339 | |
340 | void QGeoTiledMapScenePrivate::setupCamera() |
341 | { |
342 | // NOTE: The following instruction is correct only because WebMercator is a square projection! |
343 | double f = m_screenSize.height(); |
344 | |
345 | // Using fraction of zoom level, z varies between [ m_tileSize , 2 * m_tileSize [ |
346 | double z = std::pow(x: 2.0, y: m_cameraData.zoomLevel() - m_intZoomLevel) * m_tileSize; |
347 | |
348 | // calculate altitude that allows the visible map tiles |
349 | // to fit in the screen correctly (note that a larger f will cause |
350 | // the camera be higher, resulting in gray areas displayed around |
351 | // the tiles) |
352 | double altitude = f / (2.0 * z); |
353 | |
354 | // calculate center |
355 | double edge = m_scaleFactor * m_tileSize; |
356 | |
357 | // first calculate the camera center in map space in the range of 0 <-> sideLength (2^z) |
358 | QDoubleVector2D camCenterMercator = QWebMercator::coordToMercator(coord: m_cameraData.center()); |
359 | QDoubleVector3D center = (m_sideLength * camCenterMercator); |
360 | |
361 | // wrap the center if necessary (due to dateline crossing) |
362 | if (center.x() < m_tileXWrapsBelow) |
363 | center.setX(center.x() + 1.0 * m_sideLength); |
364 | |
365 | // work out where the camera center is w.r.t minimum tile bounds |
366 | center.setX(center.x() - 1.0 * m_minTileX); |
367 | center.setY(1.0 * m_minTileY - center.y()); |
368 | |
369 | // apply necessary scaling to the camera center |
370 | center *= edge; |
371 | |
372 | // calculate eye |
373 | double apertureSize = 1.0; |
374 | if (m_cameraData.fieldOfView() != 90.0) //aperture(90 / 2) = 1 |
375 | apertureSize = tan(x: QLocationUtils::radians(degrees: m_cameraData.fieldOfView()) * 0.5); |
376 | QDoubleVector3D eye = center; |
377 | eye.setZ(altitude * edge / apertureSize); |
378 | |
379 | // calculate up |
380 | |
381 | QDoubleVector3D view = eye - center; |
382 | QDoubleVector3D side = QDoubleVector3D::normal(v1: view, v2: QDoubleVector3D(0.0, 1.0, 0.0)); |
383 | QDoubleVector3D up = QDoubleVector3D::normal(v1: side, v2: view); |
384 | |
385 | // old bearing, tilt and roll code. |
386 | // Now using double matrices until distilling the transformation to QMatrix4x4 |
387 | QDoubleMatrix4x4 mBearing; |
388 | // -1.0 * bearing removed, now map north goes in the bearing direction |
389 | mBearing.rotate(angle: -1.0 * m_cameraData.bearing(), vector: view); |
390 | up = mBearing * up; |
391 | |
392 | QDoubleVector3D side2 = QDoubleVector3D::normal(v1: up, v2: view); |
393 | if (m_cameraData.tilt() > 0.01) { |
394 | QDoubleMatrix4x4 mTilt; |
395 | mTilt.rotate(angle: m_cameraData.tilt(), vector: side2); |
396 | eye = mTilt * view + center; |
397 | } |
398 | |
399 | view = eye - center; |
400 | view.normalize(); |
401 | side = QDoubleVector3D::normal(v1: view, v2: QDoubleVector3D(0.0, 1.0, 0.0)); |
402 | up = QDoubleVector3D::normal(v1: view, v2: side2); |
403 | |
404 | // QMatrix4x4 mRoll; |
405 | // mRoll.rotate(camera.roll(), view); |
406 | // up = mRoll * up; |
407 | |
408 | // near plane and far plane |
409 | |
410 | double nearPlane = 1.0; |
411 | // Clip plane. Used to be (altitude + 1.0) * edge. This does not affect the perspective. minimum value would be > 0.0 |
412 | // Since, for some reasons possibly related to how QSG works, this clipping plane is unable to clip part of tiles, |
413 | // Instead of farPlane = (altitude + m_cameraData.clipDistance()) * edge , we use a fixed large clipDistance, and |
414 | // leave the clipping only in QGeoCameraTiles::createFrustum |
415 | double farPlane = (altitude + 10000.0) * edge; |
416 | |
417 | m_cameraUp = up; |
418 | m_cameraCenter = center; |
419 | m_cameraEye = eye; |
420 | |
421 | double aspectRatio = 1.0 * m_screenSize.width() / m_screenSize.height(); |
422 | float halfWidth = 1 * apertureSize; |
423 | float halfHeight = 1 * apertureSize; |
424 | halfWidth *= aspectRatio; |
425 | |
426 | // m_projectionMatrix.setToIdentity(); |
427 | // m_projectionMatrix.frustum(-halfWidth, halfWidth, -halfHeight, halfHeight, nearPlane, farPlane); |
428 | |
429 | QRectF va = m_visibleArea; |
430 | if (va.isNull()) |
431 | va = QRectF(0, 0, m_screenSize.width(), m_screenSize.height()); |
432 | |
433 | QRectF screen = QRectF(QPointF(0,0),m_screenSize); |
434 | QPointF vaCenter = va.center(); |
435 | |
436 | QPointF screenCenter = screen.center(); |
437 | QPointF diff = screenCenter - vaCenter; |
438 | float xdiffpct = diff.x() / m_screenSize.width(); |
439 | float ydiffpct = -(diff.y() / m_screenSize.height()); |
440 | |
441 | m_projectionMatrix.setToIdentity(); |
442 | float l = -halfWidth + (2 * halfWidth) * xdiffpct; |
443 | float r = halfWidth + (2 * halfWidth) * xdiffpct; |
444 | float t = halfHeight + (2 * halfHeight) * ydiffpct; |
445 | float b = -halfHeight + (2 * halfHeight) * ydiffpct; |
446 | |
447 | m_projectionMatrix.frustum(left: l, |
448 | right: r, |
449 | bottom: b, |
450 | top: t, |
451 | nearPlane, farPlane); |
452 | } |
453 | |
454 | static bool qgeotiledmapscene_isTileInViewport_Straight(const QRectF &tileRect, const QMatrix4x4 &matrix) |
455 | { |
456 | const QRectF boundingRect = QRectF(matrix * tileRect.topLeft(), matrix * tileRect.bottomRight()); |
457 | return QRectF(-1, -1, 2, 2).intersects(r: boundingRect); |
458 | } |
459 | |
460 | static bool qgeotiledmapscene_isTileInViewport_rotationTilt(const QRectF &tileRect, const QMatrix4x4 &matrix) |
461 | { |
462 | // Transformed corners |
463 | const QPointF tlt = matrix * tileRect.topLeft(); |
464 | const QPointF trt = matrix * tileRect.topRight(); |
465 | const QPointF blt = matrix * tileRect.bottomLeft(); |
466 | const QPointF brt = matrix * tileRect.bottomRight(); |
467 | |
468 | const QRectF boundingRect = QRectF(QPointF(qMin(a: qMin(a: qMin(a: tlt.x(), b: trt.x()), b: blt.x()), b: brt.x()) |
469 | ,qMax(a: qMax(a: qMax(a: tlt.y(), b: trt.y()), b: blt.y()), b: brt.y())) |
470 | ,QPointF(qMax(a: qMax(a: qMax(a: tlt.x(), b: trt.x()), b: blt.x()), b: brt.x()) |
471 | ,qMin(a: qMin(a: qMin(a: tlt.y(), b: trt.y()), b: blt.y()), b: brt.y())) |
472 | ); |
473 | return QRectF(-1, -1, 2, 2).intersects(r: boundingRect); |
474 | } |
475 | |
476 | static bool qgeotiledmapscene_isTileInViewport(const QRectF &tileRect, const QMatrix4x4 &matrix, const bool straight) |
477 | { |
478 | if (straight) |
479 | return qgeotiledmapscene_isTileInViewport_Straight(tileRect, matrix); |
480 | return qgeotiledmapscene_isTileInViewport_rotationTilt(tileRect, matrix); |
481 | } |
482 | |
483 | void QGeoTiledMapRootNode::updateTiles(QGeoTiledMapTileContainerNode *root, |
484 | QGeoTiledMapScenePrivate *d, |
485 | double camAdjust, |
486 | QQuickWindow *window, |
487 | bool ogl) |
488 | { |
489 | // Set up the matrix... |
490 | QDoubleVector3D eye = d->m_cameraEye; |
491 | eye.setX(eye.x() + camAdjust); |
492 | QDoubleVector3D center = d->m_cameraCenter; |
493 | center.setX(center.x() + camAdjust); |
494 | QMatrix4x4 cameraMatrix; |
495 | cameraMatrix.lookAt(eye: toVector3D(in: eye), center: toVector3D(in: center), up: toVector3D(in: d->m_cameraUp)); |
496 | root->setMatrix(d->m_projectionMatrix * cameraMatrix); |
497 | |
498 | QSet<QGeoTileSpec> tilesInSG; |
499 | for (auto it = root->tiles.cbegin(), end = root->tiles.cend(); it != end; ++it) |
500 | tilesInSG.insert(value: it.key()); |
501 | const QSet<QGeoTileSpec> toRemove = tilesInSG - d->m_visibleTiles; |
502 | const QSet<QGeoTileSpec> toAdd = d->m_visibleTiles - tilesInSG; |
503 | |
504 | for (const QGeoTileSpec &s : toRemove) |
505 | delete root->tiles.take(akey: s); |
506 | bool straight = !d->isTiltedOrRotated(); |
507 | bool overzooming; |
508 | qreal pixelRatio = window->effectiveDevicePixelRatio(); |
509 | #ifdef QT_LOCATION_DEBUG |
510 | QList<QGeoTileSpec> droppedTiles; |
511 | #endif |
512 | for (QHash<QGeoTileSpec, QSGImageNode *>::iterator it = root->tiles.begin(); |
513 | it != root->tiles.end(); ) { |
514 | QSGImageNode *node = it.value(); |
515 | bool ok = d->buildGeometry(spec: it.key(), imageNode: node, overzooming) |
516 | && qgeotiledmapscene_isTileInViewport(tileRect: node->rect(), matrix: root->matrix(), straight); |
517 | |
518 | QSGNode::DirtyState dirtyBits = {}; |
519 | |
520 | if (!ok) { |
521 | #ifdef QT_LOCATION_DEBUG |
522 | droppedTiles.append(it.key()); |
523 | #endif |
524 | it = root->tiles.erase(it); |
525 | delete node; |
526 | } else { |
527 | if (isTextureLinear != d->m_linearScaling) { |
528 | if (node->texture()->textureSize().width() > d->m_tileSize * pixelRatio) { |
529 | node->setFiltering(QSGTexture::Linear); // With mipmapping QSGTexture::Nearest generates artifacts |
530 | node->setMipmapFiltering(QSGTexture::Linear); |
531 | } else { |
532 | node->setFiltering((d->m_linearScaling || overzooming) ? QSGTexture::Linear : QSGTexture::Nearest); |
533 | } |
534 | #if QT_CONFIG(opengl) |
535 | if (ogl) |
536 | static_cast<QSGDefaultImageNode *>(node)->setAnisotropyLevel(QSGTexture::Anisotropy16x); |
537 | #else |
538 | Q_UNUSED(ogl); |
539 | #endif |
540 | dirtyBits |= QSGNode::DirtyMaterial; |
541 | } |
542 | if (dirtyBits != 0) |
543 | node->markDirty(bits: dirtyBits); |
544 | it++; |
545 | } |
546 | } |
547 | |
548 | for (const QGeoTileSpec &s : toAdd) { |
549 | QGeoTileTexture *tileTexture = d->m_textures.value(akey: s).data(); |
550 | if (!tileTexture || tileTexture->image.isNull()) { |
551 | #ifdef QT_LOCATION_DEBUG |
552 | droppedTiles.append(s); |
553 | #endif |
554 | continue; |
555 | } |
556 | QSGImageNode *tileNode = window->createImageNode(); |
557 | // note: setTexture will update coordinates so do it here, before we buildGeometry |
558 | tileNode->setTexture(textures.value(akey: s)); |
559 | if (d->buildGeometry(spec: s, imageNode: tileNode, overzooming) |
560 | && qgeotiledmapscene_isTileInViewport(tileRect: tileNode->rect(), matrix: root->matrix(), straight)) { |
561 | if (tileNode->texture()->textureSize().width() > d->m_tileSize * pixelRatio) { |
562 | tileNode->setFiltering(QSGTexture::Linear); // with mipmapping QSGTexture::Nearest generates artifacts |
563 | tileNode->setMipmapFiltering(QSGTexture::Linear); |
564 | } else { |
565 | tileNode->setFiltering((d->m_linearScaling || overzooming) ? QSGTexture::Linear : QSGTexture::Nearest); |
566 | } |
567 | #if QT_CONFIG(opengl) |
568 | if (ogl) |
569 | static_cast<QSGDefaultImageNode *>(tileNode)->setAnisotropyLevel(QSGTexture::Anisotropy16x); |
570 | #endif |
571 | root->addChild(spec: s, node: tileNode); |
572 | } else { |
573 | #ifdef QT_LOCATION_DEBUG |
574 | droppedTiles.append(s); |
575 | #endif |
576 | delete tileNode; |
577 | } |
578 | } |
579 | |
580 | #ifdef QT_LOCATION_DEBUG |
581 | m_droppedTiles[camAdjust] = droppedTiles; |
582 | #endif |
583 | } |
584 | |
585 | QSGNode *QGeoTiledMapScene::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) |
586 | { |
587 | Q_D(QGeoTiledMapScene); |
588 | float w = d->m_screenSize.width(); |
589 | float h = d->m_screenSize.height(); |
590 | if (w <= 0 || h <= 0) { |
591 | delete oldNode; |
592 | return 0; |
593 | } |
594 | |
595 | bool isOpenGL = (window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL); |
596 | QGeoTiledMapRootNode *mapRoot = static_cast<QGeoTiledMapRootNode *>(oldNode); |
597 | if (!mapRoot) |
598 | mapRoot = new QGeoTiledMapRootNode(); |
599 | |
600 | #ifdef QT_LOCATION_DEBUG |
601 | mapRoot->m_droppedTiles.clear(); |
602 | d->m_mapRoot = mapRoot; |
603 | #endif |
604 | |
605 | // Setting clip rect to fullscreen, as now the map can never be smaller than the viewport. |
606 | mapRoot->setClipRect(QRect(0, 0, w, h)); |
607 | |
608 | QMatrix4x4 itemSpaceMatrix; |
609 | itemSpaceMatrix.scale(x: w / 2, y: h / 2); |
610 | itemSpaceMatrix.translate(x: 1, y: 1); |
611 | itemSpaceMatrix.scale(x: 1, y: -1); |
612 | mapRoot->root->setMatrix(itemSpaceMatrix); |
613 | |
614 | if (d->m_dropTextures) { |
615 | for (const QGeoTileSpec &s : mapRoot->tiles->tiles.keys()) |
616 | delete mapRoot->tiles->tiles.take(akey: s); |
617 | for (const QGeoTileSpec &s : mapRoot->wrapLeft->tiles.keys()) |
618 | delete mapRoot->wrapLeft->tiles.take(akey: s); |
619 | for (const QGeoTileSpec &s : mapRoot->wrapRight->tiles.keys()) |
620 | delete mapRoot->wrapRight->tiles.take(akey: s); |
621 | for (const QGeoTileSpec &spec : mapRoot->textures.keys()) |
622 | mapRoot->textures.take(akey: spec)->deleteLater(); |
623 | d->m_dropTextures = false; |
624 | } |
625 | |
626 | // Evicting loZL tiles temporarily used in place of hiZL ones |
627 | if (d->m_updatedTextures.size()) { |
628 | const QVector<QGeoTileSpec> &toRemove = d->m_updatedTextures; |
629 | for (const QGeoTileSpec &s : toRemove) { |
630 | if (mapRoot->tiles->tiles.contains(akey: s)) |
631 | delete mapRoot->tiles->tiles.take(akey: s); |
632 | |
633 | if (mapRoot->wrapLeft->tiles.contains(akey: s)) |
634 | delete mapRoot->wrapLeft->tiles.take(akey: s); |
635 | |
636 | if (mapRoot->wrapRight->tiles.contains(akey: s)) |
637 | delete mapRoot->wrapRight->tiles.take(akey: s); |
638 | |
639 | if (mapRoot->textures.contains(akey: s)) |
640 | mapRoot->textures.take(akey: s)->deleteLater(); |
641 | } |
642 | d->m_updatedTextures.clear(); |
643 | } |
644 | |
645 | QSet<QGeoTileSpec> textures; |
646 | for (auto it = mapRoot->textures.cbegin(), end = mapRoot->textures.cend(); it != end; ++it) |
647 | textures.insert(value: it.key()); |
648 | const QSet<QGeoTileSpec> toRemove = textures - d->m_visibleTiles; |
649 | const QSet<QGeoTileSpec> toAdd = d->m_visibleTiles - textures; |
650 | |
651 | for (const QGeoTileSpec &spec : toRemove) |
652 | mapRoot->textures.take(akey: spec)->deleteLater(); |
653 | for (const QGeoTileSpec &spec : toAdd) { |
654 | QGeoTileTexture *tileTexture = d->m_textures.value(akey: spec).data(); |
655 | if (!tileTexture || tileTexture->image.isNull()) |
656 | continue; |
657 | mapRoot->textures.insert(akey: spec, avalue: window->createTextureFromImage(image: tileTexture->image)); |
658 | } |
659 | |
660 | double sideLength = d->m_scaleFactor * d->m_tileSize * d->m_sideLength; |
661 | #ifdef QT_LOCATION_DEBUG |
662 | d->m_sideLengthPixel = sideLength; |
663 | #endif |
664 | mapRoot->updateTiles(root: mapRoot->tiles, d, camAdjust: 0, window, ogl: isOpenGL); |
665 | mapRoot->updateTiles(root: mapRoot->wrapLeft, d, camAdjust: +sideLength, window, ogl: isOpenGL); |
666 | mapRoot->updateTiles(root: mapRoot->wrapRight, d, camAdjust: -sideLength, window, ogl: isOpenGL); |
667 | |
668 | mapRoot->isTextureLinear = d->m_linearScaling; |
669 | |
670 | return mapRoot; |
671 | } |
672 | |
673 | QT_END_NAMESPACE |
674 | |