判斷兩個線段是否相交


我們的問題是這樣的:給定一條線段的起點為$A_1$、終點為$A_2$,另一條線段的起點為$B_1$、終點為$B_2$,問線段$A_1A_2$和線段$B_1B_2$是否相交?

我們首先解釋一下,兩條線段相交的概念是指,存在一個點,這個點同時在兩條線段上。

方法一(解方程法):

容易知道,線段$A_1A_2$上的點的集合為$A = A_1 * (1 - r_1) + A_2 * r_1$,其中$r_1 \in [0, 1]$;同理,線段$B_1B_2$上的點的集合為$B = B_1 * (1 - r_2) + B_2 * r_2$,其中$r_2 \in [0, 1]$。一般的,如果不對$r_1$(或$r_2$)的范圍作約束,得到的點集構成該線段所在的直線。那么這個問題就簡單了,我們可以首先將兩條線段延展成直線,求出兩條直線的交點,根據交點位置可以分別求出$r_1$和$r_2$,然后判斷$r_1$和$r_2$是否同時在$[0, 1]$區間即可。我們把$r_1$($r_2$)叫做點$A$($B$)在線段$A_1A_2$(線段$B_1B_2$)上的比例。

兩直線的交點滿足$A_1 * (1 - r_1) + A_2 * r_1 = B_1 * (1 - r_2) + B_2 * r_2$,稍微變形可得$(A_2 - A_1) * r_1 - (B_2 - B_1) * r_2 = B_1 - A_1$,即$$\overrightarrow{A_1A_2} * r_1 - \overrightarrow{B_1B_2} * r_2 = \overrightarrow{A_1B_1}$$,表示為向量矩陣的形式為(令$\overrightarrow{r} = (r_1, r_2)^T$):$$[\overrightarrow{A_1A_2}, - \overrightarrow{B_1B_2}] * \overrightarrow{r} = \overrightarrow{A_1B_1}$$

我們的目的是根據以上二元方程組求出$r_1$和$r_2$,但是在此之前,我們要判斷一些特殊情況:

(1). 如果$\overrightarrow{A_1A_2} = \overrightarrow{0}$,即線段$A_1A_2$是一個點,這時,等式退化為$- \overrightarrow{B_1B_2} * r_2 = \overrightarrow{A_1B_1}$,兩個方程一個未知量,如果有解,則點$A_1$(或$A_2$)在線段$B_1B_2$上,即兩“線段”相交;否則,不相交。

(2). 如果$\overrightarrow{B_1B_2} = \overrightarrow{0}$,即線段$B_1B_2$是一個點,這時,等式退化為$\overrightarrow{A_1A_2} * r_1 = \overrightarrow{A_1B_1}$,兩個方程一個未知量,如果有解,則點$B_1$(或$B_2$)在線段$A_1A_2$上,即兩“線段”相交;否則,不相交。

(3). 除以上兩種情況以外,如果兩直線平行即$\overrightarrow{A_1A_2} // \overrightarrow{B_1B_2}$,這時要分兩種情況,如果兩線段平行且有平行距離,則不相交;否則,兩線段在同一直線上,這時又分兩種情況,兩線段是否有重合部分,如果有則相交,如果沒有則不相交。至於如何判斷在同一直線上的兩條線段是否有重合部分,可以利用快速排斥實驗,后面還會講到。

(4). 除以上三種情況外,矩陣$[\overrightarrow{A_1A_2}, - \overrightarrow{B_1B_2}]$可逆,我們可以直接求出$r_1$和$r_2$,然后判斷$r_1$和$r_2$是否同時在$[0, 1]$區間即可。

方法二(外積法):

 可以看到,解方程法需要判斷幾種特殊情況,而且涉及到除法運算,在計算機實現時對調用頻率比價高、效率要求比較高的場景也不夠“完美”。下面介紹一種基於向量外積的方法,判斷邏輯更簡潔,而且也不涉及除法操作。

向量外積:

首先簡單介紹一下向量外積。向量外積,也叫向量叉乘。與內積不同的是,向量外積的結果還是一個向量,它的模為兩個向量圍成的平行四邊形的面積,方向與平行四邊形所在的平面垂直,指向由右手螺旋定則確定。寫成公式為:

$$\left | \overrightarrow{a} \times \overrightarrow{b} \right | = \left | \overrightarrow{a} \right | \bullet \left | \overrightarrow{b} \right | \bullet sin \theta $$

其中,$\theta$為兩個向量的夾角。

如果知道兩個向量的坐標$\overrightarrow{a} = (a_x, a_y, a_z)$、$\overrightarrow{b} = (b_x, b_y, b_z)$,則兩個向量的外積的坐標運算為:

$$\overrightarrow{a} \times \overrightarrow{b} = (a_yb_z - a_zb_y) \overrightarrow{i} + (a_zb_x - a_xb_z)\overrightarrow{j} + (a_xb_y - a_yb_x)\overrightarrow{k}$$

為了便於記憶,還可以寫為三階行列式的形式:

$$\overrightarrow{a} \times \overrightarrow{b} = det \left | \begin{array}{cc} \overrightarrow{i} & \overrightarrow{j} & \overrightarrow{k} \\ a_x & a_y & a_z \\ b_x & b_y & b_z \end{array} \right |$$

利用外積判斷點與線段的相對位置:

