网站建设前期规划方案百度账号注册中心
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);//获取所有ActorvtkSmartPointer<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);// 创建矩形的PolyDatavtkSmartPointer<vtkPolyData> rectanglePolyData = vtkSmartPointer<vtkPolyData>::New();rectanglePolyData->SetPoints(rectanglePoints);rectanglePolyData->SetLines(rectangleLines);// 创建矩形的MappervtkSmartPointer<vtkPolyDataMapper> rectangleMapper = vtkSmartPointer<vtkPolyDataMapper>::New();rectangleMapper->SetInputData(rectanglePolyData);// 创建矩形的ActorvtkSmartPointer<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);// 创建圆形的MappervtkSmartPointer<vtkPolyDataMapper> circleMapper = vtkSmartPointer<vtkPolyDataMapper>::New();circleMapper->SetInputConnection(circleSource->GetOutputPort());// 创建圆形的ActorvtkSmartPointer<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);// 创建嵌套矩形的PolyDatavtkSmartPointer<vtkPolyData> nestedRectanglePolyData = vtkSmartPointer<vtkPolyData>::New();nestedRectanglePolyData->SetPoints(nestedRectanglePoints);nestedRectanglePolyData->SetLines(nestedRectangleLines);// 创建嵌套矩形的MappervtkSmartPointer<vtkPolyDataMapper> nestedRectangleMapper = vtkSmartPointer<vtkPolyDataMapper>::New();nestedRectangleMapper->SetInputData(nestedRectanglePolyData);// 创建嵌套矩形的ActorvtkSmartPointer<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 进行三维图形渲染和交互有所帮助!