#include "interact/PickInteractorStyle.hpp" #include #include #include #include #include #include #include #include #include #include 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( 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 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 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