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();
}
}