Problem with Brep.ClosestPoint

Dear McNeel - maybe @dale can help

I am getting some unexpected results from Brep.ClosestPoint in Rhinocommon c#
Rhino 7, Version 7 SR5 (7.5.21100.3001, 2021-04-10), Windows 10

error_extract_brepclosest_point.3dm (65.5 KB)

closestPointError

i would expect, that the closest point is the Brep-Vertex. (and geometrical it is)
results:
closestPoint - as expected
But
(1) the ComponentIndexType claims it is a face. But it should be a BrepVertex
(2) if it was Face, the normal should point somehow vertical to one face - but i am not sure where the normal-direction comes from / relates to.

the follow code / command roughly extracts the error-part
PR_BrepClosestPtError.cs (2.6 KB)

protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        // get Surface
        const ObjectType filter = ObjectType.Surface | ObjectType.Brep;
        Rhino.DocObjects.ObjRef objref;
        Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select Brep", false, filter, out objref);
        if (rc != Rhino.Commands.Result.Success || objref == null)
            return rc;
        Brep srfB = objref.Brep();
        if (srfB == null) return Result.Failure;
        // get Point
        GetPoint gp = new Rhino.Input.Custom.GetPoint();
        gp.SetCommandPrompt("Point to evaluate");
        OptionDouble oLength = new OptionDouble(100, true, 1);
        gp.AddOptionDouble("LineLength", ref oLength);
        gp.Get();
        if (gp.CommandResult() != Rhino.Commands.Result.Success)
            return gp.CommandResult();
        // get values from input
        Point3d pt0 = gp.Point();
        double length = oLength.CurrentValue;
        // closest Point returnvalues
        Point3d closestPt = Point3d.Unset;
        Vector3d normal = Vector3d.Unset;
        ComponentIndex ci;
        double s;
        double t;
        // query
        bool result = srfB.ClosestPoint(pt0, out closestPt, out ci, out s, out t, 0, out normal);
        if (!result) return Result.Failure;

        RhinoApp.WriteLine("componentIndexType is {0}", ci.ComponentIndexType.ToString());

        if (ci.ComponentIndexType != ComponentIndexType.BrepVertex)
        {
            normal.Unitize(); // not sure if nesscessary
            Line feedbackLine = new Line(closestPt, closestPt + length * normal);
            doc.Objects.AddLine(feedbackLine);
            doc.Views.Redraw();
        }

        return Result.Success;
    }

is this a bug ? some tolerance problem ?

i will code a workarround, to query if the resulting point is close to one vertice - so if it is a deeper problem, no hurry … if i did something wrong, would be happe to know what.

Thanks. -tom

Hi @Tom_,

From Brep.ClosestPoint:

“Finds a point on a Brep that is closest to testPoint. The method searches all Brep faces looking for the one closest to testPoint. When found, if the closest point falls on the inactive region of the face, then the method finds the face’s edge that is closest to testPoint.”

Hope this helps.

– Dale

Dear @Dale - thank you - i think i understood the idea of the function.
also from the documentation:

ci

Type: Rhino.Geometry.ComponentIndex
Component index of the brep component that contains the closest point. Possible types are brep_face, brep_edge or brep_vertex.

in the case that i posted, i would expect the ci.ComponentIndexType to be BrepVertex - as the result closest point is a vertex within tolerance.
but i get BrepFace as type ; - (

do i expect something wrong ?

kind regards -tom

Hi @Tom_,

The documentation is incorrect.

The method will only return a ComponentIndex of type ComponentIndexType.BrepEdge or ComponentIndexType.BrepFace.

I’ll see that the documentation is fixed.

– Dale

ok, thanks again.
still i wonder, why - if the result is either BrepEdge or BrepFace - i would still expect to get BrepEdge - not BrepFace (what i am getting) ?

… my workarround is to test agains all BrepVertices and adjust the component Index:

internal static bool IsVertex(Brep brep, Point3d pt, out ComponentIndex ci, double tolerance = 0.001)
    {
        ci = new ComponentIndex();
        BrepVertexList corners = brep.Vertices;
        foreach (BrepVertex vertex in corners)
        {
            if (vertex.Location.DistanceTo(pt) < tolerance)
            {
                ci = vertex.ComponentIndex();
                return true;
            }
        }
        return false;
    }

Tom,
Every vertex is on at least one edge and at least one face. My point being the reported brep location given by a component index, and parameter pair (s,t) could be referring to the same point as vertex.

thanks @GregArden
(not sure if i 100% understood your second sentence - what do you mean with “my point”… ? but i think i got your main message ) … in my option it would make sense if the component index would be the most specific (a “golf” (that is also a “volkswagen”, that is also a “car” and also a vehicle …) and as fare as i see ComponentIndexType is not a bit-mask…
But for the moment i am fine with the additional test i posted above, as i do not have any performance issues.
have a nice evening.
kind regards -tom

Hi Tom,
I had the same problem some time ago and ended up testing each BrepFace if the closest point is on it. In standard cases this was faster than adjacent searches… and since I had to average the normal vectors at the closest point this was the safe way to detect a “real” vertex.
Jess