向量幾何在遊戲編程中的使用4 - 2-D物體間的碰撞響應

<4>2-D物體間的碰撞響應
-Twinsen編寫 

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

 

這次我要分析兩個球體之間的碰撞響應,這樣我們就可以結合以前的知識來編寫一款最基本的2-D檯球遊戲了,雖然粗糙了點,但卻是個很好的開始,對嗎?

一、初步分析

中學時候上物理課能夠認真聽講的人(我?哦,不包括我)應該很熟悉的記得:當兩個球體在一個理想環境下相撞之後,它們的總動量保持不變,它們的總機械能也守恆。但這個理想環境是什麼樣的呢?理想環境會不會影響遊戲的真實性?對於前者我們做出在碰撞過程中理想環境的假設:

1)首先我們要排除兩個碰撞球相互作用之外的力,也就是假設沒有外力作用於碰撞系統。
2)假設碰撞系統與外界沒有能量交換。
3)兩個球體相互作用的時間極短,且相互作用的內力很大。

有了這樣的假設,我們就可以使用動量守恆和動能守恆定律來處理它們之間的速度關係了,因爲1)確保沒有外力參與,碰撞系統內部動量守恆,我們就可以使用動量守恆定律。2)保證了我們的碰撞系統的總能量不會改變,我們就可以使用動能守恆定律。3)兩球發生完全彈性碰撞,不會粘在一起,沒有動量、能量損失。

而對於剛纔的第二個問題,我的回答是不會,經驗告訴我們,理想環境的模擬看起來也是很真實的。除非你是在進行科學研究,否則完全可以這樣理想的去模擬。


現在,我們可以通過方程來觀察碰撞前後兩球的速度關係。當兩球球心移動方向共線(1-D處理)時的速度,或不共線(2-D處理)時共線方向的速度分量滿足:

(1)m1 * v1 + m2 * v2 = m1 * v1' + m2 * v2' (動量守恆定律)
(2)1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * v1'^2 + 1/2 * m2 * v2'^2 (動能守恆定律)

這裏m1和m2是兩球的質量,是給定的,v1和v2是兩球的初速度也是我們已知的,v1'和v2'是兩球的末速度,是我們要求的。好,現在我們要推導出v1'和v2'的表達式:

