VTK 建模方法:Extrusion

VTK 建模方法:Extrusion

vtkImplicitModeller 建模的局限性

前一篇文章 《VTK 建模方法:建模基础》 介绍了如何使用 vtkImplicitModeller 类进行建模。它的局限性体现在以下 3 个方面:

  1. 场景的局限性:vtkImplicitModeller 建模生成的模型比较光滑,工业场景中经常存在有棱有角的模型,使用 vtkImplicitModeller 就不能很好完成建模。
  2. 精度的局限性:无论采样的精度有多高,vtkImplicitModeller 建模终究不是精准的表述。
  3. 性能的局限性:如果把采样精度设置得很高,vtkImplicitModeller 建模就会消耗大量的计算资源,性能表现不好。

什么是 Extrusion?

Extrusion 在塑料加工中又称挤出成型或挤塑,在橡胶加工中又称压出。是指物料通过挤出机料筒和螺杆间的作用,边受热塑化,边被螺杆向前推送,连续通过机头而制成各种截面制品或半制品的一种加工方法。

VTK 中提供了 2 个过滤器来实现 Extrusion 这种建模效果:vtkLinearExtrusionFilter 和 vtkRotationalExtrusionFilter。前者进行线性拉伸或挤压,后者进行旋转拉伸或挤压。

实例 1:圆柱

我们分别使用 vtkLinearExtrusionFilter 和 vtkRotationalExtrusionFilter 完成一个圆柱的建模。

方法一:生成一个圆面,使用 vtkLinearExtrusionFilter 对圆面进行线性拉伸成一个圆柱。

方法二:生成一个矩形,使用 vtkRotationalExtrusionFilter 对矩形进行旋转拉伸成一个圆柱。

代码:

#include "VTKExtrusion.h"

#include <vtkRegularPolygonSource.h>
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkLinearExtrusionFilter.h>
#include <vtkRotationalExtrusionFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>

VTKExtrusion::VTKExtrusion(QWidget* parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	_pVTKWidget = new QVTKOpenGLNativeWidget();
	this->setCentralWidget(_pVTKWidget);

	vtkNew<vtkRenderer> renderer;
	this->_pVTKWidget->renderWindow()->AddRenderer(renderer);
	this->_pVTKWidget->renderWindow()->Render();

	// LinearExtrusion
	vtkNew<vtkRegularPolygonSource> circle;
	circle->GeneratePolygonOn(); // 生成一个多边形,不设置的话只会生成一个边界
	circle->SetNumberOfSides(360);
	circle->SetRadius(2);
	circle->SetCenter(0, 0, 0);

	vtkNew<vtkLinearExtrusionFilter> linearExtrusion;
	linearExtrusion->SetInputConnection(circle->GetOutputPort());

	// RotationalExtrusion
	vtkNew<vtkPoints> points;
	points->InsertNextPoint(0, 0, 0);
	points->InsertNextPoint(2, 0, 0);
	points->InsertNextPoint(2, 0, 1);
	points->InsertNextPoint(0, 0, 1);

	vtkNew<vtkCellArray> lines;
	lines->InsertNextCell(4);
	lines->InsertCellPoint(0);
	lines->InsertCellPoint(1);
	lines->InsertCellPoint(2);
	lines->InsertCellPoint(3);

	vtkNew<vtkPolyData> profile;
	profile->SetPoints(points);
	profile->SetLines(lines);

	vtkNew<vtkRotationalExtrusionFilter> rotationalExtrusion;
	rotationalExtrusion->SetInputData(profile);
	rotationalExtrusion->SetAngle(360); // 旋转角度
	rotationalExtrusion->SetResolution(90); // 旋转面数(精度)

	// mapper
	vtkNew<vtkPolyDataMapper> lxMapper;
	lxMapper->SetInputConnection(linearExtrusion->GetOutputPort());
	vtkNew<vtkPolyDataMapper> rxMapper;
	rxMapper->SetInputConnection(rotationalExtrusion->GetOutputPort());

	vtkNew<vtkActor> lxActor;
	lxActor->SetMapper(lxMapper);
	vtkNew<vtkActor> rxActor;
	rxActor->SetMapper(rxMapper);
	rxActor->SetPosition(5, 0, 0);

	renderer->AddActor(lxActor);
	renderer->AddActor(rxActor);
}

