3#include <QtCore/QElapsedTimer>
5#include <QtCore/QTimer>
6#include <QtCore/QVarLengthArray>
7#include <QtCore/QtMath>
29 return QRectF(minCorner.x(), minCorner.y(), span, span);
37 const QRectF rect = patchRect(key);
38 const double margin = rect.width() * 1e-6;
39 return rect.marginsAdded(QMarginsF(margin, margin, margin, margin)).intersects(region);
42float maxHeightOf(
const QList<float>& heights)
44 float maxHeight = 0.0f;
45 for (
const float height : heights) {
46 if (std::isfinite(height)) {
47 maxHeight = std::max(maxHeight, height);
56 : QObject(parent), _camera(camera), _heightSource(heightSource), _field(field)
58 qRegisterMetaType<TileMath::TileKey>();
73void SurfaceModel::_scheduleUpdate()
78 _updatePending =
true;
79 QTimer::singleShot(0,
this, [
this] {
80 if (!_updatePending) {
91 _updatePending =
false;
99 QElapsedTimer updateTimer;
103 const double terrainZ = _maxTerrainZ();
104 const QRectF visible = _visibleGroundRect(terrainZ);
105 _culledTerrainZ = terrainZ;
107 const double cameraHeight = _camera->
distance() * std::cos(qDegreesToRadians(_camera->
tilt()));
109 const QList<TileMath::TileKey> desired = _desiredPatches(visible, cameraGround, cameraHeight);
111 QSet<TileMath::TileKey> desiredSet(desired.cbegin(), desired.cend());
118 QVarLengthArray<QRectF, 8> churnRects;
120 _addsDeferred =
false;
122 if (_patches.contains(key)) {
126 _addsDeferred =
true;
131 data.maxHeight = maxHeightOf(data.heights);
132 _patches.insert(key, std::move(data));
133 churnRects.append(patchRect(key));
144 QVarLengthArray<QRectF, 16> missingRects;
146 if (!_patches.contains(key)) {
147 missingRects.append(patchRect(key));
151 const QRectF rect = patchRect(key);
152 for (
const QRectF& missing : missingRects) {
153 if (rect.intersects(missing)) {
164 _removalsThisPass = 0;
165 _removalsDeferred =
false;
166 for (
auto it = _patches.begin(); it != _patches.end();) {
167 if (desiredSet.contains(it.key())) {
172 _removalsDeferred =
true;
177 it = _patches.erase(it);
178 churnRects.append(patchRect(removedKey));
185 for (
auto it = _patches.cbegin(); it != _patches.cend(); ++it) {
186 for (
const QRectF& rect : churnRects) {
187 if (patchTouchesRegion(it.key(), rect)) {
197 if ((adds > 0) || (_removalsThisPass > 0)) {
198 QSet<TileMath::TileKey> pinned;
199 for (
auto it = _patches.cbegin(); it != _patches.cend(); ++it) {
214 if (_addsDeferred || _removalsDeferred) {
218 const qint64 elapsedUs = updateTimer.nsecsElapsed() / 1000;
219 qCDebug(GeoMapSurfaceModelVerboseLog)
220 <<
"update pass: desired" << desired.count() <<
"resident" << _patches.count() <<
"adds" << adds <<
"removals"
221 << _removalsThisPass <<
"deferred adds" << _addsDeferred <<
"deferred removals" << _removalsDeferred
222 <<
"elapsedUs" << elapsedUs;
224 _updateStats.
totalUs += elapsedUs;
225 _updateStats.
maxUs = std::max(_updateStats.
maxUs, elapsedUs);
238 result.reserve(_patches.count());
239 for (
auto it = _patches.cbegin(); it != _patches.cend(); ++it) {
240 result.append(
Patch{it.
key(), it.value().heights,
true,
false});
247 const auto it = _patches.constFind(key);
248 if (it == _patches.cend()) {
251 return Patch{key, it.value().heights,
true,
false};
257 static constexpr int kOffsets[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
260 for (
const auto& offset : kOffsets) {
261 deltas.append(_edgeDelta(key, offset[0], offset[1]));
277 if (_patches.contains(ancestor)) {
278 const int delta = key.
zoom - ancestor.
zoom;
280 return ((
kGridSize % (1 << delta)) == 0) ? delta : 0;
286QRectF SurfaceModel::_visibleGroundRect(
double terrainZ)
const
297 const double terrainT = ((terrainZ > 0.0) && (eyeZ > terrainZ)) ? (1.0 - (terrainZ / eyeZ)) : 0.0;
306 const auto include = [&](
const QPointF& point) {
308 minX = maxX = point.x();
309 minY = maxY = point.y();
312 minX = std::min(minX, point.x());
313 maxX = std::max(maxX, point.x());
314 minY = std::min(minY, point.y());
315 maxY = std::max(maxY, point.y());
327 if (terrainT > 0.0) {
328 include(cameraGround + ((*ground - cameraGround) * terrainT));
338 include(cameraGround);
341 return QRectF(QPointF(minX, minY), QPointF(maxX, maxY))
347double SurfaceModel::_maxTerrainZ()
const
349 float maxHeight = 0.0f;
350 for (
const PatchData& data : _patches) {
351 maxHeight = std::max(maxHeight, data.maxHeight);
353 if (maxHeight <= 0.0f) {
359double SurfaceModel::_projectedPixels(
const TileMath::TileKey& key,
const QPointF& cameraGround,
360 double cameraHeight)
const
362 const QRectF rect = patchRect(key);
363 const double span = rect.width();
366 const double dx = std::max({rect.left() - cameraGround.x(), cameraGround.x() - rect.right(), 0.0});
367 const double dy = std::max({rect.top() - cameraGround.y(), cameraGround.y() - rect.bottom(), 0.0});
368 const double distance = std::hypot(std::hypot(dx, dy), cameraHeight);
370 const double metersPerPixel =
371 (2.0 * distance * std::tan(qDegreesToRadians(_camera->
fieldOfView()) / 2.0)) / _camera->
viewportSize().height();
372 return span / metersPerPixel;
375QList<TileMath::TileKey> SurfaceModel::_desiredPatches(
const QRectF& visible,
const QPointF& cameraGround,
376 double cameraHeight)
const
388 const auto lessPixels = [](
const Entry& a,
const Entry& b) {
return a.pixels < b.pixels; };
389 std::priority_queue<Entry, std::vector<Entry>,
decltype(lessPixels)> queue(lessPixels);
391 QList<TileMath::TileKey> desired;
394 if (visible.intersects(patchRect(root))) {
395 queue.push(Entry{_projectedPixels(root, cameraGround, cameraHeight), root});
398 while (!queue.empty()) {
399 const Entry entry = queue.top();
405 desired.append(entry.key);
409 QVarLengthArray<Entry, 4> children;
410 for (
int childY = 0; childY < 2; childY++) {
411 for (
int childX = 0; childX < 2; childX++) {
412 const TileMath::TileKey childKey{(entry.key.x * 2) + childX, (entry.key.y * 2) + childY,
414 if (visible.intersects(patchRect(childKey))) {
415 children.append(Entry{_projectedPixels(childKey, cameraGround, cameraHeight), childKey});
419 const int totalAfterSplit =
static_cast<int>(queue.size()) + desired.count() - 1 + children.count();
424 for (
const Entry& child : children) {
429 while (!queue.empty()) {
430 desired.append(queue.top().key);
436void SurfaceModel::_fieldRegionChanged(
const QRectF& worldRect)
443 for (
auto it = _patches.begin(); it != _patches.end(); ++it) {
444 if (!patchTouchesRegion(it.key(), worldRect)) {
447 PatchData& data = it.value();
449 data.maxHeight = maxHeightOf(data.heights);
453 qCDebug(GeoMapSurfaceModelVerboseLog)
454 <<
"regionChanged" << worldRect <<
"re-meshed" << remeshed <<
"of" << _patches.count() <<
"patches";
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QSizeF viewportSize() const
void centerElevationChanged()
qreal fieldOfView() const
QPointF cameraGroundPosition() const
qreal centerElevation() const
void fieldOfViewChanged()
bool isPositioned() const
void viewportSizeChanged()
std::optional< QPointF > groundPointCapped(const QPointF &screenPos, double maxRange) const
QGeoCoordinate center() const
void setPinnedKeys(QSet< TileMath::TileKey > keys)
QList< float > samplePatch(const TileMath::TileKey &key, int gridSize) const
void regionChanged(const QRectF &worldRect)
virtual bool requestTile(const TileMath::TileKey &key)
static constexpr double kRecullHeightMargin
SurfaceModel(GeoMapCamera *camera, HeightSource *heightSource, HeightField *field, QObject *parent=nullptr)
static constexpr int kMaxPatchAddsPerUpdate
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
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 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)
double tileSpanAtZoom(int zoom)
Edge length of one tile at zoom, in world meters.
update() call/duration counters for the perf overlay