Key Words: HLR, Outline Edge, Sihouette Edge
OpenCASCADE中关于隐藏线消除HLR法的描q就是一句话QThese algorithms are based on the principle of comparing each edge of the shape to be visualized with each of its faces, and calculating the visible and the hidden parts of each edge. x据面判断每条边Edge的遮挡关p,计算Edge可见和不可见部分。所以HLR法的输入主要ؓ边和面,计算遮挡关系依赖UK求交法。对于精的HLR法依赖_的线面求交算法,PolyAlgo法依赖多段U与|格求交法。输入的边中除了BREP中的边以外,q有一cLҎ投媄方向计算得来的,卛_轮廓UOutlineQ也UCؓContourUѝACIS的PHLR中称轮廓USihouette Edge?/p>
轮廓U的计算是HLR中比较关键的一步,本文以OpenCASCADE中简单的二次曲面的轮廓线计算入手来理解曲面的轮廓U概念,为理解Q意曲面轮廓线计算打下基础?/p>
OpenCASCADE的HLR中用类HLRTopoBRep_OutLiner来计外轮廓Uѝ轮廓线的计依赖投影方向及投媄方式Q主要计逻辑在函数Fill()中:
投媄方式主要分ؓ透视投媄Perspective和^行投影,工程囄成一般用^行投影方式。实际计类是Contap_ContourQ在cContap_Contour中又Ҏ投媄曲面cd分ؓ两种cd来处理:
其中函数PerformAna()^面、球面、圆柱面、圆锥面的外轮廓U,最l会使用cContap_ContAna。其中Ana为Analytical解析曲面的意思,q里指能用解析表辑ּ表示的二ơ曲面?/p>
cContap_ContAna能计球面、圆柱面和圆锥面的外轮廓UContourQ下面我们主要来看看q三c面的外轮廓U计结果?/p>
对于q投媄球面会生成以投媄方向为法向,以球半径为半径的一个圆Q代码如下所C:
如下图所CZ的绿色的U:
对于q投媄圆柱面会生成两条直线Q若投媄方向与圆柱面法向q时不生成轮廓U,q时是使用圆柱体中的上下两个圆的边。代码如下所C:
生成的两条直U方向ؓ圆柱面的轴方向:
圆锥面的轮廓U生成函数逻辑cMQ留l读者自行分析理解?/p>
lg所qͼBREP的HLR法需要计模型的外轮廓线。如球体的BREPҎ两个退化边Q极点)Q及两个重合边,若来投媄实质上只有重合边中的一条边有用Q而这个边q是个半圆。从理解 单的二次曲面外轮廓线计算函数入手Q再L入理解Q意曲面的外轮廓线计算Ҏ?/p>
理解HLR实现原理Q可以重构HLR代码Q也可以完全自己动手Q开发出满实际需求的自动出图E序Q自动出图是工程c设计Y件中相对核心的功能,目前国内ZPDMS做自动出囄兌Y件开发的有很多家。本着开攄心态分享这些相Ҏ较关键功能的原理Q让国内q些产品能摆脱基于AutoCAD/BricsCAD开发接口或PDMS Draft的限Ӟ开发出更好用、更自由灉|的Y件?/p>
用计机生成三维物体的真实图形,是计机囑Ş学研I的重要内容。真实图形在仿真模拟、几何造型、广告媄视和U学计算可视化等许多领域都有着q泛应用。在用显C备描q物体的囑ŞӞ必须把三l信息经q某U投影变换在二维的显C^面上l制出来。从三维投媄Cl的降维操作Q会D囑Ş的二义性。要消除q类二义性,必dl制时消除被遮挡的不可见的线或面Q习惯上UC为消除隐藏线Hidden Line Removal和隐藏面Hidden Face Removal?/p>
q是渲染昄上对消隐的需求,在根据三l模型自动生成工E图的工E设计Y件中Q对消隐的需求有所不同?/p>
工程设计软g与机械设计Y件不同,工程设计软g一ơ出图消隐的模型量大Q对出图的算法要求主要有Q?/p>
其实最后ȝ成一句话是一键根据模型生成能交付的图U。虽然现在技术上具备三维模型下R间的能力Q但是目前二l图U怾然是设计交付、加工制造主要依据。工E类设计软g主要的功能就是快速徏模,撞和自动囄生成。当模型量大Ӟ消隐速度快及自动生成的标注文字排列整齐(或满_E习惯)成了二维囄自动生成的核心技术,也是E序处理中的隄?/p>
几何内核一般都提供HLR法Q用来根据模型投q成二l工E图。OpenCASCADE的HLR提供了隐藏线消隐法?/p>
https://www.spatial.com/zh/products/cgm-hlr-hidden-line-removal
OpenCASCADE 提供了两U消隐算法:HLRBRep_Algo和HLRBRep_PolyAlgo。这些算法都是基于相同的原理Q比较Ş状每条边相对每个面的可见性,q?计算每条边的可见部分与消隐部分。算法通过计算在指定投影方向上的物体显C特性,去除或标记被面遮挡的辏V这两个法也与一些提取功能配合用,如重构一 个简化的模型{,化后新的模型pl成Q就是在投媄方向上的轮廓Uѝ?/p>
OpenCASCADE的HLR中将边分Z下类型:
从类HLRBRep_HLRToShape和类HLRBRep_PolyHLRToShape中给Zq些边的一些定义。其中Sharp Edge表示C0q箋Q非G1q箋Q的边,是一般EdgeQ?/p>
Smooth Edge表示G1q箋Q非G2 q箋Q的边;
Sewn Edge表示G2q箋的边Q?/p>
Outline Edge表示模型的轮廓边Q这U类型的边不在BREP数据中,需要根据投影方向生成;
Isoparameter Edge表示面的{参U生成的边,q种cd的边不也不在BREP数据中;
其中Sharp Edge、Smooth Edge和Sewn Edge一般都是BREP中的EDGE数据Q而Outline Edge和Isoparameter Edge是根据设|额外生成的辏V理解边的这些定义,方便对HLR法q行理解。HLR法是相对简单的法Q主要是将上述五种cd的边与面q行求交Q判断遮挡关pR?/p>
目前OpenCASCADE中的HLR法代码写得有点乱,上次在深圳ogg的俄|斯开发h员提到要重构HLR部分的代码。深入理?HLR法Qؓ自动生成囄功能打下基础?/p>
eryar@163.com
OpenCASCADE中几何曲U与曲面求交使用cGeomAPI_IntCSQ是对类IntCurveSurface_HInter的简单封装。在IntCurveSurface_HInter中对曲线和曲面求交分Z下几U类型:
本文主要介绍曲线与曲面求交的实现原理?/p>
二次曲线与二ơ曲面求交用IntAna_ConicQuad计算Q主要思\是将曲线用参数方E表C,代入二次曲面的代数方E。二ơ曲面可以用二ơ多式表示Q将二次曲线与二ơ曲面相交表C成一个多式方程Q用math_DirectPolynomialRoots对多式方程q行求解?/p>
二次曲线与自由曲面求交将曲面使用IntCurveSurface_Polyhedron在UQV上采L散得到grid|格。这个类实现与IntPolyh_MaillageAffinagecd能有重复?/p>
IntCurveSurface_ThePolygon多段U与Intf_InterferencePolygonPolyhedron |格求交Q根据多D늺与网格求交情况,扑ֈ初始|使用IntImp_IntCS计算_倹{与曲面求交的Marching法cMQ用P代法去计精交炏VP代方EؓIntImp_ZerCSParFuncQ写个方E的Value()D和Derivatives()微分计算公式?/p>
曲U与曲面求交问题转化为求曲面参数u,v和曲U参数wQ曲线C(w)曲面S(u,v)上的炚w合,建立函数如下Q?/p>
F(u,v,w)=S(u,v) - C(w)
所求的_交点满方程F(u,v,w)=0QFZ含有三个坐标的矢量,对应函数Value()Q?/p>
Fx(u,v,w)=Sx(u,v) - Cx(w) = 0
Fy(u,v,w)=Sy(u,v) - Cy(w) = 0
Fz(u,v,w)=Sz(u,v) - Cz(w) = 0
上面为含有三个方E的以u,v,w为变量的非线性方E组Q精交点就是非U性方E组的解。用类math_FunctionSetRoot应用Newton-Raphsonq代法求解非U性方E组的解。用Newtonq代法有个前提条件是要求非线性方E组一阶可|卌写出Jacobianq代矩阵Q即上述函数Derivatives()的实现原理:
自由曲线与二ơ曲面求交IntCurveSurface_TheQuadCurvExactInter Q通过cIntCurveSurface_TheQuadCurvFuncOfTheQuadCurvExactHInter建立二次曲面与曲U之间的函数Q是求解曲线上参数U的一元函数?/p>
自由曲线与自由曲面求交和二维自由曲线求交cMQ采用的L法。即曲UK过采样L成多D늺PolygonQ将曲面采样生成|格PolyhedronQ通过cIntCurveSurface_TheInterferenceOfHInter来计多D늺与网格的怺?/p>
包Intf主要用来计算二维多段Uѝ三l多D늺及网格的怺。根据离散计的_交点,再根据类IntCurveSurface_TheExactHInter使用q代法求得精交炏V这个思想与曲面和曲面求交相同?/p>
曲线与曲面求交的l果主要也是保存在类IntCurveSurface_Intersection对象中,q个cȝ设计与二l曲U求交类|不够直接?/p>
可以看到IntCurveSurface_Intersectionq个cȝ构造函数是protected的,意思是不能直接使用Q通过zcIntCurveSurface_HInter调用SetValues()函数求交结果保存v来。求交结果ؓ交点IntCurveSurface_IntersectionPoint和交UIntCurveSurface_IntersectionSegment?/p>
其中交点中IntCurveSurface_IntersectionPoint保存了三l坐标点Q交点在曲面上的U,V参数Q交点在曲线上的参数U及相交状态。交U主要是U现面和重合部分的几何奇异情冉|据?/p>
从类图上可以看出Q这个套路同LCHLR法中,理解q个套\对理解HLR法有帮助?/p>
lg所qͼOpenCASCADE中将曲线与曲面求交根据曲U和曲面cd的不同分别处理。二ơ曲U曲面求交依赖IntAna包,自由曲线和自由曲面求交用离散法Q最l实现算法与两个曲面求交的Marching法cMQ通过L得到的精交点Q再代入q代方程求得_解。其中把曲线或曲面离散的采样Ҏ有考虑曲线或曲面的曲率{,采样Ҏ量较大,会媄响性能 。曲面采L散代码与曲面求交中的有重复。从几何求交cM可以看到没有容差的输入,可以思考一下这个问题?/p>
TKGeomAlgo中除了拟合算法外Q大部分代码主要是U线求交、线面求交及面面求交法。理解这些算法的实现原理QؓBoolean法的求交逻辑打下基础?/p>
OpenCASCADE中对二维曲线求交和三l曲U求交是不同的,三维曲线求交l一使用L法,二维曲线求交Ҏ曲线cd的不同分U类型进行处理。二l曲U求交中q提供了计算自交的直接接口。在TKGeomAlgo中,主要内容是拟合、求交算法,理解求交法的实现原理,辑ֈ能阅d修改源码的状态,能够分析和解军_际遇到的问题Q理解OpenCASCADE的能力边界,Ҏ需要选择所需要的功能Q软gl果可控。本文主要介l二l曲U相交的实现原理?/p>
׃OpenCASCADE开发时间相对久q,在二l曲U求交相关代码中大量使用了宏定义的方式来实现C++ 的模板template能力Q宏定义在类的XXX_0.cxx文g中,对应模板实现?.gxx中:
q种实现方式会让代码的可L变差,不利于代码维护。应该用C++的方式对q些*.gxx代码重构Q增Z码可L和可维护性?/p>
二维求交使用cGeom2dAPI_InterCurveCurveQ?q个cL对类Geom2dInt_GInter的封装。在cGeom2dInt_GInter中,如果只输入一条曲U,可以计算自交Q如果输入两条曲U,计算两条曲线的相交?/p>
q些c都是从cIntRes2d_IntersectionzQ?/p>
从上囑֏知,二维求交l果cIntRes2d_Intersection相关zcd知二l求交与HLR法也有关系Q理解二l曲U求交逻辑Q对理解HLR代码也有帮助?/p>
当只输入一条曲U时Q可以对曲线q行自交计算Q主要实现逻辑为:若ؓ普通二ơ曲U,则不会自交;若是其他曲线Q用离散法ҎU进行自交计。代码如下图所C:
二维曲线求交l果保存到类IntRes2d_Intersection中,主要包含两部分:
因ؓcIntRes2d_Interseciton的构造函数protectedQ所以不能直接用这个类Q都是通过其派生类使用函数SetValues()计得到的交点和交U数据保存v来。这里类的设计比较繁琐,代码可读性较差?/p>
OpenCASCADE对于二维曲线求交q行分类处理Q根据曲U类型是二次曲线、参数曲U分成三c:二次曲线与二ơ曲U求交、二ơ曲U与参数曲线求交和参数曲U与参数曲线求交Q不同的求交cd采用不同的策略可以提高求交性能和稳定性。用离散法计算二维曲线自交。从求交l果来看Q也处理了几何奇异问题,xUK叠情c?/p>
对于曲线求交q有很大改进I间Q?/p>
曲线可以用代数方E表C,如圆可以用X^2+Y^2=R^2表示Q也可以用参数方EX(u)=RCos(u), Y(u)=RSin(u)表示。要判断Ҏ不是在线上,用曲U代数方E可以很直接得出l果Q但是用参数方E就没有那么直接。这也是参数曲线上点的反求问题,参数曲线上点的反求问题应用广泛,如前面所q判断点是否在曲U上、点向曲U投影、点与线的求交、点在参数曲U上的参数等Q都与点的反求问题相兟뀂本文主要结合代码介lOpenCASCADE曲线上点的反求实现原理及使用q程中的一些注意事V?/p>
在《The NURBS Book》书中将点的反求问题归结为点向曲U投pL短的问题Q如下图所C:
建立函数f(u)=C’(u).(C(u) - P)表示点到曲线距离Q当f(u)=0时ؓ点到曲线的最短距,不管点P是否在曲U上。几何意义是点到曲线L点的向量与Q意点处的切向量点UؓӞ表示在两个向量垂直的时候求得极值点。注意数学方E中垂直q个几何意义?/p>
OpenCASCADE中实现曲U上点的反求原理与《The NURBS Book》书中一致。点的反求用类GeomLib_Tool::Parameter()函数Q?/p>
输入曲线、点和最大距,计算Ҏ否在曲线上及若在曲线上,点对应参数曲U的参数U?/p>
cExtrema_ExtPC计算点P到线C的极值Extrema。根据代码注释可以看出点的反求数学方E与《The NURBS Book》书中一_
数学方程对应的类的变量ؓmyFQ类名ؓExtrema_FuncExtPCQ从cmath_FunctionWithDerivativezQ所以必dC个关键虚函数Value()和Derivative()。其代码注释说明了这两个函数的实现细节:
其中F(u)对应函数Value():
DF(u)对应函数Derivative()Q最后用Newton法math_FunctionRootsҎE进行求栏V?/p>
OpenCASCADE中点的反求GeomLib_Tool::Parameter()、点向曲U投影GeomAPI_ProjectPointOnCurve、点与曲U的交点IntTools_Context::ComputeVE{算法都是用了Extrema_ExtPCcR?/p>
当用GeomLib_Tool::Parameter()函数来判断点是否在曲U上Ӟ注意端点处点的反求要满垂直的条Ӟ即点与曲线某个端点距离于MaxDistӞ也是q回false。即对于曲线端点处的情况需要自己预先处理,直接点P与曲U端点距MMaxDist比较Q先处理端点?/p>
可以看到q里也处理的端点处的情况Q但是最后没有与MaxDist有关p,最后容差是Precision::SquareConfusion()?/p>
OpenCASCADEZl曲U提供了求交及自交的c?Geom2dAPI_InterCurveCurveQ当传入一个二l几何曲U时可以计算自交self-intersections。但是没有提供直接的三维几何曲线求交的类Q也没有直接的计自交的cR有人同学问OpenCASCADE有没有三l曲U自交的功能Q其实理解两个Edge求交法后,可以自己实现一个自交函数?/p>
因ؓOpenCASCADE中两条三l曲U求交的cLIntTools_EdgeEdgeQ其实现原理是基于包围盒的分割法。基于这个分割递归思想Q实现自交也可以参考这个思\。算法的程为:输入一条要计算自交的边EdgeQ对边进行离散采P采样得到的每段曲线的包围盒生成BVHq行怺,BVH中包围盒怺的两条曲U调用IntTools_EdgeEdge来计相交?/p>
L得到的曲U段会比较多Q如果用两个循环来检两两曲U段的相交情冉|能差,可以引入BVH提高性能?/p>
可以通过插值Interpolate来构造曲U测试,指定几个自交Ҏ构造插值曲Uѝ计结果如下图所C:
与曲U求交原理类|都是使用L的方法,可以思考一下数值算法如何处理?/p>
OpenCASCADE中提供了二维几何曲线的求交类Geom2dAPI_InterCurveCurveQ对应到三维几何只提供了GeomAPI_IntCS, GeomAPI_IntSSQ没有提供几何的GeomAPI_IntCC求交cR这些几何求交一般用的是数值算法,卌方程。对于两条几何曲UP(u1), Q(u2)Q求交就是解P(u1) - Q(u2) = 0q个方程。ؓ什么对于三l几何曲U没有提供数值算法?
对于拓朴Ҏ供了求交法IntTools_EdgeEdgeQ这个类是用类g曲面求交的离散网格法Q用了L包围盒法?/p>
Z包围合盒的算法是个递归法Q算法思\Q?/p>
W一ơ是分别分成2部分Q?/p>
在递归函数FindSolutions()中,只去对第一条边q行参数分割?部分Q?/p>
W一个辅助函数是FindParameters()Q用来更新第二条边在W一条边的的包围盒中的参数范_使用q个参数范围更新包围盒?/p>
W二个辅助函数是CheckCoincidence()Q用来检两D边是否重合。第一步是快速计,对边采样10个点Q若通过初步_检,后面再深入计。这些算法都不太高效?/p>
W三个辅助函数是IsIntersection()用来判断两边条在参数范围内是否相交?/p>
两条边求交q程中的包围盒显C出来,方便查看理解法。先试两个圆之间的怺Q?/p>
其中W一条边是绿色的圆,求交q程中的包围盒也用绿色表C;W二条边是红色的圆,求交q程中的包围盒也用红色表C。因为圆是闭合的Q第一ơ都分割?部分。将上面交点处理攑֤Q?/p>
后面都是第一条边分割?部分Q然后分别用q?部分的包围盒L与第二条边相交的参数范围Q再更新W二条边的包围盒。l放大上面交点处Q?/p>
发现对于两个圆的求交Q执行了100ơ,效率不高。又用两个Bh曲线求交来测试:
发现对于Bh曲线求交速度较快?/p>
曲线求交需要考虑重合部分Qopencascae中没有用数值算法来计算Q而是采用Z包围盒的法来处理。这U算法一般情况下可以快速找到求交解Q有旉归较深Q对于基本曲U可以像曲面求交一样分cd理以提高性能。opencascade中对于三l曲U求交算法性能q有优化I间?/p>
除了在OpenCASCADE入门指南中推荐的书籍之外Q还有一些进阶的书籍Q放在那儿有旉q看,M有些收获。悟性不I只有勤能补拙。对于看不懂的,只能?ldquo;书读NQ其义自?rdquo;安慰一下自己?/p>
王元 数学大辞?nbsp; 工具?nbsp; 方便一些定义,公式Q定理的查找?/p>
《计机辅助几何设计D》比较全面地介绍了计机辅助几何设计的发展历史及其主要内容和最新进展,包括C的Th曲线曲面?/p>
《样条函C计算几何》叙q样条函数和计算几何的基本理论和ҎQ同Ӟȝ了作者几q来在该领域中的研究成果.
《现代数学基丛书165Q散乱数据拟合的模型、方法和理论Q第二版Q》介l了多元散ؕ数据拟合的一般方法,包括多元散ؕ数据多项式插倹{基于三角剖分的插值方法、Boole和与Loons曲面、SibsonҎ或自焉q法、ShepardҎ、KrigingҎ、薄板样条方法、MQ拟插值法、径向基函数Ҏ、运?二乘法、隐函数hҎ、R函数法等。同时还特别介绍了近q来国际上越来越热ƈ在无|格微分方程数D斚w有诸多应用的径向基函数方法及其相关理论?/p>
主要内容包括几何偏微分方E的构造方法、各U微分几何算子的L化方法及其离散格式的收敛性、几何偏微分方程数值求解的有限差分法、有限元法以及水q集ҎQ还包括几何偏微分方E在曲面qx、曲面拼接、NҎ填补、自由曲面设计、曲面重构、曲面恢复、分子曲面构造以及三l实体几何Ş变中的应用?/p>
蒙皮Q?strong>SkinningQ就是将一截面曲U(section curvesQ融合在一L成曲面的q程。蒙皮只?strong>放样Q?strong>LoftingQ的新名词,放样可以q溯到计机没未诞生的时候,从那时到现在Q它一直在造船、汽车和航空工业中被q泛地应用?/p>
扫掠Q?strong>SweepQ研I的是一条截面曲U沿L路径曲线扫掠的问题。根据扫掠曲面的定义Q扫掠曲面未必都能表C成NURBS形式Q所以一般采用拟合算法来D。一U算法是Z蒙皮法,沿着路径曲线变换和采样N个截面,然后它们作为截面曲U进行蒙皮。随着采样数量N的增加,生成的拟合曲面精度也提高?/p>
本文主要介绍OpenCASCADE中扫掠造型法的用,除了上面一般的扫掠曲面Q还有一些高U用法?/p>
在DRaw Test Harness中输入命令setsweep可以看到有指定引?UGuide的选项Q?/p>
q个引导UGuide有什么用呢?下面l出一个示例:
其中Profile是扫掠截面,Spine为扫掠脊U,Guide为扫掠引导线。扫掠结果就是一个螺旋的d模型。在Draw Test Harness的例子中Q给Z个关于引导线扫掠的示例,两个dQ?/p>
把这两个例子理解基本能掌握扫掠算法的使用ҎQ从q两个例子可以看出,OpenCASCADE扫掠造型能力q不错?/p>
扫掠q有一个能力是使扫掠截面垂直于一个支撑面Q这是一个有用的选项。下面还是在Draw Test Harness中测试一下:
OpenCASCADE中扫掠造型法功能q比较强大,除了支持常规的扫掠外Q还支持带引导线的扫掠,及带引导U的多个截面的变形扫掠,q支持截面始l垂直于支持面的扫掠选项。扫掠的关键是确定截面的变换规则Q底层的蒙皮拟合法q是比较E_的。把Draw Test Harness中两个钻头的例子理解后,基本上应该能够掌握OpenCASCADE中扫掠造型的用方法?/p>
OpenCASCADE中对面的怺定义如下图所C:
三维I间中两个带有Geometry Surface的面FaceQ当两个Surface之间的距d于Face中的容差ToleranceQ则认ؓ是相交的。一般两个面之间怺得到的是交线Q还有一些情况得到的是交点,如下图所C:
布尔q算中面的相交是相对复杂的问题,除了考虑上述交线和交点的问题以外Q还要考虑有重叠的情况Q对于新生成的交U,q要考虑生成PCurveQ若面上有开孔,q要穿q开孔区域的交线排除{;最后要考虑如何保存面相交的l果。相交的计算在函敎ͼ
最l是调用IntTools_FaceFace来计两个面的相交。ƈ计结果交U和交点Q是否重叠等信息保存到BOPDS_InterfFF中:
cBOPDS_FaceInfo用来存储以下信息Q?/p>
注意PBo31和PBSc1Q一个状态是OnQ一个状态是Section。在怺处理cBOPAlgo_PaveFiller中通过函数BOPAlgo_PaveFiller::UpdateFaceInfo这些相交的状态更新?/p>
从前面的文章关于边与边、边与面是否有重叠时采用了固定采L来处理的不严谨的逻辑来看Q判断线的重叠是个复杂的问题Q判断面与面的重叠就相对更复杂。本文先从简单入手,先看对于最单的两个q面重叠的检,引出大家对于L两个面重叠区域检的思考。对q种Ҏ的情况处理在IntTools_FaceFace中的函数PerformPlanes()中实现。其中用二ơ曲面的几何求交法进行处理,源码如下Q?/p>
通过源码可以看出Q若两个q面之间的法向夹角小于TolAng及距d于TolӞ则认Z个面是一LIntAna_SameQ当距离大于TolӞ则认为没有相交IntAna_Empty?/p>
对于重叠的^面,theTangentFaces讄成true表示是重叠的。这里留下一个问题大家思考:如何判断自由曲面的重叠情况?
当面上有孔洞Ӟq要对交U进行处理,以排除掉孔洞中的交线。当使用IntTools_FaceFace来计两个面的交U时Q可以看CU的范围不正,没有处理孔洞情况Q甚至也没有处理面的边界。如下图所C红色的交线Z用IntTools_FaceFace计算得到的:
当用BOPAlgo_PaveFiller计算交线q结合PaveBlock得到交线昄如下图所C:
虽然计算两个面之间的怺处理最l是调用的IntTools_FaceFaceQ但是要得到正确的交UK要用类BOPAlgo_PaveFiller。这里也留下问题供大家思考:Z么IntTools_FaceFace计算的交U范围不正确Qؓ什么BOPAlgo_PaveFiller计算的交U正?
lg所qͼ布尔数据中面的相交的l果可能有交U,也可能有交点。将求交l果保存到FaceInfo中。从单的两个q面重叠来看Q将重叠的状态用变量theTangetFaces来保存。那L两个曲面重叠如何判断呢?面的怺虽然提供cIntTools_FaceFace来计,但是没有正确处理交线的范_Z么BOPAlgo_PaveFiller中可以正处理交U呢Q?/p>
大家中U国庆节日快乐!
在OpenCASCADE中对于边的相交分Zc:边与点,边与边,边与面,边与点的怺已经归结为点与边的相交处理了Q边的相交主要处理边与边Q边与面的相交。边与边、边与面的相交会引入一个新的数据结?公共部分Common PartQ用于保存重叠的公共部分数据?/p>
对于两条边的怺是指在两条边的某些地方的距离于边的容差之和Q主要分ZU情况,一U是两条边只有一个交点的情况Q一U是有重叠部分的情况Q先看只有一个交Ҏ况:
我们在DRAW中通过脚本构造最单的情况来测试?/p>
在处理边与边怺的函数BOPAlgo_PaveFiller::PerformEE()中,Ҏ两条边调用BOPAlgo_EdgeEdgeq行求交。从q里可以看到Pave Block的用,相当于对每两条边上的每对Pave Block部分q行求交。这里有一些优化空_目前是用的两个循环处理Q可以尝试用BVH来提升一些性能。当每对Pave Block对应的点的烦引号一致时Q即每对Pave Block的端炚w叠时Q用快速计的法来判断是否有重叠?/p>
对于边的求交l果保存到BOPDS_InterfEE中,都会保存是哪两条边相交及怺的公共部分。对于相交于一点的公共部分的类型ؓTopAbs_VERTEXQ对于有重叠部分的公共部分类型ؓTopAbs_EDGEQ?/p>
当两Ҏ有重叠部分时Q如下图所C:
如何两条边的公共部分呢Q在函数IntTools_EdgeEdge::IsCoincident()中实玎ͼ
//=======================================================================
//function : IsCoincident
//purpose :
//=======================================================================
Standard_Boolean IntTools_EdgeEdge::IsCoincident()
{
Standard_Integer i, iCnt, aNbSeg, aNbP2;
Standard_Real dT, aT1, aCoeff, aTresh, aD;
Standard_Real aT11, aT12, aT21, aT22;
GeomAPI_ProjectPointOnCurve aProjPC;
gp_Pnt aP1;
//
aTresh=0.5;
aNbSeg=23;
myRange1.Range(aT11, aT12);
myRange2.Range(aT21, aT22);
//
aProjPC.Init(myGeom2, aT21, aT22);
//
dT=(aT12-aT11)/aNbSeg;
//
iCnt=0;
for(i=0; i <= aNbSeg; ++i) {
aT1 = aT11+i*dT;
myGeom1->D0(aT1, aP1);
//
aProjPC.Perform(aP1);
aNbP2=aProjPC.NbPoints();
if (!aNbP2) {
continue;
}
//
aD=aProjPC.LowerDistance();
if(aD < myTol) {
++iCnt;
}
}
//
aCoeff=(Standard_Real)iCnt/((Standard_Real)aNbSeg+1);
return aCoeff > aTresh;
}
从上qC码可以看出,对于重叠部分的检是一条边Ҏ范围分?3D采LQ计每个点到另一条边的距,满条g的采L的数量超q?2个,基本认ؓ是重叠的。从q里可以看出q样重叠稍微有点不严}。固定采L数量对于段曲线来说数量q大Q对于很长的曲线来说数量又偏,q里有待提高。如果重叠,则将公共部分的数据保存v来:
对于试的TCL脚本不会走这个通用的判断流E,会直接有IntTools_EdgeEdge::ComputeLineLine()函数来处理这U特D情况:
从保存的数据可以看出Q公共部分的怺cd为TopAbs_VERTEXQ及交点分别在两条边上的参数。关于有重叠部分的两条边怺Q同学们可以自行使用DRAW脚本来测试一下?/p>
边与面的怺会遇到和边与边相交类似的情况Q即会有重叠部分Common Part。也分ؓ两种情况Q一U情冉|边与面只有一个交点的情况Q交点可能会有多个;一U情冉|有重叠部分的情况?/p>
我们可以在用脚本来试一下重叠的情况Q?/p>
从代码中可以看出当边的端点在面上Ӟ则会判断边与面会不会重叠Coincidence。判断逻辑与判断边是否重叠cMQ都是用固?3个采L的方式处理,q加上定位器来判断点是否在面上,因ؓ面上可能会有孔洞Q?/p>
//=======================================================================
//function : IsCoincident
//purpose :
//=======================================================================
Standard_Boolean IntTools_EdgeFace::IsCoincident()
{
Standard_Integer i, iCnt;
Standard_Real dT, aT, aD, aT1, aT2, aU, aV;
gp_Pnt aP;
TopAbs_State aState;
gp_Pnt2d aP2d;
//
GeomAPI_ProjectPointOnSurf& aProjector=myContext->ProjPS(myFace);
Standard_Integer aNbSeg=23;
if (myC.GetType() == GeomAbs_Line &&
myS.GetType() == GeomAbs_Plane)
aNbSeg = 2; // Check only three points for Line/Plane intersection
const Standard_Real aTresh = 0.5;
const Standard_Integer aTreshIdxF = RealToInt((aNbSeg+1)*0.25),
aTreshIdxL = RealToInt((aNbSeg+1)*0.75);
const Handle(Geom_Surface) aSurf = BRep_Tool::Surface(myFace);
aT1=myRange.First();
aT2=myRange.Last();
Standard_Real aBndShift = 0.01 * (aT2 - aT1);
//Shifting first and last curve points in order to avoid projection
//on surface boundary and rejection projection point with minimal distance
aT1 += aBndShift;
aT2 -= aBndShift;
dT=(aT2-aT1)/aNbSeg;
//
Standard_Boolean isClassified = Standard_False;
iCnt=0;
for(i=0; i <= aNbSeg; ++i) {
aT = aT1+i*dT;
aP=myC.Value(aT);
//
aProjector.Perform(aP);
if (!aProjector.IsDone()) {
continue;
}
//
aD=aProjector.LowerDistance();
if (aD > myCriteria) {
if (aD > 100. * myCriteria)
return Standard_False;
else
continue;
}
//
++iCnt;
//We classify only three points: in the begin, in the
//end and in the middle of the edge.
//However, exact middle point (when i == (aNbSeg + 1)/2)
//can be unprojectable. Therefore, it will not be able to
//be classified. Therefore, points with indexes in
//[aTreshIdxF, aTreshIdxL] range are made available
//for classification.
//isClassified == TRUE if MIDDLE point has been chosen and
//classified correctly.
if(((0 < i) && (i < aTreshIdxF)) || ((aTreshIdxL < i ) && (i < aNbSeg)))
continue;
if(isClassified && (i != aNbSeg))
continue;
aProjector.LowerDistanceParameters(aU, aV);
aP2d.SetX(aU);
aP2d.SetY(aV);
IntTools_FClass2d& aClass2d=myContext->FClass2d(myFace);
aState = aClass2d.Perform(aP2d);
if(aState == TopAbs_OUT)
return Standard_False;
if(i != 0)
isClassified = Standard_True;
}
//
const Standard_Real aCoeff=(Standard_Real)iCnt/((Standard_Real)aNbSeg+1);
return (aCoeff > aTresh);
}
求交l果与边与边怺cdQ会保存边与面的索引Q及公共部分的数据。除了保存这些数据以外,q和点与面相交一P更新面上的信息FaceInfoQ即有哪些边在面上?/p>
lg所qͼ边与辏V边与面怺会得到公共部分Common PartQ公共部分可能是点,也可能是重叠的边。在qo怺的边与边、边与面旉有一定的优化I间Q即使用BVH来加速检相交部分。在快速判断边与边是否重叠、边与面是否重叠部分的代码采用固定数量的采样点的处理方式不太严}。将怺的结果及q程数据都保存到BOPDS_DS中作为后面算法用?/p>
ImGui 是一个用于C++的用L面库Q跨q_、无依赖Q支持OpenGL、DirectX{多U渲染APIQ是一U即时UIQImmediate Mode User InterfaceQ库Q保留模式与x模式的区别参?a target="_blank" rel="noopener">保留模式与即时模?/strong>。ImGui渲染非常快,但界面上有大量的数据集需要渲染可能会有一些问题,需要用一些缓存技巧。缓存只是避免数据的更新逻辑耗时太久影响渲染Q实际渲染过E不存在瓉?/p>
IMGUI很轻量,q支持跨q_Q对于小的测试程序IMGUI是理想的GUI?/p>
Zopencascade的glfw sample加入IMGUIQ这样就可以开发一些带有GUI的程序。这些程序小巧且能方便跨q_Q看上去效果也不错?/p>
现在OcctImgui开源,开源地址Qhttps://github.com/eryar/OcctImgui 使用Premake来生成解x案,只需要将premake5.lua中的相关W三方库的\径修改一下,卛_以直接编译运行?/p>
目前occt的视图作为整个背景,下一步可以做成像CADRays中那Pocct的视图作囄一部分Q这样就可以使用IMGUI的Docking功能?/p>
使用IMGUI也可以开发出很Cool的界面,最后放两个ZIMGUI开发的囑Ş界面Q?/p>
https://github.com/adriengivry/Overload https://github.com/sasobadovinac/CADRays https://github.com/MeshInspector/MeshLib2 OcctImgui
3 Next
]]>
在OpenCASCADE中,布尔相关的算子Operator有General Fuse Operator(GFA)QBoolean Operator(BOA)QSection Operator(SA)QSplitter Operator(SPA)Q这些布算子都q一套数据结构BOPDS_DSQ其中存储了输入数据及中间结果数据。布算子包含两部分Q?/p>
由此可见Q布数据BOPDS_DS是布操作中的数据中转站Q将布尔操作的输入数据及中间计算l果数据都保存v来。本文主要介lBOPDS_DS保存的数据?/p>
BOPDS_DS中存储的信息有:
q里的Shapes是模型信息BOPDS_ShapeInfoQ存储模型类型,包围盒等数据Q?/p>
q里应该不需要再另外保存myTypeQ因为在myShape中可以直接获取类型信息。模型信息在初始化函数Init()中来讄Q主要是包围盒等信息Q?/p>
//=======================================================================
//function : Init
//purpose :
//=======================================================================
void BOPDS_DS::Init(const Standard_Real theFuzz)
{
Standard_Integer i1, i2, j, aI, aNb, aNbS, aNbE, aNbSx;
Standard_Integer n1, n2, n3, nV, nW, nE, aNbF;
Standard_Real aTol, aTolAdd;
TopAbs_ShapeEnum aTS;
TopoDS_Iterator aItS;
TColStd_ListIteratorOfListOfInteger aIt1, aIt2, aIt3;
TopTools_ListIteratorOfListOfShape aIt;
BOPDS_IndexRange aR;
Handle(NCollection_BaseAllocator) aAllocator;
TopTools_MapOfShape aMS;
//
// 1 Append Source Shapes
aNb=myArguments.Extent();
if (!aNb) {
return;
}
//
myRanges.SetIncrement(aNb);
//
aNbS=0;
aIt.Initialize(myArguments);
for (; aIt.More(); aIt.Next()) {
const TopoDS_Shape& aSx=aIt.Value();
//
aNbSx=0;
TotalShapes(aSx, aNbSx, aMS);
//
aNbS=aNbS+aNbSx;
}
aMS.Clear();
//
myLines.SetIncrement(2*aNbS);
//-----------------------------------------------------scope_1 f
aAllocator=
NCollection_BaseAllocator::CommonBaseAllocator();
//
//
i1=0;
i2=0;
aIt.Initialize(myArguments);
for (; aIt.More(); aIt.Next()) {
const TopoDS_Shape& aS=aIt.Value();
if (myMapShapeIndex.IsBound(aS)) {
continue;
}
aI=Append(aS);
//
InitShape(aI, aS);
//
i2=NbShapes()-1;
aR.SetIndices(i1, i2);
myRanges.Append(aR);
i1=i2+1;
}
//
aTolAdd = Max(theFuzz, Precision::Confusion()) * 0.5;
myNbSourceShapes = NbShapes();
//
// 2 Bounding Boxes
//
// 2.1 Vertex
for (j=0; j<myNbSourceShapes; ++j) {
BOPDS_ShapeInfo& aSI=ChangeShapeInfo(j);
//
const TopoDS_Shape& aS=aSI.Shape();
//
aTS=aSI.ShapeType();
//
if (aTS==TopAbs_VERTEX) {
Bnd_Box& aBox=aSI.ChangeBox();
const TopoDS_Vertex& aV=*((TopoDS_Vertex*)&aS);
const gp_Pnt& aP=BRep_Tool::Pnt(aV);
aTol = BRep_Tool::Tolerance(aV);
aBox.SetGap(aTol + aTolAdd);
aBox.Add(aP);
}
}
在初始化函数中通过两个递归函数TotalShapes()和InitShape()来收集所有模型数据,然后再分别计点、边、面的包围盒。这些包围盒数据为后面用BVH怺做准备?/p>
怺数据Interferences主要用来保存求交l果数据Q用了单的z关系Q不同的怺cd得到不同的相交结果?/p>
保存的数据有Q?/p>
其中Index1和Index2为相交的两个模型在BOPDS_DS中的索引受对于点Vertex和边Edge的相交结果,保存了相交点在边上的参数myParamQ?/p>
在DRAW中输入相关的命o可以方便地对q些数据l构q行Debug?/p>
从源码可以看出,在做求交的初始函C准备了三部分数据Q一个是BOPDS_DSQ一个是BOPDS_IteratorQ还有一部分是缓存的求交工具的数据IntTools_Context。后面将l合DRAW代码对C++源码调试Q分析布操作中求交数据BOPDS_DS保存的具体数据?/p>
OpenCASCADE中新的布工具TKBO相对已经废弃的TKBool代码更规范,更易于理解。与ModelingData和ModelingAlgorithms大的模块l织一P主要也是数据l构Data Structure+法Algorithm的组lŞ式?/p>
其中BOPDS为布中的数据结构部分,BOPAlgo为布中的算法部分。理解算法的前提是先理解数据l构DS(Data Structure)Q所以先从数据结构入手,来深入理解布操作。本文先从简单的数据l构BOPDS_Iterator开始对源码q行分析?/p>
从类的注释可以看出,q代器BOPDS_Iterator有以下两个功能:
- 扑և包围盒相交的ShapeQ?/p>
- 遍历怺的一对ShapeQ?/p>
//! The class BOPDS_Iterator is
//! 1.to compute intersections between BRep sub-shapes
//! of arguments of an operation (see the class BOPDS_DS)
//! in terms of theirs bounding boxes
//! 2.provides interface to iterate the pairs of
//! intersected sub-shapes of given type
class BOPDS_Iterator
{
public:
其中核心的算法在函数Intersect()中,代码如下所C:
//=======================================================================
// function: Intersect
// purpose:
//=======================================================================
void BOPDS_Iterator::Intersect(const Handle(IntTools_Context)& theCtx,
const Standard_Boolean theCheckOBB,
const Standard_Real theFuzzyValue)
{
const Standard_Integer aNb = myDS->NbSourceShapes();
// Prepare BVH
BOPTools_BoxTree aBoxTree;
aBoxTree.SetSize (aNb);
for (Standard_Integer i = 0; i < aNb; ++i)
{
const BOPDS_ShapeInfo& aSI = myDS->ShapeInfo(i);
if (!aSI.HasBRep())
continue;
const Bnd_Box& aBox = aSI.Box();
aBoxTree.Add (i, Bnd_Tools::Bnd2BVH (aBox));
}
// Build BVH
aBoxTree.Build();
// Select pairs of shapes with interfering bounding boxes
BOPTools_BoxPairSelector aPairSelector;
aPairSelector.SetBVHSets (&aBoxTree, &aBoxTree);
aPairSelector.SetSame (Standard_True);
aPairSelector.Select();
aPairSelector.Sort();
// Treat the selected pairs
const std::vector<BOPTools_BoxPairSelector::PairIDs>& aPairs = aPairSelector.Pairs();
const Standard_Integer aNbPairs = static_cast<Standard_Integer> (aPairs.size());
Standard_Integer iPair = 0;
const Standard_Integer aNbR = myDS->NbRanges();
for (Standard_Integer iR = 0; iR < aNbR; ++iR)
{
const BOPDS_IndexRange& aRange = myDS->Range(iR);
for (; iPair < aNbPairs; ++iPair)
{
const BOPTools_BoxPairSelector::PairIDs& aPair = aPairs[iPair];
if (!aRange.Contains (aPair.ID1))
// Go to the next range
break;
if (aRange.Contains (aPair.ID2))
// Go to the next pair
continue;
const BOPDS_ShapeInfo& aSI1 = myDS->ShapeInfo (aPair.ID1);
const BOPDS_ShapeInfo& aSI2 = myDS->ShapeInfo (aPair.ID2);
const TopAbs_ShapeEnum aType1 = aSI1.ShapeType();
const TopAbs_ShapeEnum aType2 = aSI2.ShapeType();
Standard_Integer iType1 = BOPDS_Tools::TypeToInteger (aType1);
Standard_Integer iType2 = BOPDS_Tools::TypeToInteger (aType2);
// avoid interfering of the shape with its sub-shapes
if (((iType1 < iType2) && aSI1.HasSubShape (aPair.ID2)) ||
((iType1 > iType2) && aSI2.HasSubShape (aPair.ID1)))
continue;
if (theCheckOBB)
{
// Check intersection of Oriented bounding boxes of the shapes
const Bnd_OBB& anOBB1 = theCtx->OBB (aSI1.Shape(), theFuzzyValue);
const Bnd_OBB& anOBB2 = theCtx->OBB (aSI2.Shape(), theFuzzyValue);
if (anOBB1.IsOut (anOBB2))
continue;
}
Standard_Integer iX = BOPDS_Tools::TypeToInteger (aType1, aType2);
myLists(iX).Append (BOPDS_Pair (Min (aPair.ID1, aPair.ID2),
Max (aPair.ID1, aPair.ID2)));
}
}
}
在求交函数Intersect中用BVH快速找出包围盒有相交的每对ShapeQƈ以烦引的形式记录下来。从q个函数中可以看出布操作是否用OBB的选项的作用:当不使用OBBӞ只以AABB包围盒来相交的ShapeQ当使用OBBӞ在AABB的基上进一步用包围更紧密的OBB来检相交,可以排除部分。当怺的模型中以AABB就能检出来的Q再打开OBB选项Q不会提高性能Q反而会有所降低。ؓ了减这个媄响,在IntTools_Context中缓存Cachingq些OBBQ避免构造OBB带来的性能损失?/p>
布尔q代器BOPDS_Iterator通过BVH扑և求交的模型中每对包围盒有怺的模型ƈ提供遍历每对包围盒相交的模型的功能,为后面求交作准备。从其代码实现可以看出布选项使用OBBҎ能提高是有限的Q当使用AABB能检出来的Q再使用OBB会降低性能。当使用AABB出来相交,但OBB不相交的场景Ҏ能提升明显?/p>
今日?#8220;九一八事?#8221;92周年Q落后就要挨打,吾辈仍需努力?/p>
l常用Visual Studio写一些小E序来验证OpenCASCADE的功能,每次创徏目后都配置头文Ӟ库\径,E序q行时还要配|Debug的环境变量,比较ȝ。也试qCMake和QMakeQ都不太理想。CMake学习曲线陡峭一点,q会生成一堆文件。QMake单些Q但是有的选项不支持。直到看C个开源的游戏E序OverloadQ看其编译说明用了Premake来构建?/p>
使用IMGUI生成的Y件界面比较酷炫,使用Premake生成Visual Studio解决Ҏ?/p>
构徏pȝQBuildSystemQ是用来从源码生成用户可以用的目标QTargetsQ的自动化工兗目标可以包括库Q可执行文gQ或者生成的脚本{等?/p>
目模块依赖关系l护 Q?/p>
目标的可配置化(不同pȝQWindowsQMac…Q不同^収ͼWin32QWin64QAmd64…Q?/p>
目标生成的自动化
主要用于提高开发h员的效率与稳定,试与发布的效率
-减少开发h员的知识成本Q比如对同一程Q但多种q_Q多U开发环境差异化的了解)
-减少目目变动的维护成?/p>
– VisualStudio 的编译选项规则Q配|方?/p>
– Xcode 的编译选项规则Q配|方?/p>
– 其他。。?/p>
-减少模块的维护成?/p>
– 协同人员对工E文Ӟ目录的修改冲H?/p>
减少q_Q系l生成的差异化成本(可以z地配置不同的^CpȝQ?/p>
– 通过单的配置Q可以灵z,快速地dQ修改,更新模块
– 可以方便的处理依赖关p,提高E_性和~译速度
使用脚本和配|文Ӟ实现自动清理Q生成所需q_Q系l,调试环境Q测试流E,然后发布版本?/p>
ȝ来说Q就是生成q程更加z,灉|Q高效,自动化?/p>
L的可以跨q_Q支持C++的构建系l?/p>
- CMake
- Scons
- Premake
其他q有 GNU MakeQGNU autotoolsQApache AntQ主要用于JavaQ,GradleQ主要用于JavaQ?/p>
Premake 是一U命令工P通过d目脚本Q来生成各种开发环境的目文g。主要用于:
生成开发h员喜Ƣ的q_Q工具集Q协同开发的人员Q可以用不同的q_和开发工P
通过脚本保持不同q_Q工具集下的目配置同步Q比如新建文件夹Q引入新的库文gQ?/p>
通过脚本来快速更新许多不同的大型代码库,q新生成项目(比如对依赖的多种著名库的版本更新Q?/p>
快速升U工具集的版本(比如无缝CVisualStudio 2010升到VisualStudio 2019Q?/p>
目前支持Q?/p>
Microsoft Visual Studio 2005-2019
GNU MakeQ包?Cygwin ?MinGW
XCode
Codelite
Premake 5.0 目前支持Q?/p>
32 ?64 位^?/p>
Xbox 360Q仅支持Visual StudioQ?/p>
插g模块可以支持其他语言Q框Ӟ和工具集
Premake 是存_的旧版C应用E序Q发布ؓ一个单个的Q非常小的exe文g。支持完整的Lua脚本环境
开源地址Qhttps://github.com/premake/premake-core
下蝲地址Qhttps://premake.github.io/
实例地址Q?a >https://github.com/wuguyannian/tutorial_premake
使用premake来构Z个用了glfw, occt和imgui的程序。从配置文g可以看出Q配|比CMake要简单:
workspace "OcctImgui"
configurations {"Debug", "Release"}
system "Windows"
platforms {"Win64"}
architecture "X64"
language "C++"
project "OcctImgui"
kind "ConsoleApp"
language "C++"
targetdir "build/bin/%{cfg.buildcfg}"
objdir "build/obj/%{cfg.buildcfg}"
files { "**.h", "**.cpp"}
-- Header files.
includedirs
{
"C:/OpenCASCADE-7.6.0/opencascade-7.6.0/inc",
"C:/glfw-3.3.8/include"
}
-- Library files.
links
{
"TKernel", "TKMath", "TKG2d", "TKG3d", "TKGeomBase", "TKGeomAlgo", "TKBRep", "TKTopAlgo", "TKPrim", "TKMesh", "TKService", "TKOpenGl", "TKV3d",
"glfw3"
}
filter "configurations:Debug"
defines { "DEBUG" }
symbols "On"
libdirs
{
"C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/libd",
"C:/glfw-3.3.8/lib"
}
debugenvs
{
"path=%path%;C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/bind"
}
filter "configurations:Release"
defines { "NDEBUG" }
symbols "Off"
optimize "On"
libdirs
{
"C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/lib",
"C:/glfw-3.3.8/lib"
}
debugenvs
{
"path=%path%;C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/bin"
}
通过premake生成的Visual Studio解决Ҏ很干净Q没有多余的文g。后面再要写一个小的验证程序时Q只需要复制premake5.lua修改一下即可,很方ѝ对于小的验证程序来_使用premake是理想的构徏工具?/p>
如果从事qC++ Windows客户端开发,大家对MFC、Qt、DuiLib、WxWidgets{各UDirectUI应该有了解,本篇l大家介l一个超U轻量的C++开源跨q_囑Ş界面框架ImGUI. ImGUI主要用于游戏行业Q所有的控g都需要手l实玎ͼ当然性能也是满满的,毕竟是直接用dx/opengl来实现。ImGUI仓库Qhttps://github.com/ocornut/imgui
ImGUI又称为Dear ImGuiQ它是与q_无关的C++轻量U跨q_囑Ş界面库,没有MW三方依赖,可以ImGUI的源码直接加到项目中使用Q也可以~译成dll, ImGUI使用DX或者OpenGLq行界面渲染Q对于画面质量要求较高,例如客户端游戏,4k/8k视频播放Ӟ用ImGUI是很好的选择Q当Ӟ你得非常熟悉DirectX或者OpenGLQ不然就是宝剑在手,屠龙无力。相对于Qt、MFC、DuiLib、SOUI{,ImGUI的拓展性更好,也更轻量U,当然对于开发者的要求也更?ImGUI没有cM于Qt/MFCq种Q可以拖拽控件进行搭建界面,ImGUI的所有控仉必须手写实现。ImGUI的demo基本提供了所有控件、图表等的实玎ͼ源码也有Q可以对照的学习。在PC端技术选型Ӟ如果公司有音视频、图形图像?k/8k视频业务Q或者一些简单的UI可以考虑一下用ImGUIQ毕竟是直接使用DX/OpenGL来进行绘制渲染,其它功能q接用C++来实现?/p>
OpenCASCADE提供了一个GLFW的示例程序,OpenCASCADE与IMGUI集成hQ对于实C些简单的的三维应用E序的UIQ有满满的科技感。很多游戏相关的程序都是用IMGUI来做界面?/p>
其中OpenCASCAE开源的光线q踪E序CADRays的UI是用IMGUI实现的:
IMGUI也支持DockingQ常见的控g都有Qƈ且也支持跨^収ͼ只依赖OpenGLQ生成的E序体积很小?/p>
使用GLFW配置IMGUI可以实现跨^台的界面开发,对于不复杂的应用E序是个不错的选择?/p>
?950q第一台图形显C器Q美国麻省理工大学MIT旋风I号Whirlwind IQ的诞生Q到1962qMIT林肯实验室的Ivan E. Sutherland发表题ؓ“Sketchpad: 一个hZ互的囑Şpȝ”定计算机图形学作ؓ独立U学分支。经q?0多年的发展,计算机图形学中的几何造型技术成了现在的几何内核?/p>
数学是我们从学、中学到大学一直都在学习的评Q是CU技的理论基Q是创新的源泉。几何内怸数学的联p非常紧密,l合开源几何内核opencascade谈谈学习q程和数学的认识?/p>
在刚学习的时候,L先从单的开始入手。比如,先看看直Uѝ圆是什么,怎么昄出来。长方体、圆׃{怎么用BREPq行表示Q怎么昄在屏q上。这个时候考虑问题是常人思维Q我认ؓ是高中时代?/p>
比如Q怎么计算二维直线与圆的交点呢Q上q高中的都学q,联立直线与圆的一元二ơ方E组Q将解方E组的代码固化在代码中。当计算椭圆与圆的交ҎQ也做同L处理Q就是多写点代码。高中几何学得好的,可能会说我可以用向量Q向量的Ҏ会比代数的方法速度要快?/p>
再比如,怎么计算曲线的弧长和曲面的面U呢Q计曲U可以通过ҎU进行采LQ将点连成多D늺Q再分别计算每小D늺D늚长度累加是了。面U咱们也可以cM处理Q将曲面L成三角Ş或四边ŞQ再这些三角Ş或四边Ş面积累加。这些方法都能实玎ͼ但是性能、精度都会成为问题。要计算得精度高Q采样就要密Q就会带来性能问题?/p>
C大学时代学习q高{数学、线性代数等Q有了更有力的工h解决更一般问题。面向对象编E和数学也是盔R的Q就是将问题抽象的能力。这个时候眼中没有直Uѝ圆、Bh曲线、^面、球面、Bh曲面{等Q只有线Curve和面Surface?/p>
再进一步抽象,线Curve与一元函数F(x)对应Q将面Surface与多元函数对应F(x,y)Q与数学建立了联pR?/p>
q时可以用数学工具对问题q行处理了。如计算曲线弧长Q就变成一个对切向量的U分。曲U切向量需要计一阶导敎ͼ《The NURBS Book》等书上计算Bh微分的公式就有用Q看待Bh曲线和看待圆一样了?/p>
U分的计可以用数值方法,如Newton-Cotes或GaussU分法,使用更少的P代获得更高的_ֺQ程序优化的方向也清晰?/p>
从opencascade的类math_Function和math_MultipleVarFunction可以可以看出Q许多几何问题都抽象成了数学问题。有很多人问我,怎样才算入门了opencascade呢?那就是思考问题的方式转换成数学的方式Q我觉得q入门了。再具体点可操作点呢Q?strong>首先是数据l构的入门,掌握BREP边界表示法,如在圆柱面Surface上框Z个小面片FaceQ能正常昄出来q理解opencascade中的BREPl构。其ơ是几何法入门Q就是将从math_Function和math_MultipleVarFunction所有的zcȝ数学公式写出来?/strong>q两点动手做完,我觉得可以算入门了?/p>
如果大学时代我们掌握了微分、积分、线性代敎ͼ到研I生时代应该掌握变分、偏微分方程、最优化理论{等。如偏微分方E用于构造过渡曲面,曲线曲面拟合光顺最后都抽象成带U束的非U性方E组的求解?/p>
到研I生时代因ؓ掌握的数学工P会处理看上去很简单但处理h更复杂的问题Q如上图所C的q渡BlendQ以及蒙面Skinning和扫掠Sweep{?/p>
q时也可以有一些智能算法,如遗传算法、蚁,_子PSO优化法QAI{,使用仿生、遗传变异等手段使求解P代更快收敛。ؓ传统优化法提供初始解,使传l算法能更快、更准地扑ֈ解析解,不至于深陷局部最优解中不能自拔。在opencascade中也有相兛_例,如math_PSO。这些智能算法有随机性,不像解析法那样满一定约束条件必定会扑ֈ相对准确{案。只能作为像曲面求交之网格离散法Q作为参数P代法的预处理。到q个时代Q查看相x术论文毫无压力,面对一般的几何问题都应该可以从容应寏V和研究生一样面对的问题更加具体Q会ȝ角尖?/p>
lg所qͼ几何内核可以看成一个数学库的子集,只是在几何图形上的应用。学习几何内核的q程cL于学生时代掌握的数学工具。在高中时代Q看问题很具体,只能case by case的处理,功能能做出来。到大学时代Q有了一定的抽象能力Q与面向对象~程一PQ看问题h一般性,能用更高点的数学工具来处理,有能力来兼顾_ֺ和性能。到了研I生时代Q就不怕别人来卡脖子,甚至能超别人。这个时候不要给自己NQ找准喜Ƣ的方向去钻牛角,l会有所成?/p>
Abstract. OpenCascade classify the intersection line between two surfaces. A intersection line may be either geometric: line, circle, ellipse, parabola, hyperbola as defined in the class GLine, or analytic as defined in the class ALine, or defined by a set of points(coming from a walking algorithm) as defined in the class WLine. Or described by a restriction line on one of the surfaces as RLine.
Key Words. Surface Intersection, Intersection Line
OpenCASCADE中对两个曲面求交得到的交U进行了分类Q如下类图所C:
交线d分ؓ四类Q?/p>
下面我们使用Tcl脚本在DRAW中验证一下这四类交线的来源,加深Ҏ面求交算法的理解。分cd来了ȝQ可以带着问题Qؓ什么要分这几种cdQ有什么好处?来看q篇文章。要用好开源的东西Q其实要求还是很高的Q需要对源码有相Ҏ入的理解?/p>
Analytic交线是二ơ曲面求交所得,二次曲面是因为可以统一使用二次型来表示的解析曲面,也是《解析几何》中研究的主要内宏V我们可以在DRAW中构造圆柱面与圆锥面求交验证一下。TCL脚本如下Q?/p>
# Test for IntPatch_ALine.
# Geometry surfaces.
cylinder s1 0 0 0 1 1 1 2
cone s2 0 0 0 0 0 1 1 0 0 30 3
# Topology faces
mkface f1 s1 0 2*pi -8 8
mkface f2 s2 0 2*pi -5 5
# Intersection.
bop f1 f2
bopsection r
# Display result.
vdisplay f1 f2 r
生成l果如下图所C,其中U色ZU:
DEBUG源码可以看到是用类IntPatch_ImpImpIntersection 计算求交Q即两个解析曲面求交法cR?/p>
Geometric几何曲线形式单,如果交线用几何曲U来表示Q对于后l算法有好处。如q面与圆锥面求交U,圆柱面与圆柱面求交等Q都会得到几何曲Uѝ将上面的圆柱面换成q面与圆锥面求交我们可以在DRAW验证l典的圆锥与q面交线Q根据^面位|不同,可以得到圆、椭圆、双曲线{几何曲UѝTCL脚本如下所C:
# Test for IntPatch_GLine.
cone s1 0 0 0 0 0 1 30 3
plane s2 0 0 0 0 0 1
mkface f1 s1 0 2*pi -5 5
mkface f2 s2 -8 8 -8 8
bop f1 f2
bopsection r
vdisplay f1 f2 r
计算交线l果如下图所C:
我们改变q面的法向,使其斜着与圆锥面求交Q会得到椭圆Q?/p>
q可以得到双曲线、抛物线{,同学们可以自己尝试一下?/p>
对于NURBS曲面求交Q一般会使用MarchingҎQ国内教材翻译ؓq踪法。在看《地球脉动》时Q注意到对于大草原上的水牛、大象等动物成群l队的迁徙用了q个词,q个词的字面意思有行进、行军,列队行进之意Q如果结合opencascade中的walking感觉译?strong>行进?/strong>更脓切,因ؓ在opencascade中对于求交专门有个package名ؓIntWalkQ其中类IntWalk_PWalking来用marching method对两个参数曲面进行求交。Walk有行C意,所以对于用WalkҎ得到的交U命名ؓWLine。对于NURBS曲面求交及二ơ曲面与NURBS曲面求交Q用了MarchingҎQWLine的来源是清晰的。l用上面的脚本Q只需要将上述两个面{换成NURBS曲面卛_触发Marching法进行求交。TCL脚本如下Q?/p>
# Test for IntPatch_WLine.
cone s1 0 0 0 0 0 1 30 3
plane s2 0 0 0 1 1 2
mkface f1 s1 0 2*pi -5 5
mkface f2 s2 -8 8 -8 8
nurbsconvert f1 f1
nurbsconvert f2 f2
bop f1 f2
bopsection r
vdisplay f1 f2 r
通过IntWalk_PWalking行进?/strong>配合三参数P代法Q将行进q程中的交点都保存在WLine中?/p>
虽然l果与上面看上去一P内部交线已经不是单的几何曲线了?/p>
Restriction交线是受限交U,q种cd的交U只会位于一个面上。这里我们构造一个^面及与^面重叠的一个NURBS曲面来求交进行解释。TCL脚本如下Q?/p>
DEBUG会发现这两个曲面的交UؓRLineQƈ在生成RLine时指定交U属于哪个曲面,是在S1曲面SetArcOnS1q是在S2曲面SetArcOnS2Q?/p>
生成交线如下图所C: 当然可以使用RLine来判断两个曲面是否有重叠Q但是在opencascade中两个曲面重叠叫Tangent FaceQ可以将上述NURBS面不转换Q还是用两个重叠^面来验证Q?/p>
求交最后对交线数据归ƈ时的代码有点不敢恭维Q?/p>
lg所qͼ对两个曲面求交得到的交线q行分类Q避免交UK是NURBS曲线Q可以是单的二次曲线Q提高后l算法性能。在理解源码的基上,可以Ҏ实际应用场景选择高效的算法。如若只是求两个模型之间的交U,可以直接使用曲面求交法Q一般情况下性能q是不错的。当然理解源码后Q可以结合实际应用场景可以对求交法做进一步优化?/p>
要深入理解opencascade源码Q熟l用DRAW是一个相对容易的路线。因为在DRAW中可以用Tcl脚本快速验证各U想法,甚至直接DEBUG源码Q从表向深入C源码作者直接对话?/p>
5 RLine
# Test for IntPatch_RLine.
plane s1 0 0 0 0 0 1
plane s2 0 0 0 0 0 1
mkface f1 s1 -5 5 -5 5
mkface f2 s2 -8 8 -8 8
nurbsconvert f1 f1
bop f1 f2
bopsection r
vdisplay f1 f2 r
6 Conclusion
]]>
Abstract. OpenCASCADE provides BVH to achieve high performance in AIS of visualization module. To understand BVH usage will help us to understand many code of opencascade.
Key Words. BVH, Bounding Volume Hierarchy, LBVH, SAH Algorithm
层次包围体技?(BVH) 指的是将所有包围体分层逐次地再ơ包_获得一个更大的包围体,直到包围住所有物体。实际上Q它是一个树形结构,因此可以仿照树的l构Q将两个或三个小的包围体包围成一个更大的包围体,以此cL?/p>
BVH是一U以物体BV为基q行划分的结构。它由根节点、内部节点和叶子节点l成。其中叶子节点存攄体,每个非叶子节炚w有包围体Q父节点可以把子节点包围h。每个非叶子节点的包围体大小Q是它所包含的所有物体的包围体的dQ所以它在空间上比较紧凑Q非帔R用于需要大量求怺试的应用场景,如光U追t、碰撞检、射U相交测试之cȝ应用场合中?/p>
BVH在OpenCASCADE中也有广泛地应用Q如开源版本中的模型快速碰撞检,使用cBRepExtrema_ShapeProximity. 模型选择操作Q光U跟t等法中都有应用。在
https://www.cnblogs.com/opencascade/p/6804446.html
中介l如何遍历BVH树,本文主要介绍BVH使用Ҏ?/p>
OpenCASCADE中的BVH是相对独立的一个包Q是作者根据论文实现的UC++版本ULq来的。在DRAW中的QA品质保证Bugs中提供了BVH的用示例?/p>
首先要定义层ơ包围盒的集合Set来构造BVH树,从BVH_Set基类z的集合是可以直接使用的:
如可以直接用BVH_TriangulationQ也可以直接使用BVH_BoxSetQ?/p>
从这些类名中Q我们可以看出在求模型间极DExtremaQ三l可视化Graphic3d及Select3D拑֏及布操作BOPTools中都有BVH的应用?/p>
元素通过Add函数d到BVH集合后,调用BVH()函数可以构造BVH树?/p>
对于单个BVH的遍历提供类BVH_TraverseQ一般的应用场景如求距离一点P最q的模型Q或者位于某个空间范围内的所有模型。代码如下所C:
//=======================================================================
//function : ShapeSelector
//purpose : Implement the simplest shape's selector
//=======================================================================
class ShapeSelector :
public BVH_Traverse <Standard_Real, 3, BVH_BoxSet <Standard_Real, 3, TopoDS_Shape>, Standard_Boolean>
{
public:
//! Constructor
ShapeSelector() {}
//! Sets the Box for selection
void SetBox (const Bnd_Box& theBox)
{
myBox = Bnd_Tools::Bnd2BVH (theBox);
}
//! Returns the selected shapes
const NCollection_List<TopoDS_Shape>& Shapes () const { return myShapes; }
public:
//! Defines the rules for node rejection by bounding box
virtual Standard_Boolean RejectNode (const BVH_Vec3d& theCornerMin,
const BVH_Vec3d& theCornerMax,
Standard_Boolean& theIsInside) const Standard_OVERRIDE
{
Standard_Boolean hasOverlap;
theIsInside = myBox.Contains (theCornerMin, theCornerMax, hasOverlap);
return !hasOverlap;
}
//! Defines the rules for leaf acceptance
virtual Standard_Boolean AcceptMetric (const Standard_Boolean& theIsInside) const Standard_OVERRIDE
{
return theIsInside;
}
//! Defines the rules for leaf acceptance
virtual Standard_Boolean Accept (const Standard_Integer theIndex,
const Standard_Boolean& theIsInside) Standard_OVERRIDE
{
if (theIsInside || !myBox.IsOut (myBVHSet->Box (theIndex)))
{
myShapes.Append (myBVHSet->Element (theIndex));
return Standard_True;
}
return Standard_False;
}
protected:
BVH_Box <Standard_Real, 3> myBox; //!< Selection box
NCollection_List <TopoDS_Shape> myShapes; //!< Selected shapes
};
主要是从cBVH_Traversezq写两个虚函数RejectNode()和Accept()Q即在RejectNode()中定义排除结点的规则Q在Accept()中处理满x件的情况?/p>
对于两个BVH的遍历提供类BVH_PairTraverseQ一般的应用场景有求两个Mesh之间的最q距,判断两个Mesh之间是否有碰撞等?/p>
//=======================================================================
//function : MeshMeshDistance
//purpose : Class to compute the distance between two meshes
//=======================================================================
class MeshMeshDistance : public BVH_PairDistance<Standard_Real, 3, BVH_BoxSet<Standard_Real, 3, Triangle>>
{
public:
//! Constructor
MeshMeshDistance() {}
public:
//! Defines the rules for leaf acceptance
virtual Standard_Boolean Accept (const Standard_Integer theIndex1,
const Standard_Integer theIndex2) Standard_OVERRIDE
{
const Triangle& aTri1 = myBVHSet1->Element (theIndex1);
const Triangle& aTri2 = myBVHSet2->Element (theIndex2);
Standard_Real aDistance = TriangleTriangleSqDistance (aTri1._Node1, aTri1._Node2, aTri1._Node3,
aTri2._Node1, aTri2._Node2, aTri2._Node3);
if (aDistance < myDistance)
{
myDistance = aDistance;
return Standard_True;
}
return Standard_False;
}
};
主要也是从BVH_PairTraversezq写两个虚函数RejectNode()和Accept()?/p>
关于BVH的构造提供多UBuilderQ默认是使用ZSAH法的BVH_BinnedBuilder来构造BVH树,如果要切换不同的构造器Q可以在BVH集合的构造函C传入一个?/p>
下面l出求两个Mesh之间最q距ȝCZ代码Q?/p>
// Define BVH Builder
opencascade::handle <BVH_LinearBuilder <Standard_Real, 3> > aLBuilder =
new BVH_LinearBuilder <Standard_Real, 3>();
// Create the ShapeSet
opencascade::handle <BVH_BoxSet <Standard_Real, 3, Triangle> > aTriangleBoxSet[2];
for (Standard_Integer i = 0; i < 2; ++i)
{
aTriangleBoxSet[i] = new BVH_BoxSet <Standard_Real, 3, Triangle> (aLBuilder);
TopTools_IndexedMapOfShape aMapShapes;
TopExp::MapShapes (aShape[i], TopAbs_FACE, aMapShapes);
for (Standard_Integer iS = 1; iS <= aMapShapes.Extent(); ++iS)
{
const TopoDS_Face& aF = TopoDS::Face (aMapShapes(iS));
TopLoc_Location aLoc;
const Handle(Poly_Triangulation)& aTriangulation = BRep_Tool::Triangulation(aF, aLoc);
const int aNbTriangles = aTriangulation->NbTriangles();
for (int iT = 1; iT <= aNbTriangles; ++iT)
{
const Poly_Triangle aTriangle = aTriangulation->Triangle (iT);
// Nodes indices
Standard_Integer id1, id2, id3;
aTriangle.Get (id1, id2, id3);
const gp_Pnt aP1 = aTriangulation->Node (id1).Transformed (aLoc.Transformation());
const gp_Pnt aP2 = aTriangulation->Node (id2).Transformed (aLoc.Transformation());
const gp_Pnt aP3 = aTriangulation->Node (id3).Transformed (aLoc.Transformation());
BVH_Vec3d aBVHP1 (aP1.X(), aP1.Y(), aP1.Z());
BVH_Vec3d aBVHP2 (aP2.X(), aP2.Y(), aP2.Z());
BVH_Vec3d aBVHP3 (aP3.X(), aP3.Y(), aP3.Z());
BVH_Box<Standard_Real, 3> aBox;
aBox.Add (aBVHP1);
aBox.Add (aBVHP2);
aBox.Add (aBVHP3);
aTriangleBoxSet[i]->Add (Triangle (aBVHP1, aBVHP2, aBVHP3), aBox);
}
}
// Build BVH
aTriangleBoxSet[i]->Build();
}
// Initialize selector
MeshMeshDistance aDistTool;
// Select the elements
aDistTool.SetBVHSets (aTriangleBoxSet[0].get(), aTriangleBoxSet[1].get());
Standard_Real aSqDist = aDistTool.ComputeDistance();
if (!aDistTool.IsDone())
std::cout << "Not Done" << std::endl;
else
theDI << "Distance " << sqrt (aSqDist) << "\n";
准确、稳定、高效是高品质几何内核的目标Q我们在开发Y件时Q也要时刻追求这个目标。而BVH层次包围盒技术是提升性能的一U方式,理解BVH的用,我们可以理解opencascade中快速求极值ExtremaQ交互选择SelectMgr{很多代码。甚x们也可以参与贡献Q如?/p>
Standard_Boolean BRepExtrema_Poly::Distance (const TopoDS_Shape& S1, const TopoDS_Shape& S2,
gp_Pnt& P1, gp_Pnt& P2, Standard_Real& dist)
q个O(N^2)的改造成BVH的来Ҏ一下性能?/p>
Abstract. OpenCASCADE provides NCollection_UBTree to achieve high performance search overlapped boxes. The algorithm of unbalanced binary tree of overlapped bounding boxes. Once the tree of boxes of geometric objects is constructed, the algorithm is capable of fast geometric selection of objects. The tree can be easily updated by adding to it a new object with bounding box. The time of adding to the tree of one object is O(log(N)), where N is the total number of objects, so the time of building a tree of N objects is O(N(log(N)). The search time of one object is O(log(N)). Defining various classes inheriting NCollection_UBTree::Selector we can perform various kinds of selection over the same b-tree object.
Key Words. Unbalanced Binary Tree, Binary Search Tree, Binary Sort Tree, Bounding Box
非^衡二叉树QUnbalanced Binary TreeQ又叫二叉查找树QBinary Search TreeQ或二叉排序树(Binary Sort TreeQ。它的定义很单,是左子树上所有节点的值都要小于根节点上的倹{右子树上所有节点值都要大于根节点上的倹{在二叉查找树上执行操作旉与树的高度成正比。对于一含有n个结点的完全二叉树,q些操作的最坏情况运行时间ؓO(lg(n))。但是如果树是含n个结点的U性链Q则q些操作的最坏的情况q行旉为O(n)。一随机构造的二叉查找树的期望高度为O(lg(n))Q从而这U树上操作的q_旉为O(lg(n))?/p>
几何搜烦Qgeometry searchingQ大致分两类Q一cL区域搜烦问题Qrange searching problemQ,另一cL点的定位问题Qpoint location problemQ。区域搜索问题要回答的是l定一个区域,看有多少模型属于q个区域。当Ӟ我们可以Ҏ有模型进行遍历,q种法旉复杂度ؓO(N)Q效率不高。常见的高效的区域搜索算法有k-D树,k-D树就是一U多l的q二叉树。还有比较常见的KNN问题Q这些都是计几何处理的问题?/p>
OpenCASCADE中提供一U空间查找二叉树法NCollection_UBTreeQ字面意思是非^衡二叉树Unbalanced Binary Tree。把上图中的数字换成包围盒,构造二叉查找树。ؓ了解x找二叉树单链问题Q加入随机处理,可以使查找性能辑ֈO(log(N))Q相Ҏ通遍历速度而言q是不错的。本文结合示例代码说明如何用这个非q二叉树?/p>
在OpenCASCADE中有多个函数来实现将很多无序边Edgesq接成WireQ需要查询一条边Edge的一个顶点Vertex在一定精度范围内相连的顶点Vertex有哪些?
首先Q实C个选择c,通过选择cLq行qoQ?/p>
typedef NCollection_UBTree<Standard_Integer, Bnd_Box> BoxTree;
typedef NCollection_UBTreeFiller<Standard_Integer, Bnd_Box> BoxTreeFiller;
class BoxSelector : public BoxTree::Selector
{
public:
BoxSelector(const TColgp_SequenceOfPnt& thePoints, Standard_Real theTolerance)
: Selector()
, myPoints(thePoints)
, myTolerance(theTolerance)
{
}
virtual Standard_Boolean Reject(const Bnd_Box& theBox) const
{
return theBox.IsOut(myBox);
}
virtual Standard_Boolean Accept(const Standard_Integer& theIndex)
{
if (theIndex > myPoints.Size() || theIndex == myIndex)
{
return Standard_False;
}
const gp_Pnt& aPnt = myPoints.Value(theIndex);
if (aPnt.SquareDistance(myPnt) < myTolerance)
{
myResultIndex.Append(theIndex);
return Standard_True;
}
return Standard_False;
}
void SetCurrentPoint(const gp_Pnt& thePnt, Standard_Integer theIndex)
{
myPnt = thePnt;
myBox.Add(thePnt);
myIndex = theIndex;
}
const TColStd_ListOfInteger& GetResultIndex() const
{
return myResultIndex;
}
void ClearResultIndex()
{
myResultIndex.Clear();
}
protected:
private:
const TColgp_SequenceOfPnt& myPoints;
gp_Pnt myPnt;
Bnd_Box myBox;
Standard_Integer myIndex;
Standard_Real myTolerance;
TColStd_ListOfInteger myResultIndex;
};
主要实现两个抽象函数Reject()和Accept()Q以及设|当前选择器的状态。Reject()函数用来判断要查扄Box与当前空间范围的状态,如果在外Q则q回True。当两个Box有相交时Q会调用Accept()函数Q在此函C判断两个点的距离是否在容差范围内Q若在容差范围内Q则点记录h。主函数main代码如下Q?/p>
int main(int argc, char* argv[])
{
// Fill tree with random points.
BoxTree aBoxTree;
BoxTreeFiller aTreeFiler(aBoxTree);
math_BullardGenerator aRandom;
TColgp_SequenceOfPnt aPoints;
for (Standard_Integer i = 1; i <= 100; ++i)
{
gp_Pnt aPnt(aRandom.NextReal(), aRandom.NextReal(), aRandom.NextReal());
aPoints.Append(aPnt);
Bnd_Box aBox;
aBox.Add(aPnt);
aTreeFiler.Add(i, aBox);
}
aTreeFiler.Fill();
// Query points near the given point.
BoxSelector aSelector(aPoints, 0.1);
for (Standard_Integer i = aPoints.Lower(); i <= aPoints.Upper(); ++i)
{
const gp_Pnt& aPnt = aPoints.Value(i);
aSelector.SetCurrentPoint(aPnt, i);
Standard_Integer aSize = aBoxTree.Select(aSelector);
if (aSize > 0)
{
std::cout << "Search Point : " << aPnt.X() << " \t " << aPnt.Y() << " \t " << aPnt.Z() << std::endl;
const TColStd_ListOfInteger& aResult = aSelector.GetResultIndex();
for (TColStd_ListOfInteger::Iterator aIt(aResult); aIt.More(); aIt.Next())
{
const gp_Pnt& aPoint = aPoints.Value(aIt.Value());
std::cout << "Target Point : " << aPoint.X() << " \t " << aPoint.Y() << " \t " << aPoint.Z() << std::endl;
}
std::cout << "=============================" << std::endl;
}
aSelector.ClearResultIndex();
}
return 0;
}
先用随机函数随机生成100个点Qƈ点通过BoxTreeFillerd到查找树aBoxTree中,调用Fill函数构造查找树?/p>
再用类BoxSelector来进行快速查找,查找之前先设|当前点及包围盒。然后调用aBoxTree.Select(aSelector)q行查找?/p>
cNCollection_UBTree通过构造包围盒的非q二叉树来加快区域搜烦速度。如何提高搜索速度Q是计算几何处理的范畴。在OpenCASCADE中这个类使用场景比较多,如将无序Ҏ造成Wire旉用这个类QBRepLib_MakeWire::Add(const TopTools_ListOfShape& L), ShapeAnalysis_FreeBounds::ConnectEdgesToWires()。包括后面引入的BVH都是Z提高搜烦速度Q在合适的场景中多使用q些法Q会对程序性能的提升有很大帮助?/p>
朱心雄等著《自由曲U曲面造型技术》书中对曲面求交的追t法QMarching methodQ有详细介绍Q首先曲面求交追t法的提出是1990qR.E. BARNHILL和S.N. KERSEY的一论文:A marching method for parametric surface/surface intersection感兴的可以下蝲来看看原文:
1990q我才几岁,那时安有黑白电视机已经不错的。对于一般NURBS曲面的求交,先用分割L法求得交U的拓朴l构和交点的估计|然后再应用P代法Ҏ估计值求得精交炏V如果认Z点分布不够细密,可以对网D行加密,再应用P代法得到新的_交点Q由此可以获得完整、致密的_交线Q而无需应用q踪法?/p>
q踪法的原理Q假设两曲面间共有N个交U环Q先通过某种求交Ҏ定各交U环上的一个交点,然后以该交点为初始交点,Ҏ交线的几何性质Q按照一定步长计该条交U上下一交点的近似|再应用P代法求得_交点。沿交线走向不断前进Q直到遍历整条交Uѝ追t法的优Ҏ在求得首交点后搜索交U其余交点的速度非常快,且适用范围qѝ不ZU参数曲面,只要曲面不存在非正则点,q可以求得曲面上L点的坐标位置、法矢、切矢等几何信息Q就可以用追t法求交。追t法的问题是目前无非常有效的方法来求得所有交U环的v始点。在有些情况下寻求初始点所p的时间远大于q踪法过E中所节省的时_而ؓ了节省寻求初始点的时_又可能漏掉某些交U,当在孤立交点和比较小的交U环时尤甚?/p>
OpenCASCADE实现曲面求交q踪法的cLIntWalk_PWalkingQ注意看cL释中的单词marching:
q踪法中需要解决两个问题:
使用默认_ֺ讄Q追t法得到的交Ҏ量就很大。若讄_ֺ低,交点数量会明昑և,提高计算速度?/p>
如上图所C,若两个曲面只有一个交U,使用有追t法时只需要指定交U的初始交点Q即可以得到整个交线。当两曲面有多个交线或有孤立交点Ӟ需要找出多个交U的起始交点Q?/p>
上图所CZ|交U断开生成两条交线Q一个是l色一个是U色Q这U情况就需要分别指Z个交Uv始点?/p>
lg所qͼ曲面求交q踪法的优点是在求得首交点后搜烦交线其余交点的速度非常快,且适用范围qѝ追t法的问题是目前无非常有效的方法来求得所有交U环的v始点。曲面求交一般会采用通用性较好的|格?q代?q踪法三者相l合的方法。应用网格法求得交点的初始估计|再用q代法求得精交点,q以其ؓLq行q踪Q直到得到整条精交Uѝ?/p>
IntWalk_PWalkingq踪法的步长与精度密切相养I选择合适的_ֺQ可以交线的交Ҏ量少Q提高计速度。因为精度越高追t过E中得到的追t点多Q对于每个追t点都需要用P代法计算_交点?/p>
朱心雄等著《自由曲U曲面造型技术》书中对曲面求交的P代法有详l介l,其中关于曲面q代求交的原理介l如下:为求得两个曲面精的交点QNewton-Raphsonq代法得到广泛应用,该法的优点ؓ
其缺Ҏ对初始D求较严格Q初始值选择不当Q可能导致P代不收敛Q也无法得到精的交点?/p>
在曲面求交等问题中,一般可Ҏ参与变化的参数数量将q代法分Z参数q代法和四参数P代法两种cd。我们知道,一张参数曲面有两个参数Q两张参数曲面共有四个参数变量。采用三参数q代法时Q两个曲面的四个参数中只有三个参数参与P代过E,而保持另一个参数固定不变,q实际上是计算不变参数的等参数U与另一张曲面的交点。采用四参数q代法时Q两张曲面的四个参数变量都参与P代过E,四者都可能变化。两UP代法各有其优~点。在下述情况下以应用三参数P代法为宜Q?/p>
但对于一般交点,三参数法则未必适用。首先遇到的问题是在四个参数中选择何者作Z变参敎ͼ固定参数选择不当可能降低q代收敛速度以至Ҏ不收敛,或者破坏交U拓朴结构的正确性。P代法本n不能够成独立的求交方法,主要在追t法中用,OpenCASCADE中曲面求交追t法的类是IntWalk_PWalking。前面的blog已经介绍了曲面交求的L|格法,和类IntWalk_TheInt2S实现原理Q本文主要介lOpenCASCADE曲面求交q代法的cIntWalk_TheInt2S的用法结果?/p>
前文介绍了曲面求交的L|格法,使用cIntPolyh_Intersection来计两个曲面网格的交线Q计结果是|格交线的一l交炏V把L|格的交点作P代法的输入,来检查一下P代法的计结果?/p>
Newtonq代法的输入是初始估计点分别在两个曲面上的四个参敎ͼu1, v1, u2, v2Q,及两个曲面aS1和aS2QP代终止精度TolTangency。将计算l果输出Q其中第一个距Lq代的精g|格上交点的距离Q第二个距离是精交点的u1, v1, u2,v2分别在两个曲面上点的距离Q可以看出经qNewtonq代计算后,Ҏ_交点的参数u1, v1, u2, v2计算Z个曲面上的点在指定的_ֺ下是重合的。ؓ了便于观察,计结果输出到DRAW中查看?/p>
其中U色的线是两个曲面网格的交线Q绿色的U是网gU经qNewtonq代后得到精交Uѝ再两个交U与实际曲面一hC来ҎQ?/p>
从图上可以看出,l色的交U已l能比较准确地表达两个曲面之间的怺情况。比|格交线效果好?/p>
lg所qͼ曲面求交的L|格交线作ؓNewtonq代法的初始估计点,可以得到较好的交Uѝ后面再分析一下曲面求交的q踪法,看在q踪法中是如何用离散网gU数据的。对于一般的NURBS曲面求交Q先用离散网格法或分割离散法求得交线和交点的估计|然后再应用Newtonq代法由估计值求得精交炏V如果认Z计交点分布不够细密,可以对网格加密,由此可以得到完整、致密的_交线而无需应用q踪法?/p>
朱心雄等著《自由曲U曲面造型技术》书中对曲面求交的P代法有详l介l,其中关于曲面q代求交的原理介l如下:为求得两个曲面精的交点QNewton-Raphsonq代法得到广泛应用,该法的优点ؓ
其缺Ҏ对初始D求较严格Q初始值选择不当Q可能导致P代不收敛Q也无法得到精的交点?/p>
在曲面求交等问题中,一般可Ҏ参与变化的参数数量将q代法分Z参数q代法和四参数P代法两种cd。我们知道,一张参数曲面有两个参数Q两张参数曲面共有四个参数变量。采用三参数q代法时Q两个曲面的四个参数中只有三个参数参与P代过E,而保持另一个参数固定不变,q实际上是计算不变参数的等参数U与另一张曲面的交点。采用四参数q代法时Q两张曲面的四个参数变量都参与P代过E,四者都可能变化。两UP代法各有其优~点。在下述情况下以应用三参数P代法为宜Q?/p>
但对于一般交点,三参数法则未必适用。首先遇到的问题是在四个参数中选择何者作Z变参敎ͼ固定参数选择不当可能降低q代收敛速度以至Ҏ不收敛,或者破坏交U拓朴结构的正确性。P代法本n不能够成独立的求交方法,主要在追t法中用,OpenCASCADE中曲面求交追t法的类是IntWalk_PWalking。本文主要介lOpenCASCADE曲面求交q代法的cIntWalk_TheInt2S的用法及原理?/p>
OpenCASCADE中两曲面求交q代法由cIntWalk_TheInt2S实现Q其cM主要函数有:
l定两个曲面Q和初始估计点在两个曲面上的参数(u1, v1), (u2,v2)QP代计出_交点。固定三参数的方式d分四U类型IntImp_ConstIsoparametricQ?/p>
IntImp_UIsoparametricOnCaro1Q是固定估计点在曲面1上的参数uQ?/p>
IntImp_VIsoparametricOnCaro1Q是固定估计点在曲面1上的参数vQ?/p>
IntImp_UIsoparametricOnCaro2Q是固定估计点在曲面2上的参数uQ?/p>
IntImp_VIsoparametricOnCaro2Q是固定估计点在曲面2上的参数vQ?/p>
Z避免三参数P代法找不C点的情况Q会在四个方向上分别q行计算QL一个方向上会找C点,q将扑ֈ交点的参数固定情况返回:
在函数Perform()中通用求解q代方程l,得到_交点Q?/p>
其中q代方程lؓ成员变量myZerParFuncQ方E组求解使用cmath_FunctionSetRoot。由方程l求解类注释可知Q需要方E组的一阶偏导数x度GradientQ采用的是Newtonq代法。若方程l有解Root且满精度要求,则保存下_交点的坐标值及在两个曲面上的参数值等数据?/p>
三参数P代方E类为GeomInt_TheFunctionOfTheInt2SOfThePrmPrmSvSurfacesOfWLApprox,
是从cmath_FunctionSetWithDerivativesz的,即三参数q代方程是个方程l?Function Set)。其定义在文件IntImp_ZerParFunc中,先用函数ComputeParameters()Ҏ固定参数cd来确定估计点的固定参数及另外三个参数变量的初始|
设两个参数曲面S1(u1,v1)QS2(u2,v2)Qƈ已知一交点的初估计点P0QP0点在两张曲面上对应的投媄点分别ؓP1=S1(u0,v0)和P2=S2(s0,t0)。由于P0点ؓ一估计点,所以P1和P2q不重合。设以s0作ؓ固定参数Q即当固定参数类型ؓIntImp_UIsoparametricOnCaro1Ӟ则问题{化ؓ求u*,v*,t*Q两曲面片上的点S1(u*,v*)和S2(s0,t*)重合。徏立方E组Q?/p>
R(u,v,t)=S1(u,v) - S2(s0, t)
通过函数计算两个曲面上的点pntsol1和pntsol2Q得C个变量的方程l的倹{?/p>
因ؓ要用Newtonq代法,需要提供方E组的一阶偏导数Q即Jacobian矩阵Q?/p>
函数Derivatives()用来计算一阶偏导数Q?/p>
lg所qͼq代法本w不能构成一个独立的求交ҎQ与所有不动点q代法一P应用q代法求交线之前Q首先必ȝZ点的初始估计|而交点的初始值必通过其他求交Ҏ得到。因此,q代交交常同其它求交Ҏl合使用Q作Z点精化的一U手DcP代法的主要过E是Ҏ初始估计点的几何性质Q如坐标位置、切矢、法矢、曲率等Q运用NewtonҎ得到一个较原估计点更接q于目标点(即精交点)的估计点。如此反复进行,直到求得的交Ҏx要求的精度。该法的优点是在初值比较好时其收敛速度非常快,而且能应用于L参数曲面包括Coons曲面和等距曲面,因此应用非常q泛Q其主要~点是对初始D求比较苛刻,初始?选择不当有可能导致P代不收敛?/p>
OpenCASCADE中曲面求交的q代法也不是独立的方法,与之配合的有L|格求交得到初|在追t中作用q代法。P代求交用的是三参数q代法,Ҏ三参数P代法的数学方E可知,需要计曲面上参数对应的点和切矢?/p>
由朱心雄{著《自由曲U曲面造型技术》书中对曲面求交之网格离散法描述如下Q该法的基本思想是先曲面离散ؓ由小q面片组成的|格Q当|格_密时Q可以认为已l非常接q真实曲面,对分别表CZ同曲面的两张|格Q利用^面片求交法求得的交线Qƈ以此交线q似代表曲面间的交线。这U方法原理简明,便于实现Q适用范围q,L参数曲面均可利用该法求交。但取精地交线Q则必须生成非常l密的网|q将D占用内存多,计算p大。因此,实际工作中很单一使用L|格法,而常其与其他方法结合用?/p>
OpenCASCADE中对于曲面求交也提供L|格法,其中曲面的离散网格由cIntPatch_Polyhedron表示Q两个网格面求交使用cIntPatch_InterferencePolyhedron。在实际计算两个面相交时q没有用这个类Q而是使用cIntPolyh_IntersectionQ而离散网g用类IntPolyh_MaillageAffinage?/p>
使用cIntPolyh_MaillageAffinage主要用来生成曲面的网|其中MaillageAffinage是法语,译q来是Mesh Refining|格l化Q网格精度主要是通过参数UQV方向上的采样Ҏ量来定。当不指定采L数量Ӟ默认是参数UQV方向分别10个,即默认会生成10x10个采LQ即使是q面也是生成100个采L。通过函数FillArrayOfPnt()生成采样炏V通过函数FillArrayOfTriangles()来生成三角ŞQ三角Ş的数量通过如下图所C公式计,默认数量 ?x(10-1)x(10-1)=162?/p>
对于单的q面Q如果不指定采样Ҏ量,也会生成100个采L?62个三角ŞQ?/p>
两个|格求交是通过cIntPolyh_Intersection来计,计算的结果也是两个网g间的交线。还是将交线昄出来便于观察Q?/p>
从生成的交线来看Q这个结果要比IntPatch_InterferencePolyhedron要好Q没有多余的交线。类IntPolyh_Intersection中用BVH来过滤不怺的三角ŞQ所以速度也会快很多?/p>
lg所qͼ使用cIntPolyh_Intersection来计两个曲面网格的交线。曲面网格生成直接通过参数UQV上的采样Ҏ量来定Q虽然生成网格速度快,但是_ֺ控制不好Q即使是q面也会Ҏ采样数量生成大量采样点和三角形,影响求交速度。网格求交作为曲面求交的预处理步骤,如何用更的三角形来表示曲面Q可提高|格求交性能?/p>
由朱心雄{著《自由曲U曲面造型技术》书中对曲面求交之网格离散法描述如下Q该法的基本思想是先曲面离散ؓ由小q面片组成的|格Q当|格_密时Q可以认为已l非常接q真实曲面,对分别表CZ同曲面的两张|格Q利用^面片求交法求得的交线Qƈ以此交线q似代表曲面间的交线。这U方法原理简明,便于实现Q适用范围q,L参数曲面均可利用该法求交。但取精地交线Q则必须生成非常l密的网|q将D占用内存多,计算p大。因此,实际工作中很单一使用L|格法,而常其与其他方法结合用?/p>
OpenCASCADE中对于曲面求交也提供L|格法,其中曲面的离散网格由cIntPatch_Polyhedron表示Q两个网格面求交使用cIntPatch_InterferencePolyhedron。本文主要介l曲面的|格求交cIntPatch_InterferencePolyhedron?/p>
OpenCASCADE中计两个三角网gU的cLIntPatch_InterferencePolyhedronQ这个类q可以用来计一个网格的自交情况。目前是单计两个网g所有三角Ş的相交情况,旉复杂度ؓO(nm)或O(n^2)Q对于网g角Ş数量大的情况效率很低。ؓ了稍微提高一些性能Q引入Bnd_BoundSortBox来加速过滤掉包围盒不怺的三角ŞQ减两个三角Ş怺计算?/p>
其中函数Intersect()是用来计算两个三角形的怺情况。关于两个三角Ş的快速求交计,很多|格处理库都使用了Tomas Moller’s 1997 triangle intersection routineQ如
http://geometry-central.net/surface/algorithms/intersection/ 中也提供两个|格求交函数Q?/p>
在用较q泛的网格处理库CGAL中也有相兌函敎ͼ
感兴的同学可以Ҏ一下这三个库关于两个网格求交的性能Q看谁的性能最好,使用了什么技术。这里只是将OpenCASCADE中计的求交l果输出Q首先是面的自相交情况:
其中U色部分ZU,可以看出在计自怺Ӟ会生成多余的交线。其中蓝色部分是有重叠三角Ş的情c?/p>
当计两个网gU时QM上是正确的,不过也会有多余的交线产生?/p>
lg所qͼ两个|格怺计算最直接的算法就是两两三角Şq行求交计算Q但是对于大|格会有性能问题。OpenCASCADE中两个网格求交计会得到多余的交U,目前|格L求交只是用于Bh曲面的求交计的前处理IntPatch_PrmPrmIntersectionQ从OpenCASCADE计算两个曲面交线l果来看Q离散网D中多余的交U没有媄响最l的计算l果。大家可以带着q个问题“L|格计算得到多余的交U对最l结果有影响么?”来理解IntPatch_PrmPrmIntersection中曲面求交的实现原理?/p>
由朱心雄{著《自由曲U曲面造型技术》书中对曲面求交之网格离散法描述如下Q该法的基本思想是先曲面离散ؓ由小q面片组成的|格Q当|格_密时Q可以认为已l非常接q真实曲面,对分别表CZ同曲面的两张|格Q利用^面片求交法求得的交线Qƈ以此交线q似代表曲面间的交线。这U方法原理简明,便于实现Q适用范围q,L参数曲面均可利用该法求交。但取精地交线Q则必须生成非常l密的网|q将D占用内存多,计算p大。因此,实际工作中很单一使用L|格法,而常其与其他方法结合用?/p>
OpenCASCADE中对于曲面求交也提供L|格法,其中曲面的离散网格由cIntPatch_Polyhedron表示Q两个网格面求交使用cIntPatch_InterferencePolyhedron。本文主要介l曲面的|格表示cIntPatch_Polyhedron?/p>
OpenCASCADE用于曲面求交的网格离散算法相对BRepMesh中的法要简单很多,主要思\是根据参数UQV方向上的采样Ҏ量来计算曲面上的点,再根据固定公式将采样点连成三角Ş。其中生成采L代码如下所C:
成员变量CMyPnts是采L数组QCMyU和CMyV是采L在曲面上的参数。将采样点连成三角Ş函数如下图所C:
Ҏ上述生成采样点及三角形函敎ͼ对于q面生成的三角Ş如下图所C:
其中Triangle()函数中变量line表示参数u方向上第几条U,代入具体的烦引Index来看规律Q?/p>
当参数烦?Index?Ӟline?Q得到的三角形ؓ1-4-5Q?/p>
当参数烦引Index?Ӟline?Q得到的三角形ؓ1-5-2Q?/p>
当参数烦引Index?Ӟline?Q得到的三角形ؓ2-5-6Q?/p>
当参数烦引Index?Ӟline?Q得到的三角形ؓ2-6-3Q?/p>
当参数烦引Index?Ӟline?Q得到的三角形ؓ4-7-8Q?/p>
当参数烦引Index?Ӟline?Q得到的三角形ؓ4-8-5Q?/p>
从上可以得到生成三角形的规律Q即Ҏ索引Index计算正在处理的三角Ş是参数u方向上第几条UlineQ生成这条线上在参数v方向上的所有的三角形。生成的三角形都是逆时针的?/p>
下面我们看看对于一些基本曲面,q种L|格法生成的网格效果:
球面的离散网?/p>
圆柱面的L|格
圆环面的L|格
Bh曲面
lg所qͼcIntPatch_Polyhedron中生成网格的法主要依赖曲面在参数UQV上的采样Ҏ量。默认采L数量是根据函数NbSamplesV()和NbSamplesU()生成?/p>
也可以指定采L数量Q当采样Ҏ量越多,则生成的三角形越多,|格密。当然这U方式也可用来生成曲面的昄数据Q生成速度很快Q唯一的缺h生成昄用网格的_ֺ只能通过采样Ҏ量来控制Q对于曲率变化大的曲面,若指定多的采LQ则会生成大量三角ŞQ占用大量内存空间?/p>
附上试代码Q?/p>
#include <TColgp_Array2OfPnt.hxx>
#include <Geom_Plane.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_SphericalSurface.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <Geom_BSplineSurface.hxx>
#include <GeomAdaptor_Surface.hxx>
#include <GeomAPI_PointsToBSplineSurface.hxx>
#include <IntPatch_Polyhedron.hxx>
#include <IntPatch_InterferencePolyhedron.hxx>
#pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib")
#pragma comment(lib, "TKG2d.lib")
#pragma comment(lib, "TKG3d.lib")
#pragma comment(lib, "TKGeomBase.lib")
#pragma comment(lib, "TKGeomAlgo.lib")
void makeSurface(Handle(Geom_BSplineSurface)& theSurface)
{
TColgp_Array2OfPnt aPoints(1, 5, 1, 5);
aPoints.SetValue(1, 1, gp_Pnt(-4, -4, 5));
aPoints.SetValue(1, 2, gp_Pnt(-4, -2, 5));
aPoints.SetValue(1, 3, gp_Pnt(-4, 0, 4));
aPoints.SetValue(1, 4, gp_Pnt(-4, 2, 5));
aPoints.SetValue(1, 5, gp_Pnt(-4, 4, 5));
aPoints.SetValue(2, 1, gp_Pnt(-2, -4, 4));
aPoints.SetValue(2, 2, gp_Pnt(-2, -2, 4));
aPoints.SetValue(2, 3, gp_Pnt(-2, 0, 4));
aPoints.SetValue(2, 4, gp_Pnt(-2, 2, 4));
aPoints.SetValue(2, 5, gp_Pnt(-2, 5, 4));
aPoints.SetValue(3, 1, gp_Pnt(0, -4, 3.5));
aPoints.SetValue(3, 2, gp_Pnt(0, -2, 3.5));
aPoints.SetValue(3, 3, gp_Pnt(0, 0, 3.5));
aPoints.SetValue(3, 4, gp_Pnt(0, 2, 3.5));
aPoints.SetValue(3, 5, gp_Pnt(0, 5, 3.5));
aPoints.SetValue(4, 1, gp_Pnt(2, -4, 4));
aPoints.SetValue(4, 2, gp_Pnt(2, -2, 4));
aPoints.SetValue(4, 3, gp_Pnt(2, 0, 3.5));
aPoints.SetValue(4, 4, gp_Pnt(2, 2, 5));
aPoints.SetValue(4, 5, gp_Pnt(2, 5, 4));
aPoints.SetValue(5, 1, gp_Pnt(4, -4, 5));
aPoints.SetValue(5, 2, gp_Pnt(4, -2, 5));
aPoints.SetValue(5, 3, gp_Pnt(4, 0, 5));
aPoints.SetValue(5, 4, gp_Pnt(4, 2, 6));
aPoints.SetValue(5, 5, gp_Pnt(4, 5, 5));
theSurface = GeomAPI_PointsToBSplineSurface(aPoints).Surface();
}
void writeStl(const IntPatch_Polyhedron& thePolyhedron, const std::string& theFileName)
{
// Dump surface polyhedron to STL file.
std::ofstream aStlFile(theFileName);
aStlFile << "solid polyhedron" << std::endl;
// Dump triangles.
for (Standard_Integer t = 1; t <= thePolyhedron.NbTriangles(); ++t)
{
Standard_Integer aPi1 = 0;
Standard_Integer aPi2 = 0;
Standard_Integer aPi3 = 0;
thePolyhedron.Triangle(t, aPi1, aPi2, aPi3);
const gp_Pnt& aP1 = thePolyhedron.Point(aPi1);
const gp_Pnt& aP2 = thePolyhedron.Point(aPi2);
const gp_Pnt& aP3 = thePolyhedron.Point(aPi3);
aStlFile << "facet" << std::endl;
aStlFile << "outer loop" << std::endl;
aStlFile << "vertex " << aP1.X() << " " << aP1.Y() << " " << aP1.Z() << std::endl;
aStlFile << "vertex " << aP2.X() << " " << aP2.Y() << " " << aP2.Z() << std::endl;
aStlFile << "vertex " << aP3.X() << " " << aP3.Y() << " " << aP3.Z() << std::endl;
aStlFile << "endloop" << std::endl;
aStlFile << "endfacet" << std::endl;
}
aStlFile << "endsolid polyhedron" << std::endl;
aStlFile.close();
}
void testPolyhedron()
{
// Plane surface polyhedron.
Handle(Geom_Plane) aPlane = new Geom_Plane(gp::XOY());
Handle(GeomAdaptor_Surface) aSurfaceAdaptor = new GeomAdaptor_Surface(aPlane, 0.0, 10.0, 0.0, 20.0);
IntPatch_Polyhedron aPlanePolyhedron(aSurfaceAdaptor);
writeStl(aPlanePolyhedron, "d:/plane.stl");
// Spherical surface polyhedron.
Handle(Geom_SphericalSurface) aSphericalSurface = new Geom_SphericalSurface(gp::XOY(), 3.0);
aSurfaceAdaptor = new GeomAdaptor_Surface(aSphericalSurface);
IntPatch_Polyhedron aSphericalPolyhedron(aSurfaceAdaptor);
writeStl(aSphericalPolyhedron, "d:/spherical.stl");
// Cylindrical surface polyhedron.
Handle(Geom_CylindricalSurface) aCylindricalSurface = new Geom_CylindricalSurface(gp::XOY(), 5.0);
aSurfaceAdaptor = new GeomAdaptor_Surface(aCylindricalSurface, 0.0, M_PI, 0.0, 8.0);
IntPatch_Polyhedron aCylindricalPolyhedron(aSurfaceAdaptor);
writeStl(aCylindricalPolyhedron, "d:/cylindrical.stl");
// Toroidal Surface polyhedron.
Handle(Geom_ToroidalSurface) aToroidalSurface = new Geom_ToroidalSurface(gp::XOY(), 10.0, 3.0);
aSurfaceAdaptor = new GeomAdaptor_Surface(aToroidalSurface);
IntPatch_Polyhedron aToroidalPolyhedron(aSurfaceAdaptor);
writeStl(aToroidalPolyhedron, "d:/toroidal.stl");
// BSpline surface polyhedron.
Handle(Geom_BSplineSurface) aBSplineSurface;
makeSurface(aBSplineSurface);
aSurfaceAdaptor = new GeomAdaptor_Surface(aBSplineSurface);
IntPatch_Polyhedron aPolyhedron(aSurfaceAdaptor);
writeStl(aPolyhedron, "d:/bspline.stl");
}
int main(int argc, char* argv[])
{
testPolyhedron();
return 0;
}
在传l的机械设计软g中,一般用几何约束求解器来画草图Q再通过对草图进行拉伸旋转等生成特征实现建模功能。基于参数化历史特征方式来徏模的软gl不开几何U束求解器,目前L商用软g一般用西门子D-Cubed DCM及达索的CGM。开源世界也有两Ƒև何约束求解器QSolveSpace和PlaneGCS?/p>
PlaneGCS字面意思是q面几何U束求解器,主要用于l制二维草图。因为PlaneGCS代码相对清晰Q功能简单,只能处理q面几何元素的约束,本文主要l合CZ代码介绍PlaneGCS的用方法,在会用的基础上去理解源码的实现逻辑?/p>
PlaneGCS主要包含三部分:
其中几何元素数据l构中定义的几何元素如下图所C:
从上囑֏以看刎ͼ目前支持的几何元素有点PointQ直ULineQ圆CircleQ椭圆EllipseQ双曲线HyperbolaQ抛物线ParabolaQ圆弧Arc/ArcOfEllipse/ArcOfHyperbola/ArcOfParabolaQ及Bh曲线BSplineQ不q看代码BSpline部分函数没有实现Q应该是不支持的?/p>
U束条g文g定义的约束类型如下图所C:
从约束求解文件中可以看到Q其中数学计主要用Eigen中非U性方E组求解法和boost的图graph法Q从中可以推出实现q面几何U束求解器中需要的关键技术。先掌握PlaneGCS的用法,然后再分析其背后的实现原理细节?/p>
q里l出一个简单的CZE序Q先让大家对PlaneGCS有个认识。示例程序中演示了给两条直线加上水^和垂直约束。ؓ了便于查看约束后的结果,在代码中生成Draw Test Harness脚本文g?/p>
E序代码如下所C:
/*
Copyright(C) 2023 Shing Liu(eryar@163.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "GCS.h"
#include <fstream>
void test()
{
double aPx1 = 0.0;
double aPy1 = 0.0;
double aPx2 = 3.0;
double aPy2 = 3.0;
double aPx3 = 6.0;
double aPy3 = 9.0;
GCS::VEC_pD aParameters;
aParameters.push_back(&aPx1);
aParameters.push_back(&aPy1);
aParameters.push_back(&aPx2);
aParameters.push_back(&aPy2);
aParameters.push_back(&aPx3);
aParameters.push_back(&aPy3);
GCS::Point aP1(&aPx1, &aPy1);
GCS::Point aP2(&aPx2, &aPy2);
GCS::Point aP3(&aPx3, &aPy3);
GCS::Line aLine1;
GCS::Line aLine2;
aLine1.p1 = aP1;
aLine1.p2 = aP2;
aLine2.p1 = aP2;
aLine2.p2 = aP3;
std::ofstream aTclFile("d:/gcs.tcl");
aTclFile << "# 2 lines before PlaneGCS solve" << std::endl;
aTclFile << "vinit" << std::endl;
aTclFile << "vertex aP1 " << aPx1 << " " << aPy1 << " 0" << std::endl;
aTclFile << "vertex aP2 " << aPx2 << " " << aPy2 << " 0" << std::endl;
aTclFile << "vertex aP3 " << aPx3 << " " << aPy3 << " 0" << std::endl;
aTclFile << "polyvertex aPolyline1 aP1 aP2 aP3" << std::endl;
aTclFile << "vdisplay aPolyline1 " << std::endl;
aTclFile << "vsetcolor aPolyline1 RED" << std::endl;
GCS::System aSolver;
aSolver.addConstraintHorizontal(aLine1);
aSolver.addConstraintVertical(aLine2);
if (aSolver.solve(aParameters) == GCS::Success)
{
aSolver.applySolution();
aTclFile << "# 2 lines after PlaneGCS solve" << std::endl;
aTclFile << "vertex aV1 " << aPx1 << " " << aPy1 << " 0" << std::endl;
aTclFile << "vertex aV2 " << aPx2 << " " << aPy2 << " 0" << std::endl;
aTclFile << "vertex aV3 " << aPx3 << " " << aPy3 << " 0" << std::endl;
aTclFile << "polyvertex aPolyline2 aV1 aV2 aV3" << std::endl;
aTclFile << "vdisplay aPolyline2 " << std::endl;
aTclFile << "vsetcolor aPolyline2 GREEN" << std::endl;
}
aTclFile.close();
}
int main(int argc, char* argv[])
{
test();
return 0;
}
从程序代码中可以看出PlaneGCS的用先要定义需要计的参数aParametersQ这些参数是几何元素中的数据Q都是用的指针。然后将U束加入到GCS::System中,最后代入参数调用solve函数q行求解。求解成功后使用applySolution()函数应用求解l果。求解结果在Draw中显C的l色的线如下图所C:
本文l合CZ代码演示如何使用PlaneGCSQ主要用了水^和垂直约束。PlaneGCS中还支持其他U束cdQ童鞋们可以自己探烦一下。几何造型内核和几何约束求解器常被看作是工业CAD软g的卡脖子技术,开源库一般功能不太完善,但是用来探烦背后的实现原理还是有参考借鉴意义的。希望有更多的童鞋去了解背后的原理,共同来提高国内三lCAD软g开发水q?/p>
Abstract: 曲面求交是几何造型内核最为重要也最为复杂的问题之一Q求交算法的质量Q稳定、准、快速)直接影响到几何内核的E_性和实用E度Q故h十分重要的意义。求交问题包括曲U与曲线求交、曲U与曲面求交和曲面与曲面求交Q其中最重要隑ֺ最大的当属曲面与曲面求交问题,其他求交问题可以应用曲面与曲面求交的思想予以解决。本文主要介lopencascade中曲面与曲面求交的实现原理?/p>
Key Words: Face Face Intersection, Intersection
如果说理解opencascade中面的构造原理(即理解BRepBuilderAPI_MakeFace的源码)Q我觉得是理解了BRep表示法的数据l构Modeling Data。如果说理解了曲面与曲面求交的实现原理,我觉得算是对几何内核中的核心法布尔操作有了一定的认识。曲面与曲面求交q程QIntersection AlgorithmQ中主要依赖三大工具Q拟合(Approximation AlgorithmQ、投影(Projection AlgorithmQ和定位QClassification AlgorithmQ。下面结合布操作TKBO中的曲面与曲面求交类IntTools_FaceFace源码实现分别介绍q三大工L应用场景。opencascade中的法cM般的使用套\和把大象装冰q似d分三步:
W一步,初始化;通过构造函数或Init(){函数将法c需要的参数输入q去QIntTools_FaceFace中通过SetParameters()函数讄法的参?/p>
W二步:计算Q用函数Build()Q?Perform()函数来执行计;IntTools_FaceFace中的主要实现逻辑在Perform()中?/p>
W三步:输出Q将计算l果输出。IntTools_FaceFace通过Lines()和Points()函数输出计算l果即交U和交点?/p>
opencascade中曲面的表示有两U方式,一U是参数方程S(u,v)表示Q一U是二次曲面的代数方Ef(x,y,z)=0表示。因此也曲面求交问题分为:
opencascade中计曲面求交更底层的类是IntPatch_IntersectionQ其中也是分q三U类型来处理Q?/p>
GeomGeomPerfom()对应的是代数/代数曲面求交Q?/p>
GeomParamPerform()对应的是代数/参数曲面求交Q?/p>
ParamParamPerform()对应的是参数/参数曲面求交Q?/p>
其中代数/代数曲面求交函数GeomGeomPerform()中用类IntPatch_ImpImpIntersection来计两个二ơ代数曲面的求交Q其实这是Imp~写是隐式代数方程Implicit Equation的意思?/p>
二次代数曲面的求交用包IntAna来实玎ͼq在早期文章中分析了其实现原理,主要思想是将一个二ơ曲面的参数表示代入隐式方程变成一元方E,然后对这个一元方E进行求解。例如圆柱面与二ơ代数曲面求交:
其中代数/参数曲面求交函数GeomParamPerform()中用类IntPatch_ImpPrmIntersection来计代数曲面和参数曲面的求交,q里Prm为Parametric equation参数方程的羃写。其代码注释中写到bi-parametrised surface意思双参数曲面S(u, v)?/p>
其中代数/参数曲面求交函数ParamParamPerform()中用类IntPatch_PrmPrmIntersection来计参数数曲面和参数曲面的求交?/p>
参数/参数曲面求交的基本方法有以下五种Q?/p>
相关原理介绍可以参考朱心雄{著《自由曲U曲面造型技术》。大家可以结合源码,看看opencascade中用了哪些Ҏ。曲面交U的表达涉及三个问题Q交点信息表C,交线l织及交U中交点的删除策略。交U的表达使用cIntPatch_LineQ?/p>
在类IntTools_FaceFace中函数SetParameters()中可以指定交U中交点的删除策略。通过参数ApproxCurves和ApproximationTolerance来指定交U中交点是否拟合及拟合精度。求交过E中得到的交点往往非常致密Q这栯然可以保证交U的_ֺQ但保存的数据量太大Q在实际应用中需要删除部分交炏VPratt提出使用最二乘法对交UKDQ以删除不必要的交点。这里就需要用到拟合法Approximation Algorithm?/p>
在opencascade中拟合问题被抽象成非U性方E组的求解问题,当然最二乘法也是其中Ҏ之一。拟合算法是造型内核中最基础最重要的算法,除了单的Ҏ合成U以外,q要处理带约束的情况Q如加上交点通过曲面的位|约束等。几何约束求解器中核心也是如此。法国、俄|斯数学厉害Q我惛_该是已经形成理论+应用的良性@环。数学是创新的基Q是用最单的语言来精描q自然中的规律,虽然我们理工U一直学数学Q但工作后很大一部分人很用高{数学中的工P感觉数学没什么用。学而不思则|,思而不学则D。我ȝ的学习规律就是要有实践,上学时实践就是通过做题Q数学理论的一U实践就是开发出软gE序?/p>
现在国家提倡自L觉得是大好事Q什么时候我们出一个几何内核,Z个PDE偏微方程求解器等QŞ成理论加实践的良性@环,以我们的人数和勤劻Iȝ技强国׃会太q?/p>
投媄主要用来计算曲面上的曲线对应到曲面参数空间的曲线PCurveQ生成FACE面时如果边EDGE中没有PCurveQ得到的面是不正的。投q法的实现原理在早期的文章中已l详l介l过Q投q法依赖拟合算法。投q法用在生成交U的函数中MakeCurve()Q?/p>
当不对交U进行拟合时Q生成交U及PCurve主要使用cGeomInt_SS的静态函数来得到交线Q?/p>
q样生成的交U是Bh曲线且控刉Ҏ量很大。当通过拟合可以生成更简化的Bh交线?/p>
定位工具主要用于判断点和一个区域的状态,是在区域内、外q是上。在早期的文章中有一些介l:https://www.cnblogs.com/opencascade/p/Point_Classifier.html
点定位在曲面求交中的应用是处理拓朴面的情况Q对于几何曲面,其实参数域就是其参数S(u,v)的取D围。对于拓朴面Q其参数域是通过边界Wire来限定的Q而且q会有面上开孔的情况需要处理。其实IntTools_FaceFace主要是用来计FACE中几何曲面求交的Q没有正处理定位问题,即生成的交线没有处理面的边界问题。相信看懂源码的同学可以解决q个问题?/p>
定位问题是个几何问题Q在《计几何及应用》一书中有对点的定位问题有详l算法。一般的处理Ҏ是通过点作一条半线Q计半线与多边Ş交点的个敎ͼ若交Ҏ为奇敎ͼ则点在内部,否则在外部。还有一U方法是通过计算点与多边形各点的角度来判断。这两种方式旉复杂度均为O(n)。opencascade中用的W一U方法。书中提到几U效率更高的法Q如点定位问题的分层ҎQ可以将查询旉提高到O(logn)。点定位问题的单调链Ҏ用O(nlogn)旉和O(n)I间作预处理Q查询时间可以O((logn)^2)完成。点定位的三角Şl分Ҏ(Triangulation refinement method)只用O(n)I间存放预处理结果,用O(logn)旉回答点在哪个区域Q完成预处理的时间是O(nlogn)。由此可见,opencascade的定位算法还有一些改q空间?/p>
几何内核中曲面和曲面求交是最重要最复杂的问题,处理曲面和曲面求交需要拟合Approximation Algorithm、投影Projection Algorithm和定位Classification Algorithm工具。其中拟合和投媄主要是数学问题,定位主要是个几何问题Q理解问题就能找到相应的解决工具。庆q有opencascadeq个功能相对完备的开源的几何内核Q提供了一个理pd늚q_。理解数学理论后可以去阅L码,甚至是参与和贡献Q求交、拟合、投影和定位工具都有一些改q空间?/p>
最q关注的朋友说文章发得少了,主要原因是有点忙Q不是因Z守不惛_享,当然有些朋友也提醒要留一手。对于opencascade的技术分享我是没有保留的Q因为opencascade是开源的Q如果通过q些文章分n能帮助别决一些问题,是创造h|是有意义的。时不时收到|友的感谢,收获的感动不是用金钱可以衡量的?/p>
McCad是一个开源工P能自动将BRep模型转换成CSG模型。随着核动力技术的发展Q不断开展新型反应堆的研IӞ反应堆的燃料形式和堆芯布|都较ؓ复杂Q由于蒙特卡|?MC)Ҏh强大的几何处理能力和较高的计精度Q它是模拟分析这些复杂堆芯的有效手段。通过使用McCad复杂BRep模型转换成CSG模型QCSG表示的模型可作ؓ核反应堆芯计蒙特卡|?MC)Ҏ的输入?/p>
McCadZOpenCASCADE内核已经持箋开发了20q_ZLGPL协议开源,感兴的伙伴可以下载尝试一下:
https://github.com/inr-kit/McCAD-Library
BRep转CSG个h感觉没有完美的解x案,但是应用范围很广。如果{换效果好Q其实还有一个用途,是BRep转换的CSG模型导入到PDMS中,从而解决通过机械讑֤接口MEI导入STEP/IGES后PDMS模型数据变大的问题?/p>
McCad is an open source tool for automatic conversion of B-Rep models into CSG.
McCad has been continuously developed for more than two decades. It provides automatic conversion of Boundary Representation (BREP) CAD models into Constructive Solid Geometry (CSG), the latter of which is an input syntax often used in Monte Carlo (MC) radiation transport codes. The conversion process, from BREP to CSG, is essential for high-fidelity nuclear analysis of complex nuclear facilities. McCad can convert CAD files in STEP format to different input syntaxes used with MC codes such as MCNP, TRIPOLI, Geant4, etc., so that the manual efforts on building a complex simulation model can be avoided. McCad provides an advanced algorithm to decompose complex solids into its constituent convex primitives, and generates the void space description between the solids, which is required by MC codes.
It relies on OpenCASCADE CAD kernel to perform CAD manipulations and Boolean operations. McCad has been integrated into the SALOME platform, with a detailed manual provided. It is an open-source code released under LGPL license. This code has been currently used for nuclear research institutes crossing continents, for nuclear analyses on fission reactors, fusion facilities and neutron source facilities.
OpenCASCADE的显C模块的功能性能如何Q很多h都很兛_。开源社区的FreeCAD目前的显C功能都没有使用OpenCASCADE的显C模块。早?014q时Q我在社坛提昄模块交互选择的性能问题Q?/p>
https://dev.opencascade.org/content/selection-convert-2d-not-very-efficient
当时的版本应该是6.8.0。在6.7.1的发布信息中q提C我很开心:
当时KGV也正在优化这部分的功能:
https://tracker.dev.opencascade.org/view.php?id=24623
通过引入BVH来更高效地处理选择Q这个功能集成到6.9.0的版本中了。在6.8.0版本中已l在增强昄模块的性能Q如引入culling机制Q?/p>
https://tracker.dev.opencascade.org/view.php?id=24307
?.4.0版本Qculling基本完善Q当模型出视锥体范围就从显存中去除Q?/p>
https://tracker.dev.opencascade.org/view.php?id=30223
到现在最新版本,昄模块的显C和交互功能的性能到底如何Q下面给出我的一个测试,试l果仅供参考?/p>
试电脑的配|信息如下表Q?/p>
q台电脑已经是好几年之前的配|了Q相对现在的L配置已经落后了?/p>
q个试模型是船的艏部模型,包括船体l构和舾装模型,d的三角面片数量ؓ5百万Q数FPS?5.8Q数大?2应该流畅。交互选择性能很好Q感觉不到gq,鼠标Ud到模型上可以高亮?/p>
q个试模型是一个渡轮,包括船体l构和舾装的所有模型,d的三角面片数量ؓ1?百万Q数FPS?.1Q视图操作(对视囄放、旋转、移动)已经有比较严重的延迟Q但是交互选择性能q不错,没有延迟Q鼠标移动到模型上也是实旉亮?/p>
q个是vz^台模型,包括l构和舾装模型。这个模型量最大,d??百万三角面片Q数FPS?.2Q视图操作(对视囄放、旋转、移动)已经有比较严重的延迟Q但是交互选择性能q不错,没有延迟Q鼠标移动到模型上也是实旉亮。这个最大的模型占用内存情况如下图所C:
软gd占存3.4G内存Q这其中q包含左边的设计D树的数据。当模型放大,出视图范围外的模型已经被剔除cullingQ所以可以从上图可以看出三角面片数量变少了,?百万?/p>
通过以上的测试数据,大家可以l合自己行业模型的体量来选择是否使用OpenCASCADE的显C模块。对于接q?千万三角面片的模型来_模型量已l比较大Q在q个电脑配置情况下基本能满一些大体量的模型显C及交互操作。因为对于大的设计模型,一般在设计q程中,也不是一个h设计Q而是多h多专业协同设计,一个h涉及到的模型量一般不会达?千万q个量。而当设计完成Q只需要浏览时Q如模型评审Q,q时有很多优化手段?/p>