QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GeoMapSpriteLayer.cc
Go to the documentation of this file.
1/****************************************************************************
2 *
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 *
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
7 *
8 ****************************************************************************/
9
10#include "GeoMapSpriteLayer.h"
11
12#include <QtCore/QAbstractItemModel>
13#include <QtQuick/QSGGeometryNode>
14#include <QtQuick/QSGTextureMaterial>
15#include <QtQuick/QSGTextureProvider>
16
17#include <cmath>
18
19#include "GeoMapCamera.h"
20#include "GeoScene.h"
21#include "QGCLoggingCategory.h"
22#include "SurfacePatchModel.h"
23#include "TileMath.h"
24
25QGC_LOGGING_CATEGORY(GeoMapSpriteLayerLog, "GeoMap.GeoMapSpriteLayer")
26
27GeoMapSpriteLayer::GeoMapSpriteLayer(QQuickItem* parent) : QQuickItem(parent)
28{
29 setFlag(ItemHasContents);
30 _terrainResampleTimer.setSingleShot(true);
31 _terrainResampleTimer.setInterval(250);
32 connect(&_terrainResampleTimer, &QTimer::timeout, this, &GeoMapSpriteLayer::_markPointsDirty);
33}
34
36{
37 if (scene == _scene) {
38 return;
39 }
40 qCDebug(GeoMapSpriteLayerLog) << "scene" << (scene ? "set" : "cleared");
41 if (_scene) {
42 disconnect(_scene, nullptr, this, nullptr);
43 }
44 _scene = scene;
45 if (_scene) {
46 connect(_scene, &GeoScene::sceneOriginChanged, this, &QQuickItem::update);
47 connect(_scene, &GeoScene::terrainScaleChanged, this, &QQuickItem::update);
48 connect(_scene, &GeoScene::cameraChanged, this, &GeoMapSpriteLayer::_connectCamera);
49 connect(_scene, &QObject::destroyed, this, [this] {
50 _scene = nullptr;
51 _connectCamera();
52 emit sceneChanged();
53 });
54 }
55 _connectCamera();
56 emit sceneChanged();
57}
58
60{
61 if (surfaceModel == _surfaceModel) {
62 return;
63 }
64 if (_surfaceModel) {
65 disconnect(_surfaceModel, nullptr, this, nullptr);
66 }
67 _surfaceModel = surfaceModel;
68 if (_surfaceModel) {
69 connect(_surfaceModel, &SurfacePatchModel::terrainHeightsChanged, this,
70 &GeoMapSpriteLayer::_scheduleTerrainResample);
71 connect(_surfaceModel, &QObject::destroyed, this, [this] {
72 _surfaceModel = nullptr;
73 _markPointsDirty();
75 });
76 }
77 _markPointsDirty();
79}
80
81void GeoMapSpriteLayer::setModel(QAbstractItemModel* model)
82{
83 if (model == _model) {
84 return;
85 }
86 qCDebug(GeoMapSpriteLayerLog) << "model" << (model ? "set" : "cleared");
87 if (_model) {
88 disconnect(_model, nullptr, this, nullptr);
89 }
90 _model = model;
91 if (_model) {
92 connect(_model, &QAbstractItemModel::rowsInserted, this, &GeoMapSpriteLayer::_markPointsDirty);
93 connect(_model, &QAbstractItemModel::rowsRemoved, this, &GeoMapSpriteLayer::_markPointsDirty);
94 connect(_model, &QAbstractItemModel::modelReset, this, &GeoMapSpriteLayer::_markPointsDirty);
95 connect(_model, &QAbstractItemModel::dataChanged, this, &GeoMapSpriteLayer::_markPointsDirty);
96 connect(_model, &QAbstractItemModel::layoutChanged, this, &GeoMapSpriteLayer::_markPointsDirty);
97 connect(_model, &QObject::destroyed, this, [this] {
98 _model = nullptr;
99 _markPointsDirty();
100 emit modelChanged();
101 });
102 }
103 _markPointsDirty();
104 emit modelChanged();
105}
106
107void GeoMapSpriteLayer::setCoordinates(const QVariantList& coordinates)
108{
109 if (coordinates == _coordinates) {
110 return;
111 }
112 _coordinates = coordinates;
113 _markPointsDirty();
114 emit coordinatesChanged();
115}
116
117void GeoMapSpriteLayer::setSprite(QQuickItem* sprite)
118{
119 if (sprite == _sprite) {
120 return;
121 }
122 if (_sprite) {
123 disconnect(_sprite, nullptr, this, nullptr);
124 }
125 _sprite = sprite;
126 _warnedNotProvider = false;
127 if (_sprite) {
128 connect(_sprite, &QObject::destroyed, this, [this] {
129 _sprite = nullptr;
130 update();
131 emit spriteChanged();
132 });
133 }
134 update();
135 emit spriteChanged();
136}
137
139{
140 if (spriteSize == _spriteSize) {
141 return;
142 }
143 _spriteSize = spriteSize;
144 update();
145 emit spriteSizeChanged();
146}
147
148void GeoMapSpriteLayer::_markPointsDirty()
149{
150 _pointsDirty = true;
151 update();
152}
153
154void GeoMapSpriteLayer::_scheduleTerrainResample()
155{
156 // Terrain streams in per patch: batch the burst into one resample
157 if (!_terrainResampleTimer.isActive()) {
158 _terrainResampleTimer.start();
159 }
160}
161
162void GeoMapSpriteLayer::_connectCamera()
163{
164 GeoMapCamera* const camera = _scene ? _scene->camera() : nullptr;
165 if (camera == _connectedCamera) {
166 update();
167 return;
168 }
169 if (_connectedCamera) {
170 disconnect(_connectedCamera, nullptr, this, nullptr);
171 }
172 _connectedCamera = camera;
173 if (_connectedCamera) {
174 connect(_connectedCamera, &GeoMapCamera::scenePoseChanged, this, &QQuickItem::update);
175 connect(_connectedCamera, &GeoMapCamera::viewportSizeChanged, this, &QQuickItem::update);
176 connect(_connectedCamera, &GeoMapCamera::fieldOfViewChanged, this, &QQuickItem::update);
177 connect(_connectedCamera, &QObject::destroyed, this, [this] {
178 _connectedCamera = nullptr;
179 update();
180 });
181 }
182 update();
183}
184
185void GeoMapSpriteLayer::_rebuildPointCache()
186{
187 _pointsDirty = false;
188 _points.clear();
189
190 const auto appendCoordinate = [this](const QGeoCoordinate& coordinate) {
191 if (!coordinate.isValid()) {
192 return;
193 }
194 CachedPoint point;
195 point.coordinate = coordinate;
196 point.world = TileMath::geoToWorld(coordinate);
197 point.terrainAlt = _surfaceModel ? _surfaceModel->terrainHeightAt(coordinate) : 0.0;
198 _points.push_back(point);
199 };
200
201 if (_model) {
202 const int rows = _model->rowCount();
203 _points.reserve(rows);
204 for (int row = 0; row < rows; ++row) {
205 const QObject* const object = _model->data(_model->index(row, 0), Qt::UserRole).value<QObject*>();
206 if (object) {
207 appendCoordinate(object->property("coordinate").value<QGeoCoordinate>());
208 }
209 }
210 } else {
211 _points.reserve(_coordinates.size());
212 for (const QVariant& variant : _coordinates) {
213 appendCoordinate(variant.value<QGeoCoordinate>());
214 }
215 }
216 qCDebug(GeoMapSpriteLayerLog) << "point cache rebuilt:" << _points.size() << "points";
217}
218
219// Runs on the render thread with the GUI thread blocked: safe to read the
220// scene/camera/model and (re)build the point cache here.
221QSGNode* GeoMapSpriteLayer::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* /*updatePaintNodeData*/)
222{
223 auto* node = static_cast<QSGGeometryNode*>(oldNode);
224
225 QSGTexture* texture = nullptr;
226 if (_sprite) {
227 if (_sprite->isTextureProvider()) {
228 texture = _sprite->textureProvider()->texture();
229 if (texture) {
230 // FBO-layer textures (ShaderEffectSource) only render their
231 // content when a consumer asks for it (as ShaderEffect does)
232 if (auto* const dynamicTexture = qobject_cast<QSGDynamicTexture*>(texture)) {
233 dynamicTexture->updateTexture();
234 }
235 } else {
236 // Provider exists but its layer has not rendered yet: retry next frame
237 QMetaObject::invokeMethod(this, qOverload<>(&QQuickItem::update), Qt::QueuedConnection);
238 }
239 } else if (!_warnedNotProvider) {
240 // Caller contract: sprite must have layer.enabled or be a ShaderEffectSource
241 qCWarning(GeoMapSpriteLayerLog) << "sprite item is not a texture provider";
242 _warnedNotProvider = true;
243 }
244 }
245
246 if (_pointsDirty) {
247 _rebuildPointCache();
248 }
249
250 GeoMapCamera* const camera = _scene ? _scene->camera() : nullptr;
251 if (!texture || !camera || _points.empty()) {
252 delete node;
253 return nullptr;
254 }
255
256 const double zScale = _scene->verticalScale() * _scene->terrainScale();
257 std::vector<QPointF> screenPositions;
258 screenPositions.reserve(_points.size());
259 for (const CachedPoint& point : _points) {
260 const auto screen = camera->worldToScreen(point.world, point.terrainAlt * zScale);
261 if (screen) {
262 screenPositions.push_back(*screen);
263 }
264 }
265 if (screenPositions.empty()) {
266 delete node;
267 return nullptr;
268 }
269
270 QSGGeometry* geometry = nullptr;
271 if (!node) {
272 node = new QSGGeometryNode;
273 geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0);
274 geometry->setDrawingMode(QSGGeometry::DrawTriangles);
275 node->setGeometry(geometry);
276 node->setFlag(QSGNode::OwnsGeometry);
277 auto* const material = new QSGTextureMaterial;
278 material->setFiltering(QSGTexture::Linear);
279 node->setMaterial(material);
280 node->setFlag(QSGNode::OwnsMaterial);
281 } else {
282 geometry = node->geometry();
283 }
284
285 auto* const material = static_cast<QSGTextureMaterial*>(node->material());
286 if (material->texture() != texture) {
287 material->setTexture(texture);
288 node->markDirty(QSGNode::DirtyMaterial);
289 }
290
291 const QRectF uv = texture->normalizedTextureSubRect();
292 const float u0 = static_cast<float>(uv.left());
293 const float v0 = static_cast<float>(uv.top());
294 const float u1 = static_cast<float>(uv.right());
295 const float v1 = static_cast<float>(uv.bottom());
296 const float half = static_cast<float>(_spriteSize / 2.0);
297
298 geometry->allocate(static_cast<int>(screenPositions.size()) * 6);
299 QSGGeometry::TexturedPoint2D* vertex = geometry->vertexDataAsTexturedPoint2D();
300 for (const QPointF& position : screenPositions) {
301 const float cx = static_cast<float>(position.x());
302 const float cy = static_cast<float>(position.y());
303 vertex[0].set(cx - half, cy - half, u0, v0);
304 vertex[1].set(cx + half, cy - half, u1, v0);
305 vertex[2].set(cx - half, cy + half, u0, v1);
306 vertex[3].set(cx + half, cy - half, u1, v0);
307 vertex[4].set(cx + half, cy + half, u1, v1);
308 vertex[5].set(cx - half, cy + half, u0, v1);
309 vertex += 6;
310 }
311 node->markDirty(QSGNode::DirtyGeometry);
312
313 return node;
314}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::optional< QPointF > worldToScreen(const QPointF &worldGround, double worldZ=0.0) const
void scenePoseChanged()
void fieldOfViewChanged()
void viewportSizeChanged()
void setCoordinates(const QVariantList &coordinates)
void setModel(QAbstractItemModel *model)
void setSpriteSize(qreal spriteSize)
QSGNode * updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override
void coordinatesChanged()
qreal spriteSize() const
On-screen sprite width/height in (logical) pixels.
void spriteSizeChanged()
QAbstractItemModel * model() const
void setSurfaceModel(SurfacePatchModel *surfaceModel)
QQuickItem * sprite() const
void surfaceModelChanged()
void setScene(GeoScene *scene)
SurfacePatchModel * surfaceModel() const
GeoScene * scene() const
QVariantList coordinates() const
Coordinate list alternative to model (model takes precedence when set)
void setSprite(QQuickItem *sprite)
qreal verticalScale() const
Definition GeoScene.cc:48
void sceneOriginChanged()
qreal terrainScale() const
Definition GeoScene.h:67
void terrainScaleChanged()
void cameraChanged()
GeoMapCamera * camera() const
Definition GeoScene.h:49
void terrainHeightsChanged()
terrainHeightAt answers changed somewhere: consumers re-query
Q_INVOKABLE double terrainHeightAt(const QGeoCoordinate &coordinate) const
std::optional< double > coordinate(std::string_view field, std::string_view hemisphere, bool latitude)
QPointF geoToWorld(const QGeoCoordinate &coord)
Geo -> world meters. Latitude is clamped to +/-kMaxLatitude.
Definition TileMath.cc:23