1. 程式人生 > >判斷兩條線段/直線相交,並求交點

判斷兩條線段/直線相交,並求交點

一.向量基本知識     因為後面的計算需要一些向量的基本知識,這裡只是簡單的列舉如下,如果需要更加詳細的資訊,可以自行搜尋wikipedia或google。 1.向量的概念:如果一條線段的端點是有次序之分的,我們把這種線段成為有向線段(directed segment)。如果有向線段p1p2的起點p1在座標原點,我們可以把它稱為向量(vector)p2。 2.向量加減法:設二維向量P = ( x1, y1 ),Q = ( x2 , y2 ),則向量加法定義為: P + Q = ( x1 + x2 , y1 + y2 ),同樣的,向量減法定義為: P - Q = ( x1 - x2 , y1 - y2 )。顯然有性質 P + Q = Q + P,P - Q = - ( Q - P )。 3.向量的叉積:計算向量叉積是與直線和線段相關演算法的核心部分。設向量P = ( x1, y1 ),Q = ( x2, y2 ),則向量叉積定義為由(0,0)、p1、p2和p1+p2所組成的平行四邊形的帶符號的面積,即: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共線,但可能同向也可能反向。 4.折線段的拐向判斷:折線段的拐向判斷方法可以直接由向量叉積的性質推出。對於有公共端點的線段p0p1和p1p2,通過計算(p2 - p0) × (p1 - p0)的符號便可以確定折線段的拐向:   若(p2 - p0) × (p1 - p0) > 0,則p0p1在p1點拐向右側後得到p1p2。   若(p2 - p0) × (p1 - p0) < 0,則p0p1在p1點拐向左側後得到p1p2。   若(p2 - p0) × (p1 - p0) = 0,則p0、p1、p2三點共線。 這一條判斷也可用來判斷點線上段或直線的哪一測。 為了後文的敘述方便,先定義幾個結構: struct point{     int x;     int y; }; struct v{      point start;      point end; };     計算兩條直線的叉積(cross production), 這裡由於定義的都是二維的情況,本質上說,在平面上兩個向量的叉積應該是垂直平面的,這裡函式返回的整數值即為z軸上的值: int crossProduct(v* v1, v* v2){      v vt1, vt2;     int result = 0;      vt1.start.x = 0;      vt1.start.y = 0;      vt1.end.x = v1->end.x - v1->start.x;      vt1.end.y = v1->end.y - v1->start.y;      vt2.start.x = 0;      vt2.start.y = 0;      vt2.end.x = v2->end.x - v2->start.x;      vt2.end.y = v2->end.y - v2->start.y;      result = vt1.end.x * vt2.end.y - vt2.end.x * vt1.end.y;     return result; } 二.判斷兩條直線相交     先來看一個簡單的情況,即判斷兩條直線是否相交。 第一個可能會想到的辦法,就是判斷斜率,這個在中學時代就學過了,不過斜率需要考慮垂直的特殊情況,比較麻煩。更好的辦法或許是計算兩個向量的叉積,如果為0,則是平行或者重合的,否則兩直線相交。 程式碼就不貼了,直接呼叫上面的函式就ok了。 三.判斷兩線段相交 經典方法,就是跨立試驗了,即如果一條線段跨過另一條線段,則線段的兩個端點分別在另一條線段的兩側。但是,還需要檢測邊界情況,即兩條線段中可能某條線段的某個端點正好落在另一條線段上。這也是演算法導論中介紹的演算法。 程式模擬如下: int direction(point* pi, point* pj, point* pk){      point p1, p2;      p1.x = pk->x - pi->x;      p1.y = pk->y - pi->y;      p2.x = pj->x - pi->x;      p2.y = pj->y - pi->y;     return crossProduct(&p1, &p2); } int onSegment(point* pi, point* pj, point* pk){     int minx, miny, maxx, maxy;     if (pi->x > pj->x){          minx = pj->x;          maxx = pi->x;         }     else{          minx = pi->x;          maxx = pj->x;      }     if (pi->y > pj->y){          miny = pj->y;          maxy = pi->y;         }     else{          miny = pi->y;          maxy = pj->y;      }     if (minx <= pk->x && pk->x <= maxx && miny <= pk->y && pk->y <= maxy)         return 1;     else         return 0; } int segmentIntersect(point* p1, point* p2, point* p3, point* p4){     int d1 = direction(p3, p4, p1);     int d2 = direction(p3, p4, p2);     int d3 = direction(p1, p2, p3);     int d4 = direction(p1, p2, p4);     if (d1 * d2 < 0 && d3 * d4 < 0)         return 1;     else if (!d1 && onSegment(p3, p4, p1))         return 1;     else if (!d2 && onSegment(p3, p4, p2))         return 1;     else if (!d3 && onSegment(p1, p2, p3))         return 1;     else if (!d4 && onSegment(p1, p2, p4))         return 1;     else         return 0; } 實際上,如果想改進上述演算法,還可以在跨立試驗前加一步,就是先做快速排斥試驗。那就是,先分別判斷以兩條線段為對角線的矩形是否相交,如果不相交,則兩個線段肯定不相交。 四.判斷兩條線段相交,然後計算交點 設一條線段為L0=P1P2, 另一條線段或直線為L1=Q1Q2, 要計算的就是L0和L1的交點。 1.首先判斷L0和L1是否相交(方法已在前文討論過), 如果不相交則沒有交點, 否則說明L0和L1一定有交點, 下面就將L0和L1都看作直線來考慮.  2.如果P1和P2橫座標相同, 即L0平行於Y軸  a)若L1也平行於Y軸      i.若P1的縱座標和Q1的縱座標相同, 說明L0和L1共線, 假如L1是直線的話他們有無窮的交點, 假如L1是線段的話可用"計算兩條共線線段的交點"的演算法求他們的交點(該方法在前文已討論過);     ii.否則說明L0和L1平行, 他們沒有交點;  b)若L1不平行於Y軸, 則交點橫座標為P1的橫座標, 代入到L1的直線方程中可以計算出交點縱座標;  3.如果P1和P2橫座標不同, 但是Q1和Q2橫座標相同, 即L1平行於Y軸, 則交點橫座標為Q1的橫座標, 代入到L0的直線方程中可以計算出交點縱座標;  4.如果P1和P2縱座標相同, 即L0平行於X軸 a)若L1也平行於X軸,     i.若P1的橫座標和Q1的橫座標相同, 說明L0和L1共線, 假如L1是直線的話他們有無窮的交點, 假如L1是線段的話可用"計算兩條共線線段的交點"的演算法求他們的交點(該方法在前文已討論過);     ii.否則說明L0和L1平行, 他們沒有交點;  b)若L1不平行於X軸, 則交點縱座標為P1的縱座標, 代入到L1的直線方程中可以計算出交點橫座標;  5.如果P1和P2縱座標不同, 但是Q1和Q2縱座標相同, 即L1平行於X軸, 則交點縱座標為Q1的縱座標, 代入到L0的直線方程中可以計算出交點橫座標;  6.剩下的情況就是L1和L0的斜率均存在且不為0的情況  a)計算出L0的斜率K0, L1的斜率K1; b)如果K1 = K2      i.如果Q1在L0上, 則說明L0和L1共線, 假如L1是直線的話有無窮交點, 假如L1是線段的話可用"計算兩條共線線段的交點"的演算法求他們的交點(該方法在前文已討論過);     ii.如果Q1不在L0上, 則說明L0和L1平行, 他們沒有交點. c)聯立兩直線的方程組可以解出交點來 這個演算法並不複雜, 但是要分情況討論清楚, 尤其是當兩條線段共線的情況需要單獨考慮, 所以在前文將求兩條共線線段的演算法單獨寫出來. 另外, 一開始就先利用向量叉乘判斷線段與線段(或直線)是否相交, 如果結果是相交, 那麼在後面就可以將線段全部看作直線來考慮. 需要注意的是, 我們可以將直線或線段方程改寫為ax+by+c=0的形式, 這樣一來上述過程的部分步驟可以合併, 縮短了程式碼長度, 但是由於先要求出引數, 這種演算法將花費更多的時間.