geopro/src/controller/VtkSceneController.cpp

362 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "VtkSceneController.hpp"
#include <algorithm>
#include <set>
#include <utility>
#include <QDebug>
#include <QPointer>
#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<std::string> newDs;
newDs.reserve(static_cast<std::size_t>(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<std::string> oldSet(checkedDs_.begin(), checkedDs_.end());
const std::set<std::string> newSet(newDs.begin(), newDs.end());
for (const auto& id : checkedDs_)
if (!newSet.count(id)) view_.removeDataset(id); // 移除:旧有新无 → 仅删该 ds 图元
checkedDs_ = std::move(newDs);
// 取景意图按「场景是否已有数据到场过」判定,而非 checkedDs_ 是否空——否则连续快速勾选第二个
// ds 时 checkedDs_ 已非空但首批尚未到场,会被误清取景意图,相机不对准数据 → 看似不渲染。
fitOnArrival_ = !hadArrivedData_;
// 仅当 3D 与 2D 都空才复位取景基线:否则取消全部 3D 但仍有 2D 足迹时,下个 3D 勾选会误跳取景。
if (checkedDs_.empty() && checked2dDs_.empty()) hadArrivedData_ = false;
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<std::string> newDs;
newDs.reserve(static_cast<std::size_t>(dsIds.size()));
for (const QString& id : dsIds) newDs.push_back(id.toStdString());
// 二维足迹始终画进 View3D 场景,且按 dsId 跟踪 → 一律增量 diff不全量重建不打断 3D 帘面/体)。
const std::set<std::string> oldSet(checked2dDs_.begin(), checked2dDs_.end());
const std::set<std::string> newSet(newDs.begin(), newDs.end());
for (const auto& id : checked2dDs_)
if (!newSet.count(id)) view_.removeDataset(id); // 取消勾选 → 移除该足迹图元
checked2dDs_ = std::move(newDs);
// 取景基线与 3D 路径统一用 hadArrivedData_而非"两栏皆空"):否则二维分析下若已有隐藏的 3D 数据,
// 勾选首条足迹会因 wasEmpty=false 而不取景 → 足迹落在视野外。切 tab 时 onAnalysisModeChanged 已按
// 目标维度是否有数据重置该基线,故此处首条可见维度数据能正确取景。
fitOnArrival_ = !hadArrivedData_;
if (checkedDs_.empty() && checked2dDs_.empty()) hadArrivedData_ = false;
// 足迹画进 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<VtkSceneController> 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<VtkSceneController> 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();
emit volumeRendered(QString::fromStdString(dsId)); // 缓存命中即时完成 → 撤 spinner
emit datasetRendered(QString::fromStdString(dsId));
return;
}
loadingDs_.insert(dsId);
emit datasetLoading(QString::fromStdString(dsId)); // 异步建体开始 → 列表项转 spinner
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;
qInfo().noquote() << "[volrender] addVolume dsId=" << QString::fromStdString(dsId)
<< "nx=" << it->second.vol.nx() << "ny=" << it->second.vol.ny()
<< "nz=" << it->second.vol.nz();
self->view_.addVolume(dsId, it->second, self->volumeScaleCache_[dsId]);
self->onDatasetArrived();
emit self->volumeRendered(QString::fromStdString(dsId)); // 落地完成 → 撤 spinner
emit self->datasetRendered(QString::fromStdString(dsId));
},
[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);
emit datasetLoading(QString::fromStdString(dsId)); // 剖面首次加载较慢 → 列表项转 spinner
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();
emit self->datasetRendered(QString::fromStdString(dsId)); // 帘面落地 → 复原复选框
},
[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() {
hadArrivedData_ = true; // 标记场景已有数据到场(取景意图据此判定)
view_.renderIncremental();
// 首批取景阶段(含连续勾选的多个 ds每个到场都 fit → ResetCamera 含全部 actor两个 ds 都入景;
// 后续单独勾选时 setCheckedDatasets 已据 hadArrivedData_ 置 fitOnArrival_=false相机不再跳。
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::onAnalysisModeChanged(bool is2D) {
// 切「三维分析/二维分析」tab按目标维度是否已有数据重置取景基线。
// 目标维度空 → hadArrivedData_=false切换后该维度第一条数据自动取景(治"3D 数据不知生成到哪")。
// 目标维度非空 → hadArrivedData_=true视图切换时已 fit 到该维度,后续勾选不再跳(与三维一致)。
// 显隐/相机/坐标轴由 VtkSceneView::setAnalysisMode2D 处理(上层在同一处调用);此处只管取景基线。
hadArrivedData_ = is2D ? !checked2dDs_.empty() : !checkedDs_.empty();
}
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;
// VE 烤进帘面 SetScale / 体素 image / 地形几何,须全量重建;但保留当前相机 → 原地按新夸张重绘,
// 不先跳远视角再回(用户反馈)。重建中 fitOnArrival_ 也置 false见 rebuildInternal
// 异步到场数据经 renderIncremental 在当前相机下显示。
preserveCameraOnRebuild_ = true;
rebuildInternal();
preserveCameraOnRebuild_ = false;
}
void VtkSceneController::setVolumeOpacity(double maxOpacity) {
// 运行时更新已渲染体的不透明度传递函数(不重建体,实时跟手)+ 记为后续新体默认(见 VtkSceneView
view_.setVolumeOpacity(maxOpacity);
}
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::setAxesConfig(AxesMode mode, AxesUnit unit, const AxisRangeCfg& x,
const AxisRangeCfg& y, const AxisRangeCfg& z) {
axesMode_ = mode;
axesUnit_ = unit;
axisX_ = x;
axisY_ = y;
axisZ_ = z;
// 增量:坐标轴只是 overlay prop改单位/范围/可见性无需清场景重载数据。仅把配置下发 view 后
// renderIncrementalrebuildAxes 重建坐标轴 prop + Render不动数据图元、不重置相机。
view_.setAxes(axesMode_, axesUnit_, kAxesFontSize);
view_.setAxesRanges(axisX_, axisY_, axisZ_);
view_.renderIncremental();
}
// 快捷视图 / 缩放:仅改相机,不重建场景(无须取数/重装图元)。
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);
view_.setAxesRanges(axisX_, axisY_, axisZ_);
fitOnArrival_ = !preserveCameraOnRebuild_; // 保留相机重建(改VE):到场数据不自动取景,留当前视角
// 坏 dsIdloadGrid/loadColorScale 抛异常)= best-effort 跳过emit loadFailed 但不中断。
try {
if (is2D) {
for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId));
} else {
// 回调用 QPointer<self> 守对象存活 + gen 守数据新鲜:迟到回调若已析构/作废则丢弃。
QPointer<VtkSceneController> 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()));
}
// 保留相机重建(改VE):不 ResetCamera原地按新夸张重绘。
view_.render(is2D, /*resetCamera=*/!preserveCameraOnRebuild_);
}
} // namespace geopro::controller