From c3f72fdc8d3c0ed900bd195f318102cc2fae1e56 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Wed, 17 Jun 2026 16:00:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(vtk):=20A-=E5=A4=8D=E5=88=BB=E5=8E=9F?= =?UTF-8?q?=E7=89=88=E5=9E=82=E7=9B=B4=E9=85=8D=E5=87=86.=E5=89=96?= =?UTF-8?q?=E9=9D=A2z=E7=94=A8=E7=9C=9F=E5=AE=9E=E9=AB=98=E7=A8=8B+g.y(?= =?UTF-8?q?=E5=8E=9F=E7=89=88data.y=3D=E9=AB=98=E7=A8=8B,=E9=9D=9E?= =?UTF-8?q?=E6=B7=B1=E5=BA=A6);=E5=9C=B0=E5=BD=A2=E6=94=B9=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E9=AB=98=E7=A8=8B(=E5=8E=BB=E5=9F=BA=E5=87=86?= =?UTF-8?q?=E5=87=8F=E6=B3=95)x=E5=90=8C=E4=B8=80VE;=E5=89=96=E9=9D=A2?= =?UTF-8?q?=E4=B8=8E=E5=9C=B0=E5=BD=A2=E5=90=8C=E7=B3=BB=E5=AF=B9=E9=BD=90?= =?UTF-8?q?,=E5=89=96=E9=9D=A2=E9=A1=B6=E2=89=88=E5=9C=B0=E8=A1=A8?= =?UTF-8?q?=E9=9C=B2=E5=87=BA=E5=9C=B0=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/TileBasemap.cpp | 53 ++++-------------------------- src/app/TileBasemap.hpp | 6 ++-- src/app/main.cpp | 3 ++ src/render/actors/CurtainActor.cpp | 5 +-- 4 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp index 8b2c8fa..b661209 100644 --- a/src/app/TileBasemap.cpp +++ b/src/app/TileBasemap.cpp @@ -58,7 +58,6 @@ const char* kMapboxToken = "pk.eyJ1IjoidGJ1c2FuIiwiYSI6ImNtZjY2emZneDBkY24ybXB4cmpvdmwzNWYifQ.h6tcQ380WN5AW6fZr08how"; constexpr int kDemMaxZoom = 15; constexpr int kTerrainGrid = 32; // 每瓦片网格分辨率(33x33 顶点) -constexpr double kTerrainExag = 1.0; // 地形垂向夸张(1=真实高程) // key 打包:z<<44 | x<<22 | y(z≤18, x/y<2^18 < 2^22)。 void unpackKey(long long key, int& z, int& x, int& y) { @@ -175,43 +174,19 @@ void TileBasemap::show(Kind kind) { netQueue_.clear(); // 丢弃换源前排队中的请求(在途的按 gen 自然作废) desired_.clear(); // demCache_/texCache_ 跨隐藏-重选保留 → 重选地图秒出,不重拉。 - haveBaseline_ = false; // 数据可能已换位置 → 重算基准高程 terrainProbed_ = false; kind_ = kind; if (kind == Hidden) { requestRender(); return; } - primeBaselineThenRefresh(); // 先定基准高程再铺瓦片:避免先到的粗块用错基准把地形整体抬高 + refresh(); // 四叉树覆盖:近细远粗一次铺满(地形按真实高程,与剖面同系) } -void TileBasemap::primeBaselineThenRefresh() { - if (haveBaseline_ || kind_ != Satellite) { refresh(); return; } - // 先拉"含数据中心"的 DEM 块定基准(z15),确保任何瓦片 warp 前基准已就位、全场一致。 - const auto c = frame_->toLatLon(0.0, 0.0); - const int z = kDemMaxZoom; - const geopro::render::TileXY t = geopro::render::lonLatToTile(c.lon, c.lat, z); - const long long demKey = tileKey(z, t.x, t.y); - auto cached = demCache_.find(demKey); - if (cached != demCache_.end()) { - ensureBaseline(z, t.x, t.y, cached->second); - refresh(); - return; - } - const int gen = generation_; - const QString url = - QStringLiteral("https://api.mapbox.com/v4/mapbox.terrain-rgb/%1/%2/%3.pngraw?access_token=%4") - .arg(z).arg(t.x).arg(t.y).arg(QString::fromLatin1(kMapboxToken)); - enqueueGet(url, [this, z, t, demKey, gen](QNetworkReply* r) { - r->deleteLater(); - if (gen != generation_) return; - QImage dem; - if (r->error() == QNetworkReply::NoError && dem.loadFromData(r->readAll())) { - demCache_[demKey] = dem; - ensureBaseline(z, t.x, t.y, dem); - } - refresh(); // 成功→基准就位;失败→baseline=0 兜底,但仍全场一致 - }); +void TileBasemap::setVerticalExaggeration(double ve) { + if (ve <= 0.0 || ve == ve_) return; + ve_ = ve; + if (kind_ != Hidden) show(kind_); // 重建地形(高程×新VE),与剖面 VE 保持一致 } void TileBasemap::refineTile(int z, int x, int y, std::set& out, int& count) { @@ -392,21 +367,6 @@ void TileBasemap::placeActor(long long key, vtkSmartPointer actor) { placed_[key] = actor; } -void TileBasemap::ensureBaseline(int z, int x, int y, const QImage& dem) { - if (haveBaseline_) return; - // 基准必须锚"数据中心"的地面高程(确定性):只有含数据中心的块来定,按中心点采样。 - // 否则四叉树里随便哪块先到都定基准 → 地形整体偏移 → 三维剖面相对地面忽上忽下。 - const auto c = frame_->toLatLon(0.0, 0.0); // 数据中心经纬 - const geopro::render::TileXY ct = geopro::render::lonLatToTile(c.lon, c.lat, z); - if (ct.x != x || ct.y != y) return; // 此块不含数据中心 → 不用它定基准 - const geopro::render::LonLatBox b = geopro::render::tileBounds(z, x, y); - const double fx = (c.lon - b.west) / (b.east - b.west); // 数据中心在块内的列比例 - const double fy = (b.north - c.lat) / (b.north - b.south); // 行比例(顶=北) - baseline_ = demElev(dem, fx, fy); - haveBaseline_ = true; - qInfo() << "[basemap] 地形启用 baseline(数据中心)=" << baseline_ << "m z=" << z; -} - vtkSmartPointer TileBasemap::buildFlat(int z, int x, int y, vtkSmartPointer tex) { const geopro::render::LonLatBox b = geopro::render::tileBounds(z, x, y); @@ -441,7 +401,6 @@ void TileBasemap::fetchTerrain(int z, int x, int y, long long key, vtkSmartPoint // 落地一块瓦片:DEM 有效→起伏,否则→平面兜底;并推进 inFlight/清理。 auto place = [this, key, z, x, y, dz, dx, dy, tex](const QImage* dem) { if (dem && !dem->isNull()) { - ensureBaseline(dz, dx, dy, *dem); // 用含数据中心的 DEM 块定基准(确定性) placeActor(key, buildWarped(z, x, y, dz, dx, dy, tex, *dem)); } else { placeActor(key, buildFlat(z, x, y, tex)); // DEM 拉不到 → 平面兜底 @@ -529,7 +488,7 @@ vtkSmartPointer TileBasemap::buildWarped(int sz, int sx, int sy, int d const double elev = demElev(dem, fx, fy); double p[3]; pts->GetPoint(id, p); - p[2] = base + (elev - baseline_) * kTerrainExag; + p[2] = base + elev * ve_; // 真实高程×垂直夸张:与剖面(同样真实高程×VE)同系对齐 pts->SetPoint(id, p); } pts->Modified(); diff --git a/src/app/TileBasemap.hpp b/src/app/TileBasemap.hpp index 1a69278..4e738f0 100644 --- a/src/app/TileBasemap.hpp +++ b/src/app/TileBasemap.hpp @@ -38,12 +38,12 @@ public: void show(Kind kind); // 显示某底图(Hidden 等同 hide);记住类型供 LOD 刷新复用 void hide(); // 移除全部瓦片 void refresh(); // 按当前相机重算层级+覆盖,增量更新瓦片(交互结束回调) + void setVerticalExaggeration(double ve); // 地形垂向夸张(须与剖面 VE 一致才对齐) private: static long long tileKey(int z, int x, int y); void ensureObserver(); // 首次显示时挂到交互样式的 EndInteractionEvent void requestRender(); // 合并渲染:同一事件循环轮次多次请求只渲染一帧 - void primeBaselineThenRefresh(); // 先定数据中心基准高程,再铺四叉树(保证所有瓦片同一基准) void purgeStale(); // 本轮请求全部落地后再删旧层瓦片,避免缩放空白闪烁 // 四叉树细分:按瓦片投影屏幕尺寸递归(近细远粗),收集叶瓦片到 out。 void refineTile(int z, int x, int y, std::set& out, int& count); @@ -51,7 +51,6 @@ private: void fetchTerrain(int z, int x, int y, long long key, vtkSmartPointer tex); // 拉覆盖该瓦片的 DEM(z>15 取祖先块)后落地 void placeActor(long long key, vtkSmartPointer actor); - void ensureBaseline(int z, int x, int y, const QImage& dem); // 用含数据中心的块定基准(确定性) vtkSmartPointer buildFlat(int z, int x, int y, vtkSmartPointer tex); // 平面瓦片(DEM 兜底) vtkSmartPointer buildWarped(int sz, int sx, int sy, int dz, int dx, int dy, @@ -73,6 +72,7 @@ private: std::set inFlight_; // 在途瓦片(续到起伏/平面最终落地) std::map demCache_; // DEM 块缓存(key=DEMz/x/y),跨隐藏/重选复用 std::map> texCache_; // 影像纹理缓存,重选/缩放回看免重拉 + double ve_ = 2.0; // 地形垂向夸张(与剖面 verticalExaggeration 一致才对齐) // 四叉树当前帧相机参数(refresh 写, refineTile 读):相机位置 + 投影系数 + 视锥 6 面。 double camX_ = 0, camY_ = 0, camZ_ = 0; double projK_ = 1.0; @@ -81,8 +81,6 @@ private: struct PendingGet { QString url; std::function cb; }; std::deque netQueue_; // 限并发请求队列(防瓦片暴发饱和卡死) int netInFlight_ = 0; - double baseline_ = 0.0; // 基准高程(首块中心),地形绕 z=0 起伏 - bool haveBaseline_ = false; bool terrainProbed_ = false; // 首次 fetchTerrain 打一行诊断日志 vtkSmartPointer styleObs_; // 持引用保证回调期有效 vtkSmartPointer observer_; diff --git a/src/app/main.cpp b/src/app/main.cpp index 75345f2..a0d4e14 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -414,6 +414,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re }; // 相机程序化变化(取景/预设/缩放)后,底图按新视锥重算覆盖(治首帧部分瓦片需手动微动才出)。 sceneView->onCameraChanged = [basemap]() { basemap->refresh(); }; + // 垂直夸张:地形须与剖面用同一 VE 才对齐(都按真实高程×VE)。 + QObject::connect(drawer->col3D(), &geopro::app::Column3DDataset::verticalExaggerationChanged, + basemap, [basemap](double ve) { basemap->setVerticalExaggeration(ve); }); // ── 中央“空状态”引导浮层:未接入真实 sections 时,引导首次使用者从左侧入手。── // 透明背景 + 鼠标穿透(不挡 QVTK 交互);CenterOverlay 随视口尺寸保持居中; diff --git a/src/render/actors/CurtainActor.cpp b/src/render/actors/CurtainActor.cpp index 5571186..82467e0 100644 --- a/src/render/actors/CurtainActor.cpp +++ b/src/render/actors/CurtainActor.cpp @@ -66,8 +66,9 @@ vtkSmartPointer buildCurtain(const geopro::core::Grid& g, py = 0.0; } const vtkIdType id = static_cast(j) * nx + i; - // g.y 是深度(越大越深);VTK Z 向上 → 取负,使深部在下、浅部在上(剖面不倒置)。 - points->SetPoint(id, px, py, -g.y[j]); + // g.y 是真实高程(米,越大越高,与原版 web data.y 同义):直接作世界 Z,使剖面落在 + // 真实海拔上,与同样按真实高程渲染的地形对齐(剖面顶≈地表→露出地面,复刻原版)。 + points->SetPoint(id, px, py, g.y[j]); const double val = g.valueAt(i, j); if (std::isfinite(val)) { sc->SetValue(id, val);