向量幾何在遊戲編程中的使用3-2-D邊界碰撞檢測

<3>2-D邊界碰撞檢測
-Twinsen編寫

-本人水平有限,疏忽錯誤在所難免,還請各位數學高手、編程高手不吝賜教
-我的Email-address: [email protected]

一、使用向量進行障礙檢測的原理

上次說了使用向量模擬任意角度的反彈,這次談談它的前提---障礙碰撞。

在遊戲中進行障礙碰撞檢測,基本思路是這樣的:給定一個障礙範圍,判斷物體在這次移動後會不會進入這個範圍,如果會,就發生碰撞,否則不發生碰撞。在實際操作中,是用物體的邊界來判斷還是其他部位判斷完全取決於編程者。這時候,就可以從這個部位沿着速度的方向引出一條速度向量線,判斷一下這條線段(從檢測部位到速度向量終點)和障礙邊界線有沒有交點,如果有,這個交點就是碰撞點。


上面物體A,在通過速度向量移動之後將到達B位置。但是,這次移動將不會順利進行,因爲我們發現,碰撞發生了。碰撞點就在那個紅色區域中,也就是速度向量和邊界線的交點。 我們接下來的工作就是要計算這個交點,這是一個解線性方程組的過程,那麼我們將要用到一樣工具...


二、一個解線性方程組的有力工具---克蘭姆(Cramer)法則

首先要說明一下的是,這個法則是有侷限性的,它必須在一個線性方程組的係數行列式非零的時候才能夠使用。別緊張,我會好好談談它們的。首先讓我來敘述一下這個法則(我會試着讓你感覺到這不是一堂數學課):

如果線性方程組:

A11*X1 + A12*X2 + ... + A1n*Xn = b1
A21*X1 + A22*X2 + ... + A2n*Xn = b2
...................................
An1*X1 + An2*X2 + ... + Ann*Xn = bn

的係數矩陣 A =
__               __
| A11 A12 ... A1n |
| A21 A22 ... A2n |
| ............... |
| An1 An2 ... Ann |
--               -- 

的行列式 |A| != 0 
線性方程組有解,且解是唯一的,並且解可以表示爲:

X1 = d1/d , X2 = d2/d , ... , Xn = dn/d (這就是/A/=d爲什麼不能爲零的原因)

這裏d就是行列式/A/的值,dn(n=1,2,3...)是用線性方程組的常數項b1,b2,...,bn替換系數矩陣中的第n列的值得到的矩陣的行列式的值,即:

     | b1 A12 ... A1n |
d1 = | b2 A22 ... A2n |
     | .............. |
     | bn An2 ... Ann |

     | A11 b1 ... A1n |
d2 = | A21 b2 ... A2n |
     | .............. |
     | An1 bn ... Ann | 

...

     | A11 A12 ... b1 |
dn = | A21 A22 ... b2 |
     | .............. |
     | An1 An2 ... bn |

別去點擊關閉窗口按鈕!我現在就舉個例子,由於我們現在暫時只討論2-D遊戲(3-D以後會循序漸進的談到),就來個2-D線性方程組:

(1) 4.0*X1 + 2.0*X2 = 5.0
(2) 3.0*X1 + 3.0*X2 = 6.0

這裏有兩個方程,兩個未知量,則根據上面的Cramer法則:

    | 4.0 2.0 |
d = | 3.0 3.0 | = 4.0*3.0 - 2.0*3.0 = 6.0 (2階行列式的解法,'/'對角線相乘減去'/'對角線相乘)

     | 5.0 2.0 |
d1 = | 6.0 3.0 | = 5.0*3.0 - 2.0*6.0 = 3.0

     | 4.0 5.0 |
d2 = | 3.0 6.0 | = 4.0*6.0 - 5.0*3.0 = 9.0

則 

X1 = d1/d = 3.0/6.0 = 0.5
X2 = d2/d = 9.0/6.0 = 1.5   

好了,現在就得到了方程組的唯一一組解。 
是不是已經掌握了用Cramer法則解2-D線性方程組了?如果是的話,我們繼續。


三、深入研究

這裏的2-D障礙碰撞檢測的實質就是判斷兩條線段是否有交點,注意不是直線,是線段,兩直線有交點不一定直線上的線段也有交點。現在我們從向量的角度,寫出兩條線段的方程。


現在有v1v2兩條線段,則根據向量加法:

v1e = v1b + s*v1
v2e
 = v2b + t*v2

v1b
v2b分別是兩線段的一端。s,t是兩個參數,它們的範圍是[0.0,1.0],當s,t=0.0時,v1e=v1b,v2e=v2b;當s,t=1.0時,v1ev2e分別是兩線段的另一端。s,t取遍[0.0,1.0]則v1ev2e取遍兩線段的每一點。

那麼我們要判斷v1v2有沒有交點,就讓v1e=v2e,看解出的s,t是不是在範圍內就可以了:


