Consistency of TopoDS_Face normals

I am importing large amounts of data into Open Cascade. I can not seem to figure out what the way Open Cascade assigns the orientation of normals to TopoDS_Faces that are constructed from a closed wire.

In the following example two faces are constructed from points in a CounterClockWise order, but are assigned normals pointing in opposite directions. Does the assignment of normals adheres to any convention or rationale? Or is there any way to make the normals consistent in relation to each other?

This is the way the points are located:

p36641 --- p36640 --- p36639
| | |
p36609 --- p36608 --- p36607

These are the normals that get assigned:

-0.16751,+0.00000,+0.98587
+0.05607,+0.00000,-0.99843

Thanks for your help.

Kind regards,
Thomas
IfcOpenShell.org

PS: This is the code to reproduce my problem:

#include

#include
#include

#include
#include

#include
#include
#include

#include

#include

#include

void create_loop(const gp_Pnt& p1, const gp_Pnt& p2, const gp_Pnt& p3, const gp_Pnt& p4, TopoDS_Wire& wire) {
BRepBuilderAPI_MakeWire w;
w.Add(BRepBuilderAPI_MakeEdge(p1,p2));
w.Add(BRepBuilderAPI_MakeEdge(p2,p3));
w.Add(BRepBuilderAPI_MakeEdge(p3,p4));
w.Add(BRepBuilderAPI_MakeEdge(p4,p1));
wire = w.Wire();
}

void face_normal(const TopoDS_Face& face) {
BRepGProp_Face prop(face);
double u1,u2,v1,v2;
prop.Bounds(u1,u2,v1,v2);
double u = (u1+u2)/2;
double v = (v1+v2)/2;
gp_Vec vec;
gp_Pnt pnt;
prop.Normal(u,v,pnt,vec);
char buff[64];
sprintf(buff,"%+.5f,%+.5f,%+.5f",vec.X(),vec.Y(),vec.Z());
std::cout }

int main(int argc, char** argv)
{
// The imported information is scaled, hence the scale factor f
// Even changing this factor has an influence on the orientation of the normals.
const float f = 0.3048;
gp_Pnt p36607(-3.234400499461643*f,1.316409965570497*f,7.333333333333272*f);
gp_Pnt p36608(-3.595952453545237*f,1.316409965570497*f,7.313029011113542*f);
gp_Pnt p36609(-3.952957682028909*f,1.316409965570496*f,7.252371383087095*f);
gp_Pnt p36639(-3.234400499461643*f,2.33333333333334*f ,7.333333333333272*f);
gp_Pnt p36640(-3.595952453545237*f,2.333333333333339*f,7.313029011113542*f);
gp_Pnt p36641(-3.952957682028909*f,2.333333333333339*f,7.252371383087095*f);

TopoDS_Wire w36733;
create_loop(p36608,p36640,p36641,p36609,w36733);
TopoDS_Wire w36766;
create_loop(p36607,p36639,p36640,p36608,w36766);

TopoDS_Face f36735 = BRepBuilderAPI_MakeFace(w36733);
TopoDS_Face f36768 = BRepBuilderAPI_MakeFace(w36766);

face_normal(f36735);
face_normal(f36768);

TopAbs_Orientation f36735_orientation = f36735.Orientation();
TopAbs_Orientation f36768_orientation = f36768.Orientation();
}

Thomas Krijnen's picture

Eventually I ended up writing a routine that flips the face orientation if the face normal does not align with a normal vector computed with Newell's method.
I still feel I am doing something wrong or there must be another way. Doesn't anybody have an idea? Thanks,

Kind regards,
Thomas

Here's the code:

// Check the orientation of the face by comparing the
// normal of the topological surface to the Newell's Method's
// normal. Newell's Method is used for the normal calculation
// as a simple edge cross product can give opposite results
// for a concave face boundary.
// Reference: Graphics Gems III p. 231
BRepGProp_Face prop(face);
gp_Vec normal_direction;
gp_Pnt center;
double u1,u2,v1,v2;
prop.Bounds(u1,u2,v1,v2);
prop.Normal((u1+u2)/2.0,(v1+v2)/2.0,center,normal_direction);
gp_Dir face_normal1 = gp_Dir(normal_direction.XYZ());

double x = 0, y = 0, z = 0;
gp_Pnt current, previous, first;
int n = 0;
// Iterate over the vertices of the outer wire (discarding
// any potential holes)
for ( TopExp_Explorer exp(outer_wire,TopAbs_VERTEX);; exp.Next()) {
unsigned has_more = exp.More();
if ( has_more ) {
const TopoDS_Vertex& v = TopoDS::Vertex(exp.Current());
current = BRep_Tool::Pnt(v);
} else {
current = first;
}
if ( n ) {
const double& xn = previous.X();
const double& yn = previous.Y();
const double& zn = previous.Z();
const double& xn1 = current.X();
const double& yn1 = current.Y();
const double& zn1 = current.Z();
x += (yn-yn1)*(zn+zn1);
y += (xn+xn1)*(zn-zn1);
z += (xn-xn1)*(yn+yn1);
} else {
first = current;
}
if ( !has_more ) {
break;
}
previous = current;
++n;
}

// If Newell's normal does not point in the same direction
// as the topological face normal the face orientation is
// reversed
gp_Vec face_normal2(x,y,z);
if ( face_normal1.Dot(face_normal2) < 0 ) {
TopAbs_Orientation o = face.Orientation();
face.Orientation(o == TopAbs_FORWARD ? TopAbs_REVERSED : TopAbs_FORWARD);
}

