diff --git a/src/app/TileBasemap.cpp b/src/app/TileBasemap.cpp index c907200..52c99db 100644 --- a/src/app/TileBasemap.cpp +++ b/src/app/TileBasemap.cpp @@ -218,6 +218,16 @@ void TileBasemap::setOpacity(double o) { if (kind_ != Hidden) show(kind_); // 重建套用新透明度 } +void TileBasemap::setGroundZ(double z) { + // 直接平移平面高程:瓦片几何建于相对 z(仅含逐层级 z-fighting 偏移),平面高程 groundZ_ 经 actor position 施加。 + // 拖 z 值滑块时只改所有已贴瓦片的 SetPosition,无需重下载/重建;后续 refresh() 新瓦片经 placeActor 自动取新 groundZ_。 + if (z == groundZ_) return; + groundZ_ = z; + for (auto& kv : placed_) + if (kv.second) kv.second->SetPosition(0.0, 0.0, groundZ_); + requestRender(); +} + void TileBasemap::refineTile(int z, int x, int y, std::set& out, int& count) { if (count >= kMaxLeaves) { out.insert(tileKey(z, x, y)); return; } // 安全上限:停止细分 const int n = 1 << z; @@ -439,6 +449,7 @@ void TileBasemap::fetchTile(int z, int x, int y, long long key) { void TileBasemap::placeActor(long long key, vtkSmartPointer actor) { if (!actor) return; + actor->SetPosition(0.0, 0.0, groundZ_); // 平面高程经 position 施加:几何建于相对 z,此处抬到当前 groundZ_ scene_.addActor(actor); placed_[key] = actor; } @@ -449,7 +460,7 @@ vtkSmartPointer TileBasemap::buildFlat(int z, int x, int y, 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 = groundZ_ + (z - kMinZoom) * kZEps; // 高层级略抬高,压在旧层之上防共面闪烁 + const double gz = (z - kMinZoom) * kZEps; // 仅逐层级 z-fighting 偏移(相对 z);平面高程由 actor position 施加 // PlaneSource 自动 tcoord:origin=SW→u 西0东1、v 南0北1(与翻转后纹理对齐)。 vtkNew plane; @@ -538,7 +549,7 @@ vtkSmartPointer TileBasemap::buildWarped(int sz, int sx, int sy, int d const auto sw = frame_->toLocal(sb.south, sb.west); const auto se = frame_->toLocal(sb.south, sb.east); const auto nw = frame_->toLocal(sb.north, sb.west); - const double base = groundZ_ + (sz - kMinZoom) * kZEps; + const double base = (sz - kMinZoom) * kZEps; // 仅逐层级 z-fighting 偏移(相对 z);平面高程由 actor position 施加 // PlaneSource(等距圆柱下平面插值即正确 x/y) + 自动 tcoord;再按各点真实经纬采 DEM 位移 Z。 vtkNew plane; diff --git a/src/app/TileBasemap.hpp b/src/app/TileBasemap.hpp index 5474b2c..a9119e4 100644 --- a/src/app/TileBasemap.hpp +++ b/src/app/TileBasemap.hpp @@ -42,6 +42,7 @@ public: void refresh(); // 按当前相机重算层级+覆盖,增量更新瓦片(交互结束回调) void setVerticalExaggeration(double ve); // 地形垂向夸张(须与剖面 VE 一致才对齐) void setOpacity(double o); // 底图半透明度[0,1],供渲染工具栏底图弹窗调节 + void setGroundZ(double z); // 直接平移底图平面 z(拖 z 值滑块):改所有已贴瓦片 actor 的 position,无重铺 // 数据半径提供者:刷新时查询当前所有勾选剖面的合并范围(半径,米),据此动态定底图最大范围。 void setDataRadiusProvider(std::function fn) { dataRadiusProvider_ = std::move(fn); } diff --git a/src/app/TileBasemapPlaneAdapter.hpp b/src/app/TileBasemapPlaneAdapter.hpp index 3756c76..16bd859 100644 --- a/src/app/TileBasemapPlaneAdapter.hpp +++ b/src/app/TileBasemapPlaneAdapter.hpp @@ -28,6 +28,7 @@ public: } void hide() override { bm_.hide(); } void setOpacity(double o) override { bm_.setOpacity(o); } + void setGroundZ(double z) override { bm_.setGroundZ(z); } private: TileBasemap bm_; diff --git a/src/app/VtkSceneView.cpp b/src/app/VtkSceneView.cpp index 426c68c..b0631ad 100644 --- a/src/app/VtkSceneView.cpp +++ b/src/app/VtkSceneView.cpp @@ -253,14 +253,29 @@ void VtkSceneView::addMapLine(const std::string& dsId, const geopro::data::MapLi // worldZ 已是最终世界高程(含摆放语义),不再施加 VE(足迹是水平线,非随深度的竖直图元)。 // 足迹可能是首个(且唯一)带经纬的数据 → 与帘面同样重锚原点,否则按样本默认原点投到数百公里外不可见。 anchorFrameIfNeeded(line.lat, line.lon, static_cast(line.lat.size())); - auto actor = geopro::render::buildMapLine(line.lat, line.lon, worldZ, *frame_); + // 折线几何建于 Z=0,平面高程 worldZ 经 actor SetPosition 施加 → 后续拖 z 值滑块只改 position 即直接平移, + // 无需移除+异步重载几何(setMapLinesZ 走此)。首勾/后续勾选在当前平面 z 加入者立即摆到该 z。 + auto actor = geopro::render::buildMapLine(line.lat, line.lon, 0.0, *frame_); if (actor) { + actor->SetPosition(0.0, 0.0, worldZ); scene_.addActor(actor); dsProps_[dsId].push_back(actor); mapLineDs_.insert(dsId); // 记录此 ds 为 2D 足迹(供足迹归属识别) } } +void VtkSceneView::setMapLinesZ(const std::vector& dsIds, double z) { + // 直接平移足迹:仅对属于足迹的 dsId 改其 actor 的 SetPosition(0,0,z),即时渲染,无移除+重载。 + for (const auto& dsId : dsIds) { + if (!mapLineDs_.count(dsId)) continue; + auto it = dsProps_.find(dsId); + if (it == dsProps_.end()) continue; + for (auto& prop : it->second) + if (auto* a = vtkActor::SafeDownCast(prop)) a->SetPosition(0.0, 0.0, z); + } + if (renderWindow_) renderWindow_->Render(); +} + void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) { auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_, verticalExaggeration_); diff --git a/src/app/VtkSceneView.hpp b/src/app/VtkSceneView.hpp index f57fee3..686814d 100644 --- a/src/app/VtkSceneView.hpp +++ b/src/app/VtkSceneView.hpp @@ -46,6 +46,7 @@ public: const geopro::core::ColorScale& cs) override; void addMapLine(const std::string& dsId, const geopro::data::MapLine& line, double worldZ) override; + void setMapLinesZ(const std::vector& dsIds, double z) override; void addTerrain(const geopro::data::TerrainPaths& paths) override; void removeDataset(const std::string& dsId) override; void addAnomaly(const geopro::core::Anomaly& a) override; diff --git a/src/app/panels/columns/CategorySection.cpp b/src/app/panels/columns/CategorySection.cpp index 03f172d..9ce60b8 100644 --- a/src/app/panels/columns/CategorySection.cpp +++ b/src/app/panels/columns/CategorySection.cpp @@ -484,23 +484,12 @@ void CategorySection::showPlaneZPopup(QToolButton* host) { sld->setToolTip(QStringLiteral("平面高程 z(米)")); auto syncLabel = [lab](int v) { lab->setText(QStringLiteral("平面 z:%1 米").arg(v)); }; syncLabel(sld->value()); - // 发射防抖:valueChanged 在拖动期逐整数步触发,每步直发 planeZChanged 会引发数十次 - // removeDataset+add2DDatasetAsync 抖动重摆(生产异步路径 loadingDs_ 守护会丢中间帧→足迹滞后于陈旧 z)。 - // 故 label/lastPlaneZ_ 即时回显,但 planeZChanged 经单发 QTimer(150ms)合并——每次变更重启,停手后只发一次终值。 - // 定时器 parent=this(CategorySection),存活于 modal popup(menu.exec)之外,即便 popup 已销毁也能安全发射终值。 - if (!planeZTimer_) { - planeZTimer_ = new QTimer(this); - planeZTimer_->setSingleShot(true); - planeZTimer_->setInterval(150); - connect(planeZTimer_, &QTimer::timeout, this, [this]() { - emit planeZChanged(QString::fromStdString(desc_.id), pendingPlaneZ_); - }); - } + // 直接平移平面/足迹/底图(改 actor position,即时同步)→ 无需防抖:valueChanged 每步直发 planeZChanged, + // 拖动即实时跟随、无移除+异步重载。lastPlaneZ_ 记住终值供重开 popup 回显。 connect(sld, &QSlider::valueChanged, this, [this, syncLabel](int v) { lastPlaneZ_ = v; syncLabel(v); - pendingPlaneZ_ = static_cast(v); - planeZTimer_->start(); // 重启防抖窗口:覆盖拖动、键盘、点轨——停手后一次性发射 + emit planeZChanged(QString::fromStdString(desc_.id), static_cast(v)); }); lay->addWidget(lab); lay->addWidget(sld); diff --git a/src/app/panels/columns/CategorySection.hpp b/src/app/panels/columns/CategorySection.hpp index c34da64..089e038 100644 --- a/src/app/panels/columns/CategorySection.hpp +++ b/src/app/panels/columns/CategorySection.hpp @@ -93,12 +93,10 @@ private: QTreeWidget* list_ = nullptr; QTimer* spinTimer_ = nullptr; // 驱动 busy 行 spinner 旋转(有 busy 行时运行) int spinAngle_ = 0; // 当前 spinner 角度(度) - double lastPlaneZ_ = 0.0; // 上次 z 值滑块设定的平面高程(重开 popup 时回显,无则 0) - QTimer* planeZTimer_ = nullptr; // z 值滑块发射防抖:拖动/键盘/点轨期合并为停手后一次重摆(owned by this,存活于 popup 之外) - double pendingPlaneZ_ = 0.0; // 防抖待发的平面 z(定时器到点时取此值发射 planeZChanged) + double lastPlaneZ_ = 0.0; // 上次 z 值滑块设定的平面高程(重开 popup 时回显,无则 0;直接平移故无防抖) int lastBasemapKind_ = 0; // 上次底图选择(0=矢量平面/1=无),重开 popup 时回显 int lastBasemapOpacity_ = 50; // 上次底图透明度(0–100,重开 popup 回显;默认 50) - QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(同 planeZ:停手后一次发射,免抖动重铺瓦片) + QTimer* basemapOpacityTimer_ = nullptr; // 底图透明度滑块发射防抖(透明度改动会触发瓦片重铺,仍需防抖) double pendingBasemapOpacity_ = 0.5; // 防抖待发的底图透明度[0,1](定时器到点发射 basemapOpacityChanged) }; diff --git a/src/controller/DatasetRenderStrategy.cpp b/src/controller/DatasetRenderStrategy.cpp index adcd044..88ce6c8 100644 --- a/src/controller/DatasetRenderStrategy.cpp +++ b/src/controller/DatasetRenderStrategy.cpp @@ -1,5 +1,8 @@ #include "controller/DatasetRenderStrategy.hpp" +#include +#include + #include "controller/VtkSceneController.hpp" // 完整类型 + 含 I3dSceneView(view_.removeDataset) namespace geopro::controller { @@ -52,15 +55,14 @@ void Plane2DRenderStrategy::onTypeDeactivated(const std::string& typeId) { void Plane2DRenderStrategy::setPlaneZ(const std::string& typeId, double z) { planeReg_.setPlaneZ(typeId, z); // 平面 z 真源更新(类型不存在则无操作) - // 重摆该类型全部已勾选足迹:移除旧 actor,按既有 add 路径在新 z 重新加载摆放。 - // add2DDatasetAsync 复用 rebuildGeneration_ + loadingDs_/is2DChecked 防迟到/防重复(与 add 同护栏)。 - for (const auto& [dsId, t] : dsToType_) { - if (t != typeId) continue; - ctrl_.view_.removeDataset(dsId); - ctrl_.add2DDatasetAsync(dsId, ctrl_.rebuildGeneration_, z); - } - // 底图同步升降:销毁旧实例、按新平面 z 重建(复用当前 kind/opacity),简单可靠(无 setGroundZ 增量重铺)。 - if (bms_.count(typeId)) createBasemap(typeId); + // 直接平移该类型全部已勾选足迹:只改足迹 actor 的 position(无移除+异步重载)→ 拖滑块即时跟随、无闪烁。 + std::vector dsIds; + for (const auto& [dsId, t] : dsToType_) + if (t == typeId) dsIds.push_back(dsId); + if (!dsIds.empty()) ctrl_.view_.setMapLinesZ(dsIds, z); + // 底图同步平移:直接改瓦片 position(无销毁+重建、无重下载)→ 底图与足迹一同实时跟随滑块。 + auto it = bms_.find(typeId); + if (it != bms_.end()) it->second->setGroundZ(z); ctrl_.view_.renderIncremental(); } diff --git a/src/controller/DatasetRenderStrategy.hpp b/src/controller/DatasetRenderStrategy.hpp index f11532c..c2a2735 100644 --- a/src/controller/DatasetRenderStrategy.hpp +++ b/src/controller/DatasetRenderStrategy.hpp @@ -19,6 +19,7 @@ public: virtual void show(int kind) = 0; // 0=矢量平面(Street)/其它=无(hide) virtual void hide() = 0; virtual void setOpacity(double o) = 0; // 半透明度[0,1] + virtual void setGroundZ(double z) = 0; // 直接平移平面高程 z(拖 z 值滑块):改瓦片 position,无重铺 }; // 工厂:按平面 z 造一份平面底图(底图所需 scene/渲染窗/frame/数据半径规则由 app 闭包捕获)。 // 未注入(空)则不建底图——便于无 VTK 的纯逻辑单测。 diff --git a/src/controller/I3dSceneView.hpp b/src/controller/I3dSceneView.hpp index 06e3118..9b2c458 100644 --- a/src/controller/I3dSceneView.hpp +++ b/src/controller/I3dSceneView.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include "model/ColorScale.hpp" #include "model/Field.hpp" @@ -51,6 +52,9 @@ public: // 2D 足迹:把测线/轨迹经纬折线平铺进 3D 地图(worldZ=摆放高程);按 dsId 跟踪以支持增量移除。 virtual void addMapLine(const std::string& dsId, const geopro::data::MapLine& line, double worldZ) = 0; + // 直接平移一组 2D 足迹到新平面 z(拖 z 值滑块用):改足迹 actor 的 SetPosition,无移除+异步重载。 + // 仅对属于足迹的 dsId 生效;即时渲染。默认空实现,测试 mock 无需覆盖。 + virtual void setMapLinesZ(const std::vector& /*dsIds*/, double /*z*/) {} // 3D:DEM 地形 + 影像纹理。 virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0; // 增量移除某数据集的全部图元(取消勾选时调,不影响其余 ds 与底图)。