本文的主要内容:简单介绍VTK中与布尔运算相关功能。 主要涉及vtkBooleanOperationPolyDataFilter/vtkLoopBooleanPolyDataFilter,vtkImplicitBoolean,vtkBooleanTexture三个类。
哪些人适合阅读本文:有一定VTK基础的人。
一、vtkBooleanOperationPolyDataFilter
vtkBooleanOperationPolyDataFilter和vtkLoopBooleanPolyDataFilter的用法、用途完全一样,这里只说其中一个。用途就是输入两个PolyeData,输出它们的交并差。
VTK官网描述:
计算根据两个输入曲面定义的体积计算出的并集、交集或差异体积的边界。
这两个表面不需要是流形的,但如果不是流形,可能会得到意想不到的结果。所得到的表面在过滤器的第一个输出中可用。第二个输出包含一组折线,表示两个输入曲面之间的交点。
警告:
该过滤器不是为执行2D布尔运算而设计的,事实上,它依赖于没有共面、重叠单元的输入。
写得很清楚,它对输入模型的要求比较高,对于共面或有重叠部分的输入模型无效。不只是会得到意想不到的结果,还可能直接导致程序崩溃。一般在使用之前将模型经过vtkTriangleFilter和vtkCleanPolyData进行处理。就算进行了处理,对于非结构化的输入模型还是大概率会计算失败。总而言之,这个算法不稳定,不推荐使用。
vtkSmartPointer<vtkPolyData> input1;
vtkSmartPointer<vtkPolyData> input2;
std::string operation("intersection");
vtkNew<vtkSphereSource> sphereSource1;
sphereSource1->SetCenter(0.25, 0, 0);
sphereSource1->SetPhiResolution(41);
sphereSource1->SetThetaResolution(41);
sphereSource1->Update();
input1 = sphereSource1->GetOutput();
vtkNew<vtkSphereSource> sphereSource2;
sphereSource2->SetPhiResolution(41);
sphereSource2->SetThetaResolution(41);
sphereSource2->Update();
input2 = sphereSource2->GetOutput();
vtkNew<vtkBooleanOperationPolyDataFilter> booleanOperation;
if (operation == "union")
{
booleanOperation->SetOperationToUnion();
}
else if (operation == "intersection")
{
booleanOperation->SetOperationToIntersection();
}
else if (operation == "difference")
{
booleanOperation->SetOperationToDifference();
}
else
{
std::cout << "Unknown operation: " << operation << std::endl;
return;
}
booleanOperation->SetInputData(0, input1);
booleanOperation->SetInputData(1, input2);
vtkNew<vtkPolyDataMapper> booleanOperationMapper;
booleanOperationMapper->SetInputConnection(booleanOperation->GetOutputPort());
booleanOperationMapper->ScalarVisibilityOff();
vtkNew<vtkActor> booleanOperationActor;
booleanOperationActor->SetMapper(booleanOperationMapper);
booleanOperationActor->GetProperty()->SetDiffuseColor(colors->GetColor3d("Banana").GetData());
booleanOperationActor->GetProperty()->SetSpecular(.6);
booleanOperationActor->GetProperty()->SetSpecularPower(20);
红色和绿色是输入的两个模型,黄色是它们相交的结果。
二、vtkImplicitBoolean
这个类从名字也看得出来,简单来说就是输入隐函数,输出交并差。
VTK官网描述:
由隐函数的布尔组合组成的隐函数
vtkImplicitBoolean是一个隐式函数,由隐式函数的布尔组合组成。该类有一个函数列表(FunctionList),这些函数根据指定的运算符(VTK_UNION、VTK_INTERSECTION或VTK_DIFFERENCE)组合在一起。您可以使用vtkImplicitFunction(和/或vtkImplicaitBoolean)的嵌套组合来创建复杂的隐式函数。vtkImplicitBoolean是vtkImplicaitFunction的具体实现。
操作员的工作原理如下。VTK_UNION运算符取所有隐式函数的最小值。VTK_INTERSECTION运算符取所有隐式函数的最大值。VTK_DIFFERENCE运算符从第一个隐函数中减去第二个到最后一个隐函数。VTK_UNION_OF_MAGNITUDES取隐式函数的最小绝对值。
因为都是由隐函数组成,相比vtkBooleanOperationPolyDataFilter这个就稳定得多。但是它的输入只能是vtkSphere、vtkBox或者自定义的隐函数,不能是任意模型,更不能是从stl等文件读取的模型。另外如果想要将结果显示出来通常还要用到采样函数vtkSampleFunction和vtkContourFilter,这就导致如果想要做实时的布尔运算,会一直累加隐函数,然后采样的时间消耗就会非常大。
vtkNew<vtkNamedColors> colors;
// create a sphere
vtkNew<vtkSphere> sphere;
sphere->SetRadius(1);
sphere->SetCenter(1, 0, 0);
// create a box
vtkNew<vtkBox> box;
box->SetBounds(-1, 1, -1, 1, -1, 1);
// combine the two implicit functions
vtkNew<vtkImplicitBoolean> boolean;
boolean->AddFunction(box);
boolean->AddFunction(sphere);
std::vector<vtkNew<vtkRenderer>> ren;
ren.push_back(vtkNew<vtkRenderer>());
ren.push_back(vtkNew<vtkRenderer>());
ren.push_back(vtkNew<vtkRenderer>());
ren[0]->SetViewport(0, 0, 1.0 / 3.0, 1); // Difference
ren[1]->SetViewport(1.0 / 3.0, 0, 2.0 / 3.0, 1); // Union
ren[2]->SetViewport(2.0 / 3.0, 0, 1, 1); // Intersection
// Shared camera
vtkNew<vtkCamera> camera;
camera->Azimuth(30.0);
camera->Elevation(30.0);
vtkNew<vtkColorSeries> colorSeries;
colorSeries->SetColorScheme(vtkColorSeries::BREWER_DIVERGING_SPECTRAL_3);
for (int i = 0; i < 3; ++i)
{
if (i == 0)
{
boolean->SetOperationTypeToDifference();
}
else if (i == 1)
{
boolean->SetOperationTypeToUnion();
}
else
{
boolean->SetOperationTypeToIntersection();
}
// The sample function generates a distance function from the implicit
// function. This is then contoured to get a polygonal surface.
vtkNew<vtkSampleFunction> sample;
sample->SetImplicitFunction(boolean);
sample->SetModelBounds(-1, 2, -1, 1, -1, 1);
sample->SetSampleDimensions(100, 100, 100);
sample->ComputeNormalsOff();
// contour
vtkNew<vtkContourFilter> surface;
surface->SetInputConnection(sample->GetOutputPort());
surface->SetValue(0, 0.0);
surface->Update();
vtkNew<vtkPolyData> polyData;
polyData->DeepCopy(surface->GetOutput());
// mapper
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(polyData);
mapper->ScalarVisibilityOff();
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
//actor->GetProperty()->EdgeVisibilityOn();
actor->GetProperty()->SetColor(colors->GetColor3d("peacock").GetData());
// add the actor
ren[i]->SetBackground(colorSeries->GetColor(i).GetRed() / 255.0,
colorSeries->GetColor(i).GetGreen() / 255.0,
colorSeries->GetColor(i).GetBlue() / 255.0);
ren[i]->SetActiveCamera(camera);
ren[i]->AddActor(actor);
ren[i]->ResetCamera();
}
// render window
renderWin->AddRenderer(ren[0]);
renderWin->AddRenderer(ren[1]);
renderWin->AddRenderer(ren[2]);
renderWin->Render();
从左到右依次是交并差。
三、vtkBooleanTexture
这个类,是通过隐函数可以识别内(1)、外(-1)、上(0)的能力,从而对不同部位的模型设置不同的纹理显示模式clear、solid、edge等,从而达到一种看起来做了布尔运算的效果。实际上是没有将数据集用布尔进行区分,所以也非常稳定。
VTK官网描述:
基于内部、外部和区域边界的组合生成2D纹理图。
vtkBooleanTexture是一个过滤器,用于根据内部、外部和区域边界的组合生成2D纹理图。“区域”通过2D纹理坐标隐式表示。这些纹理坐标通常使用类似vtkImplicitTextureCoords的过滤器生成,该过滤器为任何隐式函数生成纹理坐标。
vtkBooleanTexture根据s-t纹理坐标以及在区域内、区域上或区域外的概念生成贴图。区域内是指纹理坐标在(0,0.5-厚度/2)之间。外区域是纹理坐标为(0.5+厚度/2)的地方。导通区域介于(0.5厚度/2,0.5+厚度/2)之间。每个s-t纹理坐标的in、on和out组合产生16种可能的组合(见正文)。对于每种组合,可以指定不同的强度和透明度值。要指定最大强度和/或不透明度,请使用值255。最小值0会产生黑色区域(用于强度)和完全透明区域(用于透明度)。
核心代码如下:
vtkNew<vtkNamedColors> colors;
vtkNew<vtkRenderer> aren;
// define two elliptical cylinders
vtkNew<vtkQuadric> quadric1;
quadric1->SetCoefficients(1, 2, 0, 0, 0, 0, 0, 0, 0, -.07);
vtkNew<vtkQuadric> quadric2;
quadric2->SetCoefficients(2, 1, 0, 0, 0, 0, 0, 0, 0, -.07);
// create a sphere for all to use
vtkNew<vtkSphereSource> aSphere;
aSphere->SetPhiResolution(21);
aSphere->SetThetaResolution(21);
// create texture coordinates for all
vtkNew<vtkImplicitTextureCoords> tcoords;
tcoords->SetInputConnection(aSphere->GetOutputPort());
tcoords->SetRFunction(quadric1);
tcoords->SetSFunction(quadric2);
vtkNew<vtkDataSetMapper> aMapper;
aMapper->SetInputConnection(tcoords->GetOutputPort());
// create a mapper, sphere and texture map for each case
for (int i = 0; i < 16; i++)
{
auto aBoolean = MakeBooleanTexture(i, 64, 0);
vtkNew<vtkTexture> aTexture2;
aTexture2->SetInputConnection(aBoolean->GetOutputPort());
aTexture2->InterpolateOff();
aTexture2->RepeatOff();
vtkNew<vtkActor> anActor2;
anActor2->SetMapper(aMapper);
anActor2->SetTexture(aTexture2);
anActor2->SetPosition(&positions[i][0]);
anActor2->SetScale(2.0, 2.0, 2.0);
anActor2->GetProperty()->SetColor(colors->GetColor3d("MistyRose").GetData());
aren->AddActor(anActor2);
}
aren->SetBackground(colors->GetColor3d("SlateGray").GetData());
renderWin->AddRenderer(aren);
renderWin->Render();
显示了16种布尔的效果。可以看出显示效果还可以,但是有个问题就是切割之后的面不封闭。
总结:
如果想要对任意输入的两个模型进行稳定得布尔运算,只用VTK大概率是实现不了的。下一篇将会介绍几种相对稳定的布尔运算实现方法。