feat/vtk-3d-view #7
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <vtkActor.h>
|
#include <vtkActor.h>
|
||||||
#include <vtkCubeAxesActor.h>
|
#include <vtkCubeAxesActor.h>
|
||||||
|
#include <vtkProp.h>
|
||||||
#include <vtkRenderWindow.h>
|
#include <vtkRenderWindow.h>
|
||||||
#include <vtkRenderer.h>
|
#include <vtkRenderer.h>
|
||||||
#include <vtkVolume.h>
|
#include <vtkVolume.h>
|
||||||
|
|
@ -61,11 +62,24 @@ VtkSceneView::VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* render
|
||||||
frame_(std::move(frame)),
|
frame_(std::move(frame)),
|
||||||
zRefElev_(zRefElev) {}
|
zRefElev_(zRefElev) {}
|
||||||
|
|
||||||
|
void VtkSceneView::removeProps(std::vector<vtkSmartPointer<vtkProp>>& props) {
|
||||||
|
for (auto& p : props)
|
||||||
|
if (p) scene_.renderer()->RemoveViewProp(p);
|
||||||
|
props.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void VtkSceneView::clear() {
|
void VtkSceneView::clear() {
|
||||||
scene_.clear(); // RemoveAllViewProps:连同坐标轴一并移除
|
// 只移除数据 prop(按 ds 跟踪)+ 杂项(地形/测线)+ 坐标轴;不动底图(TileBasemap 自管)→ 重建不丢图。
|
||||||
currentAxes_ = nullptr; // 旧坐标轴已随 clear 移除,置空避免悬留引用
|
for (auto& kv : dsProps_) removeProps(kv.second);
|
||||||
|
dsProps_.clear();
|
||||||
|
removeProps(miscProps_);
|
||||||
|
if (currentAxes_) {
|
||||||
|
scene_.renderer()->RemoveViewProp(currentAxes_);
|
||||||
|
currentAxes_ = nullptr;
|
||||||
|
}
|
||||||
// 体素 image 失效:置空并通知上层关闭切片(防切片附着到已移除的 image)。
|
// 体素 image 失效:置空并通知上层关闭切片(防切片附着到已移除的 image)。
|
||||||
currentVolumeImage_ = nullptr;
|
currentVolumeImage_ = nullptr;
|
||||||
|
volumeOwnerDs_.clear();
|
||||||
frameAnchoredToData_ = false; // 新一轮选择重新按其首个真实剖面重锚原点
|
frameAnchoredToData_ = false; // 新一轮选择重新按其首个真实剖面重锚原点
|
||||||
if (onVolumeChanged) onVolumeChanged();
|
if (onVolumeChanged) onVolumeChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -74,10 +88,14 @@ void VtkSceneView::setVerticalExaggeration(double ve) { verticalExaggeration_ =
|
||||||
|
|
||||||
void VtkSceneView::addSurveyLine(const geopro::core::Grid& grid) {
|
void VtkSceneView::addSurveyLine(const geopro::core::Grid& grid) {
|
||||||
auto line = geopro::render::buildSurveyLine(grid, *frame_);
|
auto line = geopro::render::buildSurveyLine(grid, *frame_);
|
||||||
if (line) scene_.addActor(line);
|
if (line) {
|
||||||
|
scene_.addActor(line);
|
||||||
|
miscProps_.push_back(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneView::addCurtain(const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) {
|
void VtkSceneView::addCurtain(const std::string& dsId, const geopro::core::Grid& grid,
|
||||||
|
const geopro::core::ColorScale& cs) {
|
||||||
// 首个带经纬度的剖面到达 → 把 GeoLocalFrame 原点重锚到该剖面 lat/lon 中心:使局部坐标从 0 附近起
|
// 首个带经纬度的剖面到达 → 把 GeoLocalFrame 原点重锚到该剖面 lat/lon 中心:使局部坐标从 0 附近起
|
||||||
// (轴刻度有意义),同一选择内多条剖面共用此原点 → 相互地理配准。无经纬剖面是平面、不受原点影响。
|
// (轴刻度有意义),同一选择内多条剖面共用此原点 → 相互地理配准。无经纬剖面是平面、不受原点影响。
|
||||||
const int nx = grid.nx();
|
const int nx = grid.nx();
|
||||||
|
|
@ -91,15 +109,18 @@ void VtkSceneView::addCurtain(const geopro::core::Grid& grid, const geopro::core
|
||||||
// 就地重锚共享 frame(不换对象)→ 同持此 frame 的底图层等随即一致对齐。
|
// 就地重锚共享 frame(不换对象)→ 同持此 frame 的底图层等随即一致对齐。
|
||||||
frame_->reanchor((la0 + la1) / 2.0, (lo0 + lo1) / 2.0);
|
frame_->reanchor((la0 + la1) / 2.0, (lo0 + lo1) / 2.0);
|
||||||
frameAnchoredToData_ = true;
|
frameAnchoredToData_ = true;
|
||||||
|
if (onFrameReanchored) onFrameReanchored(); // 通知底图刷新到数据位置
|
||||||
}
|
}
|
||||||
auto curtain = geopro::render::buildCurtain(grid, cs, *frame_);
|
auto curtain = geopro::render::buildCurtain(grid, cs, *frame_);
|
||||||
if (curtain) {
|
if (curtain) {
|
||||||
curtain->SetScale(1.0, 1.0, verticalExaggeration_); // 纵向夸张成墙
|
curtain->SetScale(1.0, 1.0, verticalExaggeration_); // 纵向夸张成墙
|
||||||
scene_.addActor(curtain);
|
scene_.addActor(curtain);
|
||||||
|
dsProps_[dsId].push_back(curtain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneView::addVolume(const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) {
|
void VtkSceneView::addVolume(const std::string& dsId, const geopro::data::VolumeGrid& vol,
|
||||||
|
const geopro::core::ColorScale& cs) {
|
||||||
// 纵向夸张烤进 image 的 z 原点/间距(与帘面 SetScale 同倍,保证纵向一致)。
|
// 纵向夸张烤进 image 的 z 原点/间距(与帘面 SetScale 同倍,保证纵向一致)。
|
||||||
// 用暴露 image 的 buildVoxel 重载:保留 currentVolumeImage_ 供 P3 切片附着(几何含 VE)。
|
// 用暴露 image 的 buildVoxel 重载:保留 currentVolumeImage_ 供 P3 切片附着(几何含 VE)。
|
||||||
vtkSmartPointer<vtkImageData> image;
|
vtkSmartPointer<vtkImageData> image;
|
||||||
|
|
@ -109,10 +130,12 @@ void VtkSceneView::addVolume(const geopro::data::VolumeGrid& vol, const geopro::
|
||||||
image);
|
image);
|
||||||
if (volume) {
|
if (volume) {
|
||||||
scene_.addViewProp(volume);
|
scene_.addViewProp(volume);
|
||||||
|
dsProps_[dsId].push_back(volume);
|
||||||
currentVolumeImage_ = image;
|
currentVolumeImage_ = image;
|
||||||
currentColorScale_ = cs;
|
currentColorScale_ = cs;
|
||||||
currentVmin_ = vol.vmin;
|
currentVmin_ = vol.vmin;
|
||||||
currentVmax_ = vol.vmax;
|
currentVmax_ = vol.vmax;
|
||||||
|
volumeOwnerDs_ = dsId;
|
||||||
if (onVolumeChanged) onVolumeChanged();
|
if (onVolumeChanged) onVolumeChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +143,22 @@ void VtkSceneView::addVolume(const geopro::data::VolumeGrid& vol, const geopro::
|
||||||
void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) {
|
void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) {
|
||||||
auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_,
|
auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_,
|
||||||
verticalExaggeration_);
|
verticalExaggeration_);
|
||||||
if (terrain) scene_.addActor(terrain);
|
if (terrain) {
|
||||||
|
scene_.addActor(terrain);
|
||||||
|
miscProps_.push_back(terrain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VtkSceneView::removeDataset(const std::string& dsId) {
|
||||||
|
auto it = dsProps_.find(dsId);
|
||||||
|
if (it == dsProps_.end()) return;
|
||||||
|
removeProps(it->second);
|
||||||
|
dsProps_.erase(it);
|
||||||
|
if (volumeOwnerDs_ == dsId) { // 该 ds 的体素被移除 → 切片源失效
|
||||||
|
currentVolumeImage_ = nullptr;
|
||||||
|
volumeOwnerDs_.clear();
|
||||||
|
if (onVolumeChanged) onVolumeChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneView::setAxes(geopro::controller::AxesMode mode, geopro::controller::AxesUnit unit,
|
void VtkSceneView::setAxes(geopro::controller::AxesMode mode, geopro::controller::AxesUnit unit,
|
||||||
|
|
@ -183,4 +221,10 @@ void VtkSceneView::render(bool is2D) {
|
||||||
if (renderWindow_) renderWindow_->Render();
|
if (renderWindow_) renderWindow_->Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VtkSceneView::renderIncremental() {
|
||||||
|
// 增量渲染:仅按新包围盒重建坐标轴并提交,不动相机(勾选/取消时视角不跳)。
|
||||||
|
rebuildAxes();
|
||||||
|
if (renderWindow_) renderWindow_->Render();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace geopro::app
|
} // namespace geopro::app
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <vtkCubeAxesActor.h>
|
#include <vtkCubeAxesActor.h>
|
||||||
#include <vtkImageData.h>
|
#include <vtkImageData.h>
|
||||||
|
|
@ -13,6 +16,7 @@ namespace geopro::core { class GeoLocalFrame; }
|
||||||
namespace geopro::render { class Scene; }
|
namespace geopro::render { class Scene; }
|
||||||
class vtkRenderer;
|
class vtkRenderer;
|
||||||
class vtkRenderWindow;
|
class vtkRenderWindow;
|
||||||
|
class vtkProp;
|
||||||
|
|
||||||
namespace geopro::app {
|
namespace geopro::app {
|
||||||
|
|
||||||
|
|
@ -29,15 +33,19 @@ public:
|
||||||
void clear() override;
|
void clear() override;
|
||||||
void setVerticalExaggeration(double ve) override;
|
void setVerticalExaggeration(double ve) override;
|
||||||
void addSurveyLine(const geopro::core::Grid& grid) override;
|
void addSurveyLine(const geopro::core::Grid& grid) override;
|
||||||
void addCurtain(const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) override;
|
void addCurtain(const std::string& dsId, const geopro::core::Grid& grid,
|
||||||
void addVolume(const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) override;
|
const geopro::core::ColorScale& cs) override;
|
||||||
|
void addVolume(const std::string& dsId, const geopro::data::VolumeGrid& vol,
|
||||||
|
const geopro::core::ColorScale& cs) override;
|
||||||
void addTerrain(const geopro::data::TerrainPaths& paths) override;
|
void addTerrain(const geopro::data::TerrainPaths& paths) override;
|
||||||
|
void removeDataset(const std::string& dsId) override;
|
||||||
void setAxes(geopro::controller::AxesMode mode, geopro::controller::AxesUnit unit,
|
void setAxes(geopro::controller::AxesMode mode, geopro::controller::AxesUnit unit,
|
||||||
int fontSize) override;
|
int fontSize) override;
|
||||||
void applyCameraView(geopro::controller::ViewDir dir) override;
|
void applyCameraView(geopro::controller::ViewDir dir) override;
|
||||||
void zoom(double factor) override;
|
void zoom(double factor) override;
|
||||||
void fitView() override;
|
void fitView() override;
|
||||||
void render(bool is2D) override;
|
void render(bool is2D) override;
|
||||||
|
void renderIncremental() override;
|
||||||
|
|
||||||
// ── P3 切片交互:暴露当前体素 image(含 VE 烤入的 origin/spacing)供切片附着 ──
|
// ── P3 切片交互:暴露当前体素 image(含 VE 烤入的 origin/spacing)供切片附着 ──
|
||||||
// addVolume 用暴露 image 的 buildVoxel 重载保留;clear/无体素时置空。
|
// addVolume 用暴露 image 的 buildVoxel 重载保留;clear/无体素时置空。
|
||||||
|
|
@ -51,9 +59,13 @@ public:
|
||||||
// InteractionManager(重附着或关闭切片)。clear 时以 nullptr 触发。
|
// InteractionManager(重附着或关闭切片)。clear 时以 nullptr 触发。
|
||||||
std::function<void()> onVolumeChanged;
|
std::function<void()> onVolumeChanged;
|
||||||
|
|
||||||
|
// frame 原点重锚(首个带经纬剖面到达)后回调,供底图等随之刷新到数据所在位置。
|
||||||
|
std::function<void()> onFrameReanchored;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 按当前坐标轴设置 + 场景包围盒重建坐标轴 prop(render 末尾调)。
|
// 按当前坐标轴设置 + 场景包围盒重建坐标轴 prop(render 末尾调)。
|
||||||
void rebuildAxes();
|
void rebuildAxes();
|
||||||
|
void removeProps(std::vector<vtkSmartPointer<vtkProp>>& props); // 从 renderer 移除并清空
|
||||||
|
|
||||||
geopro::render::Scene& scene_;
|
geopro::render::Scene& scene_;
|
||||||
vtkRenderWindow* renderWindow_;
|
vtkRenderWindow* renderWindow_;
|
||||||
|
|
@ -77,6 +89,12 @@ private:
|
||||||
geopro::core::ColorScale currentColorScale_;
|
geopro::core::ColorScale currentColorScale_;
|
||||||
double currentVmin_ = 0.0;
|
double currentVmin_ = 0.0;
|
||||||
double currentVmax_ = 0.0;
|
double currentVmax_ = 0.0;
|
||||||
|
|
||||||
|
// 增量渲染:按 dsId 跟踪该数据集的 props(帘面/体素),支持单独移除而不全量重建;
|
||||||
|
// miscProps_ 为非数据集 prop(地形/测线),仅随 clear 全量移除。底图由 TileBasemap 自管、不在此。
|
||||||
|
std::map<std::string, std::vector<vtkSmartPointer<vtkProp>>> dsProps_;
|
||||||
|
std::vector<vtkSmartPointer<vtkProp>> miscProps_;
|
||||||
|
std::string volumeOwnerDs_; // 当前 currentVolumeImage_ 归属的 ds(其被移除时置空切片源)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace geopro::app
|
} // namespace geopro::app
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "model/ColorScale.hpp"
|
#include "model/ColorScale.hpp"
|
||||||
#include "model/Field.hpp"
|
#include "model/Field.hpp"
|
||||||
#include "repo/I3dSceneRepository.hpp"
|
#include "repo/I3dSceneRepository.hpp"
|
||||||
|
|
@ -25,14 +27,16 @@ public:
|
||||||
|
|
||||||
// 2D:俯视测线红线(z=0)。
|
// 2D:俯视测线红线(z=0)。
|
||||||
virtual void addSurveyLine(const geopro::core::Grid& grid) = 0;
|
virtual void addSurveyLine(const geopro::core::Grid& grid) = 0;
|
||||||
// 3D:竖直帘面(grid + colorScale 着色)。
|
// 3D:竖直帘面(grid + colorScale 着色);按 dsId 跟踪以支持增量移除。
|
||||||
virtual void addCurtain(const geopro::core::Grid& grid,
|
virtual void addCurtain(const std::string& dsId, const geopro::core::Grid& grid,
|
||||||
const geopro::core::ColorScale& cs) = 0;
|
const geopro::core::ColorScale& cs) = 0;
|
||||||
// 3D:体绘制(IDW 体素 + colorScale)。
|
// 3D:体绘制(IDW 体素 + colorScale);按 dsId 跟踪。
|
||||||
virtual void addVolume(const geopro::data::VolumeGrid& vol,
|
virtual void addVolume(const std::string& dsId, const geopro::data::VolumeGrid& vol,
|
||||||
const geopro::core::ColorScale& cs) = 0;
|
const geopro::core::ColorScale& cs) = 0;
|
||||||
// 3D:DEM 地形 + 影像纹理。
|
// 3D:DEM 地形 + 影像纹理。
|
||||||
virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0;
|
virtual void addTerrain(const geopro::data::TerrainPaths& paths) = 0;
|
||||||
|
// 增量移除某数据集的全部图元(取消勾选时调,不影响其余 ds 与底图)。
|
||||||
|
virtual void removeDataset(const std::string& dsId) = 0;
|
||||||
|
|
||||||
// 坐标轴设置(P2):显示方式 + 刻度单位 + 字号。视图据当前场景包围盒重建坐标轴 prop。
|
// 坐标轴设置(P2):显示方式 + 刻度单位 + 字号。视图据当前场景包围盒重建坐标轴 prop。
|
||||||
// None 模式 = 移除坐标轴;rebuild 时由控制器在 clear 后重新下发当前坐标轴设置。
|
// None 模式 = 移除坐标轴;rebuild 时由控制器在 clear 后重新下发当前坐标轴设置。
|
||||||
|
|
@ -45,8 +49,10 @@ public:
|
||||||
// 适配全览(P2):ResetCamera 并提交渲染。
|
// 适配全览(P2):ResetCamera 并提交渲染。
|
||||||
virtual void fitView() = 0;
|
virtual void fitView() = 0;
|
||||||
|
|
||||||
// 应用相机预设(2D 俯视 / 3D 自由)并提交渲染。
|
// 应用相机预设(2D 俯视 / 3D 自由)并提交渲染(全量重建用,会 ResetCamera)。
|
||||||
virtual void render(bool is2D) = 0;
|
virtual void render(bool is2D) = 0;
|
||||||
|
// 增量提交渲染:重建坐标轴并刷新,但不动相机(勾选/取消单个 ds 时视角不跳)。
|
||||||
|
virtual void renderIncremental() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace geopro::controller
|
} // namespace geopro::controller
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "VtkSceneController.hpp"
|
#include "VtkSceneController.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
@ -15,10 +17,85 @@ VtkSceneController::VtkSceneController(data::IDatasetRepository& dsRepo,
|
||||||
: QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {}
|
: QObject(parent), dsRepo_(dsRepo), sceneRepo_(sceneRepo), view_(view) {}
|
||||||
|
|
||||||
void VtkSceneController::setCheckedDatasets(const QStringList& dsIds) {
|
void VtkSceneController::setCheckedDatasets(const QStringList& dsIds) {
|
||||||
checkedDs_.clear();
|
std::vector<std::string> newDs;
|
||||||
checkedDs_.reserve(static_cast<std::size_t>(dsIds.size()));
|
newDs.reserve(static_cast<std::size_t>(dsIds.size()));
|
||||||
for (const QString& id : dsIds) checkedDs_.push_back(id.toStdString());
|
for (const QString& id : dsIds) newDs.push_back(id.toStdString());
|
||||||
|
|
||||||
|
// 2D 俯视测线:保持全量重建(测线非按 ds 跟踪移除)。
|
||||||
|
if (mode_ == ViewMode::Map2D) {
|
||||||
|
checkedDs_ = std::move(newDs);
|
||||||
rebuildInternal();
|
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());
|
||||||
|
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::addDatasetAsync(const std::string& dsId, unsigned long long gen) {
|
||||||
|
if (loadingDs_.count(dsId)) return; // 已在加载(重复勾选竞态)→ 不重复请求
|
||||||
|
QPointer<VtkSceneController> self(this);
|
||||||
|
if (showCurtain_) {
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (showVoxel_) {
|
||||||
|
auto cached = volumeCache_.find(dsId);
|
||||||
|
if (cached != volumeCache_.end()) {
|
||||||
|
view_.addVolume(dsId, cached->second, colorScale(dsId));
|
||||||
|
onDatasetArrived();
|
||||||
|
} else {
|
||||||
|
sceneRepo_.loadVolume(
|
||||||
|
dsId,
|
||||||
|
[self, gen, dsId](data::VolumeGrid g) {
|
||||||
|
if (!self || gen != self->rebuildGeneration_ || !self->isChecked(dsId)) return;
|
||||||
|
auto it = self->volumeCache_.emplace(dsId, std::move(g)).first;
|
||||||
|
self->view_.addVolume(dsId, it->second, self->colorScale(dsId));
|
||||||
|
self->onDatasetArrived();
|
||||||
|
},
|
||||||
|
[self, gen](const std::string& m) {
|
||||||
|
if (!self || 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneController::setViewMode(ViewMode mode) {
|
void VtkSceneController::setViewMode(ViewMode mode) {
|
||||||
|
|
@ -72,80 +149,42 @@ const geopro::core::ColorScale& VtkSceneController::colorScale(const std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneController::rebuildInternal() {
|
void VtkSceneController::rebuildInternal() {
|
||||||
const unsigned long long gen = ++rebuildGeneration_;
|
const unsigned long long gen = ++rebuildGeneration_; // 自增:作废此前所有在途增量回调
|
||||||
const bool is2D = (mode_ == ViewMode::Map2D);
|
const bool is2D = (mode_ == ViewMode::Map2D);
|
||||||
|
|
||||||
view_.clear();
|
view_.clear(); // 移除全部数据图元(保留底图);frame 重锚标志复位
|
||||||
|
loadingDs_.clear(); // 旧在途加载随之作废(回调按 gen 丢弃)
|
||||||
view_.setVerticalExaggeration(verticalExaggeration_);
|
view_.setVerticalExaggeration(verticalExaggeration_);
|
||||||
// 坐标轴设置在 clear 后下发:render 末尾据当前场景包围盒重建坐标轴 prop。
|
// 坐标轴设置在 clear 后下发:render 末尾据当前场景包围盒重建坐标轴 prop。
|
||||||
view_.setAxes(axesMode_, axesUnit_, kAxesFontSize);
|
view_.setAxes(axesMode_, axesUnit_, kAxesFontSize);
|
||||||
|
fitOnArrival_ = true; // 全量重建:到场数据自动取景
|
||||||
|
|
||||||
inRebuild_ = true;
|
// 坏 dsId(loadGrid/loadColorScale 抛异常)= best-effort 跳过:emit loadFailed 但不中断。
|
||||||
// 坏 dsId(loadGrid/loadColorScale 抛异常)= best-effort 跳过:emit loadFailed 但不中断,
|
|
||||||
// 其余勾选数据集照常渲染(非 fail-fast)。
|
|
||||||
try {
|
try {
|
||||||
if (is2D) {
|
if (is2D) {
|
||||||
for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId));
|
for (const auto& dsId : checkedDs_) view_.addSurveyLine(grid(dsId));
|
||||||
} else {
|
} else {
|
||||||
// 回调用 QPointer<self> 守对象存活(控制器是 QObject)+ gen 守数据新鲜:
|
// 回调用 QPointer<self> 守对象存活 + gen 守数据新鲜:迟到回调若已析构/作废则丢弃。
|
||||||
// 将来 Api 实现在网络线程迟到回调时,self 已析构则直接丢弃,不触 dangling。
|
|
||||||
QPointer<VtkSceneController> self(this);
|
QPointer<VtkSceneController> self(this);
|
||||||
if (showTerrain_) {
|
if (showTerrain_) {
|
||||||
sceneRepo_.loadTerrainPaths(
|
sceneRepo_.loadTerrainPaths(
|
||||||
[self, gen](data::TerrainPaths p) {
|
[self, gen](data::TerrainPaths p) {
|
||||||
if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃
|
if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃
|
||||||
self->view_.addTerrain(std::move(p));
|
self->view_.addTerrain(std::move(p));
|
||||||
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
|
self->onDatasetArrived();
|
||||||
},
|
},
|
||||||
[self, gen](const std::string& m) {
|
[self, gen](const std::string& m) {
|
||||||
if (!self || gen != self->rebuildGeneration_) return;
|
if (!self || gen != self->rebuildGeneration_) return;
|
||||||
emit self->loadFailed(QString::fromStdString(m));
|
emit self->loadFailed(QString::fromStdString(m));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (showCurtain_) {
|
for (const auto& dsId : checkedDs_) addDatasetAsync(dsId, gen);
|
||||||
for (const auto& dsId : checkedDs_) {
|
|
||||||
sceneRepo_.loadSection(
|
|
||||||
dsId,
|
|
||||||
[self, gen](data::SectionData s) {
|
|
||||||
if (!self || gen != self->rebuildGeneration_) return; // 已析构/迟到:丢弃
|
|
||||||
self->view_.addCurtain(s.grid, s.scale);
|
|
||||||
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
|
|
||||||
},
|
|
||||||
[self, gen](const std::string& m) {
|
|
||||||
if (!self || gen != self->rebuildGeneration_) return;
|
|
||||||
emit self->loadFailed(QString::fromStdString(m));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (showVoxel_) {
|
|
||||||
for (const auto& dsId : checkedDs_) {
|
|
||||||
auto cached = volumeCache_.find(dsId);
|
|
||||||
if (cached != volumeCache_.end()) {
|
|
||||||
view_.addVolume(cached->second, colorScale(dsId));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sceneRepo_.loadVolume(
|
|
||||||
dsId,
|
|
||||||
[self, gen, dsId](data::VolumeGrid g) {
|
|
||||||
if (!self) return; // 控制器已析构:丢弃
|
|
||||||
if (gen != self->rebuildGeneration_) return; // 迟到回灌:丢弃
|
|
||||||
auto it = self->volumeCache_.emplace(dsId, std::move(g)).first;
|
|
||||||
self->view_.addVolume(it->second, self->colorScale(dsId));
|
|
||||||
if (!self->inRebuild_) self->view_.render(false); // 同步路径由末尾统一 render
|
|
||||||
},
|
|
||||||
[self, gen](const std::string& m) {
|
|
||||||
if (!self || gen != self->rebuildGeneration_) return;
|
|
||||||
emit self->loadFailed(QString::fromStdString(m));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
emit loadFailed(QString::fromStdString(e.what()));
|
emit loadFailed(QString::fromStdString(e.what()));
|
||||||
}
|
}
|
||||||
|
|
||||||
inRebuild_ = false;
|
view_.render(is2D); // 设背景/相机预设/坐标轴 + ResetCamera(数据到场再由 onDatasetArrived 取景)
|
||||||
view_.render(is2D);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace geopro::controller
|
} // namespace geopro::controller
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "I3dSceneView.hpp"
|
#include "I3dSceneView.hpp"
|
||||||
|
|
@ -54,6 +55,10 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void rebuildInternal();
|
void rebuildInternal();
|
||||||
|
// 增量加入单个 ds(帘面/体素,按图层开关);回调按 gen + 仍勾选 守护,落地后增量渲染。
|
||||||
|
void addDatasetAsync(const std::string& dsId, unsigned long long gen);
|
||||||
|
void onDatasetArrived(); // 单个 ds 落地后:增量渲染 + 首批数据自动取景
|
||||||
|
bool isChecked(const std::string& dsId) const;
|
||||||
|
|
||||||
data::IDatasetRepository& dsRepo_;
|
data::IDatasetRepository& dsRepo_;
|
||||||
data::I3dSceneRepository& sceneRepo_;
|
data::I3dSceneRepository& sceneRepo_;
|
||||||
|
|
@ -76,12 +81,13 @@ private:
|
||||||
std::map<std::string, geopro::core::ColorScale> colorScaleCache_;
|
std::map<std::string, geopro::core::ColorScale> colorScaleCache_;
|
||||||
std::map<std::string, data::VolumeGrid> volumeCache_;
|
std::map<std::string, data::VolumeGrid> volumeCache_;
|
||||||
|
|
||||||
// 异步回灌防护:每次 rebuild 自增,回调比对丢弃迟到结果。
|
// 异步回灌防护:每次全量 rebuild 自增,回调比对丢弃迟到结果。
|
||||||
unsigned long long rebuildGeneration_ = 0;
|
unsigned long long rebuildGeneration_ = 0;
|
||||||
// rebuild 进行中标志:同步回调(LocalSample)在 rebuild 内立即触发时跳过自身 render,
|
|
||||||
// 由 rebuildInternal 末尾统一 render 覆盖(避免双重 ResetCamera/Render);
|
// 增量渲染状态:本批数据到场是否自动取景(全量重建/从空开始=true;增量追加=false,保持相机)。
|
||||||
// 真异步回调迟到时 inRebuild_ 已 false → 自行 render 追加。
|
bool fitOnArrival_ = true;
|
||||||
bool inRebuild_ = false;
|
// 正在加载的 ds:防重复勾选竞态重复请求;全量重建时清空。
|
||||||
|
std::set<std::string> loadingDs_;
|
||||||
|
|
||||||
const geopro::core::Grid& grid(const std::string& dsId);
|
const geopro::core::Grid& grid(const std::string& dsId);
|
||||||
const geopro::core::ColorScale& colorScale(const std::string& dsId);
|
const geopro::core::ColorScale& colorScale(const std::string& dsId);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "I3dSceneView.hpp"
|
#include "I3dSceneView.hpp"
|
||||||
|
|
@ -38,16 +40,34 @@ struct FakeView : I3dSceneView {
|
||||||
double lastZoomFactor = 0.0;
|
double lastZoomFactor = 0.0;
|
||||||
int fitCalls = 0;
|
int fitCalls = 0;
|
||||||
|
|
||||||
// clear 模型化"移除所有图元":图元计数归零(反映当前场景状态),clears 累加。
|
// 按 ds 记录图元数,使 removeDataset 能按量回退(反映增量场景状态)。
|
||||||
|
std::map<std::string, std::pair<int, int>> perDs; // dsId → (curtains, volumes)
|
||||||
|
|
||||||
|
// clear 模型化"移除所有数据图元":计数归零,clears 累加。
|
||||||
void clear() override {
|
void clear() override {
|
||||||
++clears;
|
++clears;
|
||||||
surveyLines = curtains = volumes = terrains = 0;
|
surveyLines = curtains = volumes = terrains = 0;
|
||||||
|
perDs.clear();
|
||||||
}
|
}
|
||||||
void setVerticalExaggeration(double v) override { ve = v; }
|
void setVerticalExaggeration(double v) override { ve = v; }
|
||||||
void addSurveyLine(const core::Grid&) override { ++surveyLines; }
|
void addSurveyLine(const core::Grid&) override { ++surveyLines; }
|
||||||
void addCurtain(const core::Grid&, const core::ColorScale&) override { ++curtains; }
|
void addCurtain(const std::string& dsId, const core::Grid&, const core::ColorScale&) override {
|
||||||
void addVolume(const data::VolumeGrid&, const core::ColorScale&) override { ++volumes; }
|
++curtains;
|
||||||
|
++perDs[dsId].first;
|
||||||
|
}
|
||||||
|
void addVolume(const std::string& dsId, const data::VolumeGrid&,
|
||||||
|
const core::ColorScale&) override {
|
||||||
|
++volumes;
|
||||||
|
++perDs[dsId].second;
|
||||||
|
}
|
||||||
void addTerrain(const data::TerrainPaths&) override { ++terrains; }
|
void addTerrain(const data::TerrainPaths&) override { ++terrains; }
|
||||||
|
void removeDataset(const std::string& dsId) override {
|
||||||
|
auto it = perDs.find(dsId);
|
||||||
|
if (it == perDs.end()) return;
|
||||||
|
curtains -= it->second.first;
|
||||||
|
volumes -= it->second.second;
|
||||||
|
perDs.erase(it);
|
||||||
|
}
|
||||||
void setAxes(AxesMode mode, AxesUnit unit, int fontSize) override {
|
void setAxes(AxesMode mode, AxesUnit unit, int fontSize) override {
|
||||||
++setAxesCalls;
|
++setAxesCalls;
|
||||||
lastAxesMode = mode; lastAxesUnit = unit; lastAxesFont = fontSize;
|
lastAxesMode = mode; lastAxesUnit = unit; lastAxesFont = fontSize;
|
||||||
|
|
@ -56,6 +76,7 @@ struct FakeView : I3dSceneView {
|
||||||
void zoom(double factor) override { ++zoomCalls; lastZoomFactor = factor; }
|
void zoom(double factor) override { ++zoomCalls; lastZoomFactor = factor; }
|
||||||
void fitView() override { ++fitCalls; }
|
void fitView() override { ++fitCalls; }
|
||||||
void render(bool is2D) override { ++renders; lastIs2D = is2D; }
|
void render(bool is2D) override { ++renders; lastIs2D = is2D; }
|
||||||
|
void renderIncremental() override { ++renders; }
|
||||||
|
|
||||||
int props() const { return surveyLines + curtains + volumes + terrains; }
|
int props() const { return surveyLines + curtains + volumes + terrains; }
|
||||||
};
|
};
|
||||||
|
|
@ -180,19 +201,33 @@ TEST(VtkSceneController, View3DWithTerrainAddsTerrain) {
|
||||||
EXPECT_EQ(view.curtains, 1);
|
EXPECT_EQ(view.curtains, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消勾选 → clear 后无任何图元。
|
// 取消勾选 → 增量移除该 ds 图元(不整场 clear,3D 增量路径)。
|
||||||
TEST(VtkSceneController, UncheckAllClearsScene) {
|
TEST(VtkSceneController, UncheckRemovesDatasetIncrementally) {
|
||||||
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||||
VtkSceneController c(ds, sc, view);
|
VtkSceneController c(ds, sc, view);
|
||||||
c.setViewMode(ViewMode::View3D);
|
c.setViewMode(ViewMode::View3D);
|
||||||
c.setCheckedDatasets({"ds1"});
|
c.setCheckedDatasets({"ds1"});
|
||||||
ASSERT_EQ(view.curtains, 1);
|
ASSERT_EQ(view.curtains, 1);
|
||||||
|
const int clearsAfterCheck = view.clears;
|
||||||
|
|
||||||
c.setCheckedDatasets({}); // 取消全部勾选
|
c.setCheckedDatasets({}); // 取消全部勾选 → 增量移除 ds1
|
||||||
EXPECT_EQ(view.curtains, 0);
|
EXPECT_EQ(view.curtains, 0);
|
||||||
EXPECT_EQ(view.volumes, 0);
|
EXPECT_EQ(view.volumes, 0);
|
||||||
// 最后一次重建仍调用 clear。
|
EXPECT_EQ(view.clears, clearsAfterCheck); // 增量取消不触发整场 clear
|
||||||
EXPECT_GE(view.clears, 2);
|
}
|
||||||
|
|
||||||
|
// 增量追加:已勾选 ds1 时再勾 ds2,只新增 ds2,不移除/重建 ds1。
|
||||||
|
TEST(VtkSceneController, IncrementalAddKeepsExisting) {
|
||||||
|
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
|
||||||
|
VtkSceneController c(ds, sc, view);
|
||||||
|
c.setViewMode(ViewMode::View3D);
|
||||||
|
c.setCheckedDatasets({"ds1"});
|
||||||
|
const int clearsAfterFirst = view.clears;
|
||||||
|
ASSERT_EQ(view.curtains, 1);
|
||||||
|
|
||||||
|
c.setCheckedDatasets({"ds1", "ds2"}); // 增量加 ds2
|
||||||
|
EXPECT_EQ(view.curtains, 2);
|
||||||
|
EXPECT_EQ(view.clears, clearsAfterFirst); // 不重建 → 无新 clear
|
||||||
}
|
}
|
||||||
|
|
||||||
// 纵向比例传到视图。
|
// 纵向比例传到视图。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue