feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
2 changed files with 31 additions and 18 deletions
Showing only changes of commit 29ea44560d - Show all commits

View File

@ -38,6 +38,8 @@ constexpr int kRadius = 3; // 中心瓦片 ±3 → 最多 7x7=49 块
constexpr int kMinZoom = 3; constexpr int kMinZoom = 3;
constexpr int kMaxZoom = 18; constexpr int kMaxZoom = 18;
constexpr double kGroundZ = 0.0; // 底图置于 z=0 地面参考(剖面深度向下为负,落其下) 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 kPi = 3.14159265358979323846;
constexpr double kEarthCirc = 40075016.686; // 赤道周长(米) = z0 单瓦片地面尺寸 constexpr double kEarthCirc = 40075016.686; // 赤道周长(米) = z0 单瓦片地面尺寸
constexpr double kTilesAcross = 4.0; // 视野跨度目标覆盖瓦片数(决定层级) constexpr double kTilesAcross = 4.0; // 视野跨度目标覆盖瓦片数(决定层级)
@ -149,17 +151,7 @@ void TileBasemap::refresh() {
} }
} }
// 移除离开视野/换层级的瓦片。 // 拉取缺失瓦片(旧层暂不删,留作回退;新层落地后由 purgeStale 清理)。
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;
}
}
// 拉取缺失瓦片。
for (long long key : desired_) { for (long long key : desired_) {
if (placed_.count(key) || inFlight_.count(key)) continue; if (placed_.count(key) || inFlight_.count(key)) continue;
int z, x, y; int z, x, y;
@ -167,10 +159,28 @@ void TileBasemap::refresh() {
fetchTile(z, x, y, key); fetchTile(z, x, y, key);
} }
purgeStale(); // 若无需新拉(inFlight 空)则立即清理旧层
if (rw_) rw_->Render(); if (rw_) rw_->Render();
refreshing_ = false; refreshing_ = false;
} }
void TileBasemap::purgeStale() {
// 仅当本轮所有请求都落地(inFlight 空)后再删旧层;否则缩放/平移期间老瓦片留作回退,避免空白闪烁。
// 超过硬上限则强制清理兜底内存(可能短暂空白,极少触发)。
if (!inFlight_.empty() && placed_.size() <= static_cast<size_t>(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) { 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 layerDir = (kind_ == Satellite) ? QStringLiteral("img_w") : QStringLiteral("vec_w");
const QString layer = (kind_ == Satellite) ? QStringLiteral("img") : QStringLiteral("vec"); 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 (gen != generation_) return; // 换源/隐藏后丢弃
if (kind_ == Hidden) return; if (kind_ == Hidden) return;
if (desired_.find(key) == desired_.end()) return; // 已移出视野 if (desired_.find(key) == desired_.end()) return; // 已移出视野
if (placed_.count(key)) return;
if (reply->error() != QNetworkReply::NoError) return;
QImage img; QImage img;
if (!img.loadFromData(reply->readAll())) return; if (!placed_.count(key) && reply->error() == QNetworkReply::NoError &&
placeTile(key, z, x, y, img); 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 sw = frame_->toLocal(b.south, b.west);
const auto se = frame_->toLocal(b.south, b.east); const auto se = frame_->toLocal(b.south, b.east);
const auto nw = frame_->toLocal(b.north, b.west); const auto nw = frame_->toLocal(b.north, b.west);
const double gz = kGroundZ + (z - kMinZoom) * kZEps; // 高层级略抬高,压在旧层之上防共面闪烁
// QImage → vtkImageData(RGBA),垂直翻转:使纹理 v=0 对应瓦片南边(与下方 PlaneSource tcoord 一致)。 // QImage → vtkImageData(RGBA),垂直翻转:使纹理 v=0 对应瓦片南边(与下方 PlaneSource tcoord 一致)。
const QImage rgba = img.convertToFormat(QImage::Format_RGBA8888); 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 自动 tcoordorigin→(0,0)、point1→(1,0)、point2→(0,1)。 // PlaneSource 自动 tcoordorigin→(0,0)、point1→(1,0)、point2→(0,1)。
// origin=SW(西南)、point1=SE(东) → u 西→东point2=NW(北) → v 南→北。与翻转后纹理对齐。 // origin=SW(西南)、point1=SE(东) → u 西→东point2=NW(北) → v 南→北。与翻转后纹理对齐。
vtkNew<vtkPlaneSource> plane; vtkNew<vtkPlaneSource> plane;
plane->SetOrigin(sw.x, sw.y, kGroundZ); plane->SetOrigin(sw.x, sw.y, gz);
plane->SetPoint1(se.x, se.y, kGroundZ); plane->SetPoint1(se.x, se.y, gz);
plane->SetPoint2(nw.x, nw.y, kGroundZ); plane->SetPoint2(nw.x, nw.y, gz);
vtkNew<vtkPolyDataMapper> mapper; vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(plane->GetOutputPort()); mapper->SetInputConnection(plane->GetOutputPort());

View File

@ -37,6 +37,7 @@ public:
private: private:
static long long tileKey(int z, int x, int y); static long long tileKey(int z, int x, int y);
void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent
void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁
bool computeView(double& centerLat, double& centerLon, int& zoom) const; bool computeView(double& centerLat, double& centerLon, int& zoom) const;
void fetchTile(int z, int x, int y, long long key); 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); void placeTile(long long key, int z, int x, int y, const QImage& img);