VTK(Visualization Toolkit)是一个开源的软件系统,用于三维计算机图形学、图像处理和可视化。它提供了丰富的工具和类来处理三维数据和交互。在 VTK 中,拾取操作通常通过 vtkCellPicker
或 vtkPointPicker
等类来实现。
本文将展示如何使用 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
:处理左键抬起事件。OnRightButtonDown
和OnRightButtonUp
:处理右键按下和抬起事件。OnMouseMove
:处理鼠标移动事件。
- 辅助方法 :
ComputeWorldPosition
:计算鼠标点击位置的世界坐标。IsPointInPolygon
:判断点是否在多边形内。GetActorVertices
:获取 Actor 的顶点。UpdateActorPoints
:更新 Actor 的顶点坐标。ComputeDistanceToPolygon
:计算点到多边形顶点的最小距离。
3.2 源文件 vtkCustomInteractorStyle.cpp
- 构造函数和析构函数:初始化和清理类成员变量。
OnLeftButtonDown
方法 :- 获取鼠标点击位置的世界坐标。
- 遍历渲染器中的所有 Actor,获取每个 Actor 的顶点。
- 使用
IsPointInPolygon
判断拾取点是否在多边形内。 - 如果在多边形内,使用
ComputeDistanceToPolygon
计算点到多边形顶点的最小距离,并记录离拾取点最近的 Actor。 - 释放动态分配的内存。
- 设置
SelectedActor
为离拾取点最近的 Actor。
OnLeftButtonUp
方法 :- 如果有选中的 Actor,更新其顶点坐标并重置选中状态。
OnRightButtonDown
和OnRightButtonUp
方法 :- 调用父类的方法处理右键事件。
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 创建几何对象
- 矩形 :使用
vtkPoints
和vtkCellArray
创建矩形的顶点和线段。 - 圆形 :使用
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 进行三维图形渲染和交互有所帮助!