QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PatchGeometry.cc
Go to the documentation of this file.
1#include "PatchGeometry.h"
2
3#include <algorithm>
4#include <bit>
5
6#include <QtGui/QVector3D>
7
8#include "HeightField.h"
9#include "PatchMesh.h"
10#include "QGCLoggingCategory.h"
11
12QGC_LOGGING_CATEGORY(GeoMapPatchGeometryLog, "GeoMap.PatchGeometry")
13
14PatchGeometry::PatchGeometry(QQuick3DObject* parent) : QQuick3DGeometry(parent)
15{
16 _rebuild();
17}
18
20{
21 QQuick3DGeometry::componentComplete();
22 _rebuild(); // one build for the whole batch of initial property assignments
23}
24
25void PatchGeometry::_requestRebuild()
26{
27 if (isComponentComplete()) {
28 _rebuild();
29 }
30}
31
33{
34 const int clamped = std::clamp(gridSize, kMinGridSize, kMaxGridSize);
35 if (clamped == _gridSize) {
36 return;
37 }
38 _gridSize = clamped;
39 for (const int delta : _lodDelta) {
40 if ((delta > 0) && ((_gridSize % (1 << delta)) != 0)) {
41 qCWarning(GeoMapPatchGeometryLog) << "setGridSize reset edge LOD deltas: delta" << delta
42 << "has no coincident vertices at gridSize" << _gridSize;
43 _lodDelta.fill(0);
45 break;
46 }
47 }
48 emit gridSizeChanged();
49 _requestRebuild();
50}
51
52void PatchGeometry::setSpan(qreal span)
53{
54 if (qFuzzyCompare(span, _span) || (span <= 0.0)) {
55 return;
56 }
57 _span = span;
58 emit spanChanged();
59 _requestRebuild();
60}
61
62void PatchGeometry::setHeights(const QList<float>& heights)
63{
64 if (heights == _heights) {
65 return;
66 }
67 _heights = heights;
68 emit heightsChanged();
69 _requestRebuild();
70}
71
73{
74 if (!_heightField) {
75 qCWarning(GeoMapPatchGeometryLog) << "sampleFromField rejected: no height field set, key" << key;
76 return false;
77 }
78 const QList<float> sampled = _heightField->samplePatch(key, _gridSize);
79 if (sampled.isEmpty()) {
80 qCWarning(GeoMapPatchGeometryLog)
81 << "sampleFromField rejected: field returned no samples, key" << key << "gridSize" << _gridSize;
82 return false;
83 }
84 if (sampled != _heights) {
85 _heights = sampled;
86 emit heightsChanged();
87 }
88 _rebuild();
89 return true;
90}
91
93{
94 if (heightField == _heightField) {
95 return;
96 }
97 if (_heightField) {
98 disconnect(_heightField, nullptr, this, nullptr);
99 }
100 _heightField = heightField;
101 if (_heightField) {
102 // Match setHeightField(nullptr) semantics; null first, the dying
103 // object needs no disconnect
104 connect(_heightField, &QObject::destroyed, this, [this] {
105 _heightField = nullptr;
106 emit heightFieldChanged();
107 });
108 }
109 emit heightFieldChanged();
110}
111
112void PatchGeometry::setEdgeLodDeltas(int north, int south, int west, int east)
113{
114 // Bounding delta first keeps the shift below well-defined
115 static_assert(std::has_single_bit(static_cast<unsigned>(kMaxGridSize)), "kMaxLodDelta assumes a power of two");
116 constexpr int kMaxLodDelta = std::countr_zero(static_cast<unsigned>(kMaxGridSize));
117 for (const int delta : {north, south, west, east}) {
118 if ((delta < 0) || (delta > kMaxLodDelta) || ((delta > 0) && ((_gridSize % (1 << delta)) != 0))) {
119 qCWarning(GeoMapPatchGeometryLog)
120 << "setEdgeLodDeltas rejected: delta" << delta << "has no coincident vertices at gridSize" << _gridSize;
121 return;
122 }
123 }
124 if ((north == _lodDelta[kNorth]) && (south == _lodDelta[kSouth]) && (west == _lodDelta[kWest]) &&
125 (east == _lodDelta[kEast])) {
126 return;
127 }
128 _lodDelta[kNorth] = north;
129 _lodDelta[kSouth] = south;
130 _lodDelta[kWest] = west;
131 _lodDelta[kEast] = east;
133 _requestRebuild();
134}
135
136void PatchGeometry::setEdgeLodDeltas(const QList<int>& deltas)
137{
138 if (deltas.count() != kEdgeCount) {
139 qCWarning(GeoMapPatchGeometryLog)
140 << "setEdgeLodDeltas rejected: expected {N,S,W,E}, got" << deltas.count() << "deltas";
141 return;
142 }
143 setEdgeLodDeltas(deltas[0], deltas[1], deltas[2], deltas[3]);
144}
145
146float PatchGeometry::_heightAt(int row, int col) const
147{
148 return PatchMesh::vertexHeight(_heights, _gridSize, _lodDelta, row, col);
149}
150
151void PatchGeometry::_rebuild()
152{
153 const int verticesPerEdge = _gridSize + 1;
154 const int gridVertexCount = verticesPerEdge * verticesPerEdge;
155 const int skirtVertexCount = 4 * verticesPerEdge;
156 const int vertexCount = gridVertexCount + skirtVertexCount;
157
158 const float span = static_cast<float>(_span);
159 const float half = span / 2.0f;
160 const float step = span / _gridSize;
161 // Skirt hides the seam against the coarsest constraining neighbor; that
162 // seam doubles with each level the neighbor is coarser, so scale the base
163 // depth by 2^maxDelta (the coarser level's geometric error halves per
164 // level). maxDelta is in
165 // [0, kMaxLodDelta] (setEdgeLodDeltas validates), so the shift is in
166 // range; 0 = base depth.
167 const int maxDelta = *std::max_element(_lodDelta.cbegin(), _lodDelta.cend());
168 const float skirtDepth = span * static_cast<float>(kSkirtDepthFraction) * static_cast<float>(1 << maxDelta);
169
170 // Interleaved layout: position (3f) + normal (3f) + uv (2f)
171 constexpr int kFloatsPerVertex = 8;
172 constexpr int kStride = kFloatsPerVertex * sizeof(float);
173
174 QByteArray vertexData(vertexCount * kStride, Qt::Uninitialized);
175 float* v = reinterpret_cast<float*>(vertexData.data());
176
177 float minZ = 0.0f;
178 float maxZ = 0.0f;
179
180 const auto writeVertex = [&](float x, float y, float z, const QVector3D& normal, float u, float texV) {
181 *v++ = x;
182 *v++ = y;
183 *v++ = z;
184 *v++ = normal.x();
185 *v++ = normal.y();
186 *v++ = normal.z();
187 *v++ = u;
188 *v++ = texV;
189 minZ = std::min(minZ, z);
190 maxZ = std::max(maxZ, z);
191 };
192
193 // Per-vertex normal from central differences of the height grid
194 const auto normalAt = [&](int row, int col) {
195 const float dzdx = (_heightAt(row, col + 1) - _heightAt(row, col - 1)) / (2.0f * step);
196 const float dzdy =
197 (_heightAt(row - 1, col) - _heightAt(row + 1, col)) / (2.0f * step); // row 0 = north (max y)
198 QVector3D normal(-dzdx, -dzdy, 1.0f);
199 normal.normalize();
200 return normal;
201 };
202
203 // Grid vertices, row-major from the north-west corner
204 for (int row = 0; row < verticesPerEdge; row++) {
205 const float y = half - (row * step);
206 const float texV = static_cast<float>(row) / _gridSize;
207 for (int col = 0; col < verticesPerEdge; col++) {
208 const float x = -half + (col * step);
209 const float u = static_cast<float>(col) / _gridSize;
210 writeVertex(x, y, _heightAt(row, col), normalAt(row, col), u, texV);
211 }
212 }
213
214 // Skirt vertices: boundary vertices dropped by skirtDepth, same normal/uv
215 // to avoid lighting/texture seams. Edge order: north, south, west, east.
216 const auto writeSkirtVertex = [&](int row, int col) {
217 const float x = -half + (col * step);
218 const float y = half - (row * step);
219 const float u = static_cast<float>(col) / _gridSize;
220 const float texV = static_cast<float>(row) / _gridSize;
221 writeVertex(x, y, _heightAt(row, col) - skirtDepth, normalAt(row, col), u, texV);
222 };
223 for (int col = 0; col < verticesPerEdge; col++) {
224 writeSkirtVertex(0, col);
225 }
226 for (int col = 0; col < verticesPerEdge; col++) {
227 writeSkirtVertex(_gridSize, col);
228 }
229 for (int row = 0; row < verticesPerEdge; row++) {
230 writeSkirtVertex(row, 0);
231 }
232 for (int row = 0; row < verticesPerEdge; row++) {
233 writeSkirtVertex(row, _gridSize);
234 }
235
236 // Indices: grid cells (2 triangles each, CCW seen from +z) + skirt quads
237 const int gridIndexCount = _gridSize * _gridSize * 6;
238 const int skirtIndexCount = 4 * _gridSize * 6;
239 QByteArray indexData((gridIndexCount + skirtIndexCount) * sizeof(quint32), Qt::Uninitialized);
240 quint32* idx = reinterpret_cast<quint32*>(indexData.data());
241
242 const auto gridIndex = [&](int row, int col) { return static_cast<quint32>((row * verticesPerEdge) + col); };
243 for (int row = 0; row < _gridSize; row++) {
244 for (int col = 0; col < _gridSize; col++) {
245 *idx++ = gridIndex(row, col);
246 *idx++ = gridIndex(row + 1, col);
247 *idx++ = gridIndex(row, col + 1);
248 *idx++ = gridIndex(row, col + 1);
249 *idx++ = gridIndex(row + 1, col);
250 *idx++ = gridIndex(row + 1, col + 1);
251 }
252 }
253
254 // Skirt quads between each boundary edge segment and its dropped copy.
255 // Rendered with culling disabled, so winding is not significant.
256 const auto writeSkirtQuads = [&](quint32 skirtBase, const auto& topIndex) {
257 for (int i = 0; i < _gridSize; i++) {
258 const quint32 topA = topIndex(i);
259 const quint32 topB = topIndex(i + 1);
260 const quint32 skirtA = skirtBase + i;
261 const quint32 skirtB = skirtBase + i + 1;
262 *idx++ = topA;
263 *idx++ = skirtA;
264 *idx++ = topB;
265 *idx++ = topB;
266 *idx++ = skirtA;
267 *idx++ = skirtB;
268 }
269 };
270 const quint32 skirtStart = static_cast<quint32>(gridVertexCount);
271 writeSkirtQuads(skirtStart, [&](int i) { return gridIndex(0, i); });
272 writeSkirtQuads(skirtStart + verticesPerEdge, [&](int i) { return gridIndex(_gridSize, i); });
273 writeSkirtQuads(skirtStart + (2 * verticesPerEdge), [&](int i) { return gridIndex(i, 0); });
274 writeSkirtQuads(skirtStart + (3 * verticesPerEdge), [&](int i) { return gridIndex(i, _gridSize); });
275
276 clear();
277 setStride(kStride);
278 setVertexData(vertexData);
279 setIndexData(indexData);
280 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
281 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
282 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 3 * sizeof(float), QQuick3DGeometry::Attribute::F32Type);
283 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic, 6 * sizeof(float),
284 QQuick3DGeometry::Attribute::F32Type);
285 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, QQuick3DGeometry::Attribute::U32Type);
286 setBounds(QVector3D(-half, -half, minZ), QVector3D(half, half, maxZ));
287 update();
288}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QList< float > samplePatch(const TileMath::TileKey &key, int gridSize, PatchSampler::EdgeStep *edgeStep=nullptr) const
static constexpr int kMinGridSize
void setEdgeLodDeltas(int north, int south, int west, int east)
void setHeightField(HeightField *heightField)
void spanChanged()
HeightField * heightField() const
Field for sampleFromField to sample from; not owned, may be null.
void heightsChanged()
QList< float > heights() const
(gridSize+1)^2 heights row-major from the north-west corner; empty = flat
void componentComplete() override
bool sampleFromField(const TileMath::TileKey &key)
void gridSizeChanged()
static constexpr int kMaxGridSize
static constexpr double kSkirtDepthFraction
base skirt depth (fraction of span); scaled by 2^maxEdgeDelta
void setSpan(qreal span)
void setGridSize(int gridSize)
void setHeights(const QList< float > &heights)
qreal span() const
int gridSize() const
void edgeLodDeltasChanged()
void heightFieldChanged()
float vertexHeight(const QList< float > &heights, int gridSize, const LodDeltas &lodDeltas, int row, int col)
Definition PatchMesh.h:30