VTKExtrusion::~VTKExtrusion()
{}

运行结果:

在这里插入图片描述

切换成线框模式,就可以看出两种建模的区别:

在这里插入图片描述

实例 1 修改:以一个点为参照做线性拉伸

vtkLinearExtrusionFilter 和 vtkRotationalExtrusionFilter 都默认使用 VTK_VECTOR_EXTRUSION 作矢量拉伸,我们可以设置成以一个点为参照做拉伸,通过设置参照点的坐标和拉伸系数,来达到想要的拉伸效果。

	// 以一个点为参照做线性拉伸
	linearExtrusion->SetExtrusionTypeToPointExtrusion();
	// linearExtrusion->SetExtrusionType(VTK_POINT_EXTRUSION);
	linearExtrusion->SetExtrusionPoint(0, 0, 5);
	linearExtrusion->SetScaleFactor(0.2);

运行结果:

在这里插入图片描述

实例 2:文字线性拉伸

使用 vtkLinearExtrusionFilter 对文字做线性拉伸,是 vtkLinearExtrusionFilter 的一个常用场景。

在这里插入图片描述

线框模式:

在这里插入图片描述

实例 3:圆面旋转拉伸成弹簧

将圆面的中心点设置在圆面以外,使用 vtkRotationalExtrusionFilter 类进行旋转拉伸,设置旋转角度为2160度,旋转面数为360,拉伸长度为6,就可以得到一个弹簧。

#include "VTKRotationalExtrusionSpring.h"

#include <vtkRegularPolygonSource.h>
#include <vtkNamedColors.h>
#include <vtkProperty.h>
#include <vtkRotationalExtrusionFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>

VTKRotationalExtrusionSpring::VTKRotationalExtrusionSpring(QWidget* parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	_pVTKWidget = new QVTKOpenGLNativeWidget();
	this->setCentralWidget(_pVTKWidget);

	vtkNew<vtkRenderer> renderer;
	this->_pVTKWidget->renderWindow()->AddRenderer(renderer);
	this->_pVTKWidget->renderWindow()->Render();

	vtkNew<vtkNamedColors> colors;

	// Circle
	vtkNew<vtkRegularPolygonSource> circle;
	circle->GeneratePolygonOn(); // 生成一个多边形,不设置的话只会生成一个边界
	circle->SetNumberOfSides(360);
	circle->SetRadius(0.2);
	circle->SetCenter(1, 0, 0); // 中心
	circle->SetNormal(0, 1, 0); // 法向量

	// Spring
	vtkNew<vtkRotationalExtrusionFilter> rotationalExtrusion;
	rotationalExtrusion->SetInputConnection(circle->GetOutputPort());
	rotationalExtrusion->SetAngle(2160); // 旋转角度
	rotationalExtrusion->SetResolution(360); // 旋转面数(精度)
	rotationalExtrusion->SetTranslation(6); // 拉伸长度,拉伸方向垂直于旋转

	// mapper
	vtkNew<vtkPolyDataMapper> mapper;
	mapper->SetInputConnection(rotationalExtrusion->GetOutputPort());

	// actor
	vtkNew<vtkActor> actor;
	actor->SetMapper(mapper);
	actor->GetProperty()->SetColor(colors->GetColor3d("Gold").GetData());

	renderer->AddActor(actor);
}

VTKRotationalExtrusionSpring::~VTKRotationalExtrusionSpring()
{}

运行效果:

在这里插入图片描述

实例 4:自定义 vtkRotationalExtrusionFilter

在实例3的弹簧建模中,新增一行代码:

rotationalExtrusion->SetDeltaRadius(1);

运行结果:

在这里插入图片描述

可以看到在旋转的过程中,弹簧的截面由圆变成了椭圆。

要想实现保持基础图形半径不变的旋转拉伸,我们可以自定义 vtkRotationalExtrusionFilter。下面是一个 myRotationalExtrusionFilter 类的实现,修改了 RequestData 函数,最后通过 vtkStandardNewMacro(myRotationalExtrusionFilter) 完成了该类的注册。

