140 lines
5.1 KiB
C++
140 lines
5.1 KiB
C++
#include "interact/PickInteractorStyle.hpp"
|
||
|
||
#include <chrono>
|
||
|
||
#include <vtkCamera.h>
|
||
#include <vtkCellPicker.h>
|
||
#include <vtkMath.h>
|
||
#include <vtkNew.h>
|
||
#include <vtkObjectFactory.h>
|
||
#include <vtkRenderWindow.h>
|
||
#include <vtkRenderWindowInteractor.h>
|
||
#include <vtkRenderer.h>
|
||
#include <vtkTransform.h>
|
||
|
||
namespace geopro::render::interact {
|
||
|
||
namespace {
|
||
constexpr double kDoubleClickMs = 350.0; // 两次左键按下间隔阈值
|
||
constexpr int kClickSlopPx2 = 36; // 位置相近阈值平方(6px)
|
||
|
||
// 当前单调时钟(毫秒)。用 std::chrono 避免依赖 VTK::CommonSystem(vtkTimerLog)。
|
||
double nowMs() {
|
||
return std::chrono::duration<double, std::milli>(
|
||
std::chrono::steady_clock::now().time_since_epoch())
|
||
.count();
|
||
}
|
||
} // namespace
|
||
|
||
vtkStandardNewMacro(PickInteractorStyle);
|
||
|
||
bool PickInteractorStyle::pickWorld(Vec3& out) {
|
||
auto* iren = this->GetInteractor();
|
||
if (!iren) return false;
|
||
const int* pos = iren->GetEventPosition();
|
||
// 用交互器解析被点中的 renderer(基类 FindPokedRenderer 仅设 CurrentRenderer、返回 void)。
|
||
auto* ren = iren->FindPokedRenderer(pos[0], pos[1]);
|
||
if (!ren) return false;
|
||
// CellPicker:返回表面交点世界坐标(命中切片纹理面/帘面等)。
|
||
vtkNew<vtkCellPicker> picker;
|
||
picker->SetTolerance(0.005);
|
||
if (!picker->Pick(pos[0], pos[1], 0.0, ren)) return false;
|
||
double w[3];
|
||
picker->GetPickPosition(w);
|
||
out = {w[0], w[1], w[2]};
|
||
return true;
|
||
}
|
||
|
||
void PickInteractorStyle::OnLeftButtonDown() {
|
||
auto* iren = this->GetInteractor();
|
||
Vec3 world;
|
||
const bool hit = pickWorld(world);
|
||
|
||
// 手动双击判定(GetRepeatCount 在 QVTK+Windows 不可靠,评审 M5):
|
||
// 两次左键按下间隔 < 阈值且屏幕位置相近 → 双击。
|
||
const double now = nowMs();
|
||
const int* pos = iren ? iren->GetEventPosition() : nullptr;
|
||
bool isDouble = false;
|
||
if (hit && pos && lastDownTime_ >= 0.0) {
|
||
const double dtMs = now - lastDownTime_;
|
||
const int dx = pos[0] - lastDownPos_[0];
|
||
const int dy = pos[1] - lastDownPos_[1];
|
||
if (dtMs < kDoubleClickMs && (dx * dx + dy * dy) <= kClickSlopPx2) isDouble = true;
|
||
}
|
||
if (pos) {
|
||
lastDownPos_[0] = pos[0];
|
||
lastDownPos_[1] = pos[1];
|
||
}
|
||
lastDownTime_ = now;
|
||
|
||
if (isDouble) {
|
||
// 双击命中 → 正视所在切片(manager 找最近切片 + 算相机)。
|
||
if (onDoubleClick) onDoubleClick(world);
|
||
lastDownTime_ = -1.0; // 重置,避免三击连判
|
||
return; // 不进入拖动旋转
|
||
}
|
||
if (hit) {
|
||
// 单击命中 → 选中所在切片(onPick 内仅选中, 不动相机)。
|
||
if (onPick) onPick(world);
|
||
}
|
||
// 不在按下时动相机(动相机=跳);绕选中物旋转在 Rotate() 内做(增量绕支点,不跳)。
|
||
Superclass::OnLeftButtonDown();
|
||
}
|
||
|
||
void PickInteractorStyle::Rotate() {
|
||
Vec3 c;
|
||
if (!this->CurrentRenderer || !getRotateCenter || !getRotateCenter(c)) {
|
||
Superclass::Rotate(); // 无选中物 → 默认绕焦点旋转
|
||
return;
|
||
}
|
||
auto* rwi = this->Interactor;
|
||
auto* cam = this->CurrentRenderer->GetActiveCamera();
|
||
if (!rwi || !cam) return;
|
||
const int dx = rwi->GetEventPosition()[0] - rwi->GetLastEventPosition()[0];
|
||
const int dy = rwi->GetEventPosition()[1] - rwi->GetLastEventPosition()[1];
|
||
const int* size = this->CurrentRenderer->GetRenderWindow()->GetSize();
|
||
if (size[0] <= 0 || size[1] <= 0) return;
|
||
// 与 TrackballCamera 同口径的角度映射。
|
||
const double azimuth = dx * (-20.0 / size[0]) * this->MotionFactor;
|
||
const double elevation = dy * (-20.0 / size[1]) * this->MotionFactor;
|
||
|
||
double up[3], dop[3], right[3];
|
||
cam->GetViewUp(up);
|
||
cam->GetDirectionOfProjection(dop); // 归一化的 (focal-pos)
|
||
vtkMath::Cross(dop, up, right); // 屏幕"右"轴
|
||
vtkMath::Normalize(right);
|
||
|
||
// 绕中心 c 的支点:T(c)·R(up,azimuth)·R(right,elevation)·T(-c),作用于 position/focal;up 只转不平移。
|
||
vtkNew<vtkTransform> t;
|
||
t->Identity();
|
||
t->Translate(c[0], c[1], c[2]);
|
||
t->RotateWXYZ(azimuth, up[0], up[1], up[2]);
|
||
t->RotateWXYZ(elevation, right[0], right[1], right[2]);
|
||
t->Translate(-c[0], -c[1], -c[2]);
|
||
|
||
double pos[3], foc[3], npos[3], nfoc[3], nup[3];
|
||
cam->GetPosition(pos);
|
||
cam->GetFocalPoint(foc);
|
||
t->TransformPoint(pos, npos);
|
||
t->TransformPoint(foc, nfoc);
|
||
t->TransformVector(up, nup); // 仅旋转部分作用于向量
|
||
cam->SetPosition(npos);
|
||
cam->SetFocalPoint(nfoc);
|
||
cam->SetViewUp(nup);
|
||
cam->OrthogonalizeViewUp();
|
||
if (this->AutoAdjustCameraClippingRange) this->CurrentRenderer->ResetCameraClippingRange();
|
||
rwi->Render();
|
||
}
|
||
|
||
void PickInteractorStyle::OnMouseWheelForward() {
|
||
if (onWheelStep && onWheelStep(+1)) return; // 有选中切片 → 推进,消费滚轮
|
||
Superclass::OnMouseWheelForward(); // 否则默认缩放
|
||
}
|
||
|
||
void PickInteractorStyle::OnMouseWheelBackward() {
|
||
if (onWheelStep && onWheelStep(-1)) return;
|
||
Superclass::OnMouseWheelBackward();
|
||
}
|
||
|
||
} // namespace geopro::render::interact
|