6.交互与拾取

第四阶段:交互与拾取

前面我们只会写死窗口,模型只能默认左键旋转、滚轮缩放。本阶段搞懂:交互器样式、拾取(鼠标点击拿到点ID /面片ID)、选中高亮。

4.1 交互核心对象回顾

vtkRenderWindowInteractor interactor;   // 交互总入口:接收鼠标键盘消息
vtkInteractorStyle                     // 交互行为策略:决定鼠标按下之后做什么动作

vtkRenderWindowInteractor 负责接收系统鼠标、键盘事件,但它自己不知道怎么处理这些事件,真正定义“左键干什么、右键干什么”的是 vtkInteractorStyle。

默认绑定的是 vtkInteractorStyleTrackballCamera,就是我们前面demo一直用的行为:

  • 鼠标左键拖拽:旋转相机
  • 鼠标右键拖拽:平移相机
  • 鼠标滚轮:缩放相机

注意:是相机在动,不是模型网格数据在内存里移动。只是看的角度变了,vtkPolyData点坐标完全不变。 和 vtkTransformFilter 平移有本质区别:

  • 交互拖拽:相机变换,数据不变
  • TransformFilter:真实修改所有点的坐标,数据本身改变

更换交互样式代码片段

#include <vtkInteractorStyleTrackballCamera.h>

// 创建交互样式对象
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style =
    vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();

interactor->SetInteractorStyle(style); //把样式绑定给交互器

VTK有多种style,比如 vtkInteractorStyleImage专门给图像使用;我们三维几何几乎一直用TrackballCamera。

4.2 拾取机制(Pick,非常重要,工业软件高频)

拾取:鼠标点击屏幕,反算出点击到三维模型上的哪个点、哪个面片Cell。

两类最常用拾取器:

  1. vtkCellPicker:拾取面片Cell,拿到点击到的面片ID(最常用)
  2. vtkPointPicker:拾取顶点Point,拿到点击到的点ID

拾取工作流程: 鼠标点击屏幕坐标 → 拾取器做射线求交 → 和场景里Actor模型碰撞检测 → 返回命中的CellId / PointId。

关键接口

picker->Pick(屏幕x,屏幕y,0, renderer);
// Pick执行完成之后,就可以获取结果
picker->GetCellId();    // 获取命中面片ID,-1代表没有命中任何物体
picker->GetPointId();   // 获取命中点ID
picker->GetPickPosition(); // 获取三维空间交点坐标(x,y,z)
picker->GetActor();     // 获取被点中的Actor

⚠️注意:Pick函数参数是屏幕像素坐标,不是三维坐标。

4.3 完整最小拾取Demo(只看逻辑)

功能:鼠标左键点击,打印输出命中的面片ID和三维交点坐标。 需要使用观察者回调,监听鼠标点击事件。

#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkCellPicker.h>
#include <vtkCommand.h>
#include <iostream>

// 自定义回调类:监听鼠标点击事件
class PickCallback : public vtkCommand
{
public:
    static PickCallback* New()
    {
        return new PickCallback;
    }
    virtual void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData)) override
    {
        vtkRenderWindowInteractor* interactor = static_cast<vtkRenderWindowInteractor*>(caller);

        int clickX, clickY;
        interactor->GetEventPosition(clickX, clickY); //拿到鼠标点击屏幕像素坐标

        vtkSmartPointer<vtkCellPicker> cellPicker = vtkSmartPointer<vtkCellPicker>::New();
        cellPicker->SetTolerance(0.0001);

        // 执行拾取
        cellPicker->Pick(clickX, clickY, 0, interactor->GetRenderWindow()->GetRenderers()->GetFirstRenderer());

        int cellId = cellPicker->GetCellId();
        if(cellId >= 0)
        {
            std::cout << "✅命中面片ID:" << cellId << std::endl;
            double pos[3];
            cellPicker->GetPickPosition(pos);
            std::cout << "交点三维坐标 X:" << pos[0] << " Y:" << pos[1] << " Z:" << pos[2] << std::endl;
        }
        else
        {
            std::cout << "❌没有点到模型" << std::endl;
        }
    }
};


int main()
{
    // --------生成球体模型--------
    vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
    sphereSource->SetRadius(4);
    sphereSource->SetThetaResolution(30);
    sphereSource->SetPhiResolution(30);
    sphereSource->Update();

    vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    mapper->SetInputData(sphereSource->GetOutput());

    vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
    actor->SetMapper(mapper.Get());
    actor->GetProperty()->SetColor(0.2,0.7,0.2);

    // --------渲染基础管线--------
    vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    renderer->AddActor(actor);
    renderer->SetBackground(0,0,0);

    vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
    renderWindow->AddRenderer(renderer);
    renderWindow->SetSize(800,600);

    vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
    interactor->SetRenderWindow(renderWindow);

    vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
    interactor->SetInteractorStyle(style);

    // =========绑定回调:监听鼠标左键按下事件=========
    vtkSmartPointer<PickCallback> pickCb = vtkSmartPointer<PickCallback>::New();
    interactor->AddObserver(vtkCommand::LeftButtonPressEvent, pickCb);

    interactor->Initialize();
    interactor->Start();

    return 0;
}

运行现象

窗口绿色球体,鼠标左键点击模型表面,控制台打印面片ID和三维交点;点击空白处输出没有命中模型。

代码关键点拆解

  1. vtkCommand 观察者模式:VTK标准事件回调机制。 AddObserver(事件类型,回调对象):监听某个事件发生,自动执行Execute函数。
  • vtkCommand::LeftButtonPressEvent:鼠标左键按下事件。

    后面我们会专门讲Observer观察者机制,这里先看懂用途。

  1. interactor->GetEventPosition(clickX,clickY) 获取鼠标点击的屏幕像素坐标。
  2. vtkCellPicker::Pick(x,y,0,renderer) 执行拾取计算。
  • 返回cellId == -1:未命中;>=0就是命中面片索引。

拾取器的Tolerance容差:点击点离面片小于这个距离才算命中。

4.4 重要区分(极易混淆)

  1. 相机交互拖拽(鼠标旋转缩放):只改相机,底层vtkPolyData数据完全不动。
  2. 拾取Pick:屏幕坐标 → 反向求交算出三维模型上的点/面片ID,只读,不修改任何数据。
  3. TransformFilter:真正修改点坐标,模型数据本身发生改变。

本节小结

  1. vtkRenderWindowInteractor接收事件;vtkInteractorStyle定义鼠标行为。
  2. 默认vtkInteractorStyleTrackballCamera,旋转缩放是相机变换,网格数据不变。
  3. vtkCellPicker拾取面片,vtkPointPicker拾取顶点;Pick()执行射线求交,拿到ID与三维交点。
  4. 使用vtkCommand + AddObserver监听鼠标键盘事件。

新手坑

  1. 分不清相机变换 vs 模型真实坐标变换;
  2. 拾取拿到的是屏幕像素坐标,不是三维坐标;
  3. 忘记判断cellId==-1,直接使用ID导致数组越界崩溃。

posted @ 2026-08-24 16:11  Drunker•L  阅读(20)  评论(0)    收藏  举报