VTK随笔十四:QT与VTK的交互示例(平移)

VTK(Visualization Toolkit)是一个开源的软件系统,用于三维计算机图形学、图像处理和可视化。它提供了丰富的工具和类来处理三维数据和交互。在 VTK 中,拾取操作通常通过 vtkCellPickervtkPointPicker 等类来实现。

本文将展示如何使用 vtkCellPicker 来拾取点,并判断该点是否在多个嵌套的封闭区域内。如果存在多个包含该点的封闭区域,我们将选择离拾取点最近的那个区域。之后可对选择区域进行平移操作。

实现步骤

1. 定义自定义交互器样式

首先,我们需要定义一个自定义的交互器样式类vtkCustomInteractorStyle,继承自 vtkInteractorStyleTrackballCamera。这个类将处理鼠标点击和移动事件。

vtkCustomInteractorStyle.h

复制代码
#ifndef VTKCUSTOMINTERACTORSTYLE_H
#define VTKCUSTOMINTERACTORSTYLE_H

#include <vtkInteractorStyleTrackballCamera.h>
#include <vector>
#include <vtkPoints.h>
#include <vtkActor.h>
#include <vtkSmartPointer.h>

class vtkCustomInteractorStyle : public vtkInteractorStyleTrackballCamera
{
public:
    static vtkCustomInteractorStyle* New();
    vtkTypeMacro(vtkCustomInteractorStyle, vtkInteractorStyleTrackballCamera);

    //左键按下
    virtual void OnLeftButtonDown() override;
    //左键抬起
    virtual void OnLeftButtonUp() override;
    //右键按下
    virtual void OnRightButtonDown() override;
    //右键抬起
    virtual void OnRightButtonUp() override;
    //鼠标移动
    virtual void OnMouseMove() override;

protected:
    //构造函数
    vtkCustomInteractorStyle();
    //析构
    ~vtkCustomInteractorStyle();

private:
    //将选取的屏幕点转为世界坐标
    double* ComputeWorldPosition(int x, int y);
    //使用射线法判断点是否在任意多边形内
    bool IsPointInPolygon(double* point, const std::vector<double*> &polygonPoints);
    //获取Actor的顶点
    void GetActorVertices(vtkSmartPointer<vtkActor> actor, std::vector<double*> &polygonPoints);
    //更新Actor的顶点
    void UpdateActorPoints(vtkSmartPointer<vtkActor> actor, double dx, double dy);
    //计算点与多边形顶点的的距离
    double ComputeDistanceToPolygon(double* point, const std::vector<double*> &polygonPoints);

private:
    vtkSmartPointer<vtkActor> SelectedActor;
    double InitialPosition[3];
    double InitialActorPosition[3];
    double dx;
    double dy;
};

#endif // VTKCUSTOMINTERACTORSTYLE_H

2. 实现自定义交互器样式

接下来,我们实现自定义交互器样式类中的各个方法。

vtkCustomInteractorStyle.cpp

复制代码
#include "vtkCustomInteractorStyle.h"
#include <vtkObjectFactory.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkCellPicker.h>
#include <vtkPolyDataMapper.h>
#include <vtkPolyData.h>
#include <vtkRenderer.h>
#include <vtkProperty.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>

vtkStandardNewMacro(vtkCustomInteractorStyle);

vtkCustomInteractorStyle::vtkCustomInteractorStyle()
{
}

vtkCustomInteractorStyle::~vtkCustomInteractorStyle()
{
}

void vtkCustomInteractorStyle::OnLeftButtonDown()
{
    this->SelectedActor = nullptr;
    dx = 0;
    dy = 0;

    int x, y;
    this->GetInteractor()->GetEventPosition(x, y);
    double* pickPosition = this->ComputeWorldPosition(x, y);

    //获取所有Actor
    vtkSmartPointer<vtkPropCollection> actors = this->CurrentRenderer->GetActors();
    actors->InitTraversal();

    vtkSmartPointer<vtkActor> actor = nullptr;
    vtkSmartPointer<vtkActor> closestActor = nullptr;
    double minDistance = std::numeric_limits<double>::max();

    while ((actor = dynamic_cast<vtkActor*>(actors->GetNextProp())))
    {
        std::vector<double*> polygonPoints;
        GetActorVertices(actor, polygonPoints);

        if (IsPointInPolygon(pickPosition, polygonPoints))
        {
            double distance = ComputeDistanceToPolygon(pickPosition, polygonPoints);
            if (distance < minDistance)
            {
                minDistance = distance;
                closestActor = actor;
            }
        }

        // 释放动态分配的内存
        for (const auto& point : polygonPoints)
        {
            delete[] point;
        }
        polygonPoints.clear();
    }

    if (closestActor)
    {
        this->SelectedActor = closestActor;
        this->InitialPosition[0] = pickPosition[0];
        this->InitialPosition[1] = pickPosition[1];
        this->InitialActorPosition[0] = this->SelectedActor->GetPosition()[0];
        this->InitialActorPosition[1] = this->SelectedActor->GetPosition()[1];
    }

    return;
}

