//解題思想用射線法
//該題思想是向由點P向x正方向發射一個射線,穿過多邊形線段上的個數爲奇數則在多邊形內,偶數則在多邊形外
//具體方法是:點的Y值大於等於多邊形上某個線段的最小值且小於該線段上的最大值,在該線段上取一個y值爲點P.y的點P1。如果P.x<P1.x //,則計數器加1,若計數器爲奇數則在多邊形內,若爲偶數則在多邊形外
private bool PointInFences(point pnt1, point[] fencePnts)
{
int j=0, cnt = 0;
for (int i = 0; i < fencePnts.Length; i++)
{
j = (i == fencePnts.Length - 1) ? 0 : j + 1;
if ((fencePnts[i].y!=fencePnts[j].y)&&(((pnt1.y
>= fencePnts[i].y) && (pnt1.y < fencePnts[j].y)) ||
((pnt1.y >= fencePnts[j].y) && (pnt1.y <
fencePnts[i].y))) && (pnt1.x < (fencePnts[j].x -
fencePnts[i].x) * (pnt1.y - fencePnts[i].y) / (fencePnts[j].y -
fencePnts[i].y) + fencePnts[i].x)) cnt++;
}
return (cnt%2>0)?true:false;
}
我的一哥們寫的比較精煉的一個小程序。原程序沒有考慮到點P與多邊形上的某個平行x軸的線段的兩個端點三點共線問題。我加了一個先決判斷條件就是到線段不平行於x軸。
該方法構思巧妙:
1、向X軸正方向發射射線,先判斷是否與多邊形的線段相交,若相交點的X值大於P的x值,則計數器加1.
2、通過大於等於
線段兩個端點的最小Y值,小於
線段兩個端點的最大Y值,判斷出射線與線段是否相交。——避免了P在線段的延長線上的情況對計算結果的困擾。
-----------------------------------------------------------------------------------------------------------------------------------------
12. 判斷點是否在多邊形中:
判斷點P是否在多邊形中是計算幾何中一個非常基本但是十分重要的算法。以點P爲端點,向左方作射線L,由於多邊形是有界的,所以射線L的左端一定在多
邊形外,考慮沿着L從無窮遠處開始自左向右移動,遇到和多邊形的第一個交點的時候,進入到了多邊形的內部,遇到第二個交點的時候,離開了多邊形,……所以
很容易看出當L和多邊形的交點數目C是奇數的時候,P在多邊形內,是偶數的話P在多邊形外。
但是有些特殊情況要加以考慮。如圖下圖(a)(b)(c)(d)所示。在圖(a)中,L和多邊形的頂點相交,這時候交點只能計算一個;在圖(b)
中,L和多邊形頂點的交點不應被計算;在圖(c)和(d)
中,L和多邊形的一條邊重合,這條邊應該被忽略不計。如果L和多邊形的一條邊重合,這條邊應該被忽略不計。
爲了統一起見,我們在計算射線L和多邊形的交點的時候,1。對於多邊形的水平邊不作考慮;2。對於多邊形的頂點和L相交的情況,如果該頂點是其所屬的
邊上縱座標較大的頂點,則計數,否則忽略;3。對於P在多邊形邊上的情形,直接可判斷P屬於多邊行。由此得出算法的僞代碼如下:
count ← 0;
以P爲端點,作從右向左的射線L;
for 多邊形的每條邊s
do if P在邊s上
then return true;
if s不是水平的
then if s的一個端點在L上
if 該端點是s兩端點中縱座標較大的端點
then count ← count+1
else if s和L相交
then count ← count+1;
if count mod 2 = 1
then return true;
else return false;
其中做射線L的方法是:設P'的縱座標和P相同,橫座標爲正無窮大(很大的一個正數),則P和P'就確定了射線L。
判斷點是否在多邊形中的這個算法的時間複雜度爲O(n)。
另外還有一種算法是用帶符號的三角形面積之和與多邊形面積進行比較,這種算法由於使用浮點數運算所以會帶來一定誤差,不推薦大家使用。
------------------------------------------------------------------------------------------------------------------------------------------ |
摘 要 本文提出一種新方法以檢測一個點是否在多邊形內。該方法將矢量和射線法結合,徹底解決了射線法所具有的奇異情況。實驗結果證明,該方法具有簡單、易實現、快速等優點。
1.引言
判斷點在多邊形內外是計算機圖形學的最基本算法,在計算機圖形處理、模式識別、CAD 及科學計算可視化中有着廣泛的應用。判斷點在多邊形內外的算法有主要有定向射線法、角度法。角度法要使用複雜的三角運算,計算量大;在工程上應用最多的是定向射線法,這種方法簡單、可靠,但其難以處理對邊界點及邊界與射線共線等特殊情況的處理。
2.基本概念
假設OA、OB是非零矢量,將OA繞點O旋轉到與OB方向相同的位置時,所形成的角稱爲有向角<AOB。規定逆時針旋轉爲正方向,順時針旋轉爲負方向。
設線段s的兩個頂點爲a和b,則s與直線l所形成的關係可劃分爲三大類:(a)s有且僅有一個頂點(a或者b)在l上,稱這種關係爲半跨越;(b)s的兩個頂點a和b分別在l的兩側,稱這種關係爲跨越;(c)s的兩個頂點a和b在l的同一側或線上,稱這種關係爲未跨越。
過點Q作x正方向的水平射線l。若線段s跨越(或半跨越)射線l且與點Q所成的有向角爲正,稱爲正向跨越(或正向半跨越),否則稱爲負向跨越(負向半跨越)。如圖1
, 線段P1P2、P3P4、P4P5、P6P7都屬於正向半跨越直線l,P2P3、P5P6屬於負向半跨越直線l,線段P7P8屬於負向跨越直線l,線段 P8P9、P0P1屬於未跨越直線l。設線段s與射線l交於點k,那麼線段s可以看作線段ak,kb組合而成(如圖1中的P7P8可看作由P7k、kP8 矢量和)。若將s與l的關係用f(s,l)的值來表示其權重,如下表示:
3.多邊形內外點判定算法
在本文中,假定多邊形P的頂點P0、P1、…,Pn(=P0)按逆時針方向存儲,同樣可將多邊形看作有n條有向線段Si,即 (i=1,…,n-1),頭尾相連而成。
定理1.過平面上任意一點Q作x正方向的水平射線l,若∑f(Si,l)=0 (其中i>=0,i<n) ,則點Q在多邊形P外。
證明:設射線l與多邊形P的Si 交點爲kj,且kj不屬於多邊形P的頂點,則將交點分別插入到Pi、Pi+1之間形成兩條有向線段Pikj、kjPi+1。將所有此類交點都添加進到頂點 序列中,形成一個新的多邊形P’,頂點序列爲P0、…、Pi、kj、Pi+1、…Pm、kk、Pm+1、 Pn(=P0),如圖2
(a)形成的頂點序列爲P0、P1、k0、P2、P3、P0。因此多邊形P’的有向線段與射線l的跨越情況全部轉換爲半跨越,如圖 2(b、c、d、e)四類情況。若將點Q與多邊形P’的各個頂點相連,組成一系列有向角。在多邊形P’中任選一個在射線l上的點ki作爲起始點,則每兩個 交點之間的所有有向線段所組成的有向角的代數和的關係如圖2(b、c、d、e)所示。顯然可以得出時,有向角的角度代數和爲0,所以點Q在多邊形P’外, 因P’與P同構,因此,點Q在多邊形P外。定理2.過平面上任意一點Q作x正方向的水平射線l,若 ∑f(Si,l)=0 (其中i>=0,i<n) 的值爲2或者-2,則點Q在多邊形P內。
證明過程與定理1證明類似,可以得出 值爲2或者-2時,有向角的角度代數和爲360或者-360度,從而可以得出點Q在多邊形P內。
算法的具體描述:
(1)獲得一個點Pi相對與點Q的位置,如果Pi的y座標值大於Q的y座標值,status=1,若小於,則status=-1,否則statue=0;用lastStatus記錄上一點的相對位置值。cnt表示f(si,l)的代數和。
2)temp = status – lastStatus;
if(temp > 0)
{
if(點Q在有向線段Pi-1Pi的右側)
cnt = cnt + temp;
else if (點Q在有向線段Pi-1Pi上)
return(“點Q在有向線段Pi-1Pi上”);
}
else if(temp < 0)
{
if(點Q在有向線段Pi-1Pi的左側)
cnt = cnt + temp;
else if (點Q在有向線段Pi-1Pi上)
return(“點Q在有向線段Pi-1Pi上”);
}
(3)循環(2),直到多邊形所有的頂點遍歷完畢,
if(cnt == 0)
return(“點在多邊形外”);
else return(“點在多邊形內”);
4.算法分析和小結
射線法的複雜度爲O(n), 本文算法的複雜度也爲O(n)。射線法中對每一條邊都要進行兩次以上的乘法運算,而本算法只對跨越或者半跨越射線的邊最多進行一次叉積運算;射線法需要對 奇異情況進行特殊處理,而本算法徹底解決了奇異情況的發生。在本文中,最壞的情況是所需的計算量爲(8次加減運算,2次乘法運算)*n。
-----------------------------------------------------------------------
vc知識庫
inline int
isLeft( CPoint *P0, CPoint *P1, CPoint *P2 )
{
return ( (P1->x - P0->x) * (P2->y - P0->y)
- (P2->x - P0->x) * (P1->y - P0->y) );
}
bool CPointInPolygonDlg::PointPolygonAlgorithm(CPoint *pt)
{
int wn = 0; // the winding number counter
std::vector<CPoint *>::iterator it;
// loop through all edges of the polygon
for (it=vPolygon.begin(); it<vPolygon.end()-1; it++)// edge from V[i] to V[i+1]
{
if ((*(it))->y <= pt->y) { // start y <= pt->y
if ((*(it+1))->y > pt->y) // an upward crossing
if (isLeft( *it, *(it+1), pt) > 0) // P left of edge
++wn; // have a valid up intersect
}
else { // start y > P.y (no test needed)
if ((*(it+1))->y <= pt->y) // a downward crossing
if (isLeft( *it, *(it+1), pt) < 0) // P right of edge
--wn; // have a valid down intersect
}
}
if (wn==0)
return false;
return true;
}