362 lines
18 KiB
C++
362 lines
18 KiB
C++
#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 后
|
||
// renderIncremental(rebuildAxes 重建坐标轴 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):到场数据不自动取景,留当前视角
|
||
|
||
// 坏 dsId(loadGrid/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
|