1. 程式人生 > 其它 >【圖形學】向量的點乘和叉乘

【圖形學】向量的點乘和叉乘

向量(Vector)
在幾乎所有的幾何問題中,向量(有時也稱向量)是一個基本點。向量的定義包含方向和一個數(長度)。在二維空間中,一個向量可以用一對x和y來表示。例如由點(1,3)到(5,1的向量可以用(4,-2)來表示。這裡大家要特別注意,我這樣說並不代表向量定義了起點和終點。向量僅僅定義方向和長度。

向量加法
向量也支援各種數學運算。最簡單的就是加法。我們可以對兩個向量相加,得到的仍然是一個向量。我們有:
V1(x1, y1)+V2(x2, y2)=V3(x1+x2, y1+y2)
下圖表示了四個向量相加。注意就像普通的加法一樣,相加的次序對結果沒有影響(滿足交換律),減法也是一樣的。

點乘(Dot Product)


如果說加法是憑直覺就可以知道的,另外還有一些運算就不是那麼明顯的,比如點乘和叉乘。
點乘比較簡單,是相應元素的乘積的和:
V1( x1, y1) V2(x2, y2) = x1x2 + y1y2
注意結果不是一個向量,而是一個標量(Scalar)。點乘有什麼用呢,我們有:
A B = |A||B|Cos(θ)
θ是向量A和向量B見的夾角。這裡|A|我們稱為向量A的模(norm),也就是A的長度, 在二維空間中就是|A| = sqrt(x2+y2)。這樣我們就和容易計算兩條線的夾角: Cos(θ) = AB /(|A||B|)

當然你知道要用一下反餘弦函式acos()啦。(回憶一下cos(90)=0 和cos(0) = 1還是有好處的,希望你沒有忘記。)這可以告訴我們如果點乘的結果,簡稱點積,為0的話就表示這兩個向量垂直。當兩向量平行時,點積有最大值

另外,點乘運算不僅限於2維空間,他可以推廣到任意維空間。(譯註:不少人對量子力學中的高維空間無法理解,其實如果你不要試圖在視覺上想象高維空間,而僅僅把它看成三維空間在數學上的推廣,那麼就好理解了)

叉乘(cross product)
相對於點乘,叉乘可能更有用吧。2維空間中的叉乘是:
V1(x1, y1) X V2(x2, y2) = x1y2 – y1x2
看起來像個標量,事實上叉乘的結果是個向量,方向在z軸上。上述結果是它的模。在二維空間裡,讓我們暫時忽略它的方向,將結果看成一個向量,那麼這個結果類似於上述的點積,我們有:
A x B = |A||B|Sin(θ)
然而角度 θ和上面點乘的角度有一點點不同,他是有正負的,是指從A到B的角度。因此 ,向量的外積不遵守乘法交換率,因為向量a×向量b=-向量b×向量a在物理學中,已知力與力臂求外積,就是向量的外積,即叉乘。

向量c的方向與a,b所在的平面垂直,且方向要用“右手法則”判斷。判斷方法如下:

1.右手手掌張開,四指併攏,大拇指垂直於四指指向的方向;
2.伸出右手,四指彎曲,四指與A旋轉到B方向一致,那麼大拇指指向為C向量的方向。
在這裡插入圖片描述

另外還有一個有用的特徵那就是叉積的絕對值就是A和B為兩邊說形成的平行四邊形的面積。也就是AB所包圍三角形面積的兩倍。在計算面積時,我們要經常用到叉積。

在這裡插入圖片描述

(譯註:三維及以上的叉乘參看維基:http://en.wikipedia.org/wiki/Cross_product)

點-線距離
找出一個點和一條線間的距離是經常遇見的幾何問題之一。假設給出三個點,A,B和C,你想找出點C到點A、B定出的直線間距離。第一步是找出A到B的向量AB和A到C的向量AC,現在我們用該兩向量的叉積除以|AB|,這就是我們要找的的距離了(下圖中的紅線)。
d = (AB x AC)/|AB|

如果你有基礎的高中幾何知識,你就知道原因了。上一節我們知道(AB X AC)/2是三角形ABC的面積,這個三角形的底是|AB|,高就是C到AB的距離。有時叉積得到的是一個負值,這種情況下距離就是上述結果的絕對值。
當我們要找點到線段的距離時,情況變得稍稍複雜一些。這時線段與點的最短距離可能是點到線段的某一端點,而不是點到直線的垂線。例如上圖中點C到線段AB的最短距離應該是線段BC。我們有集中不同的方法來判斷這種特殊情況。第一種情況是計算點積AB B來判定兩線段間夾角。如果點積大於等於零,那麼表示AB到BC是在-90到90度間,也就是說C到AB的垂線在AB外,那麼AB上到C距離最近的點就是B。同樣,如果BAAC大於等於零,那麼點A就是距離C最近的點。如果兩者均小於零,那麼距離最近的點就線上段AB中的莫一點。

原始碼參考如下:

 //Compute the dot product AB   BC
 int dot(int[] A, int[] B, int[] C){
     AB = new int[2];
     BC = new int[2];
     AB[0] = B[0]-A[0];
     AB[1] = B[1]-A[1];
     BC[0] = C[0]-B[0];
     BC[1] = C[1]-B[1];
     int dot = AB[0] * BC[0] + AB[1] * BC[1];
     return dot;
 }
 //Compute the cross product AB x AC
 int cross(int[] A, int[] B, int[] C){
     AB = new int[2];
     AC = new int[2];
     AB[0] = B[0]-A[0];
     AB[1] = B[1]-A[1];
     AC[0] = C[0]-A[0];
     AC[1] = C[1]-A[1];
     int cross = AB[0] * AC[1] - AB[1] * AC[0];
     return cross;
 }
 //Compute the distance from A to B
 double distance(int[] A, int[] B){
     int d1 = A[0] - B[0];
     int d2 = A[1] - B[1];
     return sqrt(d1*d1+d2*d2);
 }
 //Compute the distance from AB to C
 //if isSegment is true, AB is a segment, not a line.
 double linePointDist(int[] A, int[] B, int[] C, boolean isSegment){
     double dist = cross(A,B,C) / distance(A,B);
     if(isSegment){
         int dot1 = dot(A,B,C);
         if(dot1 > 0)return distance(B,C);
         int dot2 = dot(B,A,C);
         if(dot2 > 0)return distance(A,C);
     }
     return abs(dist);
 }

上面的程式碼看起來似乎是很繁瑣。不過我們可以看看在C++和C#中,採用了運算子過載的類point,用‘*’代表點乘,用’^‘代表叉乘(當然’+’’-'還是你所希望的),那麼看起來就簡單些,程式碼如下:

   //Compute the distance from AB to C
     //if isSegment is true, AB is a segment, not a line.
     double linePointDist(point A, point B, point C, bool isSegment){
         double dist = ((B-A)^(C-A)) / sqrt((B-A)*(B-A));
         if(isSegment){
             int dot1 = (C-B)*(B-A);
             if(dot1 > 0)return sqrt((B-C)*(B-C));
             int dot2 = (C-A)*(A-B);
             if(dot2 > 0)return sqrt((A-C)*(A-C));
         }
         return abs(dist);
     }