VTK(Visualization Toolkit)是一个开源的软件系统,用于三维计算机图形学、图像处理和可视化。它提供了丰富的工具和类来处理三维数据和交互。在 VTK 中,拾取操作通常通过 vtkCellPicker
或 vtkPointPicker
等类来实现。
本文将展示如何使用 vtkCellPicker
来拾取点,并判断该点是否在多个嵌套的封闭区域内。如果存在多个包含该点的封闭区域,我们将选择离拾取点最近的那个区域。之后可对选择区域进行平移操作。
首先,我们需要定义一个自定义的交互器样式类vtkCustomInteractorStyle,继承自 vtkInteractorStyleTrackballCamera
。这个类将处理鼠标点击和移动事件。
vtkCustomInteractorStyle.h
#ifndef VTKCUSTOMINTERACTORSTYLE_H
#define VTKCUSTOMINTERACTORSTYLE_H
#include
#include
#include
#include
#include
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 &polygonPoints);
//获取Actor的顶点
void GetActorVertices(vtkSmartPointer actor, std::vector &polygonPoints);
//更新Actor的顶点
void UpdateActorPoints(vtkSmartPointer actor, double dx, double dy);
//计算点与多边形顶点的的距离
double ComputeDistanceToPolygon(double* point, const std::vector &polygonPoints);
private:
vtkSmartPointer SelectedActor;
double InitialPosition[3];
double InitialActorPosition[3];
double dx;
double dy;
};
#endif // VTKCUSTOMINTERACTORSTYLE_H
接下来,我们实现自定义交互器样式类中的各个方法。
vtkCustomInteractorStyle.cpp
#include "vtkCustomInteractorStyle.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 actors = this->CurrentRenderer->GetActors();
actors->InitTraversal();
vtkSmartPointer actor = nullptr;
vtkSmartPointer closestActor = nullptr;
double minDistance = std::numeric_limits::max();
while ((actor = dynamic_cast(actors->GetNextProp())))
{
std::vector 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 picker = vtkSmartPointer::New();
picker->SetTolerance(0.01);
picker->Pick(x, y, 0, this->CurrentRenderer);
return picker->GetPickPosition();
}
bool vtkCustomInteractorStyle::IsPointInPolygon(double* point, const std::vector &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 actor, std::vector &polygonPoints)
{
vtkSmartPointer mapper = dynamic_cast(actor->GetMapper());
if (!mapper) return;
vtkSmartPointer polyData = mapper->GetInput();
if (!polyData) return;
vtkSmartPointer 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 actor, double dx, double dy)
{
vtkSmartPointer mapper = dynamic_cast(actor->GetMapper());
if (!mapper) return;
vtkSmartPointer polyData = mapper->GetInput();
if (!polyData) return;
vtkSmartPointer 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 &polygonPoints)
{
double x = point[0];
double y = point[1];
double minDistance = std::numeric_limits::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;
}
vtkCustomInteractorStyle.h
vtkCustomInteractorStyle
,继承自 vtkInteractorStyleTrackballCamera
。OnLeftButtonDown
:处理左键按下事件。OnLeftButtonUp
:处理左键抬起事件。OnRightButtonDown
和 OnRightButtonUp
:处理右键按下和抬起事件。OnMouseMove
:处理鼠标移动事件。ComputeWorldPosition
:计算鼠标点击位置的世界坐标。IsPointInPolygon
:判断点是否在多边形内。GetActorVertices
:获取 Actor 的顶点。UpdateActorPoints
:更新 Actor 的顶点坐标。ComputeDistanceToPolygon
:计算点到多边形顶点的最小距离。vtkCustomInteractorStyle.cpp
OnLeftButtonDown
方法:
IsPointInPolygon
判断拾取点是否在多边形内。ComputeDistanceToPolygon
计算点到多边形顶点的最小距离,并记录离拾取点最近的 Actor。SelectedActor
为离拾取点最近的 Actor。OnLeftButtonUp
方法:
OnRightButtonDown
和 OnRightButtonUp
方法:
OnMouseMove
方法:
ComputeWorldPosition
方法:
vtkCellPicker
获取拾取点的世界坐标。IsPointInPolygon
方法:
GetActorVertices
方法:
UpdateActorPoints
方法:
ComputeDistanceToPolygon
方法:
最后,我们需要在主程序中使用自定义的交互器样式。
首先在QT界面中嵌套VTK窗口,详情见VTK随笔一:初识VTK(QT中嵌入VTK窗口)-CSDN博客
主要代码:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "vtkCustomInteractorStyle.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
vtkSmartPointer colors = vtkSmartPointer::New();
// 创建矩形的点集
vtkSmartPointer rectanglePoints = vtkSmartPointer::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 rectangleLines = vtkSmartPointer::New();
vtkSmartPointer line = vtkSmartPointer::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 rectanglePolyData = vtkSmartPointer::New();
rectanglePolyData->SetPoints(rectanglePoints);
rectanglePolyData->SetLines(rectangleLines);
// 创建矩形的Mapper
vtkSmartPointer rectangleMapper = vtkSmartPointer::New();
rectangleMapper->SetInputData(rectanglePolyData);
// 创建矩形的Actor
vtkSmartPointer rectangleActor = vtkSmartPointer::New();
rectangleActor->SetMapper(rectangleMapper);
rectangleActor->GetProperty()->SetColor(colors->GetColor3d("Tomato").GetData());
// 创建圆形
vtkSmartPointer circleSource = vtkSmartPointer::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 circleMapper = vtkSmartPointer::New();
circleMapper->SetInputConnection(circleSource->GetOutputPort());
// 创建圆形的Actor
vtkSmartPointer circleActor = vtkSmartPointer::New();
circleActor->SetMapper(circleMapper);
circleActor->GetProperty()->SetColor(colors->GetColor3d("Cyan").GetData());
// 创建嵌套的矩形
vtkSmartPointer nestedRectanglePoints = vtkSmartPointer::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 nestedRectangleLines = vtkSmartPointer::New();
line = vtkSmartPointer::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 nestedRectanglePolyData = vtkSmartPointer::New();
nestedRectanglePolyData->SetPoints(nestedRectanglePoints);
nestedRectanglePolyData->SetLines(nestedRectangleLines);
// 创建嵌套矩形的Mapper
vtkSmartPointer nestedRectangleMapper = vtkSmartPointer::New();
nestedRectangleMapper->SetInputData(nestedRectanglePolyData);
// 创建嵌套矩形的Actor
vtkSmartPointer nestedRectangleActor = vtkSmartPointer::New();
nestedRectangleActor->SetMapper(nestedRectangleMapper);
nestedRectangleActor->GetProperty()->SetColor(colors->GetColor3d("Lime").GetData());
// 创建渲染器并添加Actor到渲染器
vtkSmartPointer renderer = vtkSmartPointer::New();
renderer->AddActor(rectangleActor);
renderer->AddActor(circleActor);
renderer->AddActor(nestedRectangleActor);
renderer->SetBackground(colors->GetColor3d("SlateGray").GetData());
renderer->ResetCamera();
// 设置自定义交互器样式
vtkSmartPointer style = vtkSmartPointer::New();
style->SetCurrentRenderer(renderer);
vtkSmartPointer renderWindow = vtkSmartPointer::New();
renderWindow->AddRenderer(renderer);
ui->widget->setRenderWindow(renderWindow);
ui->widget->interactor()->SetInteractorStyle(style);
// 渲染
renderWindow->Render();
}
MainWindow::~MainWindow()
{
delete ui;
}
vtkPoints
和 vtkCellArray
创建矩形的顶点和线段。vtkRegularPolygonSource
创建一个近似的圆形。vtkPolyData
设置到 vtkPolyDataMapper
中,然后将 vtkPolyDataMapper
设置到 vtkActor
中。vtkRegularPolygonSource
的输出设置到 vtkPolyDataMapper
中,然后将 vtkPolyDataMapper
设置到 vtkActor
中。vtkPolyData
设置到 vtkPolyDataMapper
中,然后将 vtkPolyDataMapper
设置到 vtkActor
中。运行上述代码后,你将看到一个包含矩形、圆形和嵌套矩形的 3D 场景。当你点击场景中的某个点时,程序会判断该点是否在多个封闭区域内,并选择离拾取点最近的那个区域。选中的区域将被移动,以响应鼠标移动事件。
本文介绍了如何在 VTK 中实现拾取点并获取离拾取点最近的包含该点的封闭区域。通过定义自定义交互器样式类 vtkCustomInteractorStyle
,我们可以处理鼠标点击和移动事件,并使用 vtkCellPicker
获取拾取点的世界坐标。通过遍历所有 Actor 并计算点到多边形顶点的最小距离,我们可以准确地选择离拾取点最近的区域。
这种方法在处理复杂的嵌套几何体时非常有用,可以确保用户选择的是最内层的区域。希望这篇文章对你在使用 VTK 进行三维图形渲染和交互有所帮助!