geopro/src/render/interact/SliceTool.cpp

140 lines
5.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "interact/SliceTool.hpp"
#include <algorithm>
#include <cmath>
#include <vtkImageData.h>
#include <vtkImagePlaneWidget.h>
#include <vtkLookupTable.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTrivialProducer.h>
#include "ColorLutBuilder.hpp"
namespace geopro::render::interact {
namespace {
// 任意切片初始法向45°XZ 面内);轴向用 SetPlaneOrientationTo*。
constexpr double kSqrt2Inv = 0.70710678118654752440;
} // namespace
SliceTool::SliceTool(vtkImageData* image, vtkRenderWindowInteractor* interactor, SliceAxis axis,
const geopro::core::ColorScale& cs, double vmin, double vmax)
: axis_(axis), image_(image), widget_(vtkSmartPointer<vtkImagePlaneWidget>::New()) {
// 经 trivial producer 把已存在的 vtkImageData 接入 widgetwidget 只暴露 SetInputConnection
// producer_ 为成员,随 SliceTool 保活(局部变量会构造后即析构→管线断裂,评审 H1
producer_ = vtkSmartPointer<vtkTrivialProducer>::New();
producer_->SetOutput(image_);
widget_->SetInputConnection(producer_->GetOutputPort());
widget_->SetInteractor(interactor);
widget_->RestrictPlaneToVolumeOn(); // 切面限制在体内,滚轮推进不跑飞
widget_->SetResliceInterpolateToLinear(); // reslice 线性插值出连续剖面(非 cutter 交线)
widget_->TextureInterpolateOn();
widget_->DisplayTextOff();
// 色阶 LUT 套用:用户自管 LUT不让 widget 用默认灰度窗位)。
auto lut = buildLut(cs, vmin, vmax);
widget_->SetLookupTable(lut);
// 轴向:固定到 X/Y/Z角度不可调符合 G22G24
// 上下=水平面=Z 法向;前后=Y 法向;左右=X 法向。
switch (axis_) {
case SliceAxis::UpDown:
widget_->SetPlaneOrientationToZAxes();
break;
case SliceAxis::FrontBack:
widget_->SetPlaneOrientationToYAxes();
break;
case SliceAxis::LeftRight:
widget_->SetPlaneOrientationToXAxes();
break;
case SliceAxis::Oblique: {
// 任意 45°F25vtkImagePlaneWidget 用 Origin/Point1/Point2 三角点定义平面
// (无 SetNormal。法向 = (Point1-Origin)×(Point2-Origin)。
// 取法向 (sin45,0,cos45)in-plane 轴1 = Y(0,1,0)轴2 = XZ 内与法向正交方向 (cos45,0,-sin45)。
// 以体中心为面心,沿两轴各展半个体范围,得一张斜插体的对角面(可继续交互旋转)。
const auto b = imageBounds();
const double cx = 0.5 * (b[0] + b[1]);
const double cy = 0.5 * (b[2] + b[3]);
const double cz = 0.5 * (b[4] + b[5]);
const double hy = 0.5 * (b[3] - b[2]);
// 轴2 半长取 X/Z 范围的较大者,保证面铺满体对角。
const double hxz = 0.5 * std::max(b[1] - b[0], b[5] - b[4]);
// 轴1 = +Y轴2 = (cos45,0,-sin45)。
const double a2x = kSqrt2Inv, a2z = -kSqrt2Inv;
// Origin = center - 0.5*axis1 - 0.5*axis2使 center 为面心)。
const double ox = cx - 0.0 - a2x * hxz;
const double oy = cy - hy - 0.0;
const double oz = cz - 0.0 - a2z * hxz;
widget_->SetOrigin(ox, oy, oz);
widget_->SetPoint1(ox + 0.0, oy + 2.0 * hy, oz + 0.0); // 沿 +Y
widget_->SetPoint2(ox + a2x * 2.0 * hxz, oy, oz + a2z * 2.0 * hxz); // 沿 (cos45,0,-sin45)
widget_->UpdatePlacement();
break;
}
}
widget_->On();
// 关闭 widget 自身的鼠标交互(窗位/光标/拖动):否则它会"吃掉"落在切片面上的左键,
// 自定义 PickInteractorStyle 收不到 → 单击选中/双击正视/绕点旋转全失效(实测根因)。
// 关掉后切片仍正常显示,点击穿透到样式;切面移动改由滚轮(advance)驱动。
widget_->InteractionOff();
}
SliceTool::~SliceTool() { close(); }
std::array<double, 6> SliceTool::imageBounds() const {
std::array<double, 6> b{{0, 0, 0, 0, 0, 0}};
if (image_) image_->GetBounds(b.data());
return b;
}
Vec3 SliceTool::normal() const {
double n[3] = {0, 0, 1};
if (widget_) widget_->GetNormal(n);
return normalize({n[0], n[1], n[2]});
}
Vec3 SliceTool::center() const {
double c[3] = {0, 0, 0};
if (widget_) widget_->GetCenter(c);
return {c[0], c[1], c[2]};
}
void SliceTool::advance(double step) {
if (!widget_) return;
// 沿法向刚性平移整张切面origin/point1/point2 同步加 normal*step。只移 origin 会让
// 面内两端点不动→平面变形/脱轴(评审 M1。RestrictPlaneToVolumeOn 负责夹在体内。
const Vec3 n = normal();
const double d[3] = {n[0] * step, n[1] * step, n[2] * step};
double o[3], p1[3], p2[3];
widget_->GetOrigin(o);
widget_->GetPoint1(p1);
widget_->GetPoint2(p2);
for (int i = 0; i < 3; ++i) {
o[i] += d[i];
p1[i] += d[i];
p2[i] += d[i];
}
widget_->SetOrigin(o);
widget_->SetPoint1(p1);
widget_->SetPoint2(p2);
widget_->UpdatePlacement();
}
double SliceTool::distanceToPlane(const Vec3& p) const {
const Vec3 c = center();
const Vec3 n = normal();
return std::abs(dot({p[0] - c[0], p[1] - c[1], p[2] - c[2]}, n));
}
void SliceTool::close() {
if (!widget_) return;
widget_->Off();
widget_->SetInteractor(nullptr); // 解除观察者,防悬挂崩溃
widget_ = nullptr; // 置空 → 二次 close()/析构真正幂等(不再 Off 已解绑 widget
}
} // namespace geopro::render::interact