碰撞檢測在3D游戲中至關重要,好的碰撞檢測要求人物在場景中可以平滑移動,遇到一定高度內的台階可以自動上去,而過高的台階則把人擋住,遇到斜率較小的斜坡可以上去,斜率過大則把人擋住,在各種前進方向被擋住的情況下都要盡可能地讓人物沿合理的方向滑動而不是被迫停下。在滿足這些要求的同時還要做到足夠精確和穩定,防止人物在特殊情況下穿牆而掉出場景。
碰撞檢測做得好了是應該的,不易被人注意到,因為這符合我們日常生活中的常識。做得差了卻很容易讓人發現,人物經常被卡住不能前進或者人物穿越了障礙。所以大部分人都覺得寫碰撞檢測代碼是件吃力不討好的事情,算法復雜、容易出bug、不容易出彩。下面還是回到正題,看看我們該如何解決這個難題。
早期3D游戲的碰撞檢測多數基於格子或者BSP樹,基於格子的系統實現簡單但精度不夠,不屬於嚴格意義的3D碰撞檢測。基於BSP樹的碰撞檢測一度十分流行,算法基本已經成熟定型,但它的固有缺點卻使它不太適合現在的游戲。BSP樹需要很長的預處理時間不適合加載時計算,BSP划分經常會產生原多邊形數三到四倍的多邊形,考慮到不用保存法線、顏色、uv等信息也要增加將近一倍的資源容量,在一個大的游戲中將模型資源的容量從200M增加到400M相信是大部分人都不願接受的。目前對於任意復雜三角形集合(mesh)的碰撞檢測多數基於BVTree(bounding volume tree),具體可以是aabb tree,obb tree或者K-dop tree,這也是當今各種物理引擎和碰撞檢測引擎流行的做法。
上面是碰撞檢測按數據結構不同的分類,按檢測方式又可以分為離散點的碰撞檢測和連續碰撞檢測(CCD continuous collisiondetection)。離散點的碰撞檢測是指定某一時刻T的兩個靜態碰撞體,看它們之間是否交迭,如果沒有交迭則返回它們最近點的距離,如果交迭則返回交迭深度,交迭方向等。連續碰撞檢測則是分別指定在T1、T2兩個時刻兩個碰撞體的位置,看它們在由T1運動到T2時刻的過程中是否發生碰撞,如果碰撞則返回第一碰撞點的位置和法線。連續碰撞檢測是最為自然的碰撞檢測,可以大大方便碰撞響應邏輯的編寫,可以很容易避免物體發生交迭或者穿越。離散點的碰撞檢測則沒有那么友好,當檢測到碰撞時兩個物體已經發生了交迭,如果其中有三角形網格對象那么已經有許多三角形發生了交迭,如何將兩個交迭的對象分開並按合理的方式運動是一個挑戰。雖然連續碰撞檢測是最自然的方式,但它的實現非常復雜,運算開銷也很大,所以目前大部分成熟的物理引擎和碰撞檢測引擎還是采用了基於離散點的碰撞檢測,為了避免物體交迭過深或者彼此穿越,它們都要采用比較小的模擬步長。
由於碰撞檢測引擎的復雜性和對效率的高要求,我們應該盡量使用目前成熟的完整引擎,而不是自己去開發。經過評估,我決定采用Opcode碰撞檢測引擎來做游戲中人物和場景的碰撞檢測。Opcode的主要功能是用aabb tree管理復雜三角形集合來和射線、球體,立方體,另一個三角形集合等進行離散點上的碰撞檢測,如果檢測到交迭則返回所有發生交迭的三角形。Opcode的特點是高度的內存使用優化和極好的效率,ODE物理引擎底層就采用它來做復雜三角形mesh的碰撞檢測,Opcode的作者也是NovodeX(PhysX前身)物理引擎的核心開發人員,據說NovodeX采用了Opcode的一個更優化版本。由此可見Opcode的成熟與效率。
確定了要使用的引擎,下面要討論的算法就和具體引擎無關了,適合於任何離散點的碰撞檢測引擎。我們用AABB包圍盒來代表場景中的人物,看看如何實現文章開頭所提出的效果。
首先解釋一下檢測地面的方式,沿人物包圍盒的四條豎邊向下投四條射線,射線的終點略低於人物的腳底(也就是說射線的長度是有限的),如果與場景發生碰撞並且碰撞平面的斜率小於某一值則取得碰撞點的高度,否則視為這條射線沒有檢測到地面。將所有射線檢測到的地面高度最大值作為最后的地面高度,如果四條射線都沒有檢測到地面則認為人物懸空。
vD = 當前幀人物位移
p0 = 人物包圍盒中心當前位置
bOnGroundP1; // 人物是否站在地面
bOnGroundP3; // 人物是否站在地面
bOnGround; // 人物是否站在地面
p1 = p0 + vD
在p1位置檢測地面
if( 檢測到地面 )
{
將包圍盒下放到地面得到位置p2
bOnGroundP1 = true;
}
else
{
p2 = p1;
bOnGroundP1 = false;
}
測試p2點的包圍盒是否與場景交迭
if( 交迭 )
{
取得所有交迭三角形的法線,將它們相加后取平均值,得到法線normal
將法線與向上的向量叉乘得到切線方向tangent
// 計算人物沿切線滑動后的位置,注意這里用p0做計算。
// 如果要使滑動更平滑可以把p0向法線方向推出一些
// p3 =p0 + normal * 0.1f + vD.Dot(tangent);
p3 = p0 +vD.Dot(tangent);
在p3位置檢測地面
if( 檢測到地面 )
{
將包圍盒下放到地面得到位置p4
bOnGroundP3 = true;
}
Else
{
p4 = p3;
bOnGroundP3 = false;
}
測試p4點的包圍盒是否與場景交迭
if( 交迭 )
{
測試p1點的包圍盒是否與場景交迭
if( 交迭 )
{
// 無法得到合理的移動位置,人物位置保持不變
// 等待下一幀玩家調整前進方向再做測試
將p0作為人物的新位置
bOnGround = true;
return;
}
else
{
將p1作為人物的新位置
bOnGround = bOnGroundP1;
return;
}
}
Else
{
將p4作為人物的新位置
bOnGround = bOnGroundP3;
return;
}
}
else
{
將p2作為人物的新位置
bOnGround = bOnGroundP1;
return;
}
上面的算法基本達到了文章開頭所提到的效果,在某些復雜情況下人物移動還有些不流暢,但沒有發現人物有穿越障礙物的現象。在大部分情況下人物的新坐標都會由p2點返回,最壞情況是人物被卡住返回p0點。在P4 3.0G的機器上可以支持120個人物在最壞情況下保持30幀的游戲幀數。
在使用廣義碰撞階段迅速排除了大量物體以后,將會使用精確到多邊形級別的精確碰撞,比如兩個凸包之間的碰撞,凸包和面片之間的碰撞,以及2次曲面和多邊形面片的碰撞,在游戲中常用的兩次曲面有,橢圓體,圓柱體,膠囊,球體等等。對於兩個凸包之間的碰撞算法目前比較流行的是SAT,分離軸測試算法可以靜態和動態的計算出兩個凸包之間的碰撞時間法向量等等。但是對於面數較多的凸包以及2次曲面卻不大適合,此時一般使用GJK算法但是GJK算法本身強壯性的實現問題一直是一個較難的問題。在我的一項使用BSP進行碰撞檢測的實驗中,人物以膠囊來模擬,房屋內部通過非SOLID 的LEAFY BSP來構造,在使用BSP剔除了大量面片以后,遇到這樣一個問題:如何在最后篩選下的三角形面片進行碰撞測試,以確定碰撞發生的時間,法向量等。
本文提出一種簡單,易懂,准確的方法用來確定一個以速度v前進的膠囊和一個凸多邊形第一次發生碰撞的時間。
首先一個膠囊是所有到某根線段的距離等於某個值r的點的集合:
如圖:虛線表示該線段這里以ab表示,r代表所有點到該線段的長度:
首先觀察靜態情況下的碰撞情況。當一個多邊形面片和膠囊碰撞的時候,實際上是該多邊形面片中存在一些點,這些點到線段ab的距離小於了r,這是因為在膠囊外部的點到線段ab的距離均大於r(膠囊是所有到某根線段的距離等於某個輸r的點的集合)。所以在兩個物體都靜止的情況下相交測試實際上是測試線段ab到多邊形的最短距離,如果該距離<R那么存在碰撞,否則不存在碰撞:
如圖這里以一個三角形為例子,左圖中該三角形的所有點到線段ab的距離均大於r所以和該膠囊不相交,而右圖中三角形的黑色區域中的點到線段ab的距離小於r所以該三角形和膠囊相交。
所以實際上只要計算一條線段到某個多邊形的距離,然后和r作比較就可以知道是否發生碰撞。而一條線段和一個多邊形的距離計算,需要分以下幾個步驟(以三角形為例)
A將線段ab的2個端點投影到三角形所在平面,並檢查投影點是否落在三角形內部,如果是的話將該投影距離作為一個候選值
B分別計算線段ab和三角形的三條邊的最短距離點對,並檢查該點對中的點是否落在線段中(ab線段和三角形的邊線段中)如果是的話將該點對的距離作為一個候選值。
C分別計算線段ab的兩個端點到三角形每條邊的投影(實際上是計算最近點對),並檢查該投影是否落在邊的線段中如果是的話作為一個候選值保存。
D分別計算三角形的3個頂點到線段ab上的投影,並檢查該投影是否落在線段ab中。如果是的話作為一個候選值保存。
E 分別計算三角形的3個頂點到線段ab的兩個頂點,把距離作為候選值保存。
這樣一來碰撞檢測就歸結為,點和線段,線段和線段,以及點和面的最短點對的計算了,
最后將上述的候選值進行比較,結果最小的那個就是三角形中到線段ab的距離。
上述方法非常容易推廣到動態的情況也就是:當膠囊以速度v運動時第一次和三角形發生碰撞的時間。問題歸結為在哪個時間T線段ab到三角形的距離等於半徑r,而這又歸結為上述A,B,C,D,E 5個子問題。如果能夠分別求出這5個子問題的時間,t1,t2,t3,t4,t5那么取這5個時間中的最小值就是膠囊和三角形發生碰撞的確切時間了。
下面以兩條直線,一條靜止,另外一條以速度v移動作為例子,來說明求得時間的過程。問題等同於:
給定一條靜止,另外一條以速度v移動的直線,求出在哪個時間T這兩條直線的距離等於半徑r。
對於兩條直線,假設直線的方程分別為:
L1:P1+r1*t;
L2:P2+r2*t;
現在架設直線L2以速度v={vx,vy,vz}移動;
根據兩條直線的距離公式
d=|P1P2 .(r1Xr2)| /|(r1Xr2)|
其中P1P2是向量代表 P2-P1,X代表叉積,.代表點積
由於r1,r2是常量不會隨着直線的移動而改變,這里令(r1Xr2)=r={x*,y*,z*}
P1={x1,y1,z1}不變,P2={x2+vx*t, y2+vy*t, z2+vz*t}其中t代表時間是個變量
帶入公式d=|P1P2 .(r1Xr2)| /|(r1Xr2)|可得
d(t)=x*(x2-x1)+y*(y2-y1)+z*(z2-z1)+(x*vx+y*vy+z*vz)t
令(x*vx+y*vy+z*vz)=a; x*(x2-x1)+y*(y2-y1)+z*(z2-z1)=b;
那么d(t) = at+b -----------------------------(1)
公式1就是兩條直線之間的距離隨着時間t變化的函數,這是一個帶符號的距離,兩邊平方可得 d^2(t)= (at+b)^2
這是一個兩次方程,假設膠囊半徑為r 那么只要求解方程(at+b)^2=r^2這個方程就可以求出子問題B的時間了,同時注意計算出時間t之后還需要計算出該時間兩條直線的線最近點對是否都處在兩條對應的線段上,如果是的話才是一個合理的時間否則就拋棄該時間。
通過同樣的推導可以分別求出其他子問題的時間取這些時間,取這些時間的最小值就是碰撞第一次發生的時間,當然在求解2次方程過程中要考慮到delta<或者=0的情況這些情況都有自己的物理意義,以上面兩條線段距離為例d^2(t)= (at+b)^2中如果a=0
那么方程恆等於b^2,考察a=0的物理意義,a=0也就是(x*vx+y*vy+z*vz)=0;其中x*,y*,z*是這兩條直線所組成的面的法向量
這表面移動速度平行於這兩條直線所組成的面。顯然距離是恆定不變的。
結論:
以上方法很容易推廣到凸多邊形,以及求出碰撞的法向量(根據最短時間是由上述A,BCDE中那種情況所引起的)。
在一般網游的室內環境檢測中,使用膠囊模擬人物已經足夠了,結合BSP的漂亮剪枝能力,可以做出比較滿意和快速的碰撞檢測,人物和其他物件的碰撞可以采用凸包比較或者GJK方法等。
周末一天沒事寫個游戲,好簡單的,里面的亮點是碰撞檢測代碼,是我在國外論壇上看到的一個算法,算是很強的了.下面我貼出來,然后講一下大致思路,關於游戲的代碼就不貼了,一大串的看了也心煩 ^^"
點擊瀏覽該文件
這個函數就是碰撞檢測的關鍵,我給它啟名叫 "描邊"
首先里面兩個參數,第一個pmc就是要描邊的mc,第二個num是要描的級數(等下會解釋到),我們先可以先看下里面的逐句解釋
function doFigure(pmc, num) {
//
為
pmc
建立一個數組
,
數組的大小是
num*2;
pmc.point = new Array(num*2);
//
從
pmc
的長和寬中取大的
,
然后除以
2
var mx = Math.max(pmc._width, pmc._height)/2;
//
把
360
℃
num
等分
var inc = 360/num;
//
定義兩個變量等下用
var n = 0;
var i = 0;
// while循環的次數為 i<num*2;while (i<(num*2)) {//從下面n+=inc知道 xs和ys就是每一個等分上x和y的所指的方向var xs = Math.cos((n*Math.PI)/180);var ys = Math.sin((n*Math.PI)/180);//以pmc的(x,y)點為定點,半徑為mx的一個圓的軌跡var x = pmc._x+(mx*xs);var y = pmc._y+(mx*ys);//如果x,y沒有接觸到pmc上,不斷循環while (!pmc.hitTest(x, y, true)) {// x,y分別減x -= xs;y -= ys;//如果都小於0,就break跳出循環if (x<0 && y<0) {break;}}//把此時的x和y,分別計入point數組中pmc.point[i] = x-pmc._x;pmc.point[i+1] = y-pmc._y;n += inc;i += 2;}}
|
他這么做到底什么意思呢?
其實point里面就是記錄了,一個圖形的邊緣的坐標.他是怎么做到的?
我們拋開所有從最里面的while循環講起吧.
while的條件是,當(x,y)點沒有接觸到pmc的時候,就不斷循環,循環的內容是 x-=xs,y-=ys;
如果我們前面什么也沒看,應該可以想象,一開始(x,y)點一定在pmc的右邊,然后不斷的減一個數值xs,ys,直到減到x,y碰到pmc為止,這樣,x,y就是pmc上的一點坐標了.
但是我們目前還不知道x和y是怎么定義的,還有就是xs和ys怎么來的
往上看
var xs = Math.cos((n*Math.PI)/180);
var ys = Math.sin((n*Math.PI)/180);
var x = pmc._x+(mx*xs);
var y = pmc._y+(mx*ys);
數學好的,一看就看出來了,這是圓的極坐標公式,就是以pmc的(x,y)為定點,mx為半徑的圓的軌跡.
看到這里,應該可以猜到,一定是讓一些點以圓的方式出現在mc的周圍,然后每個點往mc靠攏,知道每個點都靠到mc上面.
這樣我們只要解決這個圓的大小問題了,也就是mx的大小,實際上,你把mx定義成一個很大的數也沒問題,但是這樣做,會浪費很多,對於寫程序的來說,不必要的浪費是不行的^^
那么看上面的
var mx = Math.max(pmc._width, pmc._height)/2;
這里就定下了 mx的大小,也就是從mc的長和寬中找一個較大的出來,這樣可以保證畫出來的圓把整個mc都抱在了里面,大家看到除以2了吧,所以一定能想到 mc 的注冊點一定是在中心哦(這也是關鍵之一^^)
接着,和定義半徑大小一樣,我們做個360度每一度檢測一下,也是可以的,但是這樣做會很費資源,而且也沒有必要那么精細.
所以才會有一個描邊級數num,這個級數就是規定了,分num個等次來描,來記錄num個點的坐標.然后就是運用了
隨便畫個形狀,在這個mc中寫下面的代碼,場景上再建立一個mc,命名為mc2
你可以看到,你的描邊級數越高,每次檢測的就越多,所以盡量減少就可以了,像我游戲里面只定義了10.
其實這只是一個大概的數據,並不是百分百的描邊,不過這樣已經夠用了^^"
onClipEvent (load) {
numb = 100;
_root.doFigure(_name, numb);
}
onClipEvent (enterFrame) {
_root.hit = false;
i = 0;
while (i < (numb * 2)) {
x = points[i] + _x;
y = points[i + 1] + _y;
if (_root.mc2.hitTest(x, y, true)) {
_root.hit = true;
break;
}
i = i + 2;
}
}
onClipEvent (mouseDown) {
if (this.hitTest(_root._xmouse, _root._ymouse, true)) {
this.startDrag();
}
}
onClipEvent (mouseUp) {
this.stopDrag();
}
|
數學不好,解釋的不太清楚,大家不懂的再發貼問,我盡量解釋,當然也請高手幫忙完善^^!
轉】MSDN中關於OnDrawItem的說明
afx_msgvoidOnDrawItem(intnIDCtl,LPDRAWITEMSTRUCTlpDrawItemStruct);
Parameters
nIDCtl
存儲發送WM_DRAWITEM消息的控件ID,如果是菜單發送的,nIDCtl的值為0。
lpDrawItemStruct
一個指向DRAWITEMSTRUCT結構體的指針,該結構體保存有關要被繪制的項目與繪制所需要的類型等星系。
Remarks
當自繪按鈕(owner-draw button),下拉列表框(combo box),列表框(list box)視覺屬性,或者菜單發生變化時,框架為他們的owner調用該函數。
DRAWITEMSTRUCT結構的itemAction成員定義了要進行的繪制操作行為。該值確定了所需的繪制動作。
在處理完此消息之前,應用程序應當確保由DRAWITEMSTRUCT結構的成員hDC標識的設備上下文還原到默認狀態。
如果上面結構的成員hwndItem指向CButton,CMenu,CListBox或者CComboBox對象,那么就調用相應類的DrawItem虛函數。重載相應類的DrawItem成員函數來繪制各個項。
其他的一些說明:
OnPaint()這個函數是在已經有形的控件上進行畫圖的
OnPaint()
{
在這里只是畫原控件沒有的圖形
}
OnDrawItem()這個函數是自已去繪畫一個控件,根據你想要的形狀,圖案.它是建立一個控件的外表而用到的
可以這樣理解,OnDrawItem是畫窗口中的子控件的,因為它的入口參數LPDRAWITEMSTRUCT帶入不同子控件的相關參數,而且,你得把字控件設置成“自畫”類型,才會調用到OnDrawItem,順便說一下自畫,不是所有設置成自畫類型的控件都會調用父窗口的OnDrawItem,例如ListBox的自畫,你就必須重載CListBox的DrawItem方法和MeasureItem方法才可以,但象菜單,按鈕等的自畫則會調用OnDrawItem。OnPaint和OnDrawItem不在一個范疇內,他是WM_PAINT的響應函數,凡是基於CWnd的類都有OnPaint事件發生,就是說凡是窗口都有WM_PAINT事件發生。(不知道我理解的對不對)
2009產品創新數字化峰會征文:基於CAD的碰撞檢測技術及其在虛擬裝配拆卸系統中的應用
1 引言
幾何形體間的碰撞檢測在虛擬裝配和拆卸過程中幾乎無處不在,碰撞檢測的精度決定了虛擬裝配和拆卸的精度,對於目前的商用VR開發平台而言,普遍采用多邊形(通常是三角形)描述場景中的任意幾何形體,並通過紋理映射、光照模型來模型物體在真實世界的視覺效果。而多邊形模型首先是面模型,面模型不包括物體的內部信息,無法計算由面所組成的體之間的空間干涉情況,只能計算面面之間的相交情況;其次多邊形模型只是對形體幾何的逼近形式,對形體的逼近程度決定了三角形面片數量的多少,逼近程度越高,三角形網格數量越多,渲染和碰撞檢測的時間開銷越大;對形體的描述形式決定了基於多邊形網格的碰撞檢測算法難以實現物體間的精確碰撞檢測。
精確的碰撞檢測首先要保證模型是精確的,其次要保證碰撞檢測算法要基於精確模型之間的空間相交求解。在目前一些大型的商用CAD軟件中,如UG、Pro/E等,不僅采用初等解析曲面和樣條曲面定義幾何物體的外形輪廓曲面,而且采用的是體模型,能夠准確定義幾何物體的內外部空間,同時其提供的靜態干涉檢查功能和算法是成熟的,分析時間是高效的,本文通過把商用CAD系統提供的干涉檢查功能無縫集成到定制開發的虛擬裝配和拆卸系統中,一方面能夠實現形體間的精確碰撞檢測,同時能夠快速地繪制場景以實現交互,並進一步通過采用諸如包圍盒等方式構造合適的精確碰撞檢測加速策略,快速排除大量不會發生碰撞的物體對,降低復雜場景中進行精確碰撞檢測的開銷,提高虛擬裝配/拆卸系統的實時性。
2 總體技術方案
目前商用的VR開發平台均不提供基於CAD幾何模型的精確碰撞檢測功能,而只提供基於多邊形相交測試的碰撞檢測功能,不僅碰撞檢測精度難以提高,而且由於難以判別包容干涉、接觸干涉、硬干涉三種之間的區別,對於虛擬裝配和拆卸過程中廣泛存在的對齊、貼合、相切等情況難以得到正確的碰撞檢測結果,從而影響系統對這類情況做出正確的反應。
通過深入的研究和程序開發,本文在VR環境中實現了基於CAD幾何模型的快速精確碰撞檢測功能,圖1是系統結構框圖。
從圖中可以看出,CAD模型是聯系前台的場景展示和后台的碰撞檢測計算的紐帶,一方面,基於CAD的精確碰撞檢測平台在后台對CAD模型進行空間干涉計算,另一方面,前台根據CAD模型動態生成場景圖並進行繪制以用於用戶交互,從而將仿真的三維顯示和仿真計算分離。當場景中的幾何對象發生運動時,通過在二者之間建立的通訊技術同步更新運動的幾何對象在場景中的幾何方位並保持一致。幾何對象運動過程中的干涉計算采用離散時間步的方法進行計算,即:幾何對象運動時,在場景中以一定的時間間隔步進,同時把該物體運動后的空間方位通過通訊傳遞給后台用於計算的CAD碰撞檢測平台,在后台同步更新運動部件的空間方位后,與其它部件在該時刻進行靜態的碰撞干涉計算,然后得到並分析計算結果,並把計算結果通過通訊計算傳遞到前台VR環境控制端,如果部件間沒有發生干涉,則繼續進行下一個步進動作,否則在場景中發出干涉信號並做出相應的反應。這樣只要步進的距離或角度足夠合理,可以獲得幾何對象運動過程中可能發生的任何干涉情況。
圖1系統結構框圖
3 關鍵技術
3.1基於CAD的精確碰撞檢測算法及其程序實現
CAD交互界面下的干涉檢查功能無法實現批量自動化處理,顯然無法集成到定制開發的VR系統中去,為此,必須找到脫離CAD交互運行界面的程序自動化計算方法。通常而言,高端商用CAD系統提供有二次開發包,在其開發包上進行二次開發有望實現干涉檢測的程序自動化處理。
UG NX是高端CAD系統,其提供的二次開發工具NX/Open功能強大,在NX/Open的基礎上能夠開發實現任意幾何對象之間的精確碰撞檢測的自動計算。
使用NX/Open的進行干涉檢查分析的步驟如下:
1)創建一個間隙分析數據集,分配空間並設置相關屬性
2)設置間隙分析模式,允許采用實體模型或多邊形網格模型進行干涉檢查。
3)設置間隙分析規則,設置需要排除的干涉檢查對。
4)設置間隙分析中用於分析的幾何對象。
5)進行間隙分析,對上面的設置進行干涉分析。
6)對計算結果進行分析。從計算結果中找到發生硬干涉的物體對以及干涉區域。
使用NX/Open進行干涉檢查的計算結果的干涉類型分為如下4類:
1)不干涉:幾何對象間沒有發生干涉;
2)包容干涉:一個幾何對象在另一個幾何對象內部;
3)硬干涉:兩個幾何物體在三維空間存在空間位置重疊並且存在相交的空間曲面;
4)接觸干涉:兩個幾何物體在三維空間存在點或空間曲面接觸,但不存在公共的實體空間;
通過對干涉計算結果進行分析,可以得到任意兩個幾何對象間的准確的空間干涉情況。
3.2基於CAD的VR場景圖動態生成技術
商用VR開發平台不能識別並繪制CAD模型,商用的VR開發平台通常只支持多邊形網格模型,而CAD模型是由參數曲面組成的三維實體模型,為了在VR開發平台下渲染CAD模型,必須根據CAD模型的層次結構動態生成VR場景圖對象。
根據CAD模型動態創建VR場景圖的關鍵在於把CAD參數曲面轉換為某張逼近程度的多邊形網格模型,並按照規定的層次和結構組合成一顆場景樹。
對於一個UG裝配文件,采用如下方式組織VR場景圖(VR開發平台采用Vega Prime):
1)在Vega Prime場景圖根節點上創建一個vpObject組節點,UG裝配文件根節點對應於該組節點。
2)對於UG裝配中的每一個部件,在Vega Prime場景圖中創建一個vpTransform節點,並把該節點作為第一步創建的vpObject節點的子節點。根據該部件在裝配中的空間方位矩陣定義一個vpTransform節點中的方位矩陣,如果該部件是零件模型,則創建一個vsGeometry節點,並把該節點作為vpTransform的子節點;如果該部件仍然是一個裝配模型,則再創建一個vpTransform節點,並把該節點作為上一級vpTransform的子節點,把該vpTransform節點作為父節點遞歸調用步驟2;
通過如上步驟,建立了與CAD模型層次結構類似的Vega Prime場景圖結構。
CAD幾何模型到多邊形網格模型的轉換需要借助於具體的參數曲面離散和多邊形網格剖分算法,對於不同類型的的參數曲面,曲面離散和多邊形網格剖分算法復雜程度不同,文獻[1]對此有詳細的描述。
3.3 可視化前端與后台計算端的幾何對象空間方位同步機制
由於用於用戶交互的可視化前端繪制的是由參數曲面離散生成的多邊形網格,而后台碰撞檢測計算采用的是最初的CAD模型,為了保證碰撞檢測結果的正確性,必須保證這兩個環境中的部件在任意時刻的空間相對方位保持一致。基於CAD的VR場景圖動態生成技術保證了在初始時刻二者在空間方位上的一致性,同步機制需要保證在任意時刻運動部件在二者當中具有同樣的運動特性。采用如下步驟保證二者中的幾何對象空間方位的一致性:
1)在Vega Prime場景圖節點對象和CAD中的幾何對象之間建立一一映射關系,只有建立了一一映射關系,才有可能當用戶在交互界面中選擇並移動或旋轉了某個幾何對象時,系統才能指導該幾何對象對應於CAD中的哪一個幾何對象,並進行相應的平移或旋轉。
2)定義一個結構,該結構用於描述平移或旋轉的類型及所有參數以及碰撞檢測結果,變換的類型及參數用於在CAD計算端進行同樣的變換操作,碰撞檢測結果用於指示可視化前端進行相應的響應。該結構定義如下:
Struct CommunicationData{
Tag_t motionprt; //當前運動的幾何對象
Unsigned int motiontype; //0代表平移,1、2、3分別繞xyz軸旋轉
Double translate[3]; //代表進行平移的坐標值
Double angle; //代表旋轉的角度值
BOOL bIsCollision; //代表是否發生空間干涉 }
3)當可視化前端選中某個幾何對象並進行相應的運動后,填充CommunicationData中的數據並傳遞到CAD計算端,CAD計算端根據旋轉或平移的大小重新計算運動部件在CAD場景中的方位矩陣,最后采用UF_ASSEM_reposition_part_occurrence或者UF_ASSEM_reposition_instance函數把運動部件放置在新的位置。
4)CAD端計算運動部件在新的方位處與其它部件之間的空間干涉情況,如果干涉計算結果為硬干涉,則發生空間干涉,填充CommunicationData結構中的bIsCollision域並通知可視化前端,可視化前端獲取該值並做出相應的反應。
3.4 碰撞檢測加速策略
層次包圍盒方法的基本思想是用體積略大而幾何特性簡單的包圍盒來近似描述復雜的幾何對象,進而通過構造樹狀層次結構來逼近對象的幾何模型,直到幾乎完全獲得對象的幾何特性,從而只需對包圍盒重疊的部分進行進一步的相交測試。層次包圍盒最重要的特點是能夠應用於任意形狀的幾何對象的物體的碰撞檢測,因此也是目前應用最廣泛的一種碰撞檢測方法。
層次包圍盒樹根據包圍盒類型的不同又可分為包圍球、AABB、OBB、k-Dop等等,對應於每一類的包圍盒都有一個代表性的碰撞檢測算法,包圍盒的示意圖如圖6所示:
表1是對幾種基本包圍盒的碰撞檢測算法的性能比較,沿坐標軸的包圍盒AABB(axis-aligned bounding boxes)是使用的最久也是最廣的包圍盒類型,一個給定對象的AABB被定義為包含該對象且各邊平行於坐標軸的最小的六面體。其計算十分簡單,只需分別計算組成對象的基本幾何元素集合中各個元素的頂點的X、Y、Z坐標的最大值和最小值即可;兩個AABB相交當且僅當它們在三個坐標軸上的投影區間均重疊,AABB間的相交測試最多只需要6次比較運算。AABB也是眾多VR開發平台廣泛支持的包圍盒類型,為此,本文采用AABB作為選定的包圍盒類型。
本文采用AABB層次包圍盒加基於CAD模型的精確碰撞檢測算法實現碰撞檢測,其中層次包圍盒算法用於快速排除場景中沒有發生干涉的物體對,對於采用層次包圍盒算法計算后仍然干涉的物體對,用采用基於CAD模型的精確碰撞檢測算法進行准確的碰撞檢測,這樣一方面保證了碰撞檢測結果的正確性,另一方面能夠大大降低碰撞檢測的時間開銷。
表1基本包圍盒碰撞檢測算法比較
4 應用實例及結論
圖3是在Vega Prime中開發的原型系統的程序運行界面,在該原型系統中,通過選擇運動部件並設置運動路徑(平移和旋轉),程序通過計算運動部件運動過程中與其它部件的空間干涉情況來判斷該運動過程是否存在干涉,空間干涉的結果根據在運動過程中運動部件和非運動部件的UG CAD模型的空間干涉情況得出。
圖3原型系統運行界面
圖4用於碰撞檢測測試的模型
圖4是用於虛擬安裝測試的模型,需要把紅色部件插入到另一個部件的中心圓孔中(圓孔直徑與紅色物體外徑相同),實際上,在插入圓孔的過程中,它們之間只存在接觸干涉,而不存在空間硬干涉,對此,基於CAD模型的精確碰撞檢測算法可以正確識別,而基於多邊形相交測試的碰撞檢測算法只能把這類情況作為發生干涉進行處理。
表2顯示了在p4 3.4GCPU,4G內存和Nvidia Quadro Fx1400顯卡微機上進行測試時(紅色物體逐步插入圓孔中心,采樣100幀的平均值)不同碰撞檢測算法和時間開銷比較表。
表2幾種碰撞檢測算法計算的平均時間開銷
從試驗結果可以看出,基於CAD模型的精確碰撞檢測算法可以區分接觸干涉和硬干涉以及包容干涉之間的區別,同時可以在很快的時間開銷下完成幾何物體間的精確碰撞檢測,采用層次包圍盒可以進一步降低碰撞檢測的時間開銷,而采用基於多邊形一一相交測試的碰撞檢測算法的時間開銷隨着用於碰撞檢測的多邊形數目的增加而急劇增加。
實踐證明,本文提出的技術方案切實可行,不僅解決了商用VR開發系統中碰撞檢測精度難以提高的技術問題,而且用於精確碰撞檢測的時間開銷能夠滿足工程實際需要。
[參考文獻]
[1] 趙瑛峰,NX Openflight數據交換輸出接口開發技術研究[J],產品數字化實踐論文集,電子工業出版社,2008.5
// 球體-球體的碰撞 Sphere-Sphere Collision
bool IsCollidingSphereToSphere()
{
// 兩個小球的相對速度
relative_vel = Sphere2_Vel - Sphere1_Vel;
r = Sphere1_Radius + Sphere2_Radius;
relative_position = Sphere2_Center – Sphere1_Center;
// 檢查兩個小球是否已經碰撞
if ((Sqr_relative_position - (r*r)) <= 0)
return true;
// 提前跳過測試:如果兩個小球朝向遠離對方的方向移動,則返回false
float rel_distance = sqrt(Sqr_relative_pos) - (r);
//這里我們需要檢測兩次update之間是否存在碰撞
if (rel_distance < (sqrt(Sqr_relative_vel)*update_interval))
{
time_fract = rel_distance/sqrt(Sqr_relative_vel);
time_remain = update_interval – time_fract;
if ((Sqr_relative_pos - (r*r)) <= 0) { return true; } } return ((relative_movement * relative_movement - ((Sqr_relative_pos - r*r) - Sqr_relative_vel)) >
0);
}
// 球體-平面的碰撞 Sphere-Plane Collision
bool IsCollidingSphereToPlane()
{
// 提前跳過測試:檢查小球是否可以在沒有碰撞的情況下移動,
// 如果可以,則返回false;
// 如果不可以則檢測球體與平面之間的距離是否為
for (int i=0; i<6; i++)
{
// 小球的速度與平面法向量的點積
// 如果點積為,則速度矢量平行與平面,因此不可能碰撞
float unit_normal_vel_dot = normal . velocity;
if (unit_normal_vel_dot < 0.0f)
{
// 計算平面公式
float D1 = normal * point_on_plane;
float normal_pos_dot = normal . center_S;
// 計算球心到平面的距離
float distance = (D1-normal_pos_dot)/unit_normal_vel_dot;
// 這里處理球體已經接觸到盒子的情況
if (distance <= radius1) { collide = true; // 存儲這個面的法向量 } break; } else { float Projected = velocity * update_interval; // 這里處理球體在update_interval時間移動距離x, // 但是x小於球與平面的距離的情況 if (Projected > distance)
{
collide = true;
time_fract = (distance–radius)/velocity;
time_remain = update_interval - time_fract;
}
break;
}
}
return collide;
物移動控制是單機和網游中比較重要的部分,但前單機游戲使用動力學以及IK動畫等已經達到了非常逼真的地步,在大型網絡游戲中這樣的物理模擬同步是很實現的,因此在目前多數網游中仍舊是采取使用一個包圍體(盒子或者膠囊)來模擬人物。一個好的移動系統是很重要的,平滑的貼牆滑動以及下滑,跳躍等會帶給玩家順暢的手感否則則會有種奇怪的感覺,本文具體介紹了一下碰撞反應,包括貼牆滑動等的具體實現細節。包括一個demo實例 。
目前物理引擎里面大多自帶獨立於剛體的人物角色控制,但是物理引擎需要特定的物理模型命名以及比較大的物理模擬開銷度。如果需要定制自己的特別功能或者需要簡化計算(同時模擬多個延遲或者玩家的反應)。就必須自己完成人物碰撞反應控制的代碼。
要完成人物發生碰撞以后的行為控制,需要碰撞檢測系統提供以下的碰撞信息,對於每一個碰撞:
1. 碰撞發生的時間
2. 碰撞的法向量
3. 碰撞點
對於基本的人物碰撞控制反應來說,以上3點是必須的,有時還需要提供和包圍體發生碰撞的具體三角形信息。
對於場景上的物體首先使用膠囊所在的包圍盒AABB或者OBB和場景中的碰撞體包圍盒作層次碰撞裁減,至於具體怎么組織可以任意,比如可以采用AABB或者OBB樹,也可以采用簡單的球樹。但碰撞進行到樹的葉子節點后開始檢測人物的AABB盒和該AABB盒所包圍的OBJECT的碰撞情況。如果發現這2個AABB(OBB)盒將會發生碰撞,那么開始使用人物的膠囊體和景物所帶的三角面片進行精確到polygon soup級別的比較。這時候仍舊可以優化,比如說我還做了一步把一個Object中的三角形面片打成BSP樹的形式存儲起來,這樣可以大大減少膠囊和三角形碰撞檢測的次數,因為這種動態檢測是十分耗時的。有關膠囊和三角形面片的比較可以參考:http://dev.gameres.com/Program/Abstract/Arithmetic/capsule.mht中的方法。 對於BSP的划分以及AABB碰撞檢測就不用多說了~到處都可以找到文章。
對於地形而言,也是采用同樣的方法,只不過對於地形而言三角形信息不用額外存儲,只需要使用和渲染相同的三角形(對於景物來說一般不會使用渲染用的三角形而會使用更加簡化數量更少的簡化網格碰撞模型)。這里可以有很多優化的技巧,因為地形本身是規則的cell一個地形是由若干個patch(一般是16X16)組成的,而每個patch是由若干cell(一般是16X16)組成的。對於patch來說一般已經組織到了一顆QUADTREE中了因為視棱錐裁減也需要這種結構,因此碰撞檢測中的AABB-AABB階段使用這顆已經存在的QUADTREE就可以快速的完成層次碰撞檢測了。但發現某個patch中的AABB和人物的AABB發生碰撞后需要檢測每一個CELL所在的AABB和人物AABB盒的碰撞,這里可以使用點小技巧比如說首先將AABB盒投影到CELL所在的XY平面上,找出被投影覆蓋的那些CELL然后再檢測這些CELL的AABB盒是否和人物的發生碰撞。一但確定了某個CELL和人物發生碰撞那么就可以將該CELL中的三角形取出(一般為2個)依次和人物所在的膠囊進行三角形-膠囊的碰撞檢測。
這樣當碰撞檢測系統完成任務以后我們將會獲得一個碰撞信息的數組:
class CollideInFo{
public:
GFVECTOR_3D m_worldcdnorm;//碰撞法向量
GFPOINT_3D m_worldpoint;//碰撞點
float m_cdtime;//碰撞時間
};
CollideInFo collidearray[];
然后使用這個數組就可以進行碰撞后的處理了包括,沿牆滑動下滑等等。在具體說明整個人物移動控制算法之前,首先說下動態碰撞檢測和靜態碰撞檢測的區別,動態碰撞檢測是指物體A以速度V前進了T時間,在這期間第一次和物體B發生碰撞的時間。這樣的碰撞檢測必須返回第一次2個物體發生碰撞的時間。而靜態檢測是指2個不動的物體是否互相相交對於這種檢測是不需要返回時間的。動態檢測算法比靜態的復雜而且也耗用更多的時間。一個完善的碰撞系統需要解決以上2種碰撞檢測,如果你不想自己寫檢測代碼,目前比較流行的有OPCODE,SOLID庫等檢測庫 。你可以直接使用他們提供的功能,這里我采用的是自己寫檢測代碼的方法,目前只用到三角形-膠囊,AABB-AABB,OBB-OBB的碰撞檢測。 [Page]
完成了包圍體(用的是膠囊)和三角形的碰撞,膠囊和BSP,地形的碰撞檢測之后,擁有了碰撞的信息 1。碰撞時間。2。碰撞法向量。3。碰撞點。接着就可以處理人物在碰撞后的反應了。
首先人物的一次移動分2個階段,第一個是初始階段,使用靜態碰撞檢測獲得該階段的速度(具體做法在后面說)。第2階段使用該速度去做動態碰撞檢測得到碰撞信息,根據這些碰撞信息去處理碰撞后的反應。
先來看第一階段,過程對於一個膠囊我們需要獲取他周圍的臨近面片的信息,以決定這個膠囊目前所處平面的傾斜度,是否貼着不可通過的牆等等。我采用的方法是將膠囊體略為膨脹一些,然后調用靜態碰撞檢測的代碼獲取和該膨脹后的膠囊體相交的三角形面片碰撞信息如圖:
棕色的是原始的膠囊體,紅色的表示將膠囊半徑略為增加以后的膠囊體,藍色的2個地形是將膠囊膨脹以后所發生相交的2個三角形,而如果不采用膨脹的話該膠囊是不和任何三角形相交的,具體膨脹數值可以設為膠囊下落的最小高度,比如你設定膠囊離底部物體超過0.6單位屬於騰空狀態的話你就將膨脹數值設為0.6。在這個例子中我們將會檢測到2個碰撞(藍色部分)這2個碰撞法向量正好是這2個三角面的法向量(在很多情況下也可能不是這個看你的碰撞代碼中法向量是如何計算的了)。其中底部的那個是可行走平面,另外一個是不可行走平面,有了這2個碰撞平面就很簡單了,如果用戶輸入的速度和那個不可行走的平面相反(也就是撞向那個不可行走平面),那么那個不可行走平面是有效的,這樣他和底部那個可行走的平面所組成的交線就是人物的初始速度,如果用戶輸入的速度和那個不可行走的平面法向量相同,那么這個不可行走平面沒有作用人物最終的速度就是把用戶速度投影到該可行走平面上的速度。
0頂一下
具體計算是否能夠水平移動以及移動速度的算法:當給出M個不可行走平面和N個可行走平面時:
1首先將速度在N個可行走平面上分解,檢查這些分解的速度是否有效(如何判斷速度有效下面會說道)
2如果在1的時候存在一個有效的速度直接返回該速度
3沒有有效速度,這時檢查NXM個平面的交線,將速度在上面分解同時檢查是否存在有效速度
4如果存在有效速度返回該速度
5否則檢查MXM條交線的,將速度在上面分解同時檢查是否存在有效速度
6如果存在有效速度返回該速度
7否則返回0速度
那么如何判定速度是否有效呢,首先我們知道了所有碰撞的信息,給定一個速度,如果該速度和所有碰撞的法向量的夾角都是小於90度那么這就是個有效速度,(說明該分解后速度不會引起和這些碰撞面的在一次碰撞,因為該速度是將物體拉下遠離該平面的方向的)。如果只要有一個夾角大於90度那么該速度就是非有效速度,同時在移動時還要判斷該速度的傾斜角是否大於最大下滑傾斜角。注意 5是很重要的因為2個不可行走的平面所形成的交線仍舊可能是可以行走的,甚至是水平的。
以上的部分是檢查是否能夠水平移動的,如果不能水平移動那么該物體會下滑,1如果沒有檢測到碰撞平面說明物體處於騰空狀態,這時候給物體加上重力加速度,產生一個往下的速度和原來的水平速度結合起來(如果存在)。
如圖棕色是原始狀態膠囊經過上一幀移動后到達藍色的位置這時通過上訴算法可以檢測到膠囊騰空,這時他的速度為水平速度(紅色)+下滑速度(綠色)。
2如果檢測到碰撞平面但是平面以及它們的交線都是不可行走的(傾斜角大於可行走角度)那么依次將當前速度在碰撞平面分解,以及檢測每一條可能下滑的交線。得出速度后檢測是否是有效速度具體過程和前面檢查水平速度時的方法一樣,只是將速度投影到平面上的方法不一樣具體方法可以根據程序需要,我這里采用首先去掉原始速度在碰撞平面法線的分量,然后將速度分解為2個速度一個是貼着平面水平移動的速度另外一個是貼着平面下滑的速度。注意如果上一幀更新后物體處於下滑狀態那么當前速度就因該是該下滑速度,否則則是用戶輸入的速度。
如果用戶輸入的速度是跳躍也就是帶Z分量的速度那么,計算初始速度這一步需要略過直接輸入給下一階段用戶起跳的速度。
階段2計算初始速度引起的碰撞
對於多數情況來說,計算完初始速度就不會再發生碰撞了,一旦發生,那么我們依舊傳入碰撞面的那些法向量,碰撞點的信息,同樣采用前面計算初始速度時所采用的方法,計算出碰撞后的調整速度。 [Page]
整個處理過程基本上就是這樣的,其中可能還會出現一些小問題比如說誤差控制等。總結一條就是人物行走要么研着平面分解速度要么沿着2個平面的交線行走或者下滑。這個是Demo示例畫面(由於沒有人幫我做動畫。。所以demo中目前只有行走動畫,沒有起跳,下落等動畫。。)
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!希望你也加入到我們人工智能的隊伍中來!http://www.captainbed.net