feat(vtk): 角落可点击方向标gnomon→绕当前轴盒中心转到该轴(保缩放)
T3(spec 2026-07-01 §3.3/决策4·5):VtkSceneView 装配常驻右上角 gnomon (vtkOrientationMarkerWidget + vtkAxesActor 三向箭头/轴标签,相机随主相机同步)。 组装体内加 6 个方向球(±X/±Y/±Z, vtkPropAssembly)作可点击热区;左键高优先级 (1.0)观察者在角落视口内做 vtkPropPicker 硬件拾取,命中球→orbitToCurrentPivot (该方向)并 SetAbortFlag 消费(不触发相机旋转/场景拾取)。 orbitToCurrentPivot 封装决策5支点规则:有选中(useFittedAxes_)→子树盒 fittedBounds_ 中心,否则全场景 computeDataBounds 中心;无有效数据 no-op。 复用 T1 orbitToAxis/orbitPose(保当前缩放)。方向→ViewDir 对齐 CameraPreset: +Z=Top −Z=Bottom +Y=Back −Y=Front +X=Right −X=Left。 ensureGnomon 幂等(交互器就绪后于构造/render/renderIncremental 补装一次); 析构摘除观察者、禁用 widget。CMakeLists 增 RenderingAnnotation(vtkAxesActor) + FiltersSources(vtkSphereSource)。build.bat app 链接干净,test 473/473 通过。
This commit is contained in:
parent
f67913982d
commit
b1a8e02f6d
|
|
@ -5,8 +5,10 @@ find_package(VTK REQUIRED COMPONENTS
|
||||||
GUISupportQt
|
GUISupportQt
|
||||||
RenderingOpenGL2
|
RenderingOpenGL2
|
||||||
RenderingVolumeOpenGL2
|
RenderingVolumeOpenGL2
|
||||||
|
RenderingAnnotation # vtkAxesActor(角落方向标 gnomon 三向箭头 + 轴标签)
|
||||||
InteractionStyle
|
InteractionStyle
|
||||||
InteractionWidgets
|
InteractionWidgets
|
||||||
|
FiltersSources # vtkSphereSource(gnomon 6 向可点击方向球)
|
||||||
FiltersGeometry
|
FiltersGeometry
|
||||||
FiltersModeling
|
FiltersModeling
|
||||||
IOImage # vtkPNGWriter(切片导出图片)
|
IOImage # vtkPNGWriter(切片导出图片)
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,28 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <vtkActor.h>
|
#include <vtkActor.h>
|
||||||
|
#include <vtkAssemblyNode.h>
|
||||||
|
#include <vtkAssemblyPath.h>
|
||||||
|
#include <vtkAxesActor.h>
|
||||||
|
#include <vtkCallbackCommand.h>
|
||||||
#include <vtkCamera.h>
|
#include <vtkCamera.h>
|
||||||
|
#include <vtkCommand.h>
|
||||||
#include <vtkProperty.h>
|
#include <vtkProperty.h>
|
||||||
#include <vtkBoundingBox.h>
|
#include <vtkBoundingBox.h>
|
||||||
#include <vtkCubeAxesActor.h>
|
#include <vtkCubeAxesActor.h>
|
||||||
#include <vtkNew.h>
|
#include <vtkNew.h>
|
||||||
|
#include <vtkOrientationMarkerWidget.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
#include <vtkProp.h>
|
#include <vtkProp.h>
|
||||||
|
#include <vtkPropAssembly.h>
|
||||||
|
#include <vtkPropPicker.h>
|
||||||
#include <vtkPiecewiseFunction.h>
|
#include <vtkPiecewiseFunction.h>
|
||||||
#include <vtkColorTransferFunction.h>
|
#include <vtkColorTransferFunction.h>
|
||||||
#include <vtkGPUVolumeRayCastMapper.h>
|
#include <vtkGPUVolumeRayCastMapper.h>
|
||||||
#include <vtkRenderWindow.h>
|
#include <vtkRenderWindow.h>
|
||||||
|
#include <vtkRenderWindowInteractor.h>
|
||||||
#include <vtkRenderer.h>
|
#include <vtkRenderer.h>
|
||||||
|
#include <vtkSphereSource.h>
|
||||||
#include <vtkVolume.h>
|
#include <vtkVolume.h>
|
||||||
#include <vtkVolumeProperty.h>
|
#include <vtkVolumeProperty.h>
|
||||||
|
|
||||||
|
|
@ -77,6 +88,19 @@ VtkSceneView::VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* render
|
||||||
// 近裁剪容差调小:场景含近处剖面 + 远处底图(几十km),默认容差会把近裁剪面随远面推出去、
|
// 近裁剪容差调小:场景含近处剖面 + 远处底图(几十km),默认容差会把近裁剪面随远面推出去、
|
||||||
// 切掉离相机近的剖面。调小后近面可贴近,剖面不被切(代价:远处深度精度略降,不可见层无所谓)。
|
// 切掉离相机近的剖面。调小后近面可贴近,剖面不被切(代价:远处深度精度略降,不可见层无所谓)。
|
||||||
scene_.renderer()->SetNearClippingPlaneTolerance(1e-5);
|
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) {
|
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(); // 相机变了 → 底图按新视锥重算覆盖
|
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() {
|
void VtkSceneView::rebuildAxes() {
|
||||||
// 先移除上一次的坐标轴 prop:render 可能在一次 rebuild 内多次调用(末尾统一 render +
|
// 先移除上一次的坐标轴 prop:render 可能在一次 rebuild 内多次调用(末尾统一 render +
|
||||||
// 异步回灌 render),不先移除会叠加坐标轴(评审 HIGH)。移除后再算 bounds(仅数据图元)。
|
// 异步回灌 render),不先移除会叠加坐标轴(评审 HIGH)。移除后再算 bounds(仅数据图元)。
|
||||||
|
|
@ -514,6 +649,7 @@ void VtkSceneView::showSceneAxes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneView::render(bool is2D, bool resetCamera) {
|
void VtkSceneView::render(bool is2D, bool resetCamera) {
|
||||||
|
ensureGnomon(); // 构造时交互器未就绪则于此补装(幂等)
|
||||||
// 视图区背景永远深色(规范 §0.5:不随明暗切换),让色阶数据更突出。
|
// 视图区背景永远深色(规范 §0.5:不随明暗切换),让色阶数据更突出。
|
||||||
double bgR, bgG, bgB;
|
double bgR, bgG, bgB;
|
||||||
geopro::app::vtkBackground(bgR, bgG, bgB);
|
geopro::app::vtkBackground(bgR, bgG, bgB);
|
||||||
|
|
@ -539,6 +675,7 @@ void VtkSceneView::render(bool is2D, bool resetCamera) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VtkSceneView::renderIncremental() {
|
void VtkSceneView::renderIncremental() {
|
||||||
|
ensureGnomon(); // 幂等:交互器就绪后补装角落方向标
|
||||||
// 增量渲染:仅按新包围盒重建坐标轴并提交,不动相机(勾选/取消时视角不跳)。
|
// 增量渲染:仅按新包围盒重建坐标轴并提交,不动相机(勾选/取消时视角不跳)。
|
||||||
rebuildAxes();
|
rebuildAxes();
|
||||||
scene_.renderer()->ResetCameraClippingRange(); // 数据/底图变化后扩裁剪面,防被切
|
scene_.renderer()->ResetCameraClippingRange(); // 数据/底图变化后扩裁剪面,防被切
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ class vtkRenderWindow;
|
||||||
class vtkProp;
|
class vtkProp;
|
||||||
class vtkActor;
|
class vtkActor;
|
||||||
class vtkVolume;
|
class vtkVolume;
|
||||||
|
class vtkOrientationMarkerWidget;
|
||||||
|
class vtkPropPicker;
|
||||||
|
class vtkCallbackCommand;
|
||||||
|
|
||||||
namespace geopro::app {
|
namespace geopro::app {
|
||||||
|
|
||||||
|
|
@ -32,6 +35,7 @@ public:
|
||||||
// 入参生命周期须覆盖本对象(由调用方保证)。zRefElev:地形 z 基准(测线地表高程)。
|
// 入参生命周期须覆盖本对象(由调用方保证)。zRefElev:地形 z 基准(测线地表高程)。
|
||||||
VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
|
VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow,
|
||||||
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev);
|
std::shared_ptr<geopro::core::GeoLocalFrame> frame, double zRefElev);
|
||||||
|
~VtkSceneView() override; // 摘除 gnomon 左键观察者(clientData=this),禁用 marker widget
|
||||||
|
|
||||||
void clear() override;
|
void clear() override;
|
||||||
void setVerticalExaggeration(double ve) override;
|
void setVerticalExaggeration(double ve) override;
|
||||||
|
|
@ -75,6 +79,11 @@ public:
|
||||||
void showFittedAxes(const double b[6]);
|
void showFittedAxes(const double b[6]);
|
||||||
// 取消选中 → 恢复全场景总览轴(现状默认行为,立即提交渲染)。
|
// 取消选中 → 恢复全场景总览轴(现状默认行为,立即提交渲染)。
|
||||||
void showSceneAxes();
|
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 render(bool is2D, bool resetCamera = true) override;
|
||||||
void renderIncremental() override;
|
void renderIncremental() override;
|
||||||
|
|
||||||
|
|
@ -117,6 +126,12 @@ private:
|
||||||
void removeProps(std::vector<vtkSmartPointer<vtkProp>>& props); // 从 renderer 移除并清空
|
void removeProps(std::vector<vtkSmartPointer<vtkProp>>& props); // 从 renderer 移除并清空
|
||||||
// 仅数据图元(剖面/体素/地形/测线)的包围盒,不含底图 → 坐标轴/取景不被~公里级底图撑大。
|
// 仅数据图元(剖面/体素/地形/测线)的包围盒,不含底图 → 坐标轴/取景不被~公里级底图撑大。
|
||||||
bool computeDataBounds(double out[6]) const;
|
bool computeDataBounds(double out[6]) const;
|
||||||
|
// 角落可点击方向标 gnomon(T3):首次(交互器就绪)时装配 marker widget + 6 向可拾取球 + 左键观察者。
|
||||||
|
// 幂等:装配后置 gnomonReady_,重复调直接返回。render/renderIncremental/构造均可安全调用。
|
||||||
|
void ensureGnomon();
|
||||||
|
// 左键按下高优先级(先于交互样式)回调:点在 gnomon 角落视口且命中方向球 → orbitToCurrentPivot + abort
|
||||||
|
// (消费事件,阻止相机旋转/场景拾取);否则放行正常交互。
|
||||||
|
void handleGnomonClick();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// 当前所有数据图元(剖面等)合并范围的水平半径(米);无数据返回 0。供底图动态定最大范围。
|
// 当前所有数据图元(剖面等)合并范围的水平半径(米);无数据返回 0。供底图动态定最大范围。
|
||||||
|
|
@ -180,6 +195,17 @@ private:
|
||||||
|
|
||||||
// 哪些 dsProps_ 条目是 2D 足迹(addMapLine):供足迹 actor 归属识别(Task E2/F2 用)。
|
// 哪些 dsProps_ 条目是 2D 足迹(addMapLine):供足迹 actor 归属识别(Task E2/F2 用)。
|
||||||
std::set<std::string> mapLineDs_;
|
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
|
} // namespace geopro::app
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue