我正在尝试在多边形算法内部建立一个快速的 2D点,以用于命中测试(例如Polygon.contains(p:Point)
)。 对于有效技术的建议将不胜感激。 javascript
nirg的答案的C#版本在这里:我只分享代码。 这样能够节省一些时间。 html
public static bool IsPointInPolygon(IList<Point> polygon, Point testPoint) { bool result = false; int j = polygon.Count() - 1; for (int i = 0; i < polygon.Count(); i++) { if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y) { if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X) { result = !result; } } j = i; } return result; }
nirg答案的Obj-C版本,带有用于测试点的样本方法。 Nirg的回答对我来讲效果很好。 java
- (BOOL)isPointInPolygon:(NSArray *)vertices point:(CGPoint)test { NSUInteger nvert = [vertices count]; NSInteger i, j, c = 0; CGPoint verti, vertj; for (i = 0, j = nvert-1; i < nvert; j = i++) { verti = [(NSValue *)[vertices objectAtIndex:i] CGPointValue]; vertj = [(NSValue *)[vertices objectAtIndex:j] CGPointValue]; if (( (verti.y > test.y) != (vertj.y > test.y) ) && ( test.x < ( vertj.x - verti.x ) * ( test.y - verti.y ) / ( vertj.y - verti.y ) + verti.x) ) c = !c; } return (c ? YES : NO); } - (void)testPoint { NSArray *polygonVertices = [NSArray arrayWithObjects: [NSValue valueWithCGPoint:CGPointMake(13.5, 41.5)], [NSValue valueWithCGPoint:CGPointMake(42.5, 56.5)], [NSValue valueWithCGPoint:CGPointMake(39.5, 69.5)], [NSValue valueWithCGPoint:CGPointMake(42.5, 84.5)], [NSValue valueWithCGPoint:CGPointMake(13.5, 100.0)], [NSValue valueWithCGPoint:CGPointMake(6.0, 70.5)], nil ]; CGPoint tappedPoint = CGPointMake(23.0, 70.0); if ([self isPointInPolygon:polygonVertices point:tappedPoint]) { NSLog(@"YES"); } else { NSLog(@"NO"); } }
没有什么比对问题的概括定义更美了。 为了完整起见,您在序言中有一个版本,该版本可能还会阐明ray cast背后的观点: 算法
基于http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html中的简单性算法仿真 app
一些辅助谓词: ide
exor(A,B):- \+A,B;A,\+B. in_range(Coordinate,CA,CB) :- exor((CA>Coordinate),(CB>Coordinate)). inside(false). inside(_,[_|[]]). inside(X:Y, [X1:Y1,X2:Y2|R]) :- in_range(Y,Y1,Y2), X > ( ((X2-X1)*(Y-Y1))/(Y2-Y1) + X1),toggle_ray, inside(X:Y, [X2:Y2|R]); inside(X:Y, [X2:Y2|R]). get_line(_,_,[]). get_line([XA:YA,XB:YB],[X1:Y1,X2:Y2|R]):- [XA:YA,XB:YB]=[X1:Y1,X2:Y2]; get_line([XA:YA,XB:YB],[X2:Y2|R]).
给定2个点A和B(Line(A,B))的直线方程为: 测试
(YB-YA) Y - YA = ------- * (X - XA) (XB-YB)
重要的是,将直线的旋转方向设置为边界的顺时针方向,将孔设置为逆时针方向。 咱们将检查点(X,Y),即测试点是否在直线的左半平面上(这是一种品味问题,它也能够是右侧,也能够是边界方向)在这种状况下,必须更改直线),这是将光线从该点投射到右侧(或左侧)并确认与直线的交点。 咱们选择在水平方向上投射光线(一样,这是一个问题,也能够在垂直方向上进行相似的限制),所以咱们具备: spa
(XB-XA) X < ------- * (Y - YA) + XA (YB-YA)
如今咱们须要知道该点是否仅在线段的左侧(或右侧),而不是整个平面,所以咱们只须要将搜索限制在该线段便可,但这很容易,由于它位于线段内直线中只有一个点能够在垂直轴上高于Y。 因为这是一个更严格的限制,所以须要首先进行检查,所以咱们仅首先选择知足此要求的那些行,而后再检查其位置。 根据约旦曲线定理,任何投射到多边形的光线都必须以偶数条线相交。 这样就完成了,咱们将射线向右投射,而后每次与直线相交时,切换其状态。 可是,在咱们的实施中,咱们要检查符合给定限制的一揽子解决方案的长度,并肯定其内在条件。 对于多边形中的每条线,都必须这样作。 code
is_left_half_plane(_,[],[],_). is_left_half_plane(X:Y,[XA:YA,XB:YB], [[X1:Y1,X2:Y2]|R], Test) :- [XA:YA, XB:YB] = [X1:Y1, X2:Y2], call(Test, X , (((XB - XA) * (Y - YA)) / (YB - YA) + XA)); is_left_half_plane(X:Y, [XA:YA, XB:YB], R, Test). in_y_range_at_poly(Y,[XA:YA,XB:YB],Polygon) :- get_line([XA:YA,XB:YB],Polygon), in_range(Y,YA,YB). all_in_range(Coordinate,Polygon,Lines) :- aggregate(bag(Line), in_y_range_at_poly(Coordinate,Line,Polygon), Lines). traverses_ray(X:Y, Lines, Count) :- aggregate(bag(Line), is_left_half_plane(X:Y, Line, Lines, <), IntersectingLines), length(IntersectingLines, Count). % This is the entry point predicate inside_poly(X:Y,Polygon,Answer) :- all_in_range(Y,Polygon,Lines), traverses_ray(X:Y, Lines, Count), (1 is mod(Count,2)->Answer=inside;Answer=outside).
这是nirg给出的答案的C#版本,该答案来自该RPI教授 。 请注意,使用该RPI来源中的代码须要归因。 htm
边界复选框已添加到顶部。 可是,正如詹姆斯·布朗指出的那样,主代码几乎与边界框检查自己同样快,所以,若是您要检查的大多数点都在边界框内,边界框检查实际上会减慢总体操做的速度。 。 所以,您能够将边界框检出,或者另外一种选择是,若是多边形的边界框不太频繁地改变形状,则能够对其进行预先计算。
public bool IsPointInPolygon( Point p, Point[] polygon ) { double minX = polygon[ 0 ].X; double maxX = polygon[ 0 ].X; double minY = polygon[ 0 ].Y; double maxY = polygon[ 0 ].Y; for ( int i = 1 ; i < polygon.Length ; i++ ) { Point q = polygon[ i ]; minX = Math.Min( q.X, minX ); maxX = Math.Max( q.X, maxX ); minY = Math.Min( q.Y, minY ); maxY = Math.Max( q.Y, maxY ); } if ( p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY ) { return false; } // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html bool inside = false; for ( int i = 0, j = polygon.Length - 1 ; i < polygon.Length ; j = i++ ) { if ( ( polygon[ i ].Y > p.Y ) != ( polygon[ j ].Y > p.Y ) && p.X < ( polygon[ j ].X - polygon[ i ].X ) * ( p.Y - polygon[ i ].Y ) / ( polygon[ j ].Y - polygon[ i ].Y ) + polygon[ i ].X ) { inside = !inside; } } return inside; }
就像Nirg发布并由bobobobo编辑的解决方案同样。 我只是使它对javascript友好,而且使我更易读:
function insidePoly(poly, pointx, pointy) { var i, j; var inside = false; for (i = 0, j = poly.length - 1; i < poly.length; j = i++) { if(((poly[i].y > pointy) != (poly[j].y > pointy)) && (pointx < (poly[j].x-poly[i].x) * (pointy-poly[i].y) / (poly[j].y-poly[i].y) + poly[i].x) ) inside = !inside; } return inside; }