void vtkCustomInteractorStyle::OnLeftButtonUp()
{
    if (this->SelectedActor)
    {
        UpdateActorPoints(this->SelectedActor, dx, dy);
        this->SelectedActor = nullptr;
    }
    return;
}

void vtkCustomInteractorStyle::OnRightButtonDown()
{
    return;
}

void vtkCustomInteractorStyle::OnRightButtonUp()
{
    return;
}

void vtkCustomInteractorStyle::OnMouseMove()
{
    if (this->SelectedActor)
    {
        int x, y;
        this->GetInteractor()->GetEventPosition(x, y);
        double* pickPosition = this->ComputeWorldPosition(x, y);

        dx = pickPosition[0] - this->InitialPosition[0];
        dy = pickPosition[1] - this->InitialPosition[1];

        this->SelectedActor->SetPosition(this->InitialActorPosition[0] + dx, this->InitialActorPosition[1] + dy, 0.0);
        this->Interactor->Render();
    }
    this->Superclass::OnMouseMove();
}

double* vtkCustomInteractorStyle::ComputeWorldPosition(int x, int y)
{
    vtkSmartPointer<vtkCellPicker> picker = vtkSmartPointer<vtkCellPicker>::New();
    picker->SetTolerance(0.01);
    picker->Pick(x, y, 0, this->CurrentRenderer);
    return picker->GetPickPosition();
}

bool vtkCustomInteractorStyle::IsPointInPolygon(double* point, const std::vector<double*> &polygonPoints)
{
    double x = point[0];
    double y = point[1];
    int n = polygonPoints.size();
    bool inside = false;

    for (int i = 0, j = n - 1; i < n; j = i++)
    {
        double xi = polygonPoints[i][0];
        double yi = polygonPoints[i][1];
        double xj = polygonPoints[j][0];
        double yj = polygonPoints[j][1];

        bool intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
        if (intersect)
        {
            inside = !inside;
        }
    }

    return inside;
}

void vtkCustomInteractorStyle::GetActorVertices(vtkSmartPointer<vtkActor> actor, std::vector<double*> &polygonPoints)
{
    vtkSmartPointer<vtkPolyDataMapper> mapper = dynamic_cast<vtkPolyDataMapper*>(actor->GetMapper());
    if (!mapper) return;

    vtkSmartPointer<vtkPolyData> polyData = mapper->GetInput();
    if (!polyData) return;

    vtkSmartPointer<vtkPoints> points = polyData->GetPoints();
    if (!points) return;

    for (vtkIdType i = 0; i < points->GetNumberOfPoints(); ++i)
    {
        double point[3] = {0.0};
        points->GetPoint(i, point);
        polygonPoints.push_back(new double[3]{point[0], point[1], point[2]});
    }
}

void vtkCustomInteractorStyle::UpdateActorPoints(vtkSmartPointer<vtkActor> actor, double dx, double dy)
{
    vtkSmartPointer<vtkPolyDataMapper> mapper = dynamic_cast<vtkPolyDataMapper*>(actor->GetMapper());
    if (!mapper) return;

    vtkSmartPointer<vtkPolyData> polyData = mapper->GetInput();
    if (!polyData) return;

    vtkSmartPointer<vtkPoints> points = polyData->GetPoints();
    if (!points) return;

    for (vtkIdType i = 0; i < points->GetNumberOfPoints(); ++i)
    {
        double point[3] = {0.0};
        points->GetPoint(i, point);
        point[0] += dx;
        point[1] += dy;
        points->SetPoint(i, point);
    }
    polyData->Modified();
}

