基礎知識填坑---矢量的叉積

在看到有人討論如何判斷線段相交的問題的時候,發現自己有矢量這塊基礎知識的缺失,因此寫篇筆記填個坑,以便記憶。


一、矢量的基本知識

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三點共線。
這一條判斷也可用來判斷點在線段或直線的哪一測。


二、判斷兩條直線是否相交

  第一個可能會想到的辦法,就是判斷斜率,這個在中學時代就學過了,不過斜率需要考慮垂直的特殊情況,比較麻煩。計算兩個向量的叉積或許是一個更好的辦法,如果兩個向量叉乘爲0,則是平行或者重合的,否則兩直線相交。這裏貼出來一個便於理解原理的代碼如下:

#include <opencv2\highgui\highgui.hpp>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;

 struct point
{
	int x;
	int y;
};
struct v
{
	point start;
	point end;
};
int crossProduct(v* v1, v* v2)
{
	v vt1, vt2;
	int result = 0;

	vt1.start.x = v1->start.x;
	vt1.start.y = v1->start.y;
	vt1.end.x = v1->end.x - v1->start.x;
	vt1.end.y = v1->end.y - v1->start.y;
	
	vt2.start.x = v2->start.x;
	vt2.start.y = v2->start.y;
	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;
}

int main()
{
	Point p1end(1, 2);
	Point p1start(0, 0);
	Point p2end(2, 1);
	Point p2start(0, 0);
	v pt1, pt2;
	pt1.end.x = p1end.x;
	pt1.end.y = p1end.y;
	pt1.start.x = p1start.x;
	pt1.start.y = p1start.y;
	pt2.end.x = p2end.x;
	pt2.end.y = p2end.y;
	pt2.start.x = p2start.x;
	pt2.start.y = p2start.y;
	cout << "CrossProduct: " << crossProduct(&pt1, &pt2) << endl;
	system("pause");
	return 0;
}

輸出結果爲: CrossProduct: -3


如圖所示,向量P1,P2的叉乘就是圖中平行四邊形的面積=3,負號就表示向量P1在P2的逆時針方向。


三、判斷兩線段相交
          經典方法,就是跨立試驗了,即如果一條線段跨過另一條線段,則線段的兩個端點分別在另一條線段的兩側。但是,還需要檢測邊界情況,即兩條線段中可能某條線段的某個端點正好落在另一條線段上。
程序模擬如下:

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的形式, 這樣一來上述過程的部分步驟可以合併, 縮短了代碼長度, 但是由於先要求出參數, 這種算法將花費更多的時間.


鄙人才疏學淺,尚有不足還望不惜賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章