jelle's picture

interesting contribution, thanks for sharing Thomas!

Ling's picture

hello , i am a student. and i have a task that iges/step to stl.

when iges to stl , i find that some face's normal is reversed, and the problem i think is flow:
face.Orientation() is always TopAbs_FORWARD, WHy? What's wrong with this?

but , when step to stl , it's fine. the face normal is right.
can you give me some advise ,,thank you very much.

this is my code:
TopoDS_Shape shape = Reader.OneShape();
shape.Modified();
//shape.Checked();
double const deflection = 0.5;
double const angulardeflection = 0.5;
BRepTools::Clean(shape);
BRepMesh_IncrementalMesh discr(shape, deflection, false, angulardeflection); //shape or face

TopoDS_Face face;

for (TopExp_Explorer ex(shape, TopAbs_FACE); ex.More(); ex.Next())
{
TopoDS_Face face = TopoDS::Face(ex.Current());
TopLoc_Location loc;

BRepAdaptor_Surface sf(face, Standard_False);
BRepLProp_SLProps prop(sf, 1, 1e-5);

Handle(Poly_Triangulation) triangulation = BRep_Tool::Triangulation (face, loc);

//Handle(Poly_Triangulation) triangulation1 = BRepTools::Triangulation(face, 0.01);

if (!triangulation.IsNull())
{
gp_Pnt2d uv;
gp_Pnt pnt;
gp_Vec nn;

int ntriangles = triangulation -> NbTriangles();
int npoints = triangulation->NbNodes();

const TColgp_Array1OfPnt& aNodes = triangulation->Nodes();
int num = aNodes.Length();

TColgp_Array1OfPnt aPoints(1, aNodes.Length());
for( Standard_Integer i = 1; i < aNodes.Length()+1; i++)
{
aPoints(i) = aNodes(i).Transformed(loc);

mesh.add_vertex( TNOMS3D::OPoint( aPoints(i).X(), aPoints(i).Y(), aPoints(i).Z() ) );
}

const Poly_Array1OfTriangle& triangles =triangulation ->Triangles();

Standard_Integer v1,v2,v3;
for (int j = 1; j <= ntriangles; j++)
{
if (face.Orientation() == TopAbs_REVERSED)
{
triangles(j).Get(v1,v3,v2);
mesh.add_face( OpenMesh::VertexHandle(index+v1-1), OpenMesh::VertexHandle(index+v2-1), OpenMesh::VertexHandle(index+v3-1));
}
else
{
triangles(j).Get(v1,v2,v3);
mesh.add_face( OpenMesh::VertexHandle(index+v1-1), OpenMesh::VertexHandle(index+v2-1), OpenMesh::VertexHandle(index+v3-1));
}

//mesh.add_face( OpenMesh::VertexHandle(v1-1), OpenMesh::VertexHandle(v2-1), OpenMesh::VertexHandle(v3-1));

Poly_Triangle triangle = (triangulation -> Triangles())(j);
//gp_Vec nn[3];
for(int k=1; k<=3; k++)
{
uv = (triangulation -> UVNodes())(triangle(k));
prop.SetParameters (uv.X(), uv.Y());

if (prop.IsNormalDefined())
nn = prop.Normal();
else
{
// n = gp_Vec (0,0,0);
gp_Vec a(aPoints(v1),aPoints(v2));
gp_Vec b(aPoints(v1),aPoints(v3));
nn = b^a;
}
if (face.Orientation() == TopAbs_REVERSED) nn *= -1;

if( k==1 )
{
mesh.set_normal( OpenMesh::VertexHandle(index+v1-1),TNOMS3D::ONormal(nn.X(),nn.Y(),nn.Z()) );
}
if( k==2 )
{
mesh.set_normal( OpenMesh::VertexHandle(index+v2-1),TNOMS3D::ONormal(nn.X(),nn.Y(),nn.Z()) );
}
if( k==3 )
{
mesh.set_normal( OpenMesh::VertexHandle(index+v3-1),TNOMS3D::ONormal(nn.X(),nn.Y(),nn.Z()) );
}
}
}

index = index+npoints;

}

}

xiaoxiong's picture
Ling's picture

// Iterate over the vertices of the outer wire (discarding
// any potential holes)
for ( TopExp_Explorer exp(outer_wire,TopAbs_VERTEX);; exp.Next()) {

i want to ask:
what is the outer_wire, how can i get the outer.

Junjie Xue's picture

Ling | reply 2013/01/23 11:24
// Iterate over the vertices of the outer wire (discarding
// any potential holes)
for ( TopExp_Explorer exp(outer_wire,TopAbs_VERTEX);; exp.Next()) {

i want to ask:
what is the outer_wire, how can i get the outer.

+1

Thomas Krijnen's picture

Hi,

Sorry for the late reply. The code posted above is a snippet from the IfcOpenShell source code (http://sourceforge.net/p/ifcopenshell/svn/HEAD/tree/trunk/src/ifcgeom/If...). In this case the TopoDS_Wire outer_wire is used to construct the face. But I can also imagine you can use TopExp_Explorer and TopAbs_WIRE to iterate over the wires and find the wire you are interested in.

Hope that helps,

Kind regards,
Thomas