#include "VtkSceneController.hpp" #include #include #include #include #include "I3dSceneView.hpp" #include "repo/IDatasetRepository.hpp" namespace geopro::controller { namespace { // 二维足迹「顶部/底部」摆放相对参考高程(Z=0)的偏移(米):控制器无地形/参考高程源 // (地形异步、帘面经纬未必到场),故退化为 Z=0 上/下固定偏移,使足迹不与帘面顶/底面重叠遮挡。 constexpr double kTopOffsetZ = 50.0; // 顶部:参考面上方 constexpr double kBottomOffsetZ = -50.0; // 底部:参考面下方 } // namespace VtkSceneController::VtkSceneController(data::IDatasetRepository& dsRepo, data::I3dSceneRepository& sceneRepo, I3dSceneView& view, QObject* parent) : QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {} void VtkSceneController::setCheckedDatasets(const QStringList& dsIds) { std::vector newDs; newDs.reserve(static_cast(dsIds.size())); for (const QString& id : dsIds) newDs.push_back(id.toStdString()); // 2D 俯视测线:保持全量重建(测线非按 ds 跟踪移除)。 if (mode_ == ViewMode::Map2D) { checkedDs_ = std::move(newDs); rebuildInternal(); return; } // 3D:增量 diff —— 只处理新增/移除,不全量重建(底图、其余 ds、相机均不动)。 const std::set oldSet(checkedDs_.begin(), checkedDs_.end()); const std::set newSet(newDs.begin(), newDs.end()); const bool wasEmpty = checkedDs_.empty(); for (const auto& id : checkedDs_) if (!newSet.count(id)) view_.removeDataset(id); // 移除:旧有新无 → 仅删该 ds 图元 checkedDs_ = std::move(newDs); fitOnArrival_ = wasEmpty; // 仅从空开始时让到场数据自动取景;增量追加保持当前相机不跳 const unsigned long long gen = rebuildGeneration_; // 不自增:并发增量互不作废 for (const auto& id : checkedDs_) if (!oldSet.count(id)) addDatasetAsync(id, gen); // 新增:新有旧无 → 异步取数增量入场 view_.renderIncremental(); // 立即反映移除 / 触发坐标轴重算 } void VtkSceneController::setChecked2DDatasets(const QStringList& dsIds) { std::vector newDs; newDs.reserve(static_cast(dsIds.size())); for (const QString& id : dsIds) newDs.push_back(id.toStdString()); // 二维足迹始终画进 View3D 场景,且按 dsId 跟踪 → 一律增量 diff(不全量重建,不打断 3D 帘面/体)。 const std::set oldSet(checked2dDs_.begin(), checked2dDs_.end()); const std::set newSet(newDs.begin(), newDs.end()); // 此前空场景(无 3D 数据且无 2D 足迹) → 首批足迹到场自动取景;否则增量追加保持相机不跳。 const bool wasEmpty = checkedDs_.empty() && checked2dDs_.empty(); for (const auto& id : checked2dDs_) if (!newSet.count(id)) view_.removeDataset(id); // 取消勾选 → 移除该足迹图元 checked2dDs_ = std::move(newDs); fitOnArrival_ = wasEmpty; // 首批足迹(空场景)取景;否则保持当前相机不跳 // 足迹画进 View3D 场景;mode=0 关闭 → 仅记录勾选不渲染(见 set2DPlacement 切回时补画)。 if (placement2dMode_ != 0 && mode_ == ViewMode::View3D) { const unsigned long long gen = rebuildGeneration_; // 不自增:与 3D 增量互不作废 for (const auto& id : checked2dDs_) if (!oldSet.count(id)) add2DDatasetAsync(id, gen); // 新增 → 异步取足迹增量入场 } view_.renderIncremental(); // 立即反映移除 } void VtkSceneController::set2DPlacement(int mode, double customZ) { const bool changed = (mode != placement2dMode_) || (mode == 4 && customZ != customZ2d_); placement2dMode_ = mode; customZ2d_ = customZ; if (!changed || checked2dDs_.empty()) return; // 摆放变化 → 对已勾选足迹重摆:先全部移除,再按新 Z 重加(mode=0 关闭则只移除不重加)。 for (const auto& id : checked2dDs_) view_.removeDataset(id); if (placement2dMode_ != 0 && mode_ == ViewMode::View3D) { const unsigned long long gen = rebuildGeneration_; fitOnArrival_ = false; // 重摆:保持相机 for (const auto& id : checked2dDs_) add2DDatasetAsync(id, gen); } view_.renderIncremental(); } double VtkSceneController::placementZ() const { const double surf = view_.zRefElev(); // 真实地表高程基准(测线地表高程) switch (placement2dMode_) { case 1: return 0.0; // Z=0(世界原点) case 2: return surf + kTopOffsetZ; // 顶部:贴真实地表上方 case 3: return surf + kBottomOffsetZ; // 底部:真实地表下方 case 4: return customZ2d_; // 自定义 default: return 0.0; // 关闭(0) 不应走到此(调用方拦截) } } void VtkSceneController::add2DDatasetAsync(const std::string& dsId, unsigned long long gen) { if (loadingDs_.count(dsId)) return; // 已在加载(重复勾选竞态)→ 不重复请求 loadingDs_.insert(dsId); QPointer self(this); sceneRepo_.loadMapLine( dsId, [self, gen, dsId](data::MapLine line) { if (!self) return; self->loadingDs_.erase(dsId); // gen 作废 / 已取消勾选 / 摆放已关闭 → 丢弃迟到回调。 if (gen != self->rebuildGeneration_ || !self->is2DChecked(dsId) || self->placement2dMode_ == 0) { return; } // 落地时按当前摆放 Z(非请求时快照)→ 加载期间摆放变化也取最新高程。 self->view_.addMapLine(dsId, line, self->placementZ()); self->onDatasetArrived(); }, [self, gen, dsId](const std::string& m) { if (!self) return; self->loadingDs_.erase(dsId); if (gen != self->rebuildGeneration_) return; emit self->loadFailed(QString::fromStdString(m)); }); } void VtkSceneController::addDatasetAsync(const std::string& dsId, unsigned long long gen) { if (loadingDs_.count(dsId)) return; // 已在加载(重复勾选竞态)→ 不重复请求 QPointer self(this); // 按数据集类型分流(取代旧全局 showCurtain_/showVoxel_ 开关): // 三维体(dd_voxel,客户端创建)→ 体素渲染;其余剖面(dd_section 等)→ 帘面渲染。 if (sceneRepo_.isVolumeDataset(dsId)) { auto cachedGrid = volumeCache_.find(dsId); auto cachedScale = volumeScaleCache_.find(dsId); if (cachedGrid != volumeCache_.end() && cachedScale != volumeScaleCache_.end()) { view_.addVolume(dsId, cachedGrid->second, cachedScale->second); // 缓存命中(色阶随体缓存) onDatasetArrived(); return; } loadingDs_.insert(dsId); sceneRepo_.loadVolume( dsId, [self, gen, dsId](data::VolumeGrid g, core::ColorScale cs) { if (!self) return; self->loadingDs_.erase(dsId); if (gen != self->rebuildGeneration_ || !self->isChecked(dsId)) return; self->volumeScaleCache_[dsId] = cs; // 色阶随体一起缓存(mock 体在 dsRepo_ 无条目) auto it = self->volumeCache_.emplace(dsId, std::move(g)).first; self->view_.addVolume(dsId, it->second, self->volumeScaleCache_[dsId]); self->onDatasetArrived(); }, [self, gen, dsId](const std::string& m) { if (!self) return; self->loadingDs_.erase(dsId); if (gen != self->rebuildGeneration_) return; emit self->loadFailed(QString::fromStdString(m)); }); return; } // 剖面 → 帘面(着色用 loadSection 返回的 s.scale,与体的源色阶同源)。 loadingDs_.insert(dsId); sceneRepo_.loadSection( dsId, [self, gen, dsId](data::SectionData s) { if (!self) return; self->loadingDs_.erase(dsId); if (gen != self->rebuildGeneration_ || !self->isChecked(dsId)) return; // 作废/已取消 self->view_.addCurtain(dsId, s.grid, s.scale); self->onDatasetArrived(); }, [self, gen, dsId](const std::string& m) { if (!self) return; self->loadingDs_.erase(dsId); if (gen != self->rebuildGeneration_) return; emit self->loadFailed(QString::fromStdString(m)); }); } void VtkSceneController::onDatasetArrived() { view_.renderIncremental(); if (fitOnArrival_) view_.fitView(); // 全量重建/首批数据 → 自动取景;增量追加保持相机 } bool VtkSceneController::isChecked(const std::string& dsId) const { return std::find(checkedDs_.begin(), checkedDs_.end(), dsId) != checkedDs_.end(); } bool VtkSceneController::is2DChecked(const std::string& dsId) const { return std::find(checked2dDs_.begin(), checked2dDs_.end(), dsId) != checked2dDs_.end(); } void VtkSceneController::setViewMode(ViewMode mode) { mode_ = mode; rebuildInternal(); } void VtkSceneController::setLayer(SceneLayer layer, bool on) { switch (layer) { case SceneLayer::Curtain: showCurtain_ = on; break; case SceneLayer::Voxel: showVoxel_ = on; break; case SceneLayer::Terrain: showTerrain_ = on; break; } rebuildInternal(); } void VtkSceneController::setVerticalExaggeration(double ve) { verticalExaggeration_ = ve; rebuildInternal(); } void VtkSceneController::rebuild() { rebuildInternal(); } void VtkSceneController::setVolumeColorScale(const std::string& dsId, const geopro::core::ColorScale& cs) { volumeScaleCache_[dsId] = cs; // 会话级 mock 持久(再勾选命中缓存,见 addDatasetAsync) if (!isChecked(dsId)) return; // 未渲染 → 仅更缓存,下次勾选生效 auto git = volumeCache_.find(dsId); if (git == volumeCache_.end()) return; // 体网格尚未到场 → 同上 // 移除旧体素 → 以新色阶重建:addVolume 内部置 currentColorScale_ 并触发 onVolumeChanged, // InteractionManager 据此以新色阶重建该体下已勾选切片。 view_.removeDataset(dsId); view_.addVolume(dsId, git->second, cs); view_.renderIncremental(); } void VtkSceneController::setAxesMode(AxesMode mode) { axesMode_ = mode; rebuildInternal(); // 坐标轴随场景重建(clear 会移除旧坐标轴 prop) } void VtkSceneController::setAxesUnit(AxesUnit unit) { axesUnit_ = unit; rebuildInternal(); } // 快捷视图 / 缩放:仅改相机,不重建场景(无须取数/重装图元)。 void VtkSceneController::applyView(ViewDir dir) { view_.applyCameraView(dir); } void VtkSceneController::zoomIn() { view_.zoom(1.2); } void VtkSceneController::zoomOut() { view_.zoom(1.0 / 1.2); } void VtkSceneController::fit() { view_.fitView(); } const geopro::core::Grid& VtkSceneController::grid(const std::string& dsId) { auto it = gridCache_.find(dsId); if (it == gridCache_.end()) it = gridCache_.emplace(dsId, dsRepo_.loadGrid(dsId)).first; return it->second; } const geopro::core::ColorScale& VtkSceneController::colorScale(const std::string& dsId) { auto it = colorScaleCache_.find(dsId); if (it == colorScaleCache_.end()) it = colorScaleCache_.emplace(dsId, dsRepo_.loadColorScale(dsId)).first; return it->second; } void VtkSceneController::rebuildInternal() { const unsigned long long gen = ++rebuildGeneration_; // 自增:作废此前所有在途增量回调 const bool is2D = (mode_ == ViewMode::Map2D); view_.clear(); // 移除全部数据图元(保留底图);frame 重锚标志复位 loadingDs_.clear(); // 旧在途加载随之作废(回调按 gen 丢弃) view_.setVerticalExaggeration(verticalExaggeration_); // 坐标轴设置在 clear 后下发:render 末尾据当前场景包围盒重建坐标轴 prop。 view_.setAxes(axesMode_, axesUnit_, kAxesFontSize); fitOnArrival_ = true; // 全量重建:到场数据自动取景 // 坏 dsId(loadGrid/loadColorScale 抛异常)= best-effort 跳过:emit loadFailed 但不中断。 try { if (is2D) { for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId)); } else { // 回调用 QPointer 守对象存活 + gen 守数据新鲜:迟到回调若已析构/作废则丢弃。 QPointer self(this); if (showTerrain_) { sceneRepo_.loadTerrainPaths( [self, gen](data::TerrainPaths p) { if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃 self->view_.addTerrain(std::move(p)); self->onDatasetArrived(); }, [self, gen](const std::string& m) { if (!self || gen != self->rebuildGeneration_) return; emit self->loadFailed(QString::fromStdString(m)); }); } for (const auto& dsId : checkedDs_) addDatasetAsync(dsId, gen); // 二维足迹随全量重建一并重画(clear 已移除其图元);mode=0 关闭则跳过。 if (placement2dMode_ != 0) for (const auto& dsId : checked2dDs_) add2DDatasetAsync(dsId, gen); } } catch (const std::exception& e) { emit loadFailed(QString::fromStdString(e.what())); } view_.render(is2D); // 设背景/相机预设/坐标轴 + ResetCamera(数据到场再由 onDatasetArrived 取景) } } // namespace geopro::controller