QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FlightPathGeometry.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 "FlightPathGeometry.h"
11
12#include <QtCore/QByteArray>
13#include <QtCore/QPointF>
14#include <QtCore/qminmax.h>
15#include <QtCore/qnumeric.h>
16#include <QtGui/QVector3D>
17
18#include <cmath>
19
20#include "GeoScene.h"
21#include "TileMath.h"
22
23FlightPathGeometry::FlightPathGeometry(QQuick3DObject* parent) : QQuick3DGeometry(parent)
24{
25 _rebuild();
26}
27
29{
30 if (scene == _scene) {
31 return;
32 }
33 if (_scene) {
34 disconnect(_scene, nullptr, this, nullptr);
35 }
36 _scene = scene;
37 if (_scene) {
38 // verticalScale follows the origin latitude, so z offsets drift on re-anchor
39 connect(_scene, &GeoScene::sceneOriginChanged, this, &FlightPathGeometry::_rebuild);
40 connect(_scene, &QObject::destroyed, this, [this] {
41 _scene = nullptr;
42 emit sceneChanged();
43 _rebuild();
44 });
45 }
46 emit sceneChanged();
47 _rebuild();
48}
49
50void FlightPathGeometry::setPath(const QVariantList& coordinates, const QVariantList& highlights)
51{
52 _points.clear();
53 _highlights.clear();
54 _points.reserve(coordinates.size());
55 _highlights.reserve(coordinates.size());
56 for (qsizetype i = 0; i < coordinates.size(); i++) {
57 const QGeoCoordinate coordinate = coordinates[i].value<QGeoCoordinate>();
58 if (coordinate.isValid()) {
59 _points.append(coordinate);
60 _highlights.append((i < highlights.size()) && highlights[i].toBool());
61 }
62 }
64 emit pointCountChanged();
65 _rebuild();
66}
67
68void FlightPathGeometry::appendPoint(const QGeoCoordinate& coordinate)
69{
70 if (!coordinate.isValid()) {
71 return;
72 }
73 _points.append(coordinate);
74 _highlights.append(false);
75 if (_points.size() == 1) {
77 }
78 emit pointCountChanged();
79 if (!_scene || (_points.size() < 3)) {
80 _rebuild();
81 return;
82 }
83
84 // Incremental: only the new vertex pair and the previous point's blended tangent change
85 const qsizetype last = _points.size() - 1;
86 _positions.append(_positionFor(coordinate));
87 _segmentDirections.append(_segmentDirection(last - 1));
88 _vertexData.resize(_vertexData.size() + (2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float))));
89 _writeVertexPair(last - 1, _tangentAt(last - 1));
90 _writeVertexPair(last, _tangentAt(last));
91 _growBounds(_positions.last());
92 setVertexData(_vertexData);
93 setBounds(_minBounds, _maxBounds);
94 update();
95}
96
97void FlightPathGeometry::updateLastPoint(const QGeoCoordinate& coordinate)
98{
99 if (!coordinate.isValid() || _points.isEmpty()) {
100 return;
101 }
102 _points.last() = coordinate;
103 if (_points.size() == 1) {
105 }
106 if (!_scene || (_points.size() < 2)) {
107 _rebuild();
108 return;
109 }
110
111 // Incremental: rewrite the last two vertex pairs and upload just that byte range
112 const qsizetype last = _points.size() - 1;
113 _positions.last() = _positionFor(coordinate);
114 _segmentDirections.last() = _segmentDirection(last - 1);
115 _writeVertexPair(last - 1, _tangentAt(last - 1));
116 _writeVertexPair(last, _tangentAt(last));
117 _growBounds(_positions.last()); // grow-only: bounds stay conservative if the point retreats
118 const qsizetype offset = (last - 1) * 2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float));
119 const qsizetype length = 4 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float));
120 setVertexData(static_cast<int>(offset), QByteArray(_vertexData.constData() + offset, length));
121 setBounds(_minBounds, _maxBounds);
122 update();
123}
124
126{
127 if (_points.isEmpty()) {
128 return;
129 }
130 _points.clear();
131 _highlights.clear();
133 emit pointCountChanged();
134 _rebuild();
135}
136
137void FlightPathGeometry::_rebuild()
138{
139 _positions.clear();
140 _segmentDirections.clear();
141 _vertexData.clear();
142
143 if (!_scene || (_points.size() < 2)) {
144 clear();
145 setStride(kFloatsPerVertex * sizeof(float));
146 setVertexData(QByteArray());
147 setPrimitiveType(QQuick3DGeometry::PrimitiveType::TriangleStrip);
148 setBounds(QVector3D(), QVector3D());
149 update();
150 return;
151 }
152
153 _positions.reserve(_points.size());
154 for (const QGeoCoordinate& point : _points) {
155 _positions.append(_positionFor(point));
156 }
157
158 _segmentDirections.reserve(_positions.size() - 1);
159 for (qsizetype i = 0; i < _positions.size() - 1; i++) {
160 _segmentDirections.append(_segmentDirection(i));
161 }
162
163 _vertexData.resize(_positions.size() * 2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float)));
164 _minBounds = _positions.first();
165 _maxBounds = _positions.first();
166 for (qsizetype i = 0; i < _positions.size(); i++) {
167 _writeVertexPair(i, _tangentAt(i));
168 _growBounds(_positions[i]);
169 }
170
171 clear();
172 setStride(kFloatsPerVertex * sizeof(float));
173 setVertexData(_vertexData);
174 setPrimitiveType(QQuick3DGeometry::PrimitiveType::TriangleStrip);
175 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
176 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 3 * sizeof(float), QQuick3DGeometry::Attribute::F32Type);
177 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic, 6 * sizeof(float),
178 QQuick3DGeometry::Attribute::F32Type);
179 setBounds(_minBounds, _maxBounds);
180 update();
181}
182
183// Anchor-relative offset in double: exact regardless of scene origin
184QVector3D FlightPathGeometry::_positionFor(const QGeoCoordinate& coordinate) const
185{
186 const QGeoCoordinate& anchor = _points.first();
187 const QPointF anchorWorld = TileMath::geoToWorld(anchor);
188 const double anchorAltitude = std::isfinite(anchor.altitude()) ? anchor.altitude() : 0.0;
189 const QPointF world = TileMath::geoToWorld(coordinate);
190 const double altitude = std::isfinite(coordinate.altitude()) ? coordinate.altitude() : 0.0;
191 return QVector3D(static_cast<float>(world.x() - anchorWorld.x()), static_cast<float>(world.y() - anchorWorld.y()),
192 static_cast<float>((altitude - anchorAltitude) * _scene->verticalScale()));
193}
194
195// Unit 3D tangent of the segment starting at point index (vertical climbs keep
196// a valid direction); degenerate segments (repeated points) reuse the previous direction
197QVector3D FlightPathGeometry::_segmentDirection(qsizetype index) const
198{
199 const QVector3D direction = _positions[index + 1] - _positions[index];
200 if (!qFuzzyIsNull(direction.lengthSquared())) {
201 return direction.normalized();
202 }
203 return (index > 0) ? _segmentDirections[index - 1] : QVector3D(0.0f, 1.0f, 0.0f);
204}
205
206// Interior points blend adjacent segments for a smooth join
207QVector3D FlightPathGeometry::_tangentAt(qsizetype index) const
208{
209 QVector3D direction;
210 if (index == 0) {
211 direction = _segmentDirections.first();
212 } else if (index == _positions.size() - 1) {
213 direction = _segmentDirections.last();
214 } else {
215 direction = _segmentDirections[index - 1] + _segmentDirections[index];
216 }
217 if (qFuzzyIsNull(direction.lengthSquared())) {
218 direction = _segmentDirections[index - 1]; // 180-degree reversal: either side works
219 }
220 direction.normalize();
221 return direction;
222}
223
224void FlightPathGeometry::_writeVertexPair(qsizetype index, const QVector3D& tangent)
225{
226 const QVector3D& position = _positions[index];
227 const float highlight = _highlights[index] ? 1.0f : 0.0f;
228 float* vertex = reinterpret_cast<float*>(_vertexData.data()) + (index * 2 * kFloatsPerVertex);
229 for (const float side : {-1.0f, 1.0f}) {
230 *vertex++ = position.x();
231 *vertex++ = position.y();
232 *vertex++ = position.z();
233 *vertex++ = tangent.x();
234 *vertex++ = tangent.y();
235 *vertex++ = tangent.z();
236 *vertex++ = side;
237 *vertex++ = highlight;
238 }
239}
240
241void FlightPathGeometry::_growBounds(const QVector3D& position)
242{
243 _minBounds = QVector3D(qMin(_minBounds.x(), position.x()), qMin(_minBounds.y(), position.y()),
244 qMin(_minBounds.z(), position.z()));
245 _maxBounds = QVector3D(qMax(_maxBounds.x(), position.x()), qMax(_maxBounds.y(), position.y()),
246 qMax(_maxBounds.z(), position.z()));
247}
static constexpr int kFloatsPerVertex
position 3, tangent 3, side flag, highlight flag
GeoScene * scene() const
FlightPathGeometry(QQuick3DObject *parent=nullptr)
void setScene(GeoScene *scene)
Q_INVOKABLE void setPath(const QVariantList &coordinates, const QVariantList &highlights=QVariantList())
Q_INVOKABLE void clearPath()
void anchorCoordinateChanged()
Q_INVOKABLE void appendPoint(const QGeoCoordinate &coordinate)
Q_INVOKABLE void updateLastPoint(const QGeoCoordinate &coordinate)
Replace the last point (TrajectoryPoints emits this for colinear motion)
qreal verticalScale() const
Definition GeoScene.cc:48
void sceneOriginChanged()
GPSPositionReport position(const GPSDecodedPosition &source, const GPSIntegrityReport &diagnostic, uint64_t nowUs)
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