Brep.ClosestPoint Normal is not normal

the vector normal retrieved with the Brep.ClosestPoint method works fine if the closest component is a face, but if it’s an edge, it returns an edge tangent vector rather than a normal to the Brep. This seems to be intentional, as it’s documented this way in the SDK - but is there a reason? I would think in most use cases it would be advantageous to have the normal to the Brep always be normal to the Brep…

I guess the reason is that the normal is not defined on a sharp edge: which face should be used for the normal?

The “logical” behavior (at least for my use) is the average of the normals of the two faces at that point.

I agree, that makes sense. I wonder if that would work on non-manifold edges too? But maybe in that case it should not show a normal at all…

I think the behavior is perfectly consistent.

BReps in the first place are just a collection of edges and faces. Each edge and face is tested for the CP and the closest result is returned. Now if the closest component is an edge, the tested geometry was a curve. The normal of a curve is its tangent vector.

If you see an edge, you can then retrieve the BrepEdge. Get the adjacent faces and calculate the CPs and normals for those. Then you decide what to do with them. Now McNeel could define a default case, like in your current example, but:

As Menno mentioned, edges in a BRep may or may not be naked or manifold. Since non manifold Breps don’t have that well defined normal flow, all normal pointing outward, returning a blind average of adjacent normals is probably not a good idea. You may find that the CP is actually the end of your edge so you’re looking at a corner, manifold, naked or whatnot…

To account for all those special cases, provide indicators and make the function adaptable to your current case would result in one big chunky function call and maybe lots of special case overloads. Since getting your own edge normal is pretty straight forward, I’d have a hard time to justify the effort for that clumsy and hard to read function call.

I see the complication, although I stand by my point from a practical usability standpoint. The normal of a curve is NOT its tangent. Curves have normal planes and tangent vectors while surfaces have tangent planes and normal vectors, which of course complicates things somewhat. But nitpicking aside, an example practical case where I would want a normal is to use it to move a sample point slightly off of the surface of a volume - it’s frustrating to me that if I rely on this method, sometimes the point will move outwards and sometimes it will move along an edge.

I get that I can find the normals of the adjacent faces myself given the edge component, and I have done just that for my own purposes. It only seems odd to me because conceptually I expect normal to be “out.”

I can see your frustration, but already your nitpicking shows the problem. A curve doesn’t have a unique “normal direction”. So you cannot simply return a single direction for a curve. Since the current implementation returns the type of object, the point lies on, you are required to check on your own and act accordingly.

A possible solution is to limit the results to points on faces only. Either by option or by overload. Then your default average is a good first assumption for most BReps but you’ll have problems with non-manifolds.

The only real advantage of McNeel coding that for you is that they could cache intermediate results and not calculate the normals for the adjacent faces again.

Well there you go. Normals of a BRep are facing “out”, because they are required to do so by definition of a BRep.

There’s a nice dicussion over at the Maxwell Render forums about how to best render an ice cube in a glass of water or a bubble of air in a liquid with various layers of material transitions facing this and that way. Once you define the atmosphere to be the outside medium, “outside” can qickly become “inside” and normals are expected to point inward.

Today I ran into this problem too and I remembered this discussion. This is how I solved it in RhinoCommon C#, by averaging the normals on the adjacent faces to the edge.

Point3d closest;
ComponentIndex ci;
Vector3d brepNormal;
double s, t;
brep.ClosestPoint(p00, out closest, out ci, out s, out t, 0, out brepNormal);

// if the closest point is found on an edge, average the face normals
if (ci.ComponentIndexType == ComponentIndexType.BrepEdge)
{
    BrepEdge edge = brep.Edges[ci.Index];
    int[] faces = edge.AdjacentFaces();
    brepNormal = Vector3d.Zero;
    for (int i = 0; i < faces.Length; ++i)
    {
        BrepFace bf = edge.Brep.Faces[i];
        if (bf.ClosestPoint(closest, out s, out t))
        {
            Vector3d faceNormal = bf.NormalAt(s, t);
            brepNormal += faceNormal;
        }
    }
}
                
brepNormal.Unitize();
1 Like

Same here but what I needed was the vector towards the test point :wink:

I found and error in the code listed above, the line

BrepFace bf = edge.Brep.Faces[i];

should read

BrepFace bf = edge.Brep.Faces[faces[i]];

@clement

1 Like

three years later… :wink: unfortunately there is no function to get the “adjacent” faces of a vertex. Right now I’m testing if the closest point is on a vertex, then I check all edges if the vertex is on an edge and finally collecting all adjacent faces. But there might be cases where there are many more edges than faces. So it might be slower than checking all surfaces if the closest point is on it.

Hallo Jess,

mesh.Vertices.GetVertexFaces(vertexIndex). (Rhino 6?)
Supposed to there are no duplicated vertices.

Hi Tom,
I talking about vertices of a brep.
For Meshes there is.
Thanks, Jess

oh, should have read to whole post.:astonished:

For brepcorners there might be another 2 solutions I could imagine.

  1. What if you create a vertex map for your brep beforehand.
    Basically a lookup table for adjacent faces.
    If you find out that your closest point epsilion equals a brepcorner,
    you just look into it find the closest point of these faces, averaging it.
  2. Or you create a couple of additional points in spherical arrangement (tiny radius) and check closest point for them again. This might be problematic as well, but depending on your brep size and the probability of this event, it may be a valid solution as well

or just the subject of the thread :wink:

The approach I’ve described above works well. Just for non-tangent nurbs singularities (like the tip of a cone) I do not have a fast solution to get the the average normal vector. I’ll ignore that :wink:

Thank you @menno

I’ve imagined one way to handle the pyramid tip case (getting the average brep normal at a vertex, edge or face) is to find the closest edge to the 1 brep face returned by iterating only it’s eg. 4 edges. If the closest point is not within tolerance to one of the edges use the single face normal.

If it is within tolerance, i measure the distance to the edge (Distance_A). Next i measure the distances to the brep vertices for edge.StartVertex (Distance S) and edge.EndVertex (Distance E). If Distance A is smaller than S and E, i average the face normal of the 2 adjacent edge faces.

If not, i choose either E or S depending on what is closer, then find the edges connected to the vertex using vertex.EdgeIndices(), get the faces from the edges like above and from this averaging the normals.

Would this work ? I does not require to iterate over all vertices, edges or faces.

_
c.

I found a simple workaround if you need it for offset situations or something like that.
You can cut the brep, divide curve, find the closest points to the edges. Normal from thos 2 points and you have a more acurate average vector than using the two surfaces