由(1),得到v1' = (m1 * v1 + m2 * v2 - m2 * v2') / m1,代入(2),得
1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * (m1 * v1 + m2 * v2 - m2 * v2')^2 + 1/2 * m2 * v2'^2

=> v2' = (2 * m2 * v1 + v2 * (m1 - m2)) / (m1 + m2)

=> v1' = (2 * m1 * v2 + v1 * (m1 - m2)) / (m1 + m2)

我們現在得到的公式可以用於處理當兩球球心移動方向共線(1-D處理)時的速度關係,或者不共線(2-D處理)時共線方向的速度分量的關係。不管是前者還是後者,我們都需要把它們的速度分解到同一個軸上才能應用上述公式進行處理。

二、深入分析

首先我要說明一件事情:當兩球碰撞時,它們的速度可以分解爲球心連線方向的分速度和碰撞點切線方向的分速度。而由於它們之間相互作用的力只是在切點上,也就是球心連線方向上,因此我們只用處理這個方向上的力。而在切線方向上,它們不存在相互作用的力,而且在理想環境下也沒有外力,因此這個方向上的力在碰撞前後都不變,因此不處理。好,知道了這件事情之後,我們就知道該如何把兩球的速度分解到同一個軸上進行處理。


現在看上面的分析圖,s和t是我們根據兩個相碰球m1和m2的位置建立的輔助軸,我們一會就將把速度投影到它們上面。v1v2分別是m1和m2的初速度,v1'v2'是它們碰撞後的末速度,也就是我們要求的。s'是兩球球心的位置向量,t'是它的逆時針正交向量。s1s'的單位向量,t1t'的單位向量。

我們的思路是這樣的:首先我們假設兩球已經相碰(在程序中可以通過計算兩球球心之間的距離來判斷)。接下來我們計算一下s't',注意s't'的方向正反無所謂(一會將解釋),現在設m1球心爲(m1x, m1y),m2球心爲(m2x, m2y),則s'爲(m1x-m2x, m1y-m2y),t'爲(m2y-m1y, m1x-m2x)(第一篇的知識)。

則設
sM = sqrt((m1x-m2x)^2+(m1y-m2y)^2),
tM = sqrt((m2y-m1y)^2+(m1x-m2x)^2),有

s1 
= ((m1x-m2x)/sM, (m1y-m2y)/sM) = (s1x, s1y)
t1 = ((m2y-m1y)/tM, (m1x-m2x)/tM) = (t1x, t1y)

現在s和t軸的單位向量已經求出了,我們根據向量點乘的幾何意義,計算v1v2s1t1方向上的投影值,然後將s軸上投影值代
入公式來計算s方向碰撞後的速度。注意,根據剛纔的說明,t方向的速度不計算,因爲沒有相互作用的力,因此,t方向的分速度不變。
所以我們要做的就是:把v1投影到s和t方向上,再把v2投影到s和t方向上,用公式分別計算v1v2在s方向上的投影的末速度,然後把得到的末速度在和原來v1v2在t方向上的投影速度再合成,從而算出v1'v2'。好,我們接着這個思路做下去:

先算v1(v1x, v1y)在s和t軸的投影值,分別設爲v1s和v1t:

v1s = v1.s1
=> v1s = v1x * s1x + v1y * s1y 

v1t = v1.t1
=> v1t = v1x * t1x + v1y * t1y

再算v2(v2x, v2y)在s和t軸的投影值,分別設爲v2s和v2t:

v2s = v2.s1
=> v2s = v2x * s1x + v2y * s1y

v2t = v2.t1
=> v2t = v2x * t1x + v2y * t1y

接下來用公式

v1' = (2 * m1 * v2 + v1 * (m1 - m2)) / (m1 + m2)
 
v2' = (2 * m2 * v1 + v2 * (m1 - m2)) / (m1 + m2) 

計算v1s和v2s的末值v1s'和v2s',重申v1t和v2t不改變:

假設m1 = m2 = 1

v1s' = (2 * 1 * v2s + v1s * (1 - 1)) / (1 + 1)
v2s' = (2 * 1 * v1s + v2s * (1 - 1)) / (1 + 1)

=> v1s' = v2s
=> v2s' = v1s

好,下一步,將v1s'和v1t再合成得到v1',將v2s'和v2t再合成得到v2',我們用向量和來做:

首先求出v1t和v2t在t軸的向量v1t'v2t'(將數值變爲向量)

v1t' = v1t * t1 = (v1t * t1x, v1t * t1y)
v2t' = v2t * t1 = (v2t * t1x, v2t * t1y)

再求出v1s'和v2s'在s軸的向量v1s'v2s'(將數值變爲向量)

v1s'
= v1s' * s1 = (v1s' * s1x, v1s' * s1y) 
v2s'
= v2s' * s1 = (v2s' * s2x, v2s' * s2y)

最後,合成,得

v1' = v1t' + v1s' = (v1t * t1x + v1s' * s1x, v1t * t1y + v1s' * s1y)
v2' v2t' + v2s' = (v2t * t1x + v2s' * s2x, v2t * t1y + v2s' * s2y)

從而就求出了v1'v2'。下面解釋爲什麼說s't'的方向正反無所謂:不論我們在計算s'時使用m1的球心座標減去m2的球心座標還是相反的相減順序,由於兩球的初速度的向量必有一個和s1是夾角大於90度小於270度的,而另外一個與s1的夾角在0度和90度之間或者說在270度到360度之間,則根據向量點積的定義|a|*|b|*cosA,計算的到的兩個投影值一個爲負另一個爲正,也就是說,速度方向相反,這樣就可以用上面的公式區求得末速度了。同時,求出的末速度也是方向相反的,從而在轉換爲v1s'v2s'時也是正確的方向。同樣的,求t'既可以是用s'逆時針90度得到也可以是順時針90度得到。

三、編寫代碼

按照慣例,該編寫代碼了,其實編寫的代碼和上面的推導過程極爲相似。但爲了完整,我還是打算寫出來。

// 用於球體碰撞響應的函數,其中v1a和v2a爲兩球的初速度向量,
// v1f和v2f是兩球的末速度向量。
// m1和m2是兩球的位置向量
// s'的分量爲(sx, sy),t'的分量爲(tx, ty)
// s1是s的單位向量,分量爲(s1x, s1y)
// t1是t的單位向量,分量爲(t1x, t1y)

void Ball_Collision(v1a, v2a, &v1f, &v2f, m1, m2){

// 求出s'
double sx = m1.x - m2.x ; 
double sy = m1.y - m2.y ;

// 求出s1
double s1x = sx / sqrt(sx*sx + sy*sy) ;
double s1y = sy / sqrt(sx*sx + sy*sy) ;

// 求出t'
double tx = -sy ;
double ty = sx ;

// 求出t1
double t1x = tx / sqrt(tx*tx + ty*ty) ;
double t1y = ty / sqrt(tx*tx + ty*ty) ;

// 求v1a在s1上的投影v1s
double v1s = v1a.x * s1x + v1a.y * s1y ;

// 求v1a在t1上的投影v1t
double v1t = v1a.x * t1x + v1a.y * t1y ;

// 求v2a在s1上的投影v2s
double v2s = v2a.x * s1x + v2a.y * s1y ;

// 求v2a在t1上的投影v2t
double v2t = v2a.x * t1x + v2a.y * t1y ;

// 用公式求出v1sf和v2sf
double v1sf = v2s ;
double v2sf = v1s ;

// 最後一步,注意這裏我們簡化一下,直接將v1sf,v1t和v2sf,v2t投影到x,y軸上,也就是v1'和v2'在x,y軸上的分量
// 先將v1sf和v1t轉化爲向量 

double nsx = v1sf * s1x ;
double nsy = v1sf * s1y ;
double ntx = v1t * t1x ;
double nty = v1t * t1y ;

// 投影到x軸和y軸
// x軸單位向量爲(1,0),y軸爲(0,1)
// v1f.x = 1.0 * (nsx * 1.0 + nsy * 0.0) ;
// v1f.y = 1.0 * (nsx * 0.0 + nsy * 1.0) ;
// v1f.x+= 1.0 * (ntx * 1.0 + nty * 0.0) ;
// v1f.y+= 1.0 * (ntx * 0.0 + nty * 1.0) ;

v1f.x = nsx + ntx ;
v1f.y = nsy + nty ;

// 然後將v2sf和v2t轉化爲向量 

nsx = v2sf * s1x ;
nsy = v2sf * s1y ;
ntx = v2t * t1x ;
nty = v2t * t1y ;

// 投影到x軸和y軸
// x軸單位向量爲(1,0),y軸爲(0,1)
// v2f.x = 1.0 * (nsx * 1.0 + nsy * 0.0) ;
// v2f.y = 1.0 * (nsx * 0.0 + nsy * 1.0) ;
// v2f.x+= 1.0 * (ntx * 1.0 + nty * 0.0) ;
// v2f.y+= 1.0 * (ntx * 0.0 + nty * 1.0) ;

v2f.x = nsx + ntx ;
v2f.y = nsy + nty ;

}// end of function

呼~~是不是感覺有點亂阿?不管怎麼樣,我有這種感覺。但我們確實完成了它。希望你能夠理解這個計算的過程,你完全可以依照這個過程自己編寫更高效的代碼,讓它看上去更清楚:)至此位置,我們已經掌握了編寫一個檯球遊戲的基本知識了,Let's make it!

事實上,一切纔剛剛起步,我們還有很多沒有解決的問題,比如旋轉問題,擊球的角度問題等等,你還會深入的研究一下,對嗎?一旦你有了目標,堅持下去,保持激情,總會有成功的一天:)這次就到這裏,下次我們接着研究,Bye for now~~


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