From 29ea44560d4298c763b127c23c618cc8c009026e Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 17 Jun 2026 08:54:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(vtk):=20=E5=BA=95=E5=9B=BELOD=E6=B6=88?= =?UTF-8?q?=E9=99=A4=E7=BC=A9=E6=94=BE=E7=A9=BA=E7=99=BD=E9=97=AA=E7=83=81?= =?UTF-8?q?(=E5=BB=B6=E8=BF=9F=E6=B8=85=E7=90=86=E6=97=A7=E5=B1=82,?= =?UTF-8?q?=E8=90=BD=E5=9C=B0=E5=90=8E=E5=86=8D=E5=88=A0)+=E6=8C=89?= =?UTF-8?q?=E5=B1=82=E7=BA=A7Z=E5=81=8F=E7=A7=BB=E9=98=B2=E5=85=B1?= =?UTF-8?q?=E9=9D=A2z-fighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/TileBasemap.cpp | 48 +++++++++++++++++++++++++---------------- src/app/TileBasemap.hpp | 1 + 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp index 7af4d07..0218950 100644 --- a/src/app/TileBasemap.cpp +++ b/src/app/TileBasemap.cpp @@ -38,6 +38,8 @@ constexpr int kRadius = 3; // 中心瓦片 ±3 → 最多 7x7=49 块 constexpr int kMinZoom = 3; constexpr int kMaxZoom = 18; constexpr double kGroundZ = 0.0; // 底图置于 z=0 地面参考(剖面深度向下为负,落其下) +constexpr double kZEps = 0.02; // 每层级 Z 微偏移:高层级压上面,避免共面瓦片 z-fighting +constexpr int kHardCap = 400; // 瓦片硬上限:超过则即便未落地也强制清理,兜底内存 constexpr double kPi = 3.14159265358979323846; constexpr double kEarthCirc = 40075016.686; // 赤道周长(米) = z0 单瓦片地面尺寸 constexpr double kTilesAcross = 4.0; // 视野跨度目标覆盖瓦片数(决定层级) @@ -149,17 +151,7 @@ void TileBasemap::refresh() { } } - // 移除离开视野/换层级的瓦片。 - for (auto it = placed_.begin(); it != placed_.end();) { - if (desired_.find(it->first) == desired_.end()) { - scene_.renderer()->RemoveViewProp(it->second); - it = placed_.erase(it); - } else { - ++it; - } - } - - // 拉取缺失瓦片。 + // 拉取缺失瓦片(旧层暂不删,留作回退;新层落地后由 purgeStale 清理)。 for (long long key : desired_) { if (placed_.count(key) || inFlight_.count(key)) continue; int z, x, y; @@ -167,10 +159,28 @@ void TileBasemap::refresh() { fetchTile(z, x, y, key); } + purgeStale(); // 若无需新拉(inFlight 空)则立即清理旧层 if (rw_) rw_->Render(); refreshing_ = false; } +void TileBasemap::purgeStale() { + // 仅当本轮所有请求都落地(inFlight 空)后再删旧层;否则缩放/平移期间老瓦片留作回退,避免空白闪烁。 + // 超过硬上限则强制清理兜底内存(可能短暂空白,极少触发)。 + if (!inFlight_.empty() && placed_.size() <= static_cast(kHardCap)) return; + bool removed = false; + for (auto it = placed_.begin(); it != placed_.end();) { + if (desired_.find(it->first) == desired_.end()) { + scene_.renderer()->RemoveViewProp(it->second); + it = placed_.erase(it); + removed = true; + } else { + ++it; + } + } + if (removed && rw_) rw_->Render(); +} + void TileBasemap::fetchTile(int z, int x, int y, long long key) { const QString layerDir = (kind_ == Satellite) ? QStringLiteral("img_w") : QStringLiteral("vec_w"); const QString layer = (kind_ == Satellite) ? QStringLiteral("img") : QStringLiteral("vec"); @@ -195,11 +205,12 @@ void TileBasemap::fetchTile(int z, int x, int y, long long key) { if (gen != generation_) return; // 换源/隐藏后丢弃 if (kind_ == Hidden) return; if (desired_.find(key) == desired_.end()) return; // 已移出视野 - if (placed_.count(key)) return; - if (reply->error() != QNetworkReply::NoError) return; QImage img; - if (!img.loadFromData(reply->readAll())) return; - placeTile(key, z, x, y, img); + if (!placed_.count(key) && reply->error() == QNetworkReply::NoError && + img.loadFromData(reply->readAll())) { + placeTile(key, z, x, y, img); // 仅新瓦片落地;其余(失败/已存在)只推动清理 + } + purgeStale(); // 本块已解决 → 若整轮落地则清理被顶替的旧层 }); } @@ -208,6 +219,7 @@ void TileBasemap::placeTile(long long key, int z, int x, int y, const QImage& im const auto sw = frame_->toLocal(b.south, b.west); const auto se = frame_->toLocal(b.south, b.east); const auto nw = frame_->toLocal(b.north, b.west); + const double gz = kGroundZ + (z - kMinZoom) * kZEps; // 高层级略抬高,压在旧层之上防共面闪烁 // QImage → vtkImageData(RGBA),垂直翻转:使纹理 v=0 对应瓦片南边(与下方 PlaneSource tcoord 一致)。 const QImage rgba = img.convertToFormat(QImage::Format_RGBA8888); @@ -228,9 +240,9 @@ void TileBasemap::placeTile(long long key, int z, int x, int y, const QImage& im // PlaneSource 自动 tcoord:origin→(0,0)、point1→(1,0)、point2→(0,1)。 // origin=SW(西南)、point1=SE(东) → u 西→东;point2=NW(北) → v 南→北。与翻转后纹理对齐。 vtkNew plane; - plane->SetOrigin(sw.x, sw.y, kGroundZ); - plane->SetPoint1(se.x, se.y, kGroundZ); - plane->SetPoint2(nw.x, nw.y, kGroundZ); + plane->SetOrigin(sw.x, sw.y, gz); + plane->SetPoint1(se.x, se.y, gz); + plane->SetPoint2(nw.x, nw.y, gz); vtkNew mapper; mapper->SetInputConnection(plane->GetOutputPort()); diff --git a/src/app/TileBasemap.hpp b/src/app/TileBasemap.hpp index b0cdf83..b4f23b1 100644 --- a/src/app/TileBasemap.hpp +++ b/src/app/TileBasemap.hpp @@ -37,6 +37,7 @@ public: private: static long long tileKey(int z, int x, int y); void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent + void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁 bool computeView(double& centerLat, double& centerLon, int& zoom) const; void fetchTile(int z, int x, int y, long long key); void placeTile(long long key, int z, int x, int y, const QImage& img);