8#include <QtCore/QElapsedTimer>
10#include <QtCore/QString>
11#include <QtCore/QTimer>
12#include <QtCore/QVarLengthArray>
13#include <QtCore/QtMath>
14#include <QtPositioning/QGeoCoordinate>
36 return QRectF(minCorner.x(), minCorner.y(), span, span);
44 const QRectF rect = patchRect(key);
45 const double margin = rect.width() * 1e-6;
46 return rect.marginsAdded(QMarginsF(margin, margin, margin, margin)).intersects(region);
49float maxHeightOf(
const QList<float>& heights)
51 float maxHeight = 0.0f;
52 for (
const float height : heights) {
53 if (std::isfinite(
height)) {
54 maxHeight = std::max(maxHeight,
height);
61QString boundaryLabel(
int row,
int col,
int gridSize)
63 const QChar edge = (row == 0) ? QLatin1Char(
'N')
64 : (row == gridSize) ? QLatin1Char(
'S')
65 : (col == 0) ? QLatin1Char(
'W')
67 return edge + QStringLiteral(
" edge");
73 : QObject(parent), _camera(camera), _heightSource(heightSource), _field(field)
75 qRegisterMetaType<TileMath::TileKey>();
81 for (
auto it = _patches.begin(); it != _patches.end(); ++it) {
82 _samplePatch(it.key(), it.value());
97void SurfaceModel::_scheduleUpdate()
102 _updatePending =
true;
103 QTimer::singleShot(0,
this, [
this] {
104 if (!_updatePending) {
115 _updatePending =
false;
123 QElapsedTimer updateTimer;
127 const double terrainZ = _maxTerrainZ();
128 const QRectF visible = _visibleGroundRect(terrainZ);
129 _culledTerrainZ = terrainZ;
131 const double cameraHeight = _camera->
distance() * std::cos(qDegreesToRadians(_camera->
tilt()));
133 const QList<TileMath::TileKey> desired = _desiredPatches(visible, cameraGround, cameraHeight);
135 QSet<TileMath::TileKey> desiredSet(desired.cbegin(), desired.cend());
137 QVarLengthArray<QRectF, 8> churnRects;
138 const AddResult added = _addDesiredPatches(desired, churnRects);
139 const int removals = _removeStalePatches(desired, desiredSet, churnRects);
140 _notifyEdgeChurn(churnRects);
142 if ((added.adds > 0) || (removals > 0)) {
149 if (_addsDeferred || _removalsDeferred) {
153 const qint64 elapsedUs = updateTimer.nsecsElapsed() / 1000;
154 qCDebug(GeoMapSurfaceModelVerboseLog)
155 <<
"update pass: desired" << desired.count() <<
"resident" << _patches.count() <<
"adds" << added.adds
156 <<
"removals" << removals <<
"deferred adds" << _addsDeferred <<
"deferred removals" << _removalsDeferred
157 <<
"elapsedUs" << elapsedUs <<
"addUs" << added.addUs;
159 _updateStats.
totalUs += elapsedUs;
160 _updateStats.
maxUs = std::max(_updateStats.
maxUs, elapsedUs);
168SurfaceModel::AddResult SurfaceModel::_addDesiredPatches(
const QList<TileMath::TileKey>& desired,
169 QVarLengthArray<QRectF, 8>& churnRects)
172 _addsDeferred =
false;
173 QElapsedTimer addTimer;
176 if (_patches.contains(key)) {
180 _addsDeferred =
true;
184 _samplePatch(key, data);
185 _patches.insert(key, std::move(data));
186 churnRects.append(patchRect(key));
191 result.addUs = addTimer.nsecsElapsed() / 1000;
201int SurfaceModel::_removeStalePatches(
const QList<TileMath::TileKey>& desired,
202 const QSet<TileMath::TileKey>& desiredSet, QVarLengthArray<QRectF, 8>& churnRects)
209 QVarLengthArray<QRectF, 16> missingRects;
211 if (!_patches.contains(key)) {
212 missingRects.append(patchRect(key));
216 const QRectF rect = patchRect(key);
217 for (
const QRectF& missing : missingRects) {
218 if (rect.intersects(missing)) {
226 _removalsDeferred =
false;
227 for (
auto it = _patches.begin(); it != _patches.end();) {
228 if (desiredSet.contains(it.key())) {
233 _removalsDeferred =
true;
238 if (it.value().cliffSinceMs >= 0) {
239 qCWarning(GeoMapSurfaceModelCliffsLog) <<
"cliff removed with patch" << removedKey <<
"after"
240 << (_cliffClock.elapsed() - it.value().cliffSinceMs) <<
"ms";
242 it = _patches.erase(it);
243 churnRects.append(patchRect(removedKey));
252void SurfaceModel::_notifyEdgeChurn(
const QVarLengthArray<QRectF, 8>& churnRects)
254 for (
auto it = _patches.cbegin(); it != _patches.cend(); ++it) {
255 for (
const QRectF& rect : churnRects) {
256 if (patchTouchesRegion(it.key(), rect)) {
268void SurfaceModel::_repinAncestors()
270 QSet<TileMath::TileKey> pinned;
273 while (!pinned.contains(key)) {
281 for (
auto it = _patches.cbegin(); it != _patches.cend(); ++it) {
283 for (
int dy = -1; dy <= 1; dy++) {
284 for (
int dx = -1; dx <= 1; dx++) {
308 result.reserve(_patches.count());
309 for (
auto it = _patches.cbegin(); it != _patches.cend(); ++it) {
310 result.append(
Patch{it.
key(), it.value().heights,
true,
false, it.value().edgeStep, it.value().edgeStepAt,
311 it.value().edgeCliff});
318 const auto it = _patches.constFind(key);
319 if (it == _patches.cend()) {
323 key, it.value().heights,
true,
false, it.value().edgeStep, it.value().edgeStepAt, it.value().edgeCliff};
331 const QRectF ownerRect = patchRect(owner);
333 const double onEdge = ownerRect.width() * 1e-9;
334 const int westSteps = ((world.x() - ownerRect.left()) <= onEdge) ? 1 : 0;
335 const int northSteps = ((ownerRect.bottom() - world.y()) <= onEdge) ? 1 : 0;
337 auto it = _patches.cend();
338 for (
int dy = 0; (dy <= northSteps) && (it == _patches.cend()); dy++) {
339 for (
int dx = 0; (dx <= westSteps) && (it == _patches.cend()); dx++) {
344 if (it == _patches.cend()) {
347 const QRectF rect = patchRect(key);
348 const double cell = rect.width() /
kGridSize;
350 const double col = (world.x() - rect.left()) / cell;
351 const double row = ((rect.top() + rect.height()) - world.y()) / cell;
355 deltas[0] = _edgeDelta(key, 0, -1);
358 deltas[1] = _edgeDelta(key, 0, 1);
361 deltas[2] = _edgeDelta(key, -1, 0);
364 deltas[3] = _edgeDelta(key, 1, 0);
374 static constexpr int kOffsets[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
377 for (
const auto& offset : kOffsets) {
378 deltas.append(_edgeDelta(key, offset[0], offset[1]));
394 if (_patches.contains(ancestor)) {
395 const int delta = key.
zoom - ancestor.
zoom;
397 return ((
kGridSize % (1 << delta)) == 0) ? delta : 0;
403QRectF SurfaceModel::_visibleGroundRect(
double terrainZ)
const
414 const double terrainT = ((terrainZ > 0.0) && (eyeZ > terrainZ)) ? (1.0 - (terrainZ / eyeZ)) : 0.0;
423 const auto include = [&](
const QPointF& point) {
425 minX = maxX = point.x();
426 minY = maxY = point.y();
429 minX = std::min(minX, point.x());
430 maxX = std::max(maxX, point.x());
431 minY = std::min(minY, point.y());
432 maxY = std::max(maxY, point.y());
444 if (terrainT > 0.0) {
445 include(cameraGround + ((*ground - cameraGround) * terrainT));
455 include(cameraGround);
458 return QRectF(QPointF(minX, minY), QPointF(maxX, maxY))
464double SurfaceModel::_maxTerrainZ()
const
466 float maxHeight = 0.0f;
467 for (
const PatchData& data : _patches) {
468 maxHeight = std::max(maxHeight, data.maxHeight);
470 if (maxHeight <= 0.0f) {
476double SurfaceModel::_projectedPixels(
const TileMath::TileKey& key,
const QPointF& cameraGround,
477 double cameraHeight)
const
479 const QRectF rect = patchRect(key);
480 const double span = rect.width();
483 const double dx = std::max({rect.left() - cameraGround.x(), cameraGround.x() - rect.right(), 0.0});
484 const double dy = std::max({rect.top() - cameraGround.y(), cameraGround.y() - rect.bottom(), 0.0});
485 const double distance = std::hypot(std::hypot(dx, dy), cameraHeight);
487 const double metersPerPixel =
489 return span / metersPerPixel;
492QList<TileMath::TileKey> SurfaceModel::_desiredPatches(
const QRectF& visible,
const QPointF& cameraGround,
493 double cameraHeight)
const
505 const auto lessPixels = [](
const Entry& a,
const Entry& b) {
return a.pixels < b.pixels; };
506 std::priority_queue<Entry, std::vector<Entry>,
decltype(lessPixels)> queue(lessPixels);
508 QList<TileMath::TileKey> desired;
511 if (visible.intersects(patchRect(root))) {
512 queue.push(Entry{_projectedPixels(root, cameraGround, cameraHeight), root});
515 while (!queue.empty()) {
516 const Entry entry = queue.top();
522 desired.append(entry.key);
526 QVarLengthArray<Entry, 4> children;
527 for (
int childY = 0; childY < 2; childY++) {
528 for (
int childX = 0; childX < 2; childX++) {
529 const TileMath::TileKey childKey{(entry.key.x * 2) + childX, (entry.key.y * 2) + childY,
531 if (visible.intersects(patchRect(childKey))) {
532 children.append(Entry{_projectedPixels(childKey, cameraGround, cameraHeight), childKey});
536 const int totalAfterSplit =
static_cast<int>(queue.size()) + desired.count() - 1 + children.count();
541 for (
const Entry& child : children) {
546 while (!queue.empty()) {
547 desired.append(queue.top().key);
553void SurfaceModel::_fieldRegionChanged(
const QRectF& worldRect)
560 for (
auto it = _patches.begin(); it != _patches.end(); ++it) {
561 if (!patchTouchesRegion(it.key(), worldRect)) {
564 _samplePatch(it.key(), it.value());
568 qCDebug(GeoMapSurfaceModelVerboseLog)
569 <<
"regionChanged" << worldRect <<
"re-meshed" << remeshed <<
"of" << _patches.count() <<
"patches";
585 data.maxHeight = maxHeightOf(data.heights);
586 data.edgeStep = edgeStep.
step;
587 data.edgeCliff =
false;
588 if (!monitorCliffs) {
589 data.cliffSinceMs = -1;
592 const QRectF rect = patchRect(key);
593 const double cell = rect.width() /
kGridSize;
595 QPointF(rect.left() + (edgeStep.
col * cell), (rect.top() + rect.height()) - (edgeStep.
row * cell));
598 const double groundCell = cell * std::cos(qDegreesToRadians(stepAt.latitude()));
600 const bool cliff = edgeStep.
step >= (kCliffLogSlope * groundCell);
601 data.edgeCliff = cliff;
602 if (cliff && (data.cliffSinceMs < 0)) {
603 data.cliffSinceMs = _cliffClock.elapsed();
604 const QString interiorSource = (edgeStep.
ownZoom < 0) ? QStringLiteral(
"no elevation data (flat 0 m)")
605 : QStringLiteral(
"z%1 tile").arg(edgeStep.
ownZoom);
606 qCWarning(GeoMapSurfaceModelCliffsLog).noquote()
607 <<
"cliff appeared: patch" << key << boundaryLabel(edgeStep.
row, edgeStep.
col,
kGridSize) <<
"step"
608 << edgeStep.
step <<
"m over a" << qRound(groundCell) <<
"m cell"
609 <<
"at" << stepAt <<
"| patch interior:" << interiorSource
610 << QStringLiteral(
"| edge vertex: z%1 tile").arg(edgeStep.
boundaryZoom);
611 }
else if (!cliff && (data.cliffSinceMs >= 0)) {
612 qCWarning(GeoMapSurfaceModelCliffsLog)
613 <<
"cliff cleared: patch" << key <<
"after" << (_cliffClock.elapsed() - data.cliffSinceMs) <<
"ms";
614 data.cliffSinceMs = -1;
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void rawValueChanged(const QVariant &value)
QSizeF viewportSize() const
void centerElevationChanged()
QPointF cameraGroundPosition() const
qreal centerElevation() const
void fieldOfViewChanged()
qreal verticalFieldOfView() const
bool isPositioned() const
void viewportSizeChanged()
std::optional< QPointF > groundPointCapped(const QPointF &screenPos, double maxRange) const
QGeoCoordinate center() const
QList< float > samplePatch(const TileMath::TileKey &key, int gridSize, PatchSampler::EdgeStep *edgeStep=nullptr) const
void setPinnedKeys(QSet< TileMath::TileKey > keys)
void regionChanged(const QRectF &worldRect)
virtual bool requestTile(const TileMath::TileKey &key)
static SettingsManager * instance()
FlyViewSettings * flyViewSettings() const
static constexpr double kRecullHeightMargin
SurfaceModel(GeoMapCamera *camera, HeightSource *heightSource, HeightField *field, QObject *parent=nullptr)
static constexpr int kMaxPatchAddsPerUpdate
void surfaceHeightsChanged()
static constexpr double kMaxRangeMultiplier
visible-range cap in camera distances
std::optional< Patch > patch(const TileMath::TileKey &key) const
Single-patch lookup; std::nullopt when the key is not resident.
static constexpr int kMaxPatchRemovalsPerUpdate
static constexpr int kGridSize
QList< Patch > patches() const
std::optional< double > renderedHeightAt(const QPointF &world) const
void patchEdgeDeltasChanged(const TileMath::TileKey &key)
QList< int > edgeLodDeltas(const TileMath::TileKey &key) const
void patchRemoved(const TileMath::TileKey &key)
static constexpr int kVisibleSampleGrid
void patchAdded(const TileMath::TileKey &key)
static constexpr double kRefinePixelThreshold
subdivide above this projected size
void patchMeshChanged(const TileMath::TileKey &key)
The patch's mesh content changed (heights re-sampled): consumers must re-pull heights.
UpdateStats takeUpdateStats()
Returns the counters accumulated since the previous call and resets them.
static constexpr int kMaxPatches
double distance(const Ecef &left, const Ecef &right)
Straight-line distance between left and right, in metres.
std::array< int, 4 > LodDeltas
double surfaceHeight(const QList< float > &heights, int gridSize, const LodDeltas &lodDeltas, double row, double col)
double worldSize()
Full mercator world extent (2*pi*R) in world meters.
QPointF tileMinCorner(const TileKey &key)
South-west (minimum x/y) corner of a tile in world meters.
bool isValidKey(const TileKey &key)
True if zoom is within [kMinZoom, kMaxZoom] and x/y address a tile at that zoom.
double mercatorScale(double latitude)
QGeoCoordinate worldToGeo(const QPointF &world)
World meters -> geo (altitude 0)
double tileSpanAtZoom(int zoom)
Edge length of one tile at zoom, in world meters.
TileKey tileForWorld(const QPointF &world, int zoom)
Tile containing a world point. World coordinates are clamped to the world extent.
int row
boundary vertex of the largest step
int boundaryZoom
zoom of the tile the boundary vertex resolved to
int ownZoom
zoom of the patch's backing tile; -1 = no data
update() call/duration counters for the perf overlay
qint64 addTotalUs
time inside the patch-add loop (delegate creation share)