v1e = v2e
=> v1b + s*v1 = v2b + t*v2
=> s*v1 - t*v2 = v2b - v1b
寫成分量形式:

s*x_v1 - t*x_v2 = x_v2b - x_v1b
s*y_v1 - t*y_v2 = y_v2b - y_v1b


現在是兩個方程式,兩個未知數,則根據Cramer法則:

    | x_v1 -x_v2 |   | 4.0 -2.0 |
d = | y_v1 -y_v2 | = | 1.0 -3.0 | = -10.0

     | x_v2b-x_v1b -x_v2 |   | 5.0 -2.0 |
d1 = | y_v2b-y_v1b -y_v2 | = | 2.0 -3.0 | = -11.0           

s = d1/d = -11.0/-10.0 = 1.1 > 1.0

現在s已經計算出來,沒有在[0.0,1.0]內,所以兩線段沒有交點,從圖上看很直觀。t沒有必要再計算了。所以是物體與障礙沒有發生碰撞。如果計算出的s,t都在[0.0,1.0]內,則把它們帶入原方程組,計算出v1e或者v2e,它的分量就是碰撞點的分量。

四、理論上的東西已經夠多的了,開始寫程序

我現在要寫一個用於處理障礙碰撞檢測的函數,爲了測試它,我還準備安排一些障礙:


這是一個凸多邊形,我讓一個質點在初始位置(10,8),然後給它一個隨機速度,這個隨機速度的兩個分速度在區間[1.0,4.0]內,同時檢測是否與邊界發生碰撞。當碰撞發生時,就讓它回到初始位置,重新給一個隨機速度。

// 首先我要記下凸多邊形的邊界座標
float poly[2][8] = { 
{ 6.0f , 2.0f , 4.0f , 8.0f , 14.0f , 18.0f , 14.0f , 6.0f } , // 所有點的x分量,最後一個點和第一個點重合
{ 2.0f , 6.0f , 10.0f , 14.0f , 12.0f , 8.0f , 4.0f , 2.0f } // 所有點的y分量
} ;
// 定義一些變量
float x,y ; // 這是質點的位置變量
float vx , vy ; // 質點的速度向量分量

// 好,開始編寫碰撞檢測函數
bool CollisionTest() { // 當發生碰撞時返回true,否則返回false

float s , t ; // 線段方程的兩個參數
// 各個參量
float x_v1 , x_v2 , y_v1 , y_v2 ; 
float x_v2b , x_v1b , y_v2b , y_v1b ; 

for( int i = 0 ; i < 8-1 ; ++i ) { // 循環到倒數第二個點

// 障礙線段
x_v1 = poly[0][i+1]-poly[0][i] ;
y_v1 = poly[1][i+1]-poly[1][i] ; 
// 物體速度向量
x_v2 = vx ;
y_v2 = vy ;
// 障礙向量初始點
x_v1b = poly[0][i] ;
y_v1b = poly[1][i] ;
// 物體位置
x_v2b = x ;
y_v2b = y ;
// 計算d,d1和d2
//    | x_v1 -x_v2 |   
//d = | y_v1 -y_v2 | 
//     | x_v2b-x_v1b -x_v2 |
//d1 = | y_v2b-y_v1b -y_v2 |
//     | x_v1 x_v2b-x_v1b |
//d2 = | y_v1 y_v2b-y_v1b |

d = (x_v1*(-y_v2))-((-x_v2)*y_v1) ;
d1 = ((x_v2b-x_v1b)*(-y_v2))-((-x_v2)*(y_v2b-y_v1b)) ;
d2 = (x_v1*(y_v2b-y_v1b))-((x_v2b-x_v1b)*y_v1) ;

// 判斷d是否爲零
if( abs(d) < 0.001f ) // 如果等於零做近似處理,abs()用於求絕對值
d = 0.001f ; 

// 計算參量s,t
s = d1/d ;
t = d2/d ;
// 判斷是否發生碰撞
// 如果發生了就返回true
if( 0.0f <= s && 1.0f >= s && 0.0f <= t && 1.0f >= t ) 
return true ;

} // for( int i = 0 ; i < 8-1 ; ++i )

// 沒有發生碰撞,返回false
return false ;

} // end of function

// 現在對函數做測試
// 初始化質點
x = 10.0f , y = 8.0f ;
vx = vy = (float)(rand()%4+1) ; 

// 進入主循環中
// 假設現在已經在主循環中 
if( CollisionTest() ) { // 如果物體與質點發生碰撞
x = 10.0f , y = 8.0f ;
vx = vy = (float)(rand()%4+1) ;
}
// 質點移動
x+=vx ;
y+=vy ;
現在你就可以結合上次的討論模擬一個完整的理想物理情景:一個物體在不規則障礙中移動、反彈,永不停息...除非...

至此爲止我們討論了2-D遊戲的障礙碰撞檢測以及它的編程實現,在此過程中涉及到了線性代數學的知識,以後隨着深入還會不斷的加入更多的數學、物理知識。下次我們繼續討論,BYE! 


發佈了7 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章