double vtkCustomInteractorStyle::ComputeDistanceToPolygon(double* point, const std::vector<double*> &polygonPoints)
{
    double x = point[0];
    double y = point[1];
    double minDistance = std::numeric_limits<double>::max();

    for (const auto& polyPoint : polygonPoints)
    {
        double distance = std::sqrt((polyPoint[0] - x) * (polyPoint[0] - x) + (polyPoint[1] - y) * (polyPoint[1] - y));
        if (distance < minDistance)
        {
            minDistance = distance;
        }
    }

    return minDistance;
}

3. 代码解释

3.1 头文件 vtkCustomInteractorStyle.h
  • 类声明 :定义了一个自定义交互器样式类 vtkCustomInteractorStyle,继承自 vtkInteractorStyleTrackballCamera
  • 方法声明
    • OnLeftButtonDown:处理左键按下事件。
    • OnLeftButtonUp:处理左键抬起事件。
    • OnRightButtonDownOnRightButtonUp:处理右键按下和抬起事件。
    • OnMouseMove:处理鼠标移动事件。
  • 辅助方法
    • ComputeWorldPosition:计算鼠标点击位置的世界坐标。
    • IsPointInPolygon:判断点是否在多边形内。
    • GetActorVertices:获取 Actor 的顶点。
    • UpdateActorPoints:更新 Actor 的顶点坐标。
    • ComputeDistanceToPolygon:计算点到多边形顶点的最小距离。
3.2 源文件 vtkCustomInteractorStyle.cpp
  • 构造函数和析构函数:初始化和清理类成员变量。
  • OnLeftButtonDown 方法
    • 获取鼠标点击位置的世界坐标。
    • 遍历渲染器中的所有 Actor,获取每个 Actor 的顶点。
    • 使用 IsPointInPolygon 判断拾取点是否在多边形内。
    • 如果在多边形内,使用 ComputeDistanceToPolygon 计算点到多边形顶点的最小距离,并记录离拾取点最近的 Actor。
    • 释放动态分配的内存。
    • 设置 SelectedActor 为离拾取点最近的 Actor。
  • OnLeftButtonUp 方法
    • 如果有选中的 Actor,更新其顶点坐标并重置选中状态。
  • OnRightButtonDownOnRightButtonUp 方法
    • 调用父类的方法处理右键事件。
  • OnMouseMove 方法
    • 如果有选中的 Actor,根据鼠标移动更新 Actor 的位置。
  • ComputeWorldPosition 方法
    • 使用 vtkCellPicker 获取拾取点的世界坐标。
  • IsPointInPolygon 方法
    • 使用射线法判断点是否在多边形内。
  • GetActorVertices 方法
    • 获取 Actor 的顶点坐标。
  • UpdateActorPoints 方法
    • 更新 Actor 的顶点坐标。
  • ComputeDistanceToPolygon 方法
    • 计算点到多边形顶点的最小距离。

4. 使用自定义交互器样式

最后,我们需要在主程序中使用自定义的交互器样式。

首先在QT界面中嵌套VTK窗口,详情见VTK随笔一:初识VTK(QT中嵌入VTK窗口)-CSDN博客

主要代码:

