一、封闭性检测
如果一条边只被一个多边形包含,那么这条边就是边界边。
是否存在边界边是检测一个网格模型是否封闭的重要特征。
vtkFeatureEdges是一个非常重要的类,该类能够提取多边形网格模型中四种类型的边。
1)边界边。即只被一个多边形或者一条边包含的边。
2)非流形边。被3个或者3个以上的多边形包含的边即为非流形边。
3)特征边。上节中也提到,需要设置一个特征角的阈值,当包含同一条边的两个三角形的法向量的夹角大于该阈值时,即为一个特征边。
4)流形边。只被两个多边形包含的边即为流形边。
可以使用该类来检测是否存在边界边,并依此来判断网格是否封闭。检测出网格是否封闭之后可以通过类vtkFillHoleFilter将漏洞填补起来。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkSelectionNode.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkInformation.h>
#include <vtkExtractSelection.h>
#include <vtkPolyDataMapper.h>
#include <vtkDataSetSurfaceFilter.h>
#include <vtkFeatureEdges.h>
#include <vtkFillHolesFilter.h>
#include <vtkPolyDataNormals.h>
#include <vtkCamera.h>
#include <QDebug>
void GenerateData(vtkSmartPointer<vtkPolyData> input)
{
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->Update();
vtkSmartPointer<vtkIdTypeArray> ids = vtkSmartPointer<vtkIdTypeArray>::New();
ids->SetNumberOfComponents(1);
ids->InsertNextValue(2);
ids->InsertNextValue(10);
vtkSmartPointer<vtkSelectionNode> selectionNode = vtkSmartPointer<vtkSelectionNode>::New();
selectionNode->SetFieldType(vtkSelectionNode::CELL);
selectionNode->SetContentType(vtkSelectionNode::INDICES);
selectionNode->SetSelectionList(ids);
selectionNode->GetProperties()->Set(vtkSelectionNode::INVERSE(), 1);
vtkSmartPointer<vtkSelection> selection = vtkSmartPointer<vtkSelection>::New();
selection->AddNode(selectionNode);
vtkSmartPointer<vtkExtractSelection> extractSelection = vtkSmartPointer<vtkExtractSelection>::New();
extractSelection->SetInputData(0, sphereSource->GetOutput());
extractSelection->SetInputData(1, selection);
extractSelection->Update();
vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter = vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
surfaceFilter->SetInputConnection(extractSelection->GetOutputPort());
surfaceFilter->Update();
input->ShallowCopy(surfaceFilter->GetOutput());
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPolyData> input = vtkSmartPointer<vtkPolyData>::New();
GenerateData(input);
vtkSmartPointer<vtkFeatureEdges> featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
featureEdges->SetInputData(input);
featureEdges->BoundaryEdgesOn();
featureEdges->FeatureEdgesOff();
featureEdges->ManifoldEdgesOff();
featureEdges->NonManifoldEdgesOff();
featureEdges->Update();
int numberOfOpenEdges = featureEdges->GetOutput()->GetNumberOfCells();
if(numberOfOpenEdges)
{
qDebug()<<"该网格模型不是封闭的...";
}
else
{
qDebug()<<"该网格模型是封闭的...";
return 0;
}
vtkSmartPointer<vtkFillHolesFilter> fillHolesFilter = vtkSmartPointer<vtkFillHolesFilter>::New();
fillHolesFilter->SetInputData(input);
fillHolesFilter->Update();
vtkSmartPointer<vtkPolyDataNormals> normals = vtkSmartPointer<vtkPolyDataNormals>::New();
normals->SetInputConnection(fillHolesFilter->GetOutputPort());
normals->ConsistencyOn();
normals->SplittingOff();
normals->Update();
double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};
vtkSmartPointer<vtkPolyDataMapper> originalMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
originalMapper->SetInputData(input);
vtkSmartPointer<vtkProperty> backfaceProp = vtkSmartPointer<vtkProperty>::New();
backfaceProp->SetDiffuseColor(0.89,0.81,0.34);
vtkSmartPointer<vtkActor> originalActor = vtkSmartPointer<vtkActor>::New();
originalActor->SetMapper(originalMapper);
originalActor->SetBackfaceProperty(backfaceProp);
originalActor->GetProperty()->SetDiffuseColor(1.0, 0.3882, 0.2784);
vtkSmartPointer<vtkPolyDataMapper> edgeMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
edgeMapper->SetInputData(featureEdges->GetOutput());
vtkSmartPointer<vtkActor> edgeActor =
vtkSmartPointer<vtkActor>::New();
edgeActor->SetMapper(edgeMapper);
edgeActor->GetProperty()->SetEdgeColor(0.,0.,1.0);
edgeActor->GetProperty()->SetEdgeVisibility(1);
edgeActor->GetProperty()->SetLineWidth(5);
vtkSmartPointer<vtkPolyDataMapper> filledMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
filledMapper->SetInputData(normals->GetOutput());
vtkSmartPointer<vtkActor> filledActor = vtkSmartPointer<vtkActor>::New();
filledActor->SetMapper(filledMapper);
filledActor->GetProperty()->SetDiffuseColor(1.0, 0.3882, 0.2784);
vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->AddActor(originalActor);
leftRenderer->AddActor(edgeActor);
leftRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->AddActor(filledActor);
rightRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataClosed");
w.show();
leftRenderer->GetActiveCamera()->SetPosition(0, -1, 0);
leftRenderer->GetActiveCamera()->SetFocalPoint(0, 0, 0);
leftRenderer->GetActiveCamera()->SetViewUp(0, 0, 1);
leftRenderer->GetActiveCamera()->Azimuth(30);
leftRenderer->GetActiveCamera()->Elevation(30);
leftRenderer->ResetCamera();
rightRenderer->SetActiveCamera(leftRenderer->GetActiveCamera());
return a.exec();
}
运行效果:
二、联通区域分析
许多图形数据中,并非只包含一个对象(连通区域)。而在处理这些图形数据时,有时需要对每一个对象单独处理或者让其单独显示。比如利用MarchingCube 方法提取三维图像中的等值面,得到的结果往往是存在多个连通的对象区域,这时就需要对图形数据做连通区域分析,提取每个连通区域并计算其属性信息,以此来得到需要的连通区域。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkConeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkAppendPolyData.h>
#include <vtkPolyDataConnectivityFilter.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->SetRadius(10);
sphereSource->SetThetaResolution(10);
sphereSource->SetPhiResolution(10);
sphereSource->Update();
vtkSmartPointer<vtkConeSource> coneSource = vtkSmartPointer<vtkConeSource>::New();
coneSource->SetRadius(5);
coneSource->SetHeight(10);
coneSource->SetCenter(25,0,0);
coneSource->Update();
vtkSmartPointer<vtkAppendPolyData> appendFilter = vtkSmartPointer<vtkAppendPolyData>::New();
appendFilter->AddInputData(sphereSource->GetOutput());
appendFilter->AddInputData(coneSource->GetOutput());
appendFilter->Update();
vtkSmartPointer<vtkPolyDataConnectivityFilter> connectivityFilter = vtkSmartPointer<vtkPolyDataConnectivityFilter>::New();
connectivityFilter->SetInputData(appendFilter->GetOutput());
connectivityFilter->SetExtractionModeToCellSeededRegions();
connectivityFilter->AddSeed(100);
connectivityFilter->Update();
vtkSmartPointer<vtkPolyDataMapper> originalMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
originalMapper->SetInputConnection(appendFilter->GetOutputPort());
originalMapper->Update();
vtkSmartPointer<vtkActor> originalActor = vtkSmartPointer<vtkActor>::New();
originalActor->SetMapper(originalMapper);
vtkSmartPointer<vtkPolyDataMapper> extractedMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
extractedMapper->SetInputConnection(connectivityFilter->GetOutputPort());
extractedMapper->Update();
vtkSmartPointer<vtkActor> extractedActor = vtkSmartPointer<vtkActor>::New();
extractedActor->SetMapper(extractedMapper);
double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};
vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->AddActor(originalActor);
leftRenderer->SetBackground(0.8, 0.8, 0.8);
vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->AddActor(extractedActor);
rightRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataConnectedCompExtract");
w.show();
return a.exec();
}
运行效果:
vtkPolyDataConnectivityFilter类用于实现连通区域分析,它有以下典型函数:
1、SetExtractionModeToLargestRegion():用于提取具有最多点的连通区域。
2、SetExtractionModeToAllRegion():用于连通区域标记,配合函数ColorRegionsOn()一起使用。
3、SetExtractionModeToSpecifiedRegion():用于提取一个或多个连通区域,需要通过AddSpecifiedRegion()来添加需要提取的边界号。
4、SetExtractionModeToClosestPointRegion():该模式下需要使用 SetClosestPoint()函数设置一个空间点坐标,其执行结果为离该点最近的连通区域。
5、SetExtractionModeToPointSeededRegions():该模式下需要使用 AddSeed()函数添加种子点,提取种子点所在的区域。
6、SetExtractionModeToCellSeededRegions():该模式下需要使用 AddSeed()函数添加种子单元,提取种子单元所在的区域。
三、多分辨率处理
模型抽取(Decimation)和细化(Subdivision)是两个相反的操作,是三角形网格模型多分辨率处理中的两个重要操作。使用这两个操作可以在保持模型拓扑结构的同时,得到不同分辨率的网格模型。模型抽取的作用是减少模型数据中的点数据和单元数据,便于模型的后续处理与交互渲染,这类似于图像数据的降采样。而网格细化则是利用一定的细化规则,在给定的初始网格中插入新的点,从而不断细化出新的网格单元,在极限细化情况下,该网格能够收敛于一个光滑曲面。
1、网格抽取
VTK 中主要有三种网格抽取类:vtkDecimatePro、vtkQuadricDecimation 和 vtkQuadricClustering。vtkDecimatePro 是最常用的,是用一种边塌陷的方法来删除点和单元,处理速度比较快,而且可以方便地控制网格抽取的幅度,得到不同级别的模型数据。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkPolyDataReader.h>
#include <vtkDecimatePro.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkCamera.h>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPolyDataReader> reader = vtkSmartPointer<vtkPolyDataReader>::New();
reader->SetFileName("D:/data/fran_cut.vtk");
reader->Update();
vtkSmartPointer<vtkPolyData> original = reader->GetOutput();
qDebug() << "抽取前:------------" ;
qDebug() << "模型点数为: " << original->GetNumberOfPoints();
qDebug() << "模型面数为: " << original->GetNumberOfPolys();
vtkSmartPointer<vtkDecimatePro> decimate = vtkSmartPointer<vtkDecimatePro>::New();
decimate->SetInputData(original);
decimate->SetTargetReduction(.80);
decimate->Update();
vtkSmartPointer<vtkPolyData> decimated = decimate->GetOutput();
qDebug() << "抽取后:" << "------------";
qDebug() << "模型点数为:" << decimated->GetNumberOfPoints();
qDebug() << "模型面数为:" << decimated->GetNumberOfPolys();
vtkSmartPointer<vtkPolyDataMapper> origianlMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
origianlMapper->SetInputData(original);
vtkSmartPointer<vtkActor> origianlActor = vtkSmartPointer<vtkActor>::New();
origianlActor->SetMapper(origianlMapper);
vtkSmartPointer<vtkPolyDataMapper> decimatedMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
decimatedMapper->SetInputData(decimated);
vtkSmartPointer<vtkActor> decimatedActor = vtkSmartPointer<vtkActor>::New();
decimatedActor->SetMapper(decimatedMapper);
double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};
vtkSmartPointer<vtkRenderer> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->AddActor(origianlActor);
leftRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->AddActor(decimatedActor);
rightRenderer->SetBackground(1.0, 1.0, 1.0);
leftRenderer->GetActiveCamera()->SetPosition(0, -1, 0);
leftRenderer->GetActiveCamera()->SetFocalPoint(0, 0, 0);
leftRenderer->GetActiveCamera()->SetViewUp(0, 0, 1);
leftRenderer->GetActiveCamera()->Azimuth(30);
leftRenderer->GetActiveCamera()->Elevation(30);
leftRenderer->ResetCamera();
rightRenderer->SetActiveCamera(leftRenderer->GetActiveCamera());
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataDecimation");
w.show();
return a.exec();
}
运行效果:
vtkOuadricDecimation也可以实现三角网格的简化,并能较好地逼近原模型。该类虽然提供了 SetTargetReduction()函数用于设置模型简化程度,但是最终简化率并非严格等于程序中设置的简化率。可以通过GetActualReduction()函数来获取最终模型简化率。
vtkQuadricClustering 是三种模型抽取类中最快的一种,能够处理大数据模型。通过 StartAppend()、Append()和 EndAppend()函数可以将整个模型分为多个网格片处理,从而避免一次性处理整个模型,减少内存开支,提高处理效率。
三个网格抽取类都接受的是vtkPolyData的三角网格数据:如果vkPolyData 数据为多边形网格数据,需要先通过 vtkTriangleFilter 将多边形网络数据转换为三角网格数据。
2、网格细化
VTK中实现网格细化的类有 vtkLinearSubdivisionFilter、vtkLoopSubdivisionFilter 和vtkButerflySubdivisionFilter。
vtkInterpolatingSubdivisionFilter 内部提供了 SetNumberOfSubdivisions()函数来设置细化的次数,其中每次细化后模型的三角面片的个数将是细化前的4倍。因此,在对网格模型进行n次细分后,该模型的面片个数将成为原始模型面片数目的4n倍。vtkLinearSubdivisionFilter 实现了一种线性细分算法,每次细分将每个三角片面生成4个新的面片,该算法比较简单,速度快,但是细分后不能产生光滑的模型。vtkLoopSubdivisionFilter 实现了Loop 细分算法,每次细分会将一个三角面片生成4个三角面片。该方法能够生成光滑的连续曲面,应用比较广泛。vtkButterflySubdivisionFilter实现了蝶形细分算法。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkSphereSource.h>
#include <vtkPolyDataAlgorithm.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkLinearSubdivisionFilter.h>
#include <vtkLoopSubdivisionFilter.h>
#include <vtkButterflySubdivisionFilter.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkPolyData> originalMesh;
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->Update();
originalMesh = sphereSource->GetOutput();
double numberOfViewports = 3;
int numberOfSubdivisions = 2;
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
for(unsigned i = 0; i < numberOfViewports; i++)
{
vtkSmartPointer<vtkPolyDataAlgorithm> subdivisionFilter;
switch(i)
{
case 0:
subdivisionFilter = vtkSmartPointer<vtkLinearSubdivisionFilter>::New();
dynamic_cast<vtkLinearSubdivisionFilter *> (subdivisionFilter.GetPointer())->SetNumberOfSubdivisions(numberOfSubdivisions);
break;
case 1:
subdivisionFilter = vtkSmartPointer<vtkLoopSubdivisionFilter>::New();
dynamic_cast<vtkLoopSubdivisionFilter *> (subdivisionFilter.GetPointer())->SetNumberOfSubdivisions(numberOfSubdivisions);
break;
case 2:
subdivisionFilter = vtkSmartPointer<vtkButterflySubdivisionFilter>::New();
dynamic_cast<vtkButterflySubdivisionFilter *> (subdivisionFilter.GetPointer())->SetNumberOfSubdivisions(numberOfSubdivisions);
break;
default:
break;
}
subdivisionFilter->SetInputData(originalMesh);
subdivisionFilter->Update();
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderWindow->AddRenderer(renderer);
renderer->SetViewport(static_cast<double>(i)/numberOfViewports,0,static_cast<double>(i+1)/numberOfViewports,1);
renderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(subdivisionFilter->GetOutputPort());
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
renderer->AddActor(actor);
renderer->ResetCamera();
}
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("PolyDataSubdivision");
w.show();
return a.exec();
}
运行效果:
模型细化算子仅对三角网格数据有效,因此在处理多边形数据时,需要通过vtkTriangleFilter将多边形数据转换为三角网格数据。