feat/vtk-merged-dataset-column #10
|
|
@ -5,8 +5,10 @@ find_package(VTK REQUIRED COMPONENTS
|
|||
GUISupportQt
|
||||
RenderingOpenGL2
|
||||
RenderingVolumeOpenGL2
|
||||
RenderingAnnotation # vtkAxesActor(角落方向标 gnomon 三向箭头 + 轴标签)
|
||||
InteractionStyle
|
||||
InteractionWidgets
|
||||
FiltersSources # vtkSphereSource(gnomon 6 向可点击方向球)
|
||||
FiltersGeometry
|
||||
FiltersModeling
|
||||
IOImage # vtkPNGWriter(切片导出图片)
|
||||
|
|
|
|||
|
|
@ -9,17 +9,28 @@
|
|||
#include <QString>
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkAssemblyNode.h>
|
||||
#include <vtkAssemblyPath.h>
|
||||
#include <vtkAxesActor.h>
|
||||
#include <vtkCallbackCommand.h>
|
||||
#include <vtkCamera.h>
|
||||
#include <vtkCommand.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkBoundingBox.h>
|
||||
#include <vtkCubeAxesActor.h>
|
||||
#include <vtkNew.h>
|
||||
#include <vtkOrientationMarkerWidget.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkProp.h>
|
||||
#include <vtkPropAssembly.h>
|
||||
#include <vtkPropPicker.h>
|
||||
#include <vtkPiecewiseFunction.h>
|
||||
#include <vtkColorTransferFunction.h>
|
||||
#include <vtkGPUVolumeRayCastMapper.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkSphereSource.h>
|
||||
#include <vtkVolume.h>
|
||||
#include <vtkVolumeProperty.h>
|
||||
|
||||
|
|
@ -77,6 +88,19 @@ VtkSceneView::VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* render
|
|||
// 近裁剪容差调小:场景含近处剖面 + 远处底图(几十km),默认容差会把近裁剪面随远面推出去、
|
||||
// 切掉离相机近的剖面。调小后近面可贴近,剖面不被切(代价:远处深度精度略降,不可见层无所谓)。
|
||||
scene_.renderer()->SetNearClippingPlaneTolerance(1e-5);
|
||||
ensureGnomon(); // 交互器若已就绪即装配角落方向标(否则首帧 render 时补装)
|
||||
}
|
||||
|
||||
VtkSceneView::~VtkSceneView() {
|
||||
// 摘除左键观察者(其 clientData=this,本对象析构后若留存会悬垂)+ 禁用 marker widget。
|
||||
// 渲染窗口/交互器可能已在 Qt 拆台中先行析构,全程判空。
|
||||
if (renderWindow_) {
|
||||
if (auto* iren = renderWindow_->GetInteractor()) {
|
||||
if (gnomonClickTag_ != 0) iren->RemoveObserver(gnomonClickTag_);
|
||||
}
|
||||
}
|
||||
gnomonClickTag_ = 0;
|
||||
if (gnomonWidget_) gnomonWidget_->SetEnabled(0);
|
||||
}
|
||||
|
||||
void VtkSceneView::removeProps(std::vector<vtkSmartPointer<vtkProp>>& props) {
|
||||
|
|
@ -464,6 +488,117 @@ void VtkSceneView::orbitToAxis(geopro::controller::ViewDir dir, const double piv
|
|||
if (onCameraChanged) onCameraChanged(); // 相机变了 → 底图按新视锥重算覆盖
|
||||
}
|
||||
|
||||
void VtkSceneView::orbitToCurrentPivot(geopro::controller::ViewDir dir) {
|
||||
// 支点 = 当前坐标轴盒中心(决策 5):有选中(贴合轴)→选中子树盒 fittedBounds_;否则全场景数据盒。
|
||||
double b[6];
|
||||
if (useFittedAxes_) {
|
||||
for (int i = 0; i < 6; ++i) b[i] = fittedBounds_[i];
|
||||
} else if (!computeDataBounds(b)) {
|
||||
return; // 无有效数据包围盒 → 无支点可绕,静默不动
|
||||
}
|
||||
const double pivot[3] = {0.5 * (b[0] + b[1]), 0.5 * (b[2] + b[3]), 0.5 * (b[4] + b[5])};
|
||||
orbitToAxis(dir, pivot); // 复用 T1:绕 pivot 转到 dir 轴、保留当前缩放
|
||||
}
|
||||
|
||||
void VtkSceneView::ensureGnomon() {
|
||||
// 幂等装配:交互器就绪后建一次。marker widget 把内部渲染器叠加到主渲染器另一图层,其相机随主相机
|
||||
// 同步 → 三向标随场景朝向转(widget 的核心价值)。6 个方向球组进同一 vtkPropAssembly 作可点击热区。
|
||||
if (gnomonReady_ || !renderWindow_) return;
|
||||
auto* iren = renderWindow_->GetInteractor();
|
||||
if (!iren) return; // QVTK 尚未提供交互器 → 下一帧 render 再补装
|
||||
|
||||
// 经典 XYZ 三箭头 + 轴标签作视觉主体(不参与拾取,避免命中箭头无方向语义)。
|
||||
vtkNew<vtkAxesActor> axes;
|
||||
axes->SetPickable(0);
|
||||
axes->AxisLabelsOn();
|
||||
|
||||
vtkNew<vtkPropAssembly> marker;
|
||||
marker->AddPart(axes);
|
||||
|
||||
// 6 个方向球:±X/±Y/±Z。位置对称于原点(widget 要求 marker 包围盒关于原点对称以正确同步相机)。
|
||||
// 正向球置于对应箭头尖端(与 axes 默认 TotalLength≈1 匹配)、稍亮;负向球在对侧、稍暗但仍可见可点。
|
||||
// 方向 → ViewDir 与 CameraPreset 语义一致:+Z=Top、−Z=Bottom、+Y=Back、−Y=Front、+X=Right、−X=Left。
|
||||
struct DirSpec {
|
||||
geopro::controller::ViewDir dir;
|
||||
double pos[3];
|
||||
double col[3];
|
||||
};
|
||||
const double L = 1.0; // 球心到原点距离(与轴长匹配)
|
||||
const DirSpec specs[6] = {
|
||||
{geopro::controller::ViewDir::Right, {L, 0, 0}, {1.0, 0.30, 0.30}}, // +X
|
||||
{geopro::controller::ViewDir::Left, {-L, 0, 0}, {0.55, 0.16, 0.16}}, // −X
|
||||
{geopro::controller::ViewDir::Back, {0, L, 0}, {0.30, 1.0, 0.30}}, // +Y
|
||||
{geopro::controller::ViewDir::Front, {0, -L, 0}, {0.16, 0.55, 0.16}}, // −Y
|
||||
{geopro::controller::ViewDir::Top, {0, 0, L}, {0.40, 0.55, 1.0}}, // +Z
|
||||
{geopro::controller::ViewDir::Bottom, {0, 0, -L}, {0.22, 0.30, 0.60}}, // −Z
|
||||
};
|
||||
gnomonDirs_.clear();
|
||||
for (const auto& s : specs) {
|
||||
vtkNew<vtkSphereSource> sphere;
|
||||
sphere->SetRadius(0.18);
|
||||
sphere->SetThetaResolution(16);
|
||||
sphere->SetPhiResolution(16);
|
||||
sphere->SetCenter(s.pos[0], s.pos[1], s.pos[2]);
|
||||
vtkNew<vtkPolyDataMapper> mapper;
|
||||
mapper->SetInputConnection(sphere->GetOutputPort());
|
||||
auto actor = vtkSmartPointer<vtkActor>::New();
|
||||
actor->SetMapper(mapper);
|
||||
actor->GetProperty()->SetColor(s.col[0], s.col[1], s.col[2]);
|
||||
actor->SetPickable(1);
|
||||
marker->AddPart(actor);
|
||||
gnomonDirs_[actor.Get()] = s.dir; // 组装体持 actor 保活;此处仅记裸指针→方向
|
||||
}
|
||||
|
||||
gnomonWidget_ = vtkSmartPointer<vtkOrientationMarkerWidget>::New();
|
||||
gnomonWidget_->SetOrientationMarker(marker);
|
||||
gnomonWidget_->SetDefaultRenderer(scene_.renderer()); // 父渲染器(相机同步源)
|
||||
gnomonWidget_->SetInteractor(iren);
|
||||
gnomonWidget_->SetViewport(0.82, 0.80, 1.0, 1.0); // 右上角:避开左上工具条 + 底部沿线条
|
||||
gnomonWidget_->SetInteractive(0); // 禁 widget 自身拖动/缩放,仅作方向选择器
|
||||
gnomonWidget_->SetEnabled(1);
|
||||
|
||||
gnomonPicker_ = vtkSmartPointer<vtkPropPicker>::New();
|
||||
|
||||
// 左键高优先级(1.0)观察者:先于交互样式(0.0),命中方向球 → orbit + abort 消费(阻止相机旋转/拾取)。
|
||||
gnomonClickCmd_ = vtkSmartPointer<vtkCallbackCommand>::New();
|
||||
gnomonClickCmd_->SetClientData(this);
|
||||
gnomonClickCmd_->SetCallback([](vtkObject*, unsigned long, void* client, void*) {
|
||||
static_cast<VtkSceneView*>(client)->handleGnomonClick();
|
||||
});
|
||||
gnomonClickTag_ = iren->AddObserver(vtkCommand::LeftButtonPressEvent, gnomonClickCmd_, 1.0);
|
||||
|
||||
gnomonReady_ = true;
|
||||
}
|
||||
|
||||
void VtkSceneView::handleGnomonClick() {
|
||||
if (!gnomonWidget_ || !gnomonPicker_ || !renderWindow_) return;
|
||||
auto* iren = renderWindow_->GetInteractor();
|
||||
auto* gren = gnomonWidget_->GetRenderer();
|
||||
if (!iren || !gren) return;
|
||||
const int ex = iren->GetEventPosition()[0];
|
||||
const int ey = iren->GetEventPosition()[1];
|
||||
// 仅当点击落在 gnomon 角落视口矩形内才拾取(否则放行正常场景交互,且省去全场景每次左键的硬件拾取)。
|
||||
const double* vp = gren->GetViewport(); // 归一化 [xmin,ymin,xmax,ymax](已含父视口换算)
|
||||
const int* sz = renderWindow_->GetSize();
|
||||
if (sz[0] <= 0 || sz[1] <= 0) return;
|
||||
const double fx = static_cast<double>(ex) / sz[0];
|
||||
const double fy = static_cast<double>(ey) / sz[1];
|
||||
if (fx < vp[0] || fx > vp[2] || fy < vp[1] || fy > vp[3]) return; // 不在角落 → 放行
|
||||
// 角落内硬件拾取:命中某方向球 → 取其 ViewDir → 绕当前轴盒中心转到该轴(保留缩放)。
|
||||
if (gnomonPicker_->PickProp(ex, ey, gren)) {
|
||||
vtkProp* leaf = nullptr;
|
||||
if (auto* path = gnomonPicker_->GetPath()) {
|
||||
if (auto* node = path->GetLastNode()) leaf = node->GetViewProp(); // 组装体叶子=方向球
|
||||
}
|
||||
if (!leaf) leaf = gnomonPicker_->GetViewProp();
|
||||
auto it = gnomonDirs_.find(leaf);
|
||||
if (it != gnomonDirs_.end()) {
|
||||
orbitToCurrentPivot(it->second);
|
||||
if (gnomonClickCmd_) gnomonClickCmd_->SetAbortFlag(1); // 消费:不触发相机旋转/场景拾取
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VtkSceneView::rebuildAxes() {
|
||||
// 先移除上一次的坐标轴 prop:render 可能在一次 rebuild 内多次调用(末尾统一 render +
|
||||
// 异步回灌 render),不先移除会叠加坐标轴(评审 HIGH)。移除后再算 bounds(仅数据图元)。
|
||||
|
|
@ -514,6 +649,7 @@ void VtkSceneView::showSceneAxes() {
|
|||
}
|
||||
|
||||
void VtkSceneView::render(bool is2D, bool resetCamera) {
|
||||
ensureGnomon(); // 构造时交互器未就绪则于此补装(幂等)
|
||||
// 视图区背景永远深色(规范 §0.5:不随明暗切换),让色阶数据更突出。
|
||||
double bgR, bgG, bgB;
|
||||
geopro::app::vtkBackground(bgR, bgG, bgB);
|
||||
|
|
@ -539,6 +675,7 @@ void VtkSceneView::render(bool is2D, bool resetCamera) {
|
|||
}
|
||||
|
||||
void VtkSceneView::renderIncremental() {
|
||||
ensureGnomon(); // 幂等:交互器就绪后补装角落方向标
|
||||
// 增量渲染:仅按新包围盒重建坐标轴并提交,不动相机(勾选/取消时视角不跳)。
|
||||
rebuildAxes();
|
||||
scene_.renderer()->ResetCameraClippingRange(); // 数据/底图变化后扩裁剪面,防被切
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ class vtkRenderWindow;
|
|||
class vtkProp;
|
||||
class vtkActor;
|
||||
class vtkVolume;
|
||||
class vtkOrientationMarkerWidget;
|
||||
class vtkPropPicker;
|
||||
class vtkCallbackCommand;
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
|
|
@ -32,6 +35,7 @@ public:
|
|||
// 入参生命周期须覆盖本对象(由调用方保证)。zRefElev:地形 z 基准(测线地表高程)。
|
||||
VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
|
||||
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev);
|
||||
~VtkSceneView() override; // 摘除 gnomon 左键观察者(clientData=this),禁用 marker widget
|
||||
|
||||
void clear() override;
|
||||
void setVerticalExaggeration(double ve) override;
|
||||
|
|
@ -75,6 +79,11 @@ public:
|
|||
void showFittedAxes(const double b[6]);
|
||||
// 取消选中 → 恢复全场景总览轴(现状默认行为,立即提交渲染)。
|
||||
void showSceneAxes();
|
||||
// ── 可点击方向标 gnomon(spec §3.3;T3)─────────────────────────────────────────
|
||||
// 绕【当前坐标轴盒中心】转到 dir 轴、保留当前缩放:支点 = 有选中(useFittedAxes_)→选中子树盒
|
||||
// fittedBounds_ 中心,否则全场景数据盒 computeDataBounds 中心。无有效数据 → no-op。
|
||||
// 封装决策 5 的支点规则,调用方只需给方向(角落 gnomon 点击即调此)。
|
||||
void orbitToCurrentPivot(geopro::controller::ViewDir dir);
|
||||
void render(bool is2D, bool resetCamera = true) override;
|
||||
void renderIncremental() override;
|
||||
|
||||
|
|
@ -117,6 +126,12 @@ private:
|
|||
void removeProps(std::vector<vtkSmartPointer<vtkProp>>& props); // 从 renderer 移除并清空
|
||||
// 仅数据图元(剖面/体素/地形/测线)的包围盒,不含底图 → 坐标轴/取景不被~公里级底图撑大。
|
||||
bool computeDataBounds(double out[6]) const;
|
||||
// 角落可点击方向标 gnomon(T3):首次(交互器就绪)时装配 marker widget + 6 向可拾取球 + 左键观察者。
|
||||
// 幂等:装配后置 gnomonReady_,重复调直接返回。render/renderIncremental/构造均可安全调用。
|
||||
void ensureGnomon();
|
||||
// 左键按下高优先级(先于交互样式)回调:点在 gnomon 角落视口且命中方向球 → orbitToCurrentPivot + abort
|
||||
// (消费事件,阻止相机旋转/场景拾取);否则放行正常交互。
|
||||
void handleGnomonClick();
|
||||
|
||||
public:
|
||||
// 当前所有数据图元(剖面等)合并范围的水平半径(米);无数据返回 0。供底图动态定最大范围。
|
||||
|
|
@ -180,6 +195,17 @@ private:
|
|||
|
||||
// 哪些 dsProps_ 条目是 2D 足迹(addMapLine):供足迹 actor 归属识别(Task E2/F2 用)。
|
||||
std::set<std::string> mapLineDs_;
|
||||
|
||||
// ── 可点击方向标 gnomon(T3)──────────────────────────────────────────────────
|
||||
// marker widget(内部叠加渲染器,相机随主相机同步 → 三向标随场景转);SetInteractive(false)
|
||||
// 禁自身拖动/缩放,仅作可点击方向选择器。gnomonPicker_ 在其内部渲染器上做硬件拾取。
|
||||
vtkSmartPointer<vtkOrientationMarkerWidget> gnomonWidget_;
|
||||
vtkSmartPointer<vtkPropPicker> gnomonPicker_;
|
||||
vtkSmartPointer<vtkCallbackCommand> gnomonClickCmd_; // 左键观察者命令(可条件 SetAbortFlag 消费)
|
||||
unsigned long gnomonClickTag_ = 0; // 观察者句柄(析构时摘除)
|
||||
bool gnomonReady_ = false; // 已装配(幂等 ensureGnomon)
|
||||
// 6 个方向球 actor → ViewDir 映射(拾取命中球 → 该方向)。actor 由 marker 组装体持有保活。
|
||||
std::map<vtkProp*, geopro::controller::ViewDir> gnomonDirs_;
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
Loading…
Reference in New Issue