假設有向線段為$\overrightarrow{AB}$,點為$C$,首先計算外積$\overrightarrow{AC} \times \overrightarrow{AB} = (\overrightarrow{AC}_x \cdot \overrightarrow{AB}_y - \overrightarrow{AC}_y \cdot \overrightarrow{AB}_x)\overrightarrow{k}$ (因為有$\overrightarrow{AB}_z = 0 $、$\overrightarrow{AC}_z = 0 $)。根據右手螺旋定則,如果$\overrightarrow{k}$的系數為正數,說明點C在線段AB的右側;如果為負數,說明點C在線段AB的左側;如果為0,說明點C在線段AB所在的直線上。寫成偽代碼為:

  //*************************************************************************
  // \brief: 計算兩個向量的外積(叉乘)。可以根據結果的符號判斷三個點的位置關系。
  // \Param: Point A 兩個向量的公共起點。
  // \Param: Point B 第一個向量的終點。
  // \Param: Point C 第二個向量的終點。
  // \Returns: double 向量AC與向量AB的外積。如果結果為正數,表明點C在直線AB(直線方向為從A到B)的右側;
  // 如果結果為負數,表明點C在直線AB(直線方向為從A到B)的左側;如果結果為0,表明點C在直線AB上。
 //*************************************************************************

double cross(Point A, Point B, Point C) {
    double cross1 = (C.x - A.x) * (B.y - A.y); double cross2 = (C.y - A.y) * (B.x - A.x); return (cross1 - cross2); }

線段相交判斷:

回到一開始的問題,要判斷線段$A_1A_2$和線段$B_1B_2$是否相交,首先計算:

T1 = cross(A1, A2, B1);
T2 = cross(A1, A2, B2);
T3 = cross(B1, B2, A1);
T4 = cross(B1, B2, A2);

根據以上結果即可判斷兩條線段的相交關系:

(1). 如果(T1 * T2) > 0) || (T3 * T4) > 0,說明一條線段的兩個端點在另一條線段的同側,這兩條線段肯定不相交。

(2). 如果T1 == 0 && T2 == 0,說明兩條線段共線,是否相交還需要進一步判斷。這時可以通過判斷兩條線段張成的矩形是否相交來判斷,而兩個矩形是否相交可以通過快速排斥實驗來判斷。快速排斥實驗稍后介紹。

(3). 其他情況,兩個線段一定相交。

可以看到,這種方法不需要對線段的起終點重合(線段退化為一個點)做特殊判斷,也不需要對線段平行(除了共線的情況)做特殊判斷。純幾何方法,邏輯更簡潔。

對第3種情況補充說明如下:除了第1、2種情況外(T1、T2不同號、且不同為0),T1、T2的取值還有6種情況:(+, 0)、(+, -)、(-, 0)、(-, +)、(0, +)、(0, -)。當T1、T2為(+, 0)時,T3、T4的取值只可能是(-, 0)、(-, +)、(0, +),無論哪種情況,兩線段都相交。當T1、T2為(+, -)時,T3、T4的取值只可能是(-, 0)、(-, +)、(0, +),無論哪種情況,兩線段都相交;當T1、T2為(-, 0)或(-, +)時,情況與前兩個類似。當T1、T2為(0, +)時,T3、T4的取值只可能是(0, -)、(+, -)、(+, 0),無論哪種情況,兩線段都相交;當T1、T2為(0, -)時,與前一種情況類似。

 

補充:快速排斥實驗

快速排斥實驗用於判斷兩個矩形是否相交,因為是比較簡單、也比較基礎的方法,在這里就不詳細介紹了,對原理感興趣的可以參考http://blog.sina.com.cn/s/blog_71dbfe2e0101f7zb.html 一文。直接給出偽代碼實現:

//*************************************************************************
// \brief: 	快速排斥實驗,判斷兩個線段張成的矩形區域是否相交。
// \Param: 	Point S1 第一條線段的起點。
// \Param: 	Point E1 第一條線段的終點。
// \Param: 	Point S2 第二條線段的起點。
// \Param: 	Point E2 第二條線段的終點。
// \Returns: 	bool 兩個線段張成的矩形區域是否相交。具有對稱性,即交換兩條線段(參數S1與S2交換、E1與E2交換),結果不變。
//*************************************************************************
bool rectsIntersect(Point S1, Point E1, Point S2, Point E2) {
  if ( Gmin(S1.y, E1.y) <= Gmax(S2.y, E2.y) && Gmax(S1.y, E1.y) >= Gmin(S2.y, E2.y) && Gmin(S1.x, E1.x) <= Gmax(S2.x, E2.x) && Gmax(S1.x, E1.x) >= Gmin(S2.x, E2.x)) { return true; } return false; }

因此,判斷兩條線段相交的偽代碼為:

bool segmentsIntersect(Point A1, Point A2, Point B1, Point B2) {
    double T1 = cross(A1, A2, B1);
    double T2 = cross(A1, A2, B2);
double T3 = cross(B1, B2, A1);
double T4 = cross(B1, B2, A2);
  if (((T1 * T2) > 0) || ((T3 * T4) > 0)) { // 一條線段的兩個端點在另一條線段的同側,不相交。(可能需要額外處理以防止乘法溢出,視具體情況而定。) return false; } else if(T1 == 0 && T2 == 0) { // 兩條線段共線,利用快速排斥實驗進一步判斷。此時必有 T3 == 0 && T4 == 0。 return rectsIntersect(A1, A2, B1, B2); } else { // 其它情況,兩條線段相交。 return true;
} }

此文完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM