fix(vtk): D39 改为自定义 Rotate 绕支点增量旋转(真不跳)

前法错: 设焦点=切片中心会把相机位置挪走(透视视差)→画面平移=跳; 诊断只验了视向、漏看位置。
正确: 按下完全不动相机(不跳); 重写 Rotate(): 有选中物时, 用 T(c)R(up)R(right)T(-c) 把
相机 position/focal/up 绕选中切片中心 c 增量旋转→c 在世界/屏幕都不动、场景绕它转、无跳。
无选中回退默认绕焦点。ctest 221/221
This commit is contained in:
gaozheng 2026-06-16 11:33:03 +08:00
parent 8d94247dd9
commit 5809b88a44
2 changed files with 52 additions and 21 deletions

View File

@ -4,10 +4,13 @@
#include <vtkCamera.h> #include <vtkCamera.h>
#include <vtkCellPicker.h> #include <vtkCellPicker.h>
#include <vtkMath.h>
#include <vtkNew.h> #include <vtkNew.h>
#include <vtkObjectFactory.h> #include <vtkObjectFactory.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h> #include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h> #include <vtkRenderer.h>
#include <vtkTransform.h>
namespace geopro::render::interact { namespace geopro::render::interact {
@ -74,30 +77,55 @@ void PickInteractorStyle::OnLeftButtonDown() {
// 单击命中 → 选中所在切片onPick 内仅选中, 不动相机)。 // 单击命中 → 选中所在切片onPick 内仅选中, 不动相机)。
if (onPick) onPick(world); if (onPick) onPick(world);
} }
// D39: 有选中三维体/切片时,按下开始拖动前把焦点设到其中心——焦点与位置同步平移同一 delta // 不在按下时动相机(动相机=跳);绕选中物旋转在 Rotate() 内做(增量绕支点,不跳)。
// 视向/距离不变(画面不跳),之后默认 TrackballCamera 即绕该中心旋转。无选中则绕默认焦点。
// 只在"按下"时设(不是选中时),故切换点选切片不会跳。
if (getRotateCenter && iren) {
Vec3 c;
if (getRotateCenter(c)) {
const int* p2 = iren->GetEventPosition();
if (auto* ren = iren->FindPokedRenderer(p2[0], p2[1])) {
if (auto* cam = ren->GetActiveCamera()) {
double f[3], pp[3];
cam->GetFocalPoint(f);
cam->GetPosition(pp);
cam->SetFocalPoint(c[0], c[1], c[2]);
cam->SetPosition(pp[0] + (c[0] - f[0]), pp[1] + (c[1] - f[1]),
pp[2] + (c[2] - f[2]));
ren->ResetCameraClippingRange();
}
}
}
}
// 始终保留 TrackballCamera 默认拖动(旋转/平移)。
Superclass::OnLeftButtonDown(); 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/focalup 只转不平移。
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() { void PickInteractorStyle::OnMouseWheelForward() {
if (onWheelStep && onWheelStep(+1)) return; // 有选中切片 → 推进,消费滚轮 if (onWheelStep && onWheelStep(+1)) return; // 有选中切片 → 推进,消费滚轮
Superclass::OnMouseWheelForward(); // 否则默认缩放 Superclass::OnMouseWheelForward(); // 否则默认缩放

View File

@ -34,6 +34,9 @@ public:
void OnLeftButtonDown() override; void OnLeftButtonDown() override;
void OnMouseWheelForward() override; void OnMouseWheelForward() override;
void OnMouseWheelBackward() override; void OnMouseWheelBackward() override;
// 绕选中物中心旋转(D39):有 getRotateCenter 时, 绕该中心增量旋转整个相机(位置+焦点+up),
// 中心在世界/屏幕都不动→不跳; 否则回退默认(绕焦点)。
void Rotate() override;
protected: protected:
PickInteractorStyle() = default; PickInteractorStyle() = default;