这篇博文记录一个解决函数体建模的初步思路。
项目开发需要使用函数描述一个三维模型,然后显示出模型的样子,大概原理是:
现有函数 F(x,y,z)= C ,然后给函数参数限定限定一个范围 如 { |x| < 2,|y| < 2 ,|z| < 2},对函数F求解,C < 0 的区域构成模型。
目前有两种显示方案,一种是本文记录的方案,继承vtkImplicitFuction 实现一个隐函数类,然后求得该隐函数在函数区域内,C=0的等值面。这样相当于获取了模型的表面,然后使用OCC的面缝补算法,对将表面缝合成为一个实体。
另一种由于相性与该项目有些出入,被舍弃掉,大概思路是,在函数的限定范围内划分出结构化网格,然后使用网格坐标确定网格所在的位置在函数中是否是C < 0,将C < 0的网格收集起来,使用vtkStructGrid 类将网格显示出来,就可以得到模型。得到的模型精度取决于你的网格精度,这是被舍弃的原因之一,还有就是在极坐标系内,网格单元比较难被vtk正常的处理。
vtkImplicitFunction
这个类为vtk提供了隐函数接口,只要继承它,然后实现
virtual double EvaluateFunction(double x[3]); virtual void EvaluateGradient(double x[3], double g[3]);
这两个纯虚函数就可以了。
其中double EvaluateFunction(double x[3]) 用于输入一个xzy参数,然后返回函数的值。
官方文档中指出void EvaluateGradient(double x[3], double g[3])需要计算出x点的在函数中的梯度,这个梯度似乎是高数中的一个概念(表示不是很懂),然后将梯度放入g中,就目前的测试代码来看不是很明白这个梯度在隐函数中发挥的作用是什么,我在实现自定义隐函数中给了这个函数一个空的实现(函数中什么也不做),似乎对最后的结果没有什么影响,希望有大佬能为我解惑。
vtkSampleFunction
它的主要作用是对隐函数进行采样,看vtkImplicitFuction的文档可知隐函数类是无法生成vtkDataSet这种类型的数据类的,所以需要使用vtkSampleFunction对函数进行采样生成数据。它会对隐函数的点进行采样,生成点集和点上的标量值(函数值),所以 要创建闭合曲面必须与 vtkContourFilter一起使用。
void SetSampleDimensions (int dim[3])
用于设置隐函数的采样率,即每个方向的数据采样个数,不调用设置则默认是50。
virtual void SetCapping (vtkTypeBool) virtual vtkTypeBool GetCapping () virtual void CappingOn () virtual void CappingOff () virtual void SetCapValue (double) virtual double GetCapValue ()
这一些列函数用于设置采样时是否对曲面进行封顶,原理是在采样时,当数据采样碰到函数边界时,且当这个位置的值 < 0,那么将这个位置的标量设置为预先准备好的标量值,这个值使用SetCapValue(double) 进行设置,然后使用vtkContourFilter获取等值面时根据这个值获取,就可以对曲面进行封顶,具体效果后面的示例中会体现。
关于等值面提取的过程可以参照http://cppdebug.com/archives/252
示例
使用上次提取等值面的函数实现一个简单的流程
class MyFuntion :public vtkImplicitFunction { public: MyFuntion(); ~MyFuntion() = default; virtual double EvaluateFunction(double d[3]); virtual void EvaluateGradient(double x[3], double g[3]); };
#include "MyFunction.h" MyFuntion::MyFuntion() { } double MyFuntion::EvaluateFunction(double d[3]) { double x = d[0]; double y = d[1]; double z = d[2]; return pow(x*x + (9.0 / 4.0)*y*y + z*z- 1.0,3) - x*x*pow(z,3) - (9.0 / 80.0)*y*y * pow(z,3); } void MyFuntion::EvaluateGradient(double x[3], double g[3]) { //作用不明,空实现似乎没有影响 }
//创建一个隐函数 MyFuntion *function = new MyFuntion(); //对函数进行采样 vtkNew<vtkSampleFunction> sample; sample->SetSampleDimensions(50, 50, 50); sample->SetImplicitFunction(function); //设置函数采样范围 double value = 2.0; double xmin = -value, xmax = value, ymin = -value, ymax = value, zmin = -value, zmax = value; sample->SetModelBounds(xmin, xmax, ymin, ymax, zmin, zmax); //提取等值面 vtkNew<vtkContourFilter> filter; filter->SetInputConnection(sample->GetOutputPort()); filter->SetValue(0, 0); vtkNew<vtkPolyDataMapper> mapper; mapper->SetInputConnection(filter->GetOutputPort()); mapper->ScalarVisibilityOff(); vtkNew<vtkActor> actor; actor->SetMapper(mapper); vtkNew<vtkRenderer> renderer; vtkNew<vtkRenderWindow> window; window->AddRenderer(renderer); vtkNew<vtkRenderWindowInteractor> interactor; interactor->SetRenderWindow(window); renderer->AddActor(actor); window->Render(); interactor->Start();
如图,确实和之前提取等值面的示例如出一辙,由于这个函数在{x[-2,2],y[-2,2],z[-2,2]}这个范围内本身就是封闭的, 这里并没有使用封顶这么一个功能,来尝试一下将采样包围盒缩小到1。
果然是程序员的心都是空空的QAQ!
遇到非封闭的函数,就可以使用封顶功能了~~
sample->CappingOn(); sample->SetCapValue(0);
锵锵锵~~~
这下不是空的了
这个方案只是一个大概思路,后面也许会更新OCC缝合部分(也许),然后函数梯度也没搞懂是干嘛的,不过已经在vtk社区中发帖问了,如果有结果后续会贴上。
另欢迎留言讨论。
2022.3.22更新
文章评论
大佬,这样获得的还是一个空心的polydata吧
@加把劲 那要看你怎么定义空心了,一个封闭的polydata是可以用作体渲染的。