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)
51{
52 _points.clear();
53 _points.reserve(coordinates.size());
54 for (const QVariant& variant : coordinates) {
55 const QGeoCoordinate coordinate = variant.value<QGeoCoordinate>();
56 if (coordinate.isValid()) {
57 _points.append(coordinate);
58 }
59 }
61 emit pointCountChanged();
62 _rebuild();
63}
64
65void FlightPathGeometry::appendPoint(const QGeoCoordinate& coordinate)
66{
67 if (!coordinate.isValid()) {
68 return;
69 }
70 _points.append(coordinate);
71 if (_points.size() == 1) {
73 }
74 emit pointCountChanged();
75 if (!_scene || (_points.size() < 3)) {
76 _rebuild();
77 return;
78 }
79
80 // Incremental: only the new vertex pair and the previous point's blended tangent change
81 const qsizetype last = _points.size() - 1;
82 _positions.append(_positionFor(coordinate));
83 _segmentDirections.append(_segmentDirection(last - 1));
84 _vertexData.resize(_vertexData.size() + (2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float))));
85 _writeVertexPair(last - 1, _tangentAt(last - 1));
86 _writeVertexPair(last, _tangentAt(last));
87 _growBounds(_positions.last());
88 setVertexData(_vertexData);
89 setBounds(_minBounds, _maxBounds);
90 update();
91}
92
93void FlightPathGeometry::updateLastPoint(const QGeoCoordinate& coordinate)
94{
95 if (!coordinate.isValid() || _points.isEmpty()) {
96 return;
97 }
98 _points.last() = coordinate;
99 if (_points.size() == 1) {
101 }
102 if (!_scene || (_points.size() < 2)) {
103 _rebuild();
104 return;
105 }
106
107 // Incremental: rewrite the last two vertex pairs and upload just that byte range
108 const qsizetype last = _points.size() - 1;
109 _positions.last() = _positionFor(coordinate);
110 _segmentDirections.last() = _segmentDirection(last - 1);
111 _writeVertexPair(last - 1, _tangentAt(last - 1));
112 _writeVertexPair(last, _tangentAt(last));
113 _growBounds(_positions.last()); // grow-only: bounds stay conservative if the point retreats
114 const qsizetype offset = (last - 1) * 2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float));
115 const qsizetype length = 4 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float));
116 setVertexData(static_cast<int>(offset), QByteArray(_vertexData.constData() + offset, length));
117 setBounds(_minBounds, _maxBounds);
118 update();
119}
120
122{
123 if (_points.isEmpty()) {
124 return;
125 }
126 _points.clear();
128 emit pointCountChanged();
129 _rebuild();
130}
131
132void FlightPathGeometry::_rebuild()
133{
134 _positions.clear();
135 _segmentDirections.clear();
136 _vertexData.clear();
137
138 if (!_scene || (_points.size() < 2)) {
139 clear();
140 setStride(kFloatsPerVertex * sizeof(float));
141 setVertexData(QByteArray());
142 setPrimitiveType(QQuick3DGeometry::PrimitiveType::TriangleStrip);
143 setBounds(QVector3D(), QVector3D());
144 update();
145 return;
146 }
147
148 _positions.reserve(_points.size());
149 for (const QGeoCoordinate& point : _points) {
150 _positions.append(_positionFor(point));
151 }
152
153 _segmentDirections.reserve(_positions.size() - 1);
154 for (qsizetype i = 0; i < _positions.size() - 1; i++) {
155 _segmentDirections.append(_segmentDirection(i));
156 }
157
158 _vertexData.resize(_positions.size() * 2 * kFloatsPerVertex * static_cast<qsizetype>(sizeof(float)));
159 _minBounds = _positions.first();
160 _maxBounds = _positions.first();
161 for (qsizetype i = 0; i < _positions.size(); i++) {
162 _writeVertexPair(i, _tangentAt(i));
163 _growBounds(_positions[i]);
164 }
165
166 clear();
167 setStride(kFloatsPerVertex * sizeof(float));
168 setVertexData(_vertexData);
169 setPrimitiveType(QQuick3DGeometry::PrimitiveType::TriangleStrip);
170 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
171 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 3 * sizeof(float), QQuick3DGeometry::Attribute::F32Type);
172 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic, 6 * sizeof(float),
173 QQuick3DGeometry::Attribute::F32Type);
174 setBounds(_minBounds, _maxBounds);
175 update();
176}
177
178// Anchor-relative offset in double: exact regardless of scene origin
179QVector3D FlightPathGeometry::_positionFor(const QGeoCoordinate& coordinate) const
180{
181 const QGeoCoordinate& anchor = _points.first();
182 const QPointF anchorWorld = TileMath::geoToWorld(anchor);
183 const double anchorAltitude = std::isfinite(anchor.altitude()) ? anchor.altitude() : 0.0;
184 const QPointF world = TileMath::geoToWorld(coordinate);
185 const double altitude = std::isfinite(coordinate.altitude()) ? coordinate.altitude() : 0.0;
186 return QVector3D(static_cast<float>(world.x() - anchorWorld.x()), static_cast<float>(world.y() - anchorWorld.y()),
187 static_cast<float>((altitude - anchorAltitude) * _scene->verticalScale()));
188}
189
190// Unit 3D tangent of the segment starting at point index (vertical climbs keep
191// a valid direction); degenerate segments (repeated points) reuse the previous direction
192QVector3D FlightPathGeometry::_segmentDirection(qsizetype index) const
193{
194 const QVector3D direction = _positions[index + 1] - _positions[index];
195 if (!qFuzzyIsNull(direction.lengthSquared())) {
196 return direction.normalized();
197 }
198 return (index > 0) ? _segmentDirections[index - 1] : QVector3D(0.0f, 1.0f, 0.0f);
199}
200
201// Interior points blend adjacent segments for a smooth join
202QVector3D FlightPathGeometry::_tangentAt(qsizetype index) const
203{
204 QVector3D direction;
205 if (index == 0) {
206 direction = _segmentDirections.first();
207 } else if (index == _positions.size() - 1) {
208 direction = _segmentDirections.last();
209 } else {
210 direction = _segmentDirections[index - 1] + _segmentDirections[index];
211 }
212 if (qFuzzyIsNull(direction.lengthSquared())) {
213 direction = _segmentDirections[index - 1]; // 180-degree reversal: either side works
214 }
215 direction.normalize();
216 return direction;
217}
218
219void FlightPathGeometry::_writeVertexPair(qsizetype index, const QVector3D& tangent)
220{
221 const QVector3D& position = _positions[index];
222 float* vertex = reinterpret_cast<float*>(_vertexData.data()) + (index * 2 * kFloatsPerVertex);
223 for (const float side : {-1.0f, 1.0f}) {
224 *vertex++ = position.x();
225 *vertex++ = position.y();
226 *vertex++ = position.z();
227 *vertex++ = tangent.x();
228 *vertex++ = tangent.y();
229 *vertex++ = tangent.z();
230 *vertex++ = side;
231 *vertex++ = 0.0f;
232 }
233}
234
235void FlightPathGeometry::_growBounds(const QVector3D& position)
236{
237 _minBounds = QVector3D(qMin(_minBounds.x(), position.x()), qMin(_minBounds.y(), position.y()),
238 qMin(_minBounds.z(), position.z()));
239 _maxBounds = QVector3D(qMax(_maxBounds.x(), position.x()), qMax(_maxBounds.y(), position.y()),
240 qMax(_maxBounds.z(), position.z()));
241}
static constexpr int kFloatsPerVertex
position 3, tangent 3, side flag 2
GeoScene * scene() const
FlightPathGeometry(QQuick3DObject *parent=nullptr)
void setScene(GeoScene *scene)
Q_INVOKABLE void clearPath()
Q_INVOKABLE void setPath(const QVariantList &coordinates)
Replace the whole path (QGeoCoordinate variants, TrajectoryPoints::list() format)
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()
QPointF geoToWorld(const QGeoCoordinate &coord)
Geo -> world meters. Latitude is clamped to +/-kMaxLatitude.
Definition TileMath.cc:23