我正在嘗試在多邊形算法內部建立一個快速的 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; }