VTK 建模方法:Extrusion
VTK 建模方法:Extrusion
vtkImplicitModeller 建模的局限性
前一篇文章 《VTK 建模方法:建模基础》 介绍了如何使用 vtkImplicitModeller 类进行建模。它的局限性体现在以下 3 个方面:
- 场景的局限性:vtkImplicitModeller 建模生成的模型比较光滑,工业场景中经常存在有棱有角的模型,使用 vtkImplicitModeller 就不能很好完成建模。
- 精度的局限性:无论采样的精度有多高,vtkImplicitModeller 建模终究不是精准的表述。
- 性能的局限性:如果把采样精度设置得很高,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,运行结果: