Find closest point on TinSurface

By Augusto Goncalves

A question came up on how find a point XY on a surface. Actually this is quite easy and direct if the XY coordinate is inside the surface borders, just call TinSurface methods to Find[Edge/Vertex/Triangle]AtXY at a given coordinate, but this method throw an exception when the point is outside its borders.

One idea can be based on brute-force algorithm. Of course this can be implemented in many different (and improved) ways, so below is one possible alternative. This also shows how use AutoCAD Geometry classes and methods. The following FindClosestEdgeAtXY sample method iterate through the edges and to find the closest from a given point.

private static TinSurfaceEdge FindClosestEdgeAtXY(

  TinSurface surface, Point3d point)

{

  //in case the point is inside the surface

  try { return surface.FindEdgeAtXY(point.X, point.Y); }

  catch { }

 

  double closestDistance = double.MaxValue;

  TinSurfaceEdge closestEdge = null;

 

  foreach (TinSurfaceTriangle

    triangle in surface.Triangles)

  {

    TinSurfaceEdge edge = null;

    double distance =

      ClosestEdgeOfTriangle(triangle,

      point, ref edge);

 

    if (distance < closestDistance)

    {

      closestDistance = distance;

      closestEdge = edge;

    }

  }

 

  return closestEdge;

}

 

private static double ClosestEdgeOfTriangle(

  TinSurfaceTriangle triangle,

  Point3d point,

  ref TinSurfaceEdge edge)

{

  // closest point on each edge

  Point3d e1 = ClosestPointOnEdge(triangle.Edge1, point);

  Point3d e2 = ClosestPointOnEdge(triangle.Edge2, point);

  Point3d e3 = ClosestPointOnEdge(triangle.Edge3, point);

 

  // closest of the three points

  double d1 = e1.DistanceTo(point);

  double d2 = e2.DistanceTo(point);

  double d3 = e3.DistanceTo(point);

 

  if (d1 < d2)

    if (d1 < d3)

    {

      edge = triangle.Edge1;

      return d1;

    }

    else

    {

      edge = triangle.Edge3;

      return d3;

    }

  else

  {

    edge = triangle.Edge2;

    return d2;

  }

}

 

private static Point3d ClosestPointOnEdge(

  TinSurfaceEdge edge, Point3d point)

{

  using (LineSegment3d lineOverEdge =

    new LineSegment3d(

      edge.Vertex1.Location, edge.Vertex2.Location))

  {

    using (
PointOnCurve3d
closestPointOnCurve =

      lineOverEdge.GetClosestPointTo(point))

    {

      return closestPointOnCurve.Point;

    }

  }

}

As this will return a TinSurfaceEdge object, the next step is find the closest point coordinate on that edge. Sure this is duplicated as the method already did that, but the following command sample find this point and append a AutoCAD DBPoint so it is visible on the drawing.

[CommandMethod("findClosestEdge")]

public static void CmdFindClosestEdge()

{

  Editor ed = Application.DocumentManager.

    MdiActiveDocument.Editor;

 

  // select the surface

  PromptEntityOptions peoSurface =

    new PromptEntityOptions("Select the surface: ");

  peoSurface.SetRejectMessage(

    "\nOnly tin surfaces are allowed");

  peoSurface.AddAllowedClass(typeof(TinSurface), true);

  PromptEntityResult perSurface = ed.GetEntity(peoSurface);

  if (perSurface.Status != PromptStatus.OK) return;

 

  // specify the point

  PromptPointResult pprPoint =

    ed.GetPoint("Specify the point: ");

  if (pprPoint.Status != PromptStatus.OK) return;

  Point3d point = pprPoint.Value;

 

  Database db = Application.DocumentManager.

    MdiActiveDocument.Database;

  using (Transaction trans =

    db.TransactionManager.StartTransaction())

  {

    // open the surface

    TinSurface surface = trans.GetObject(

      perSurface.ObjectId, OpenMode.ForRead)

      as TinSurface;

 

    // find the closest edge

    TinSurfaceEdge closestEdge = FindClosestEdgeAtXY(

      surface, point);

 

    // now (duplicated) find the closest point over the edge

    using (LineSegment3d lineOverEdge =

      new LineSegment3d(closestEdge.Vertex1.Location,

        closestEdge.Vertex2.Location))

    {

      using (PointOnCurve3d closestPointOnCurve =

        lineOverEdge.GetClosestPointTo(point))

      {

        // get the point

        Point3d closestPoint = closestPointOnCurve.Point;

 

        // draw a point for debug

        BlockTableRecord curSpace = trans.GetObject(

          db.CurrentSpaceId, OpenMode.ForWrite)

          as BlockTableRecord;

        DBPoint newPoint = new DBPoint(closestPoint);

        curSpace.AppendEntity(newPoint);

        trans.AddNewlyCreatedDBObject(newPoint, true);

      }

    }

    trans.Commit();

  }

}


Comments

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading