0.前言
最近不断遇到类似的几何位置问题,一直没有花时间去总结,本文总结了我常用点跟多边形的位置判断方法以及代码。希望能够对大家有所帮助。
文中所指的多边形均为凸多边形,一些描述可能有误,欢迎指正。
1.测试的多边形
在开始之前,我们需要先构建好测试环境。
我构建了一个比较特殊的多边形,如下。
/ \
| |
|_|
从最上面的顶点顺时针坐标(屏幕坐标系)分别为:(40,10) (60,30)(60,50) (20,50) (20,30)
2.射线判别法
根据对多边形的了解,我们可以得出如下结论:
如果一个点在多边形内部,任意角度做射线肯定会与多边形要么有一个交点,要么有与多边形边界线重叠。
如果一个点在多边形外部,任意角度做射线要么与多边形有一个交点,要么有两个交点,要么没有交点,要么有与多边形边界线重叠。
利用上面的结论,我们只要判断这个点与多边形的交点个数,就可以判断出点与多边形的位置关系了。
首先罗列下注意事项:
l 射线跟多边形的边界线重叠的情况
l 区别内部点和外部点的射线在有一个交点时的情况
对于第一个注意事项,可以将射线角度设为零度,这样子只需要判断两个相邻顶点的Y值是否相等即可。然后再判断这个点的X值方位。
对于第二个注意事项,网上许多文章都说到做射线以后交点为奇数则表示在多边形内部,这是一个错误的观点,不仅对于凹多边形不成立,对于凸多边形也不成立。
例如:从外部点做射线刚好经过一顶点,这样子交点个数就为奇数,但是该点却不在多边形内部。
至于要如何区分这两种情况呢,我能想到了一个不完美的方法,外部点的射线跟多边形有一个交点的时候,该交点肯定为顶点,如果该射线上移一位或者下移 一位,要么变成有两个交点要么没有交点。当然为了安全起见,这里把射线尽量往相邻点中心移动。这样子就能够判断出是外部点的射线跟多边形有一个交点。
不过这个方法并不完美,因为有了移位操作,可能会把内部点移动出外部。而且如果判断点在(60,30)位置,判断的时候先遇到(20,30),然后移位操作,就判断就出错了。
为了解决这些问题,我在起初先扫描一次判断点是否在顶点上虽然影响了一点效率,而且当判定点距离多边形一个单位时,判断可能会有误。不过只要不是需要高精度的话,这个方法还是很有效的。
代码如下:
bool InsidePolygon1( POINTD *polygon,int N,POINTD pt ){ int i,j; bool inside,redo; redo = true; for (i = 0;i < N;++i) { if (polygon[i].x == pt.x && // 是否在顶点上 polygon[i].y == pt.y ) { redo = false; inside = true; break; } } while (redo) { redo = false; inside = false; for (i = 0,j = N - 1;i < N;j = i++) { if ( (polygon[i].y < pt.y && pt.y < polygon[j].y) || (polygon[j].y < pt.y && pt.y < polygon[i].y) ) { if (pt.x <= polygon[i].x || pt.x <= polygon[j].x) { double _x = (pt.y-polygon[i].y)*(polygon[j].x-polygon[i].x)/(polygon[j].y-polygon[i].y)+polygon[i].x; if (pt.x < _x) // 在线的左侧 inside = !inside; else if (pt.x == _x) // 在线上 { inside = true; break; } } } else if ( pt.y == polygon[i].y) { if (pt.x < polygon[i].x) // 交点在顶点上 { polygon[i].y > polygon[j].y ? --pt.y : ++pt.y; redo = true; break; } } else if ( polygon[i].y == polygon[j].y && // 在水平的边界线上 pt.y == polygon[i].y && ( (polygon[i].x < pt.x && pt.x < polygon[j].x) || (polygon[j].x < pt.x && pt.x < polygon[i].x) ) ) { inside = true; break; } } } return inside;}
3.角度和判别法
角度和判别法就是判定点与多边形所有相邻顶点组成的角的角度和来判断点与多边形的位置关系。
如果点在多边形内部,只要该点不在边界线或者顶点上,则角度和为三百六十度。
如果在边界线上,则角度和为一百八十度。
如果点在多边形外部,则角度和无法达到三百六十度。但是角度和可能会达到一百八十度,比如判断点在(60,10)。
代码如下:
// 根据需要不判断顶点 bool IsPointInLine( const POINTD &pt,const POINTD &pt1,const POINTD &pt2 ){ bool inside = false; if (pt.y == pt1.y && pt1.y == pt2.y && ((pt1.x < pt.x && pt.x < pt2.x) || (pt2.x < pt.x && pt.x < pt1.x)) ) { inside = true; } else if (pt.x == pt1.x && pt1.x == pt2.x && ((pt1.y < pt.y && pt.y < pt2.y) || (pt2.y < pt.y && pt.y < pt1.y)) ) { inside = true; } else if ( ((pt1.y < pt.y && pt.y < pt2.y) || (pt2.y < pt.y && pt.y < pt1.y)) && ((pt1.x < pt.x && pt.x < pt2.x) || (pt2.x < pt.x && pt.x < pt1.x)) ) { if (0 == (pt.y-pt1.y)/(pt2.y-pt1.y)-(pt.x - pt1.x) / (pt2.x-pt1.x)) { inside = true; } } return inside;} bool InsidePolygon2( POINTD *polygon,int N,POINTD p ){ int i,j; double angle = 0; bool inside = false; for (i = 0,j = N - 1;i < N;j = i++) { if (polygon[i].x == p.x && // 是否在顶点上 polygon[i].y == p.y) { inside = true; break; } else if (IsPointInLine(p,polygon[i],polygon[j])) // 是否在边界线上 { inside = true; break; } double x1,y1,x2,y2; x1 = polygon[i].x - p.x; y1 = polygon[i].y - p.y; x2 = polygon[j].x - p.x; y2 = polygon[j].y - p.y; double radian = atan2(y1,x1) - atan2(y2,x2); radian = abs(radian); if (radian > M_PI) radian = 2* M_PI - radian; angle += radian; // 计算角度和 } if ( fabs(6.28318530717958647692 - angle) < 1e-7 ) inside = true; return inside;}
有的人管角度和判别法叫做转角法,还有一个类似的方法叫弧长法。
4.面积和判别法
根据角度和判别法,我们可以继续演化,可以得出如下结论:
如果一个点在多边形内部,该点与多边形所有相邻顶点组成的三角形面积和为多边形面积。反之不成立。
求三角形面积:
已知三角形A(x1,y1)B(x2,y2)C(x3,y3),则面积公式为:
|x1 x2 x3|
S(A,B,C) = |y1 y2 y3| * 0.5 (当三点为逆时针时为正,顺时针则为负的)
|1 1 1 |
= ( x1*y2 - x1*y3 - x2*y1 + x2*y3 + x3*y1 - x3*y2 ) * 0.5
求多边形面积:
根据上面求三角形的方法,
已知多边形A1A2A3...An(顺或逆时针都可以),设平面上有任意的一点P,则面积公式为:
S(A1,A2,A3...An)
= S(P,A1,A2)+ S(P,A2,A3)+...+S(P,An,A1)
既然P是可以任取,为了方便用(0,0)好了。
代码如下:
bool InsidePolygon3( POINTD *polygon,int N,POINTD pt ){ int i,j; bool inside = false; double polygon_area = 0; double trigon_area = 0; for (i = 0,j = N - 1;i < N;j = i++) { polygon_area += polygon[i].x * polygon[j].y - polygon[j].x * polygon[i].y; trigon_area += abs( pt.x * polygon[i].y - pt.x * polygon[j].y - polygon[i].x * pt.y + polygon[i].x * polygon[j].y + polygon[j].x * pt.y - polygon[j].x * polygon[i].y ); } trigon_area *= 0.5; polygon_area = abs(polygon_area * 0.5); if ( fabs(trigon_area - polygon_area) < 1e-7 ) inside = true; return inside;}
多边形面积计算
行列式如何展开
5.点线判别法
经网友glshader提示,添加了这个方法。
如果判断点在所有边界线的同侧,就能判定该点在多边形内部。
判断方法就是判断两条同起点射线斜率差。
代码如下:
bool InsidePolygon4( POINTD *polygon,int N,POINTD p ){ int i,j; bool inside = false; int count1 = 0; int count2 = 0; for (i = 0,j = N - 1;i < N;j = i++) { double value = (p.x - polygon[j].x) * (polygon[i].y - polygon[j].y) - (p.y - polygon[j].y) * (polygon[i].x - polygon[j].x); if (value > 0) ++count1; else if (value < 0) ++count2; } if (0 == count1 || 0 == count2) { inside = true; } return inside;}