复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <vtkSmartPointer.h>
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkNamedColors.h>
#include <vtkRegularPolygonSource.h>
#include <vtkCellPicker.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkCommand.h>
#include <vtkCallbackCommand.h>
#include <vtkLine.h>
#include <vtkProperty.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkCamera.h>
#include "vtkCustomInteractorStyle.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    vtkSmartPointer<vtkNamedColors> colors = vtkSmartPointer<vtkNamedColors>::New();

    // 创建矩形的点集
    vtkSmartPointer<vtkPoints> rectanglePoints = vtkSmartPointer<vtkPoints>::New();
    rectanglePoints->InsertNextPoint(0.0, 0.0, 0.0); // 左下角
    rectanglePoints->InsertNextPoint(1.0, 0.0, 0.0); // 右下角
    rectanglePoints->InsertNextPoint(1.0, 1.0, 0.0); // 右上角
    rectanglePoints->InsertNextPoint(0.0, 1.0, 0.0); // 左上角

    // 创建矩形的线段
    vtkSmartPointer<vtkCellArray> rectangleLines = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkLine> line = vtkSmartPointer<vtkLine>::New();

    // 左下角到右下角
    line->GetPointIds()->SetId(0, 0);
    line->GetPointIds()->SetId(1, 1);
    rectangleLines->InsertNextCell(line);

    // 右下角到右上角
    line->GetPointIds()->SetId(0, 1);
    line->GetPointIds()->SetId(1, 2);
    rectangleLines->InsertNextCell(line);

    // 右上角到左上角
    line->GetPointIds()->SetId(0, 2);
    line->GetPointIds()->SetId(1, 3);
    rectangleLines->InsertNextCell(line);

    // 左上角到左下角
    line->GetPointIds()->SetId(0, 3);
    line->GetPointIds()->SetId(1, 0);
    rectangleLines->InsertNextCell(line);

    // 创建矩形的PolyData
    vtkSmartPointer<vtkPolyData> rectanglePolyData = vtkSmartPointer<vtkPolyData>::New();
    rectanglePolyData->SetPoints(rectanglePoints);
    rectanglePolyData->SetLines(rectangleLines);

    // 创建矩形的Mapper
    vtkSmartPointer<vtkPolyDataMapper> rectangleMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    rectangleMapper->SetInputData(rectanglePolyData);

    // 创建矩形的Actor
    vtkSmartPointer<vtkActor> rectangleActor = vtkSmartPointer<vtkActor>::New();
    rectangleActor->SetMapper(rectangleMapper);
    rectangleActor->GetProperty()->SetColor(colors->GetColor3d("Tomato").GetData());

    // 创建圆形
    vtkSmartPointer<vtkRegularPolygonSource> circleSource = vtkSmartPointer<vtkRegularPolygonSource>::New();
    circleSource->SetNumberOfSides(100); // 多边形近似圆形
    circleSource->SetRadius(0.5);
    circleSource->SetCenter(2.0, 0.5, 0.0);
    circleSource->SetNormal(0.0, 0.0, 1.0);

    // 创建圆形的Mapper
    vtkSmartPointer<vtkPolyDataMapper> circleMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    circleMapper->SetInputConnection(circleSource->GetOutputPort());

    // 创建圆形的Actor
    vtkSmartPointer<vtkActor> circleActor = vtkSmartPointer<vtkActor>::New();
    circleActor->SetMapper(circleMapper);
    circleActor->GetProperty()->SetColor(colors->GetColor3d("Cyan").GetData());

    // 创建嵌套的矩形
    vtkSmartPointer<vtkPoints> nestedRectanglePoints = vtkSmartPointer<vtkPoints>::New();
    nestedRectanglePoints->InsertNextPoint(1.2, 0.2, 0.0); // 左下角
    nestedRectanglePoints->InsertNextPoint(1.8, 0.2, 0.0); // 右下角
    nestedRectanglePoints->InsertNextPoint(1.8, 0.8, 0.0); // 右上角
    nestedRectanglePoints->InsertNextPoint(1.2, 0.8, 0.0); // 左上角

    // 创建嵌套矩形的线段
    vtkSmartPointer<vtkCellArray> nestedRectangleLines = vtkSmartPointer<vtkCellArray>::New();
    line = vtkSmartPointer<vtkLine>::New();

    // 左下角到右下角
    line->GetPointIds()->SetId(0, 0);
    line->GetPointIds()->SetId(1, 1);
    nestedRectangleLines->InsertNextCell(line);

    // 右下角到右上角
    line->GetPointIds()->SetId(0, 1);
    line->GetPointIds()->SetId(1, 2);
    nestedRectangleLines->InsertNextCell(line);

    // 右上角到左上角
    line->GetPointIds()->SetId(0, 2);
    line->GetPointIds()->SetId(1, 3);
    nestedRectangleLines->InsertNextCell(line);

    // 左上角到左下角
    line->GetPointIds()->SetId(0, 3);
    line->GetPointIds()->SetId(1, 0);
    nestedRectangleLines->InsertNextCell(line);

    // 创建嵌套矩形的PolyData
    vtkSmartPointer<vtkPolyData> nestedRectanglePolyData = vtkSmartPointer<vtkPolyData>::New();
    nestedRectanglePolyData->SetPoints(nestedRectanglePoints);
    nestedRectanglePolyData->SetLines(nestedRectangleLines);

    // 创建嵌套矩形的Mapper
    vtkSmartPointer<vtkPolyDataMapper> nestedRectangleMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    nestedRectangleMapper->SetInputData(nestedRectanglePolyData);

    // 创建嵌套矩形的Actor
    vtkSmartPointer<vtkActor> nestedRectangleActor = vtkSmartPointer<vtkActor>::New();
    nestedRectangleActor->SetMapper(nestedRectangleMapper);
    nestedRectangleActor->GetProperty()->SetColor(colors->GetColor3d("Lime").GetData());

    // 创建渲染器并添加Actor到渲染器
    vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    renderer->AddActor(rectangleActor);
    renderer->AddActor(circleActor);
    renderer->AddActor(nestedRectangleActor);
    renderer->SetBackground(colors->GetColor3d("SlateGray").GetData());
    renderer->ResetCamera();

    // 设置自定义交互器样式
    vtkSmartPointer<vtkCustomInteractorStyle> style = vtkSmartPointer<vtkCustomInteractorStyle>::New();
    style->SetCurrentRenderer(renderer);

    vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    renderWindow->AddRenderer(renderer);
    ui->widget->setRenderWindow(renderWindow);
    ui->widget->interactor()->SetInteractorStyle(style);

    // 渲染
    renderWindow->Render();
}