namespace
{
	class myRotationalExtrusionFilter : public vtkRotationalExtrusionFilter
	{
	public:
		static myRotationalExtrusionFilter* New();
	protected:
		int RequestData(vtkInformation* vtkNotUsed(request),
			vtkInformationVector** inputVector, vtkInformationVector* outputVector)
		{
			// get the info objects
			vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
			vtkInformation* outInfo = outputVector->GetInformationObject(0);

			// get the input and output
			vtkPolyData* input = vtkPolyData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
			vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

			vtkIdType numPts, numCells;
			vtkPointData* pd = input->GetPointData();
			vtkCellData* cd = input->GetCellData();
			vtkPolyData* mesh;
			vtkPoints* inPts;
			vtkCellArray* inVerts, * inLines, * inPolys, * inStrips;
			int numEdges;
			const vtkIdType* pts = nullptr;
			vtkIdType npts = 0;
			vtkIdType cellId, ptId, ncells;
			double x[3], newX[3], angleIncr, radIncr, transIncr;
			vtkPoints* newPts;
			vtkCellArray* newLines = nullptr, * newPolys = nullptr, * newStrips;
			vtkCell* edge;
			vtkIdList* cellIds;
			int i, j, k;
			vtkIdType p1, p2;
			vtkPointData* outPD = output->GetPointData();
			vtkCellData* outCD = output->GetCellData();
			bool abort = false;

			// Initialize / check input
			//
			vtkDebugMacro(<< "Rotationally extruding data");

			numPts = input->GetNumberOfPoints();
			numCells = input->GetNumberOfCells();
			if (numPts < 1 || numCells < 1)
			{
				vtkErrorMacro(<< "No data to extrude!");
				return 1;
			}

			double normalizedRotationAxis[3] = { this->RotationAxis[0], this->RotationAxis[1],
			  this->RotationAxis[2] };
			double norm = vtkMath::Normalize(normalizedRotationAxis);

			// if norm is equal to zero, the extrusion cannot be done
			if (norm == 0.0)
			{
				vtkErrorMacro(<< "Cannot perform extrusion around an axis with a norm of 0.");
				return 0;
			}

			// Build cell data structure.
			//
			mesh = vtkPolyData::New();
			inPts = input->GetPoints();
			inVerts = input->GetVerts();
			inLines = input->GetLines();
			inPolys = input->GetPolys();
			inStrips = input->GetStrips();
			mesh->SetPoints(inPts);
			mesh->SetVerts(inVerts);
			mesh->SetLines(inLines);
			mesh->SetPolys(inPolys);
			mesh->SetStrips(inStrips);
			if (inPolys || inStrips)
			{
				mesh->BuildLinks();
			}

			// Allocate memory for output. We don't copy normals because surface geometry
			// is modified.
			//
			outPD->CopyNormalsOff();
			outPD->CopyAllocate(pd, (this->Resolution + 1) * numPts);
			newPts = vtkPoints::New();
			newPts->Allocate((this->Resolution + 1) * numPts);
			if ((ncells = inVerts->GetNumberOfCells()) > 0)
			{
				newLines = vtkCellArray::New();
				newLines->AllocateEstimate(ncells, this->Resolution + 1);
			}
			// arbitrary initial allocation size
			ncells = inLines->GetNumberOfCells() + inPolys->GetNumberOfCells() / 10 +
				inStrips->GetNumberOfCells() / 10;
			ncells = (ncells < 100 ? 100 : ncells);
			newStrips = vtkCellArray::New();
			newStrips->AllocateEstimate(ncells, 2 * (this->Resolution + 1));
			outCD->CopyNormalsOff();
			outCD->CopyAllocate(cd, ncells);

			// copy points
			for (ptId = 0; ptId < numPts; ptId++) // base level
			{
				newPts->InsertPoint(ptId, inPts->GetPoint(ptId));
				outPD->CopyData(pd, ptId, ptId);
			}
			this->UpdateProgress(0.1);

			radIncr = this->DeltaRadius / this->Resolution;
			transIncr = this->Translation / this->Resolution;
			angleIncr = vtkMath::RadiansFromDegrees(this->Angle) / this->Resolution;

			double rotationAngleAndAxis[4] = { 0, this->RotationAxis[0], this->RotationAxis[1],
			  this->RotationAxis[2] };

			double c[3];
			input->GetCenter(c);
			double newC[3];

			for (i = 1; i <= this->Resolution; i++)
			{
				this->UpdateProgress(0.1 + 0.5 * (i - 1) / this->Resolution);
				for (ptId = 0; ptId < numPts; ptId++)
				{
					inPts->GetPoint(ptId, x);

					rotationAngleAndAxis[0] = i * angleIncr;
					vtkMath::RotateVectorByWXYZ(c, rotationAngleAndAxis, newC);
					vtkMath::RotateVectorByWXYZ(x, rotationAngleAndAxis, newX);

					newC[0] += normalizedRotationAxis[0] * i * transIncr;
					newC[1] += normalizedRotationAxis[1] * i * transIncr;
					newC[2] += normalizedRotationAxis[2] * i * transIncr;
					newX[0] += normalizedRotationAxis[0] * i * transIncr;
					newX[1] += normalizedRotationAxis[1] * i * transIncr;
					newX[2] += normalizedRotationAxis[2] * i * transIncr;

					double projection[3];
					double radialVector[3];
					vtkMath::ProjectVector(newC, normalizedRotationAxis, projection);
					vtkMath::Subtract(newC, projection, radialVector);

					newC[0] += radialVector[0] * i * radIncr;
					newC[1] += radialVector[1] * i * radIncr;
					newC[2] += radialVector[2] * i * radIncr;

					vtkMath::ProjectVector(newX, normalizedRotationAxis, projection);
					vtkMath::Subtract(newX, projection, radialVector);

					newX[0] += radialVector[0] * i * radIncr;
					newX[1] += radialVector[1] * i * radIncr;
					newX[2] += radialVector[2] * i * radIncr;

					double d0 = c[0] - x[0];
					double d1 = c[1] - x[1];
					double d2 = c[2] - x[2];
					double r = sqrt(d0 * d0 + d1 * d1 + d2 * d2);

					double newD0 = newC[0] - newX[0];
					double newD1 = newC[1] - newX[1];
					double newD2 = newC[2] - newX[2];
					double newR = sqrt(newD0 * newD0 + newD1 * newD1 + newD2 * newD2);

					double dbScale = r / newR;

					newX[0] = newC[0] + (newX[0] - newC[0]) * dbScale;
					newX[1] = newC[1] + (newX[1] - newC[1]) * dbScale;
					newX[2] = newC[2] + (newX[2] - newC[2]) * dbScale;

					newPts->InsertPoint(ptId + i * numPts, newX);
					outPD->CopyData(pd, ptId, ptId + i * numPts);
				}
			}

			// To ensure that cell attributes are in consistent order with the
			// cellId's, we process the verts, lines, polys and strips in order.
			vtkIdType newCellId = 0;
			int type;
			if (newLines) // there are verts which produce lines
			{
				for (cellId = 0; cellId < numCells && !abort; cellId++)
				{
					type = mesh->GetCellType(cellId);
					if (type == VTK_VERTEX || type == VTK_POLY_VERTEX)
					{
						mesh->GetCellPoints(cellId, npts, pts);
						for (i = 0; i < npts; i++)
						{
							ptId = pts[i];
							newLines->InsertNextCell(this->Resolution + 1);
							for (j = 0; j <= this->Resolution; j++)
							{
								newLines->InsertCellPoint(ptId + j * numPts);
							}
							outCD->CopyData(cd, cellId, newCellId++);
						}
					} // if a vertex or polyVertex
				}   // for all cells
			}     // if there are verts generating lines
			this->UpdateProgress(0.25);
			abort = this->CheckAbort();

			// If capping is on, copy 2D cells to output (plus create cap). Notice
			// that polygons are done first, then strips.
			//
			if (this->Capping &&
				(this->Angle != 360.0 || this->DeltaRadius != 0.0 || this->Translation != 0.0))
			{
				if (inPolys->GetNumberOfCells() > 0)
				{
					newPolys = vtkCellArray::New();
					newPolys->AllocateCopy(inPolys);

					for (cellId = 0; cellId < numCells && !abort; cellId++)
					{
						type = mesh->GetCellType(cellId);
						if (type == VTK_TRIANGLE || type == VTK_QUAD || type == VTK_POLYGON)
						{
							mesh->GetCellPoints(cellId, npts, pts);
							newPolys->InsertNextCell(npts, pts);
							outCD->CopyData(cd, cellId, newCellId++);
							newPolys->InsertNextCell(npts);
							for (i = 0; i < npts; i++)
							{
								newPolys->InsertCellPoint(pts[i] + this->Resolution * numPts);
							}
							outCD->CopyData(cd, cellId, newCellId++);
						}
					}
				}

				for (cellId = 0; cellId < numCells && !abort; cellId++)
				{
					type = mesh->GetCellType(cellId);
					if (type == VTK_TRIANGLE_STRIP)
					{
						mesh->GetCellPoints(cellId, npts, pts);
						newStrips->InsertNextCell(npts, pts);
						outCD->CopyData(cd, cellId, newCellId++);
						newStrips->InsertNextCell(npts);
						for (i = 0; i < npts; i++)
						{
							newStrips->InsertCellPoint(pts[i] + this->Resolution * numPts);
						}
						outCD->CopyData(cd, cellId, newCellId++);
					}
				}
			} // if capping
			this->UpdateProgress(0.5);
			abort = this->CheckAbort();

			// Now process lines, polys and/or strips to produce strips
			//
			if (inLines->GetNumberOfCells() || inPolys->GetNumberOfCells() || inStrips->GetNumberOfCells())
			{
				cellIds = vtkIdList::New();
				cellIds->Allocate(VTK_CELL_SIZE);
				vtkGenericCell* cell = vtkGenericCell::New();

				for (cellId = 0; cellId < numCells && !abort; cellId++)
				{
					type = mesh->GetCellType(cellId);
					if (type == VTK_LINE || type == VTK_POLY_LINE)
					{
						mesh->GetCellPoints(cellId, npts, pts);
						for (i = 0; i < (npts - 1); i++)
						{
							p1 = pts[i];
							p2 = pts[i + 1];
							newStrips->InsertNextCell(2 * (this->Resolution + 1));
							for (j = 0; j <= this->Resolution; j++)
							{
								newStrips->InsertCellPoint(p2 + j * numPts);
								newStrips->InsertCellPoint(p1 + j * numPts);
							}
							outCD->CopyData(cd, cellId, newCellId++);
						}
					} // if a line

					else if (type == VTK_TRIANGLE || type == VTK_QUAD || type == VTK_POLYGON ||
						type == VTK_TRIANGLE_STRIP)
					{ // create strips from boundary edges
						mesh->GetCell(cellId, cell);
						numEdges = cell->GetNumberOfEdges();
						for (i = 0; i < numEdges; i++)
						{
							edge = cell->GetEdge(i);
							for (j = 0; j < (edge->GetNumberOfPoints() - 1); j++)
							{
								p1 = edge->PointIds->GetId(j);
								p2 = edge->PointIds->GetId(j + 1);
								mesh->GetCellEdgeNeighbors(cellId, p1, p2, cellIds);

								if (cellIds->GetNumberOfIds() < 1) // generate strip
								{
									newStrips->InsertNextCell(2 * (this->Resolution + 1));
									for (k = 0; k <= this->Resolution; k++)
									{
										newStrips->InsertCellPoint(p2 + k * numPts);
										newStrips->InsertCellPoint(p1 + k * numPts);
									}
									outCD->CopyData(cd, cellId, newCellId++);
								} // if boundary edge
							}   // for each sub-edge
						}     // for each edge
					}       // for each polygon or triangle strip
				}         // for all cells

				cellIds->Delete();
				cell->Delete();
			} // if strips are being generated
			this->UpdateProgress(1.00);

			// Update ourselves and release memory
			//
			output->SetPoints(newPts);
			newPts->Delete();
			mesh->Delete();

			if (newLines)
			{
				output->SetLines(newLines);
				newLines->Delete();
			}

			if (newPolys)
			{
				output->SetPolys(newPolys);
				newPolys->Delete();
			}

			output->SetStrips(newStrips);
			newStrips->Delete();

			output->Squeeze();

			return 1;
		}
	};

	vtkStandardNewMacro(myRotationalExtrusionFilter);
}

最后在建模代码中把 vtkRotationalExtrusionFilter 换成自定义的 myRotationalExtrusionFilter,运行结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值