fix(vtk): 切片单击=仅选中(不动相机,去跳)+高亮反馈

- onPicked 去掉改相机焦点/位置(实测仍致点击跳变)→ 单击仅选中命中切片;
  拖动旋转回归默认 TrackballCamera(绕场景中心,不跳)。'以切片为中心旋转'(C38)
  因致跳且预期不清,暂去, 后续用更稳方式再加。
- SliceTool::setSelected: 选中切片边框高亮(亮黄粗线)、其余暗灰; InteractionManager
  在 单击/新增/关闭/双击 后 updateSelectionVisual → 解决'选中无视觉反馈'。
- ctest 221/221
This commit is contained in:
gaozheng 2026-06-16 10:12:20 +08:00
parent ff3ce27978
commit 8a06014e0b
4 changed files with 35 additions and 15 deletions

View File

@ -59,6 +59,11 @@ void InteractionManager::safeRender() {
if (renderWindow_ && !destroying_) renderWindow_->Render();
}
void InteractionManager::updateSelectionVisual() {
for (std::size_t i = 0; i < slices_.size(); ++i)
slices_[i]->setSelected(static_cast<int>(i) == selected_);
}
void InteractionManager::setVolumeImage(vtkImageData* image, const geopro::core::ColorScale& cs,
double vmin, double vmax) {
// 体素重建/变更:先释放旧切片(旧 image 即将失效),再附着新 image。
@ -74,6 +79,7 @@ void InteractionManager::addSlice(SliceAxis axis) {
auto tool = std::make_unique<SliceTool>(image_, interactor_, axis, colorScale_, vmin_, vmax_);
slices_.push_back(std::move(tool));
selected_ = static_cast<int>(slices_.size()) - 1; // 新切片选中
updateSelectionVisual();
safeRender();
}
@ -84,6 +90,7 @@ void InteractionManager::closeSelected() {
// 选中停在原位就近(删后该位变成下一张;删的是末张则退一张),不跳回 0评审 M2
selected_ = slices_.empty() ? -1
: std::min(selected_, static_cast<int>(slices_.size()) - 1);
updateSelectionVisual();
safeRender();
}
@ -124,22 +131,13 @@ int InteractionManager::nearestSlice(const Vec3& worldPoint) const {
}
void InteractionManager::onPicked(const Vec3& worldPoint) {
// 旋转中心设到命中点spec C38/D39焦点与相机位置**同步平移同一 delta**
// 使渲染图像不变(视向/距离不变)、只把旋转中心移到命中点——否则只改焦点会让视图突然跳变(评审)。
if (renderer_) {
if (auto* cam = renderer_->GetActiveCamera()) {
double f[3], p[3];
cam->GetFocalPoint(f);
cam->GetPosition(p);
const double d[3] = {worldPoint[0] - f[0], worldPoint[1] - f[1], worldPoint[2] - f[2]};
cam->SetFocalPoint(worldPoint[0], worldPoint[1], worldPoint[2]);
cam->SetPosition(p[0] + d[0], p[1] + d[1], p[2] + d[2]);
renderer_->ResetCameraClippingRange();
}
}
// 若命中点落在某切片上(阈值内),选中之(供滚轮推进/关闭)。
// 单击 = 仅选中命中的切片(不动相机 → 绝不跳;拖动旋转交给默认 TrackballCamera
// 原"把焦点移到命中点以绕其旋转"实测会让视图跳变,去掉。选中切片高亮 + 供滚轮推进/关闭。
const int idx = nearestSlice(worldPoint);
if (idx >= 0) selected_ = idx;
if (idx >= 0) {
selected_ = idx;
updateSelectionVisual();
}
safeRender();
}
@ -149,6 +147,7 @@ void InteractionManager::onDoubleClicked(const Vec3& worldPoint) {
auto* cam = renderer_->GetActiveCamera();
if (!cam) return;
selected_ = idx;
updateSelectionVisual();
const Vec3 focal = slices_[static_cast<std::size_t>(idx)]->center();
const Vec3 normal = slices_[static_cast<std::size_t>(idx)]->normal();
const double dist = cam->GetDistance(); // 保持当前观察距离

View File

@ -71,6 +71,9 @@ private:
// 统一重绘:析构进行中(destroying_)跳过,避免 Qt 拆台时对半析构窗口 Render 崩溃(评审 H3
void safeRender();
// 按 selected_ 刷新各切片高亮(选中亮黄、其余暗淡)。
void updateSelectionVisual();
vtkRenderWindowInteractor* interactor_;
vtkRenderWindow* renderWindow_;
vtkRenderer* renderer_;

View File

@ -6,6 +6,7 @@
#include <vtkImageData.h>
#include <vtkImagePlaneWidget.h>
#include <vtkLookupTable.h>
#include <vtkProperty.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTrivialProducer.h>
@ -129,6 +130,20 @@ double SliceTool::distanceToPlane(const Vec3& p) const {
return std::abs(dot({p[0] - c[0], p[1] - c[1], p[2] - c[2]}, n));
}
void SliceTool::setSelected(bool sel) {
if (!widget_) return;
// 切片边框 = widget 的 PlaneProperty选中→亮黄粗线未选中→暗灰细线。
if (auto* prop = widget_->GetPlaneProperty()) {
if (sel) {
prop->SetColor(1.0, 0.9, 0.1);
prop->SetLineWidth(3.0);
} else {
prop->SetColor(0.5, 0.5, 0.5);
prop->SetLineWidth(1.0);
}
}
}
void SliceTool::close() {
if (!widget_) return;
widget_->Off();

View File

@ -45,6 +45,9 @@ public:
// 沿法向推进切面滚轮D46origin += normal*step夹在 image 包围盒内。
void advance(double step);
// 选中视觉反馈:选中→高亮边框(亮黄+粗线),未选中→暗淡细线。
void setSelected(bool sel);
// 世界点到本切面(无限平面)的垂直距离绝对值。供 picker 命中判定"点在哪张切片上"。
double distanceToPlane(const Vec3& worldPoint) const;