MainWindow::~MainWindow()
{
    delete ui;
}

5. 代码解释

5.1 创建几何对象
  • 矩形 :使用 vtkPointsvtkCellArray 创建矩形的顶点和线段。
  • 圆形 :使用 vtkRegularPolygonSource 创建一个近似的圆形。
  • 嵌套矩形:创建一个嵌套在矩形内的较小矩形。
5.2 创建Mapper和Actor
  • 矩形 :将 vtkPolyData 设置到 vtkPolyDataMapper 中,然后将 vtkPolyDataMapper 设置到 vtkActor 中。
  • 圆形 :将 vtkRegularPolygonSource 的输出设置到 vtkPolyDataMapper 中,然后将 vtkPolyDataMapper 设置到 vtkActor 中。
  • 嵌套矩形 :将 vtkPolyData 设置到 vtkPolyDataMapper 中,然后将 vtkPolyDataMapper 设置到 vtkActor 中。
5.3 设置Actor的名称
  • 为每个 Actor 设置名称,方便调试和识别。
5.4 添加Actor到渲染器
  • 将所有 Actor 添加到渲染器中,并设置背景颜色。
5.5 渲染并启动交互
  • 渲染场景并启动交互器,使用户可以通过鼠标进行操作。

6. 运行效果

运行上述代码后,你将看到一个包含矩形、圆形和嵌套矩形的 3D 场景。当你点击场景中的某个点时,程序会判断该点是否在多个封闭区域内,并选择离拾取点最近的那个区域。选中的区域将被移动,以响应鼠标移动事件。

7. 总结

本文介绍了如何在 VTK 中实现拾取点并获取离拾取点最近的包含该点的封闭区域。通过定义自定义交互器样式类 vtkCustomInteractorStyle,我们可以处理鼠标点击和移动事件,并使用 vtkCellPicker 获取拾取点的世界坐标。通过遍历所有 Actor 并计算点到多边形顶点的最小距离,我们可以准确地选择离拾取点最近的区域。

这种方法在处理复杂的嵌套几何体时非常有用,可以确保用户选择的是最内层的区域。希望这篇文章对你在使用 VTK 进行三维图形渲染和交互有所帮助!

相关推荐
李匠202415 分钟前
C++学习之工厂模式-套接字通信
c++·学习
freyazzr28 分钟前
Leedcode刷题 | Day30_贪心算法04
数据结构·c++·算法·leetcode·贪心算法
李匠20242 小时前
C++学习之金融类安全传输平台项目git
c++·学习
Hello eveybody4 小时前
C++二进制
c++
牛奶咖啡.85410 小时前
第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 A 组真题
c语言·数据结构·c++·算法·蓝桥杯
狄加山67511 小时前
Qt模型-视图架构
开发语言·qt
Dream it possible!12 小时前
CCF CSP 第35次(2024.09)(1_密码_C++)(哈希表)
c++·散列表·ccf csp·csp
旧时光林12 小时前
蓝桥杯 分解质因数(唯一分解定理)
数据结构·c++·算法·蓝桥杯·模拟·枚举
njsgcs12 小时前
ubuntu24.04 cmake 报错 libldap-2.5.so.0 解决办法
开发语言·c++
notfindjob13 小时前
QT Sqlite数据库-教程001 创建数据库和表-下
数据库·qt·sqlite