下面這個函數在我寫的計算幾何庫函數里面有,那個庫可以在http://algorithm.126.com/的資源中心 - 代碼角 找到。
算法簡單說明:
首先判斷以兩條線段為對角線的矩形是否相交,如果不相交兩條線段肯定也不相交。
(所謂以a1b2為對角錢的矩形就是以兩邊長為|a1.x – b2.x|和|a1.y – b2.y|以及a1b2為對角線的矩形)。
如果相交的話,利用矢量叉乘判斷兩條線段是否相互跨越,如果相互跨越顯然就相交,反之則不相交。算法不難,但是一些特殊情況需要考慮到,比如兩條相段共線且在斷點處相交。下面的代碼經過測試了,應該沒有bug,如果你真的發現了bug請告訴我:)
/******************************************************** * * * 返回(P1-P0)*(P2-P0)的叉積。 *
* 若結果為正,則<P0,P1>在<P0,P2>的順時針方向; * * 若為0則<P0,P1><P0,P2>共線; *
* 若為負則<P0,P1>在<P0,P2>的在逆時針方向; * * 可以根據這個函數確定兩條線段在交點處的轉向, *
* 比如確定p0p1和p1p2在p1處是左轉還是右轉,只要 *
* 求(p2-p0)*(p1-p0),若<0則左轉,>0則右轉,=0則 *
* 共線 * * *
\********************************************************/
float multiply(TPoint p1,TPoint p2,TPoint p0) {
return((p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y)); }
//確定兩條線段是否相交
int intersect(TLineSeg u,TLineSeg v) {
return( (max(u.a.x,u.b.x)>=min(v.a.x,v.b.x))&&
(max(v.a.x,v.b.x)>=min(u.a.x,u.b.x))&&
(max(u.a.y,u.b.y)>=min(v.a.y,v.b.y))&&
(max(v.a.y,v.b.y)>=min(u.a.y,u.b.y))&& (以兩線段為對角線的矩形是否相交) (multiply(v.a,u.b,u.a)*multiply(u.b,v.b,u.a)>=0)&&
(multiply(u.a,v.b,v.a)*multiply(v.b,u.b,v.a)>=0));(是否相互跨立) }
忘記了說明TPoint和TLineSeg的定義了:
struct TPoint { float x,y; };
struct TLineSeg { TPoint a,b; };
上面的算法避免了除法運算,所以不會出現計算誤差
NOwcan兄的方法雖然簡單,但是求兩條直線的交點需要用到除法,當兩條線段相交但是很接近平行的時候,會有精度上的誤差,所以我的方法不用除法更好一點。這是計算幾何中的經典算法
計算幾何常用算法(一共23個)
1. 矢量減法
設二維矢量 P = (x1,y1) ,Q = (x2,y2)
則矢量減法定義為: P - Q = ( x1 - x2 , y1 - y2 ) 顯然有性質 P - Q = - ( Q - P )
如不加說明,下面所有的點都看作矢量,兩點的減法就是矢量相減;
2.矢量叉積
設矢量P = (x1,y1) ,Q = (x2,y2)
則矢量叉積定義為: P × Q = x1*y2 - x2*y1 得到的是一個標量
顯然有性質 P × Q = - ( Q × P ) P × ( - Q ) = - ( P × Q ) 如不加說明,下面所有的點都看作矢量,點的乘法看作矢量叉積; 叉乘的重要性質:
> 若 P × Q > 0 , 則P 在Q的順時針方向 > 若 P × Q < 0 , 則P 在Q的逆時針方向
> 若 P × Q = 0 , 則P 與Q共線,但可能同向也可能反向
3.判斷點在線段上
設點為Q,線段為P1P2 ,判斷點Q在該線段上的依據是: ( Q - P1 ) × ( P2 - P1 ) = 0 => 共線 且 Q 在以 P1,P2為對角頂點的矩形內
4.判斷兩線段是否相交
我們分兩步確定兩條線段是否相交: (1). 快速排斥試驗
設以線段 P1P2 為對角線的矩形為R, 設以線段 Q1Q2 為對角線的矩形為T,如果R和T不相交,顯然兩線段不會相交; (2). 跨立試驗
如果兩線段相交,則兩線段必然相互跨立對方,如圖1所示。在圖1中,P1P2跨立Q1Q2 ,則矢量 ( P1 - Q1 ) 和
( P2 - Q1 )位於矢量( Q2 - Q1 ) 的兩側,即
( P1 - Q1 ) × ( Q2 - Q1 ) * ( P2 - Q1 ) × ( Q2 - Q1 ) < 0 上式可改寫成
( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) > 0 當 ( P1 - Q1 ) × ( Q2 - Q1 ) = 0 時,說明 ( P1 - Q1 ) 和 ( Q2 - Q1 )共線,但是因為已經通過快速排斥試驗,所以 P1 一定在線段 Q1Q2上;同理,( Q2 - Q1 ) ×( P2 - Q1 ) = 0 說明 P2 一定在線段 Q1Q2上。 所以判斷P1P2跨立Q1Q2的依據是:
( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) ≥ 0 同理判斷Q1Q2跨立P1P2的依據是:
( Q1 - P1 ) × ( P2 - P1 ) * ( P2 - P1 ) × ( Q2 - P1 ) ≥ 0 至此已經完全解決判斷線段是否相交的問題。
5.判斷線段和直線是否相交
如果線段 P1P2和直線Q1Q2相交,則P1P2跨立Q1Q2,即:
( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) ≥ 0
6.判斷矩形是否包含點
只要判斷該點的橫坐標和縱坐標是否夾在矩形的左右邊和上下邊之間。 判斷線段、折線、多邊形是否在矩形中
因為矩形是個凸集,所以只要判斷所有端點是否都在矩形中就可以了。
7.判斷矩形是否在矩形中
只要比較左右邊界和上下邊界就可以了。(左右邊界相互包含且上下邊界相互包含)
8.判斷圓是否在矩形中
圓在矩形中的充要條件是:圓心在矩形中且圓的半徑小於等於圓心到矩形四邊的距離的最小值。
9.判斷點是否在多邊形中
以點P為端點,向左方作射線L,由於多邊形是有界的,所以射線L的左端一定在多邊形外,考慮沿着L從無窮遠處開始自左向右移動,遇到和多邊形的第一個交點的時候,進入到了多邊形的內部,遇到第二個交點的時候,離開了多邊形,……所以很容易看出當L和多邊形的交點數目C是奇數的時候,P在多邊形內,是偶數的話P在多邊形外。
但是有些特殊情況要加以考慮。如果L和多邊形的頂點相交,有些情況下交點只能計算一個,有些情況下交點不應被計算(你自己畫個圖就明白了);如果L和多邊形的一條邊重合,這條邊應該被忽略不計。為了統一起見,我們在計算射線L和多邊形的交點的時候,1。對於多邊形的水平邊不作考慮;2。對於多邊形的頂點和L相交的情況,如果該頂點是其所屬的邊上縱坐標較大的頂點,則計數,否則忽略;3。對於P在多邊形邊上的情形,直接可判斷P屬於多邊行。由此得出算法的偽代碼如下:
1. count ← 0;
2. 以P為端點,作從右向左的射線L;
3, for 多邊形的每條邊s
4. do if P在邊s上
5. then return true;
6. if s不是水平的
7. then if s的一個端點在L上且該端點是s兩端點中縱坐標較大的端點
9. then count ← count+1
10. else if s和L相交
11. then count ← count+1;
12. if count mod 2 = 1
13. then return true
14. else return false;
其中做射線L的方法是:設P'的縱坐標和P相同,橫坐標為正無窮大(很大的一個正數),則P和P'就確定了射線L。這個算法的復雜度為O(n)。
10.判斷線段是否在多邊形內
線段在多邊形內的一個必要條件是線段的兩個端點都在多邊形內;
如果線段和多邊形的某條邊內交(兩線段內交是指兩線段相交且交點不在兩線段的端點),因為多邊形的邊的左右兩側分屬多邊形內外不同部分,所以線段一定會有一部分在多邊形外。於是我們得到線段在多邊形內的第二個必要條件:線段和多邊形的所有邊都不內交;
線段和多邊形交於線段的兩端點並不會影響線段是否在多邊形內;但是如果多邊形的某個頂點和線段相交,還必須判斷兩相鄰交點之間的線段是否包含與多邊形內部。因此我們可以先求出所有和線段相交的多邊形的頂點,然后按照X-Y坐標排序,這樣相鄰的兩個點就是在線段上相鄰的兩交點,如果任意相鄰兩點的中點也在多邊形內,則該線段一定在多邊形內。證明如下: 命題1:
如果線段和多邊形的兩相鄰交點P1 ,P2的中點P' 也在多邊形內,則P1, P2之間的所有點都在多邊形內。 證明:
假設P1,P2之間含有不在多邊形內的點,不妨設該點為Q,在P1, P'之間,因為多邊形是閉合曲線,所以其內外部之間有界,而P1屬於多邊行內部,Q屬於多邊性外部,P'屬於多邊性內部,P1-Q-P'完全連續,所以P1Q和QP'一定跨越多邊形的邊界,因此在P1,P'之間至少還有兩個該線段和多邊形的交點,這和P1P2是相鄰兩交點矛盾,故命題成立。證畢 由命題1直接可得出推論: 推論2:
設多邊形和線段PQ的交點依次為P1,P2,……Pn,其中Pi和Pi+1是相鄰兩交點,線段PQ在多邊形內的充要條件是:P,Q在多邊形內且對於i =1, 2,……, n-1,Pi ,Pi+1的中點也在多邊形內。
在實際編程中,沒有必要計算所有的交點,首先應判斷線段和多邊形的邊是否內交,倘若線段和多邊形的某條邊內交則線段一定在多邊形外;如果線段和多邊形的每一條邊都不內交,則線段和多邊形的交點一定是線段的端點或者多邊形的頂點,只要判斷點是否在線段上就可以了。 至此我們得出算法如下:
1. if 線端PQ的端點不都在多邊形內
2. then return false;
3. 點集pointSet初始化為空;
4. for 多邊形的每條邊s
5. do if 線段的某個端點在s上
6. then 將該端點加入pointSet;
7. else if s的某個端點在線段PQ上
8. then 將該端點加入pointSet;
9. else if s和線段PQ相交 // 這時候可以肯定是內交
10. then return false;
11. 將pointSet中的點按照X-Y坐標排序,X坐標小的排在前面,對於X坐標相同的點,Y坐標小的排在前面;
12. for pointSet中每兩個相鄰點 pointSet[i] , pointSet[ i+1]
13. do if pointSet[i] , pointSet[ i+1] 的中點不在多邊形中
14. then return false;
15. return true;
這個算法的復雜度也是O(n)。其中的排序因為交點數目肯定遠小於多邊形的頂點數目n,所以最多是常數級的復雜度,幾乎可以忽略不計。
11.判斷折線在多邊形內
只要判斷折線的每條線段是否都在多邊形內即可。設折線有m條線段,多邊形有n個頂點,則復雜度為O(m*n)。
12.判斷多邊形是否在多邊形內
只要判斷多邊形的每條邊是否都在多邊形內即可。判斷一個有m個頂點的多邊形是否在一個有n個頂點的多邊形內復雜度為O(m*n)。
13.判斷矩形是否在多邊形內
將矩形轉化為多邊形,然后再判斷是否在多邊形內。
14.判斷圓是否在多邊形內
只要計算圓心到多邊形的每條邊的最短距離,如果該距離大於等於圓半徑則該圓在多邊形內。計算圓心到多邊形每條邊最短距離的算法在后文闡述。
15.判斷點是否在圓內
計算圓心到該點的距離,如果小於等於半徑則該點在圓內。
16.判斷線段、折線、矩形、多邊形是否在圓內
因為圓是凸集,所以只要判斷是否每個頂點都在圓內即可。
17.判斷圓是否在圓內
設兩圓為O1,O2,半徑分別為r1, r2,要判斷O2是否在O1內。先比較r1,r2的大小,如果r1<r2則O2不可能在O1內;