已经提供了代码下载地址:提取TopoDS_Shape的剖分数据进行光滑显示
Open CASCADE(Computer Aided Software for Computer Aided Design and Engineering),它的前身是Euclide软件,在1970年(应该是由法国南都大学实验室的两位研究人员)开发用作流体建模,之后这两位研究员在1979年成立了公司并随后在1980年将公司的大部分股权卖给了法国公司Matra Datavision,Matra Datavision于1989年在网上发布了开源项目Open CASCADE,后来更名为Open CASCADE Technology。这段历史可参考:Euclid (computer program)
我公司做的一个项目正好用到了Open CASCADE Technology,主要是用它来做几何运算。那么这其中就涉及到对OCCT的模型表示结构TopoDS_Shape进行剖分信息提取,我们的渲染引擎是自己的。我从最开始接触OCCT,就对OCCT使用TopoDS_Shape进行渲染显示进行了调试分析,经过将近两周的时间终于找到了对TopoDS_Shape进行剖分的源码并用在了自己的项目当中。
在这个过程中我也学习了FreeCAD对OCCT的使用(FreeCAD是基于OCCT开发的一个开源项目),也在网上找了一些关于对OCCT的拓扑结构TopoDS_Shape的剖分使用。在这个过程中我发现了一个问题,不论是FreeCAD还是网上其他的一些资料,在对面类型和体类型(TopAbs_FACE, TopAbs_SHELL, TopAbs_SOLID, TopAbs_COMSOLID和TopAbs_COMPOUND)的TopoDS_Shape的剖分使用有一些不太对的地方。OCCT在对面类型和体类型的TopoDS_Shape做剖分时是使用的mesh refinement(网格精分)技术,清楚mesh refinement的都知道,当对曲面进行网格精分,然后在显示的时候为了使它能够达到平滑的效果,就需要用到顶点的法向(法向是决定模型平滑度的重要因素)。其实OCCT它已经帮我们计算好了每个顶点的法向,但是我在分析FreeCAD和网上其他一些资料的时候它们都没有直接使用OCCT提供的法向,而是手动去单独计算每个三角面片的法向,这个做法我觉得是很不可取的,首先单独计算每个三角面片的法向,那么如何在后期不做处理的话渲染显示出来的效果会完全不可能达到平滑的效果,下面附一张对比图:
这两个球体的半径均为100,在使用BRepMesh::Mesh方法做网格精分是设置的偏差度均为1.0,左边的球体顶点的法向我是使用OCCT提供的计算的法向,右边的球体顶点的法向我是通过手动去计算每个单独的三角面片的法向,使用的渲染引擎是OpenScenGraph,这两个球体在组装渲染数据的时候唯一的区别也就是顶点的法向取值不同,通过这幅对比图你可以看到很明显的效果,左边的图很光滑,右边的图把三角面片的棱全都显示了出来,没有平滑的效果。但是FreeCAD在使用TopoDS_Shape的剖分信息时,它对定点法向的计算正是通过上图右边球体同样的计算方式,即单独计算每个三角面片的法向,而它渲染出来的效果仍然也是平滑的,那这是为什么呢?我的猜测是FreeCAD在后期做了处理,也就是在着色器中做了平滑处理,不知道是不是使用的Phong Shader。那么对比上图左边球体对TopoDS_Shape进行剖分信息提取和FreeCAD中的剖分信息提取以及后期平滑处理的效率,很明显我直接使用OCCT给我提供的计算好的顶点法向去组装渲染数据更加快捷更加有效!(PS:OCCT在做网格精分时计算顶点法向的技术应该是采用的Normal Average[法向平均]技术,具体没有研究!)
下面我将OCCT使用面类型和体类型的TopoDS_Shape提取剖分信息的源码文件提供出来,它里面提供了使用了OCCT提供的计算好的顶点法向的代码:
OpenCASCADE源码库/StdPrs_ShadedShape.cxx,这个文件的ShadeFromShape方法就是了,注意,ShapeFromShape方法并没有对TopoDS_Shape进行mesh refinement,进行这一步操作的方法是StdPrs_ShadedShape::Add方法,此方法在对TopoDS_Shape进行mesh refinement之后再调用的ShapeFromShape方法,话不多说,附上源码(希望借鉴此方法的人最好是能够运行OCCT提供的例子Triangulation并调试到这段代码进行分析,这段代码你并不能直接使用):
// =======================================================================
// function : ShadeFromShape
// purpose :
// =======================================================================
static Standard_Boolean ShadeFromShape (const TopoDS_Shape& theShape,
const Handle (Prs3d_Presentation)& thePresentation,
const Handle (Prs3d_Drawer)& theDrawer,
const Standard_Boolean theHasTexels,
const gp_Pnt2d& theUVOrigin,
const gp_Pnt2d& theUVRepeat,
const gp_Pnt2d& theUVScale)
{
StdPrs_ToolShadedShape SST;
Handle(Poly_Triangulation) T;
TopLoc_Location aLoc;
gp_Pnt p;
Standard_Integer decal;
Standard_Integer t[3], n[3];
Standard_Integer nbTriangles = 0, nbVertices = 0;
Standard_Real aUmin (0.0), aUmax (0.0), aVmin (0.0), aVmax (0.0), dUmax (0.0), dVmax (0.0);
// precision for compare square distances
const double aPreci = Precision::SquareConfusion();
if (!theDrawer->ShadingAspectGlobal())
{
Handle(Graphic3d_AspectFillArea3d) anAsp = theDrawer->ShadingAspect()->Aspect();
if (StdPrs_ToolShadedShape::IsClosed (theShape))
{
anAsp->SuppressBackFace();
}
else
{
anAsp->AllowBackFace();
}
Prs3d_Root::CurrentGroup (thePresentation)->SetGroupPrimitivesAspect (anAsp);
}
for (SST.Init (theShape); SST.MoreFace(); SST.NextFace())
{
const TopoDS_Face& aFace = SST.CurrentFace();
T = SST.Triangulation (aFace, aLoc);
if (!T.IsNull())
{
nbTriangles += T->NbTriangles();
nbVertices += T->NbNodes();
}
}
if (nbVertices > 2 && nbTriangles > 0)
{
Handle(Graphic3d_ArrayOfTriangles) aPArray
= new Graphic3d_ArrayOfTriangles (nbVertices, 3 * nbTriangles,
Standard_True, Standard_False, theHasTexels, Standard_True);
for (SST.Init (theShape); SST.MoreFace(); SST.NextFace())
{
const TopoDS_Face& aFace = SST.CurrentFace();
T = SST.Triangulation (aFace, aLoc);
if (T.IsNull())
{
continue;
}
const gp_Trsf& aTrsf = aLoc.Transformation();
Poly_Connect pc (T);
// Extracts vertices & normals from nodes
const TColgp_Array1OfPnt& aNodes = T->Nodes();
const TColgp_Array1OfPnt2d& aUVNodes = T->UVNodes();
TColgp_Array1OfDir aNormals (aNodes.Lower(), aNodes.Upper());
SST.Normal (aFace, pc, aNormals);
if (theHasTexels)
{
BRepTools::UVBounds (aFace, aUmin, aUmax, aVmin, aVmax);
dUmax = (aUmax - aUmin);
dVmax = (aVmax - aVmin);
}
decal = aPArray->VertexNumber();
for (Standard_Integer aNodeIter = aNodes.Lower(); aNodeIter <= aNodes.Upper(); ++aNodeIter)
{
p = aNodes (aNodeIter);
if (!aLoc.IsIdentity())
{
p.Transform (aTrsf);
aNormals (aNodeIter).Transform (aTrsf);
}
if (theHasTexels && aUVNodes.Upper() == aNodes.Upper())
{
const gp_Pnt2d aTexel = gp_Pnt2d ((-theUVOrigin.X() + (theUVRepeat.X() * (aUVNodes (aNodeIter).X() - aUmin)) / dUmax) / theUVScale.X(),
(-theUVOrigin.Y() + (theUVRepeat.Y() * (aUVNodes (aNodeIter).Y() - aVmin)) / dVmax) / theUVScale.Y());
aPArray->AddVertex (p, aNormals (aNodeIter), aTexel);
}
else
{
aPArray->AddVertex (p, aNormals (aNodeIter));
}
}
// Fill parray with vertex and edge visibillity info
const Poly_Array1OfTriangle& aTriangles = T->Triangles();
for (Standard_Integer aTriIter = 1; aTriIter <= T->NbTriangles(); ++aTriIter)
{
pc.Triangles (aTriIter, t[0], t[1], t[2]);
if (SST.Orientation (aFace) == TopAbs_REVERSED)
aTriangles (aTriIter).Get (n[0], n[2], n[1]);
else
aTriangles (aTriIter).Get (n[0], n[1], n[2]);
gp_Pnt P1 = aNodes (n[0]);
gp_Pnt P2 = aNodes (n[1]);
gp_Pnt P3 = aNodes (n[2]);
gp_Vec V1 (P1, P2);
if (V1.SquareMagnitude() <= aPreci)
{
continue;
}
gp_Vec V2 (P2, P3);
if (V2.SquareMagnitude() <= aPreci)
{
continue;
}
gp_Vec V3 (P3, P1);
if (V3.SquareMagnitude() <= aPreci)
{
continue;
}
V1.Normalize();
V2.Normalize();
V1.Cross (V2);
if (V1.SquareMagnitude() > aPreci)
{
aPArray->AddEdge (n[0] + decal, t[0] == 0);
aPArray->AddEdge (n[1] + decal, t[1] == 0);
aPArray->AddEdge (n[2] + decal, t[2] == 0);
}
}
}
Prs3d_Root::CurrentGroup (thePresentation)->AddPrimitiveArray (aPArray);
}
return Standard_True;
}