Cad c# 射线法判断点在多边形内外
1、向量叉乘法
2、射线法原理
射线法是判断点与多边形位置关系的经典算法,核心思想是:
从目标点发出一条水平向右的射线(数学上可视为 y = p_y, x \geq p_x 的射线),统计该射线与多边形边的交点数量:
- 偶数次(含0次)相交:点在多边形外部
- 奇数次相交:点在多边形内部
- 点在边上:直接判定为内部或边界(根据需求处理)
关键边界处理
1. 边为水平线段( y_1 = y_2 = p_y ):
射线与边重合,若点在边上则直接判定为边界,否则忽略(不计数)。
2. 点正好是多边形顶点:
若顶点的两个相邻边分别在射线上下两侧,则计为1次交点;否则不计(避免重复计数)。
3. 射线经过边的顶点(非目标点):
仅当该顶点是边的“下端点”(即另一顶点 y > p_y )时,计为1次交点(采用“左闭右开”规则避免重复)。
CAD环境下的实现(基于AutoCAD API)
假设使用 Autodesk.AutoCAD.DatabaseServices 中的 Polyline 和 Point3d 类型,且所有点的 Z=0 (XY平面)。
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using System;
public static class PointInPolylineChecker
{
/// <summary>
/// 射线法判断点是否在闭合Polyline内部(处理直线段,忽略凸度/圆弧,如需处理圆弧需额外交点计算)
/// </summary>
/// <param name="point">目标点(Z=0)</param>
/// <param name="polyline">闭合Polyline(顶点按顺序排列,需闭合,最后一点可与第一点重复)</param>
/// <param name="includeBoundary">是否包含边界(点在边上时返回true)</param>
/// <returns>true=内部或边界,false=外部</returns>
public static bool IsPointInside(Point3d point, Polyline polyline, bool includeBoundary = true)
{
double px = point.X;
double py = point.Y;
int vertexCount = polyline.NumberOfVertices;
int intersectionCount = 0;
for (int i = 0; i < vertexCount; i++)
{
// 获取当前边的两个端点(闭合,最后一个顶点连接到第一个顶点)
Point3d p1 = polyline.GetPoint3dAt(i);
Point3d p2 = polyline.GetPoint3dAt((i + 1) % vertexCount);
// 处理点在边上的情况(优先判断边界)
if (includeBoundary && IsPointOnSegment(point, p1, p2))
return true;
double y1 = p1.Y;
double y2 = p2.Y;
// 边为水平线段(与射线共线),跳过(不计入交点)
if (Math.Abs(y1 - py) < 1e-8 && Math.Abs(y2 - py) < 1e-8)
continue;
// 边的两个端点都在射线下方或上方,不相交
if ((y1 < py - 1e-8 && y2 < py - 1e-8) || (y1 > py + 1e-8 && y2 > py + 1e-8))
continue;
// 计算交点的x坐标(射线为y=py,x≥px)
double xIntersect;
double dx = p2.X - p1.X;
double dy = p2.Y - p1.Y;
// 处理垂直边(避免除零)
if (Math.Abs(dy) < 1e-8)
continue; // 已处理水平边,此处为垂直边但y不相等,不可能与射线相交
double t = (py - y1) / dy; // 参数t∈[0,1]表示交点在边上的位置
xIntersect = p1.X + t * dx;
// 交点必须在射线右侧(xIntersect ≥ px - 1e-8,允许微小误差)且在边的范围内(t∈[0,1])
if (xIntersect >= px - 1e-8 && t >= -1e-8 && t <= 1 + 1e-8)
{
// 处理射线经过顶点的情况(左闭右开规则:仅当顶点是边的“下端点”时计数)
if ((y1 < py + 1e-8 && y2 >= py + 1e-8) || (y2 < py + 1e-8 && y1 >= py + 1e-8))
{
// 排除顶点在射线上且相邻边同向的情况(避免重复计数)
if (!(Math.Abs(y1 - py) < 1e-8 && Math.Abs(y2 - py) < 1e-8))
intersectionCount++;
}
}
}
// 奇数次相交则在内部,边界情况已提前处理
return intersectionCount % 2 == 1;
}
/// <summary>
/// 判断点是否在线段上(包含端点,允许1e-8精度误差)
/// </summary>
private static bool IsPointOnSegment(Point3d point, Point3d p1, Point3d p2)
{
double cross = (point.X - p1.X) * (p2.Y - p1.Y) - (point.Y - p1.Y) * (p2.X - p1.X);
if (Math.Abs(cross) > 1e-8) return false; // 点不在直线上
double minX = Math.Min(p1.X, p2.X) - 1e-8;
double maxX = Math.Max(p1.X, p2.X) + 1e-8;
double minY = Math.Min(p1.Y, p2.Y) - 1e-8;
double maxY = Math.Max(p1.Y, p2.Y) + 1e-8;
return point.X >= minX && point.X <= maxX && point.Y >= minY && point.Y <= maxY;
}
}