fix(vtk): P3 切片交互手感修复(用户实测反馈)

- 删左上「视图详情」遗留的禁用「切片」复选框(P1占位,已被P3工具条取代)
- 点击跳变 + 绕点旋转无效: onPicked 焦点与相机位置同步平移同一delta,
  图像不跳、旋转中心移到命中点(原来只改焦点致视图突变)
- 双击正视无效: 弃 vtkRenderHWindowInteractor::GetRepeatCount(QVTK+Win不可靠),
  改 std::chrono 手动判双击(间隔<350ms且位置相近); 避开 vtkTimerLog 依赖
- 选中移除: 随上面拾取修复,单击切片可选中→关闭移除选中项(不再只能倒序)
This commit is contained in:
gaozheng 2026-06-16 09:09:38 +08:00
parent 85d4ff57df
commit 87b90a2022
4 changed files with 52 additions and 12 deletions

View File

@ -416,20 +416,15 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
chkVoxel->setChecked(false); chkVoxel->setChecked(false);
auto* chkTerrain = new QCheckBox(QStringLiteral("地形DEM+影像)")); auto* chkTerrain = new QCheckBox(QStringLiteral("地形DEM+影像)"));
chkTerrain->setChecked(false); chkTerrain->setChecked(false);
auto* chkSlice = new QCheckBox(QStringLiteral("切片dd_slice")); // 切片交互已由 P3 左下「切片」工具条提供,此处不再放(原 P1 占位禁用项已移除)。
chkSlice->setChecked(false);
if (!crsAvailable) { // PROJ 不可用 → 体素/地形层(都需配准)禁用并提示 if (!crsAvailable) { // PROJ 不可用 → 体素/地形层(都需配准)禁用并提示
const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用"); const QString tip = QStringLiteral("PROJ 数据(proj.db)缺失,配准不可用");
chkVoxel->setEnabled(false); chkVoxel->setToolTip(tip); chkVoxel->setEnabled(false); chkVoxel->setToolTip(tip);
chkTerrain->setEnabled(false); chkTerrain->setToolTip(tip); chkTerrain->setEnabled(false); chkTerrain->setToolTip(tip);
} }
// 切片dd_slice交互切片留待 P3本轮禁用。
chkSlice->setEnabled(false);
chkSlice->setToolTip(QStringLiteral("(切片交互 P3 接入)"));
layerLayout->addWidget(layerTitle); layerLayout->addWidget(layerTitle);
layerLayout->addWidget(chkCurtain); layerLayout->addWidget(chkCurtain);
layerLayout->addWidget(chkVoxel); layerLayout->addWidget(chkVoxel);
layerLayout->addWidget(chkSlice);
layerLayout->addWidget(chkTerrain); layerLayout->addWidget(chkTerrain);
layerPanel->setVisible(false); // 默认二维,不显示图层浮层 layerPanel->setVisible(false); // 默认二维,不显示图层浮层

View File

@ -124,10 +124,18 @@ int InteractionManager::nearestSlice(const Vec3& worldPoint) const {
} }
void InteractionManager::onPicked(const Vec3& worldPoint) { void InteractionManager::onPicked(const Vec3& worldPoint) {
// 焦点设到命中点 → 拖动绕其旋转spec C38/D39 // 旋转中心设到命中点spec C38/D39焦点与相机位置**同步平移同一 delta**
// 使渲染图像不变(视向/距离不变)、只把旋转中心移到命中点——否则只改焦点会让视图突然跳变(评审)。
if (renderer_) { if (renderer_) {
if (auto* cam = renderer_->GetActiveCamera()) 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->SetFocalPoint(worldPoint[0], worldPoint[1], worldPoint[2]);
cam->SetPosition(p[0] + d[0], p[1] + d[1], p[2] + d[2]);
renderer_->ResetCameraClippingRange();
}
} }
// 若命中点落在某切片上(阈值内),选中之(供滚轮推进/关闭)。 // 若命中点落在某切片上(阈值内),选中之(供滚轮推进/关闭)。
const int idx = nearestSlice(worldPoint); const int idx = nearestSlice(worldPoint);

View File

@ -1,5 +1,7 @@
#include "interact/PickInteractorStyle.hpp" #include "interact/PickInteractorStyle.hpp"
#include <chrono>
#include <vtkCellPicker.h> #include <vtkCellPicker.h>
#include <vtkNew.h> #include <vtkNew.h>
#include <vtkObjectFactory.h> #include <vtkObjectFactory.h>
@ -8,6 +10,18 @@
namespace geopro::render::interact { 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); vtkStandardNewMacro(PickInteractorStyle);
bool PickInteractorStyle::pickWorld(Vec3& out) { bool PickInteractorStyle::pickWorld(Vec3& out) {
@ -32,13 +46,31 @@ void PickInteractorStyle::OnLeftButtonDown() {
Vec3 world; Vec3 world;
const bool hit = pickWorld(world); const bool hit = pickWorld(world);
if (hit && iren && iren->GetRepeatCount() > 0) { // 手动双击判定GetRepeatCount 在 QVTK+Windows 不可靠,评审 M5
// 双击命中 → 正视所在切片(交给 manager 找最近切片 + 算相机)。 // 两次左键按下间隔 < 阈值且屏幕位置相近 → 双击。
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); if (onDoubleClick) onDoubleClick(world);
return; // 不进入拖动旋转 lastDownTime_ = -1.0; // 重置,避免三击连判
return; // 不进入拖动旋转
} }
if (hit) { if (hit) {
// 单击命中 → 选中 + 以命中点为焦点(拖动绕其旋转)。 // 单击命中 → 选中 + 设旋转中心为命中点(拖动绕其旋转)。
if (onPick) onPick(world); if (onPick) onPick(world);
} }
// 始终保留 TrackballCamera 默认拖动(旋转/平移)。 // 始终保留 TrackballCamera 默认拖动(旋转/平移)。

View File

@ -38,6 +38,11 @@ protected:
private: private:
// 在当前鼠标位置拾取世界点;命中返回 true 并填 out。 // 在当前鼠标位置拾取世界点;命中返回 true 并填 out。
bool pickWorld(Vec3& out); bool pickWorld(Vec3& out);
// 手动双击判定QVTK+Windows 下 vtkRenderWindowInteractor::GetRepeatCount() 不可靠(评审 M5
// 记上次左键按下时刻+屏幕位置,两次按下间隔 < kDoubleClickMs 且位置相近视为双击。
double lastDownTime_ = -1.0; // 单调时钟(毫秒)-1=无
int lastDownPos_[2] = {0, 0};
}; };
} // namespace geopro::render::interact } // namespace geopro::render::interact