參考地址請看圖片水印:http://www.cnblogs.com/iamzhanglei/archive/2012/06/07/2539751.html
http://blog.sina.com.cn/s/blog_6a1bf1310101g7zy.html
包圍體是一個簡單的幾何空間,里面包含着復雜形狀的物體。為物體添加包圍體的目的是快速的進行碰撞檢測或者進行精確的碰撞檢測之前進行過濾(即當包圍體碰撞,才進行精確碰撞檢測和處理)。包圍體類型包括球體、軸對齊包圍盒(AABB)、有向包圍盒(OBB)、8-DOP以及凸殼。如圖1所示。
圖1 依次是球體、AABB、OBB
可以看到圖1是3D包圍體,在2D包圍體如圖2所示:
圖2 依次是球體、AABB、OBB
包圍球
包圍球碰撞檢測方法是用球體包圍整個幾何體, 無論是幾何體還是相交測試都很簡單; 但是它的緊密性太差。因為除了在3 個坐標軸上分布得比較均勻的幾何體外, 幾乎都會留下較大的空隙, 需要花費大量的預處理時間, 以構造一個好的層次結構逼近對象。當物體變形之后,包圍球樹需要重新計算。因此,它是使用得比較少的一種包圍盒。當對象發生旋轉運動時, 包圍球不需作任何更新, 這是包圍球的較優秀特性; 當幾何對象進行頻繁的旋轉運動時, 采用包圍球可能得到較好結果。
AABB盒
AABB盒,一個3D的AABB就是一個簡單的六面體,每一邊都平行於一個坐標平面,矩形邊界框不一定都是立方體,它的長、寬、高可以彼此不同。
先介紹AABB的表達方法,AABB內的點滿足以下條件:
xmin≤x≤xmax
ymin≤y≤ymax
zmin≤z≤zmax
因此只需要知道兩個特別重要的頂點(xmin,ymin,zmin)、(xmax,ymax,zmax),記作:
float[] min = new float []{0.0f,0.0f,0.0f};
float[] max = new float []{0.0f,0.0f,0.0f};
中心點是兩個頂點的中點,代表了包裝盒的質點。
float[] center = new float []{0.0f,0.0f,0.0f};
中心點的計算方法如下:
float [] center(){
center[0] = (min[0] + max[0])*0.5f;
center[1] = (min[1] + max[1])*0.5f;
center[2] = (min[2] + max[2])*0.5f;
return center;
}
通過這兩個頂點可以知道以下屬性。
float xSize() { return (max[0]-min[0]); }
float ySize() { return (max[1]-min[1]); }
float zSize() { return (max[2]-min[2]); }
float size(){ return (max[0]-min[0])*(max[1]-min[1])*(max[2]-min[2]);}
當添加一個頂點到包裝盒時,需要先與這兩個頂點進行比較。
void add(float []p) {
if (p[0] < min[0]) min[0] = p[0];
if (p[0] > max[0]) max[0] = p[0];
if (p[1] < min[1]) min[1] = p[1];
if (p[1] > max[1]) max[1] = p[1];
if (p[2] < min[2]) min[2] = p[2];
if (p[2] > max[2]) max[2] = p[2];
}
檢測包裝盒是否為空,可以將這兩個頂點進行比較。
boolean isEmpty() {
return (min[0] > max[0]) || (min[1] > max[1]) || (min[2] > max[2]);
}
檢測某個點是否屬於AABB范圍之內的代碼如下:
boolean contains(float []p){
return
(p[0] >= min[0]) && (p[0] <= max[0]) &&
(p[1] >= min[1]) && (p[1] <= max[1]) &&
(p[2] >= min[2]) && (p[2] <= max[2]);
}
AABB的靜態檢測比較簡單,檢測兩個靜止包裝盒是否相交,它是一種布爾測試,測試結果只有相交或者不相交。這里我們還提供了獲取相交范圍信息的方法,一般來說,這種測試的目的是為了返回一個布爾值。碰撞的示意如圖10-34所示。
圖10-34 包裝盒的碰撞
檢測靜態AABB碰撞的方法如下:
boolean intersectAABBs(AABB box2,AABB boxIntersect)
{
float []box2_min = box2.getMin();
float []box2_max = box2.getMax();
if (min[0] > box2_max[0]) return false;
if (max[0] < box2_min[0]) return false;
if (min[1] > box2_max[1]) return false;
if (max[1] < box2_min[1]) return false;
if (min[2] > box2_max[2]) return false;
if (max[2] < box2_min[2]) return false;
if (boxIntersect != null) {
float []box_intersect_min = new float[3];
float []box_intersect_max = new float[3];
box_intersect_min[0] = Math.max(min[0], box2_min[0]);
box_intersect_max[0] = Math.min(max[0], box2_max[0]);
box_intersect_min[1] = Math.max(min[1], box2_min[1]);
box_intersect_max[1] = Math.min(max[1], box2_max[1]);
box_intersect_min[2] = Math.max(min[2], box2_min[2]);
box_intersect_max[2] = Math.min(max[2], box2_max[2]);
}
return true;
}
可以利用AABB的結構來加快新的AABB的計算速度,而不用變換8個頂點,再從這8個頂點中計算新AABB。下面簡單地回顧4×4矩陣變換一個3D點的過程。
通過原邊界框(xmin,ymin,zmin,xmax,ymax,zmax)計算新邊界框(,,,,,),現在的任務是計算的速度。換句話說,希望找到m11x+m12y+m13z+m14的最小值。其中[x,y,z]是原8個頂點中的任意一個。
變換的目的是找出這些點經過變換后哪一個的x坐標最小。看第一個乘積m11x,為了最小化乘積,必須決定是用xmin還是xmax來替換其中的x。顯然,如果m11>0,用xmin能得到最小化的乘積;如果m11<0,則用xmax能得到最小化乘積。
比較方便的是,不管xmin還是xmax中哪一個被用來計算,都可以用另外一個來計算。可以對矩陣中的9個元素中的每一個都應用這個計算過程(其他元素不影響大小)。
根據變換矩陣和原有的AABB包裝盒計算新的AABB包裝盒的代碼如下:
void setToTransformedBox(Transform t)
{
if (isEmpty()) { //判斷包裝盒是否為空
return;
}
float[] m = new float [16];
t.get(m); //將變換矩陣存入數組
float minx=0,miny=0,minz=0;
float maxx=0,maxy=0,maxz=0;
minx += m[3]; //x方向上平移
maxx += m[3]; //x方向上平移
miny += m[7]; //y方向上平移
maxy += m[7]; //y方向上平移
minz += m[11]; //z方向上平移
maxz += m[11]; //z方向上平移
if (m[0] > 0.0f) {
minx += m[0] * min[0]; maxx += m[0] * max[0];
} else {
minx += m[0] * max[0]; maxx += m[0] * min[0];
}
if (m[1] > 0.0f) {
minx += m[1] * min[1]; maxx += m[1] * max[1];
} else {
minx += m[1] * max[1]; maxx += m[1] * min[1];
}
if (m[2] > 0.0f) {
minx += m[2] * min[2]; maxx += m[2] * max[2];
} else {
minx += m[2] * max[2]; maxx += m[2] * min[2];
}
if (m[4] > 0.0f) {
miny += m[4] * min[0]; maxy += m[4] * max[0];
} else {
miny += m[4] * max[0]; maxy += m[4] * min[0];
}
if (m[5] > 0.0f) {
miny += m[5] * min[1]; maxy += m[5] * max[1];
} else {
miny += m[5] * max[1]; maxy += m[5] * min[1];
}
if (m[6] > 0.0f) {
miny += m[6] * min[2]; maxy += m[6] * max[2];
} else {
miny += m[6] * max[2]; maxy += m[6] * min[2];
}
if (m[8] > 0.0f) {
minz += m[8] * min[0]; maxz += m[8] * max[0];
} else {
minz += m[8] * max[0]; maxz += m[8] * min[0];
}
if (m[9] > 0.0f) {
minz += m[9] * min[1]; maxz += m[9] * max[1];
} else {
minz += m[9] * max[1]; maxz += m[9] * min[1];
}
if (m[10] > 0.0f) {
minz += m[10] * min[2]; maxz += m[10] * max[2];
} else {
minz += m[10] * max[2]; maxz += m[10] * min[2];
}
min[0] = minx; min[1] = miny; min[2] = minz; //用新的AABB坐標替換原有坐標
max[0] = maxx; max[1] = maxy; max[2] = maxz; //用新的AABB坐標替換原有坐標
}
為了使用AABB包裝盒進行碰撞檢測,將這些方法和屬性封裝為AABB類,代碼如下:
import java.lang.Math;
import javax.microedition.m3g.Transform;
class AABB{
public AABB(){}
float [] getMin(){return min;}
float [] getMax(){return max;}
void setMin(float x,float y,float z){min[0]=x;min[1]=y;min[2]=z;}
void setMax(float x,float y,float z){max[0]=x;max[1]=y;max[2]=z;}
void reset(){
for(int i =0;i<3;i++)
{
min[i]=0;
max[i]=0;
}
}
//其他方法同上
}
為了檢驗碰撞檢測的使用構造了兩個立方體,並各自綁定了一個包裝盒。
mesh1 = createCube(); //創建立方體1
mesh1.setTranslation(1.0f, 0.0f,0.0f) ; //平移
mesh1.setOrientation(90,0.0f,1.0f,0.0f); //旋轉
mesh1.setScale(0.5f,0.5f,0.5f); //縮放
box1 = new AABB(); //包裝盒
box1.setMin(-1.0f,-1.0f,-1.0f); //設置包裝盒1的最小頂點
box1.setMax(1.0f,1.0f,1.0f); //設置包裝盒1的最大頂點
mesh1.getCompositeTransform(cubeTransform); //獲取立方體1的混合矩陣
box1.setToTransformedBox(cubeTransform); //將變換矩陣應用到包裝盒中
world.addChild(mesh1); //將立方體1添加到場景中
mesh2 = createCube(); //創建立方體2
mesh2.setTranslation(-0.5f, 0.0f,0.0f) ; //平移
mesh2.setScale(0.5f,0.5f,0.5f); //縮放
box2 = new AABB(); //包裝盒
box2.setMin(-1.0f,-1.0f,-1.0f); //設置包裝盒2的最小頂點
box2.setMax(1.0f,1.0f,1.0f); //設置包裝盒2的最大頂點
mesh2.getCompositeTransform(cubeTransform); //獲取立方體2的混合矩陣
box2.setToTransformedBox(cubeTransform); //將變換矩陣應用到包裝盒2中
world.addChild(mesh2); //將立方體2添加到場景中
檢測包裝盒1和包裝盒2是否碰撞的代碼如下:
isCollided = box1.intersectAABBs(box2,null); //檢測兩個AABB包裝盒是否碰撞
編譯運行程序,設置兩個立方體不同的位置和角度,可以比較精確地檢測出它們的碰撞情況,如圖10-35所示。
檢測兩個靜止AABB的碰撞情況比較簡單,只需要在每一維上單獨檢查它們的重合程度即可。如果在所有維上都沒有重合,那么這兩個AABB就不會相交。
AABB間的動態檢測稍微復雜一些,考慮一個由頂點smin和smax指定的靜態包裝盒和一個由頂點mmin和mmax指定的動態包裝盒(如果兩個都是動態的,可以根據相對運動視作如此)。運動的速度由向量s給出,運動時間t假定為0~1。
圖10-35 靜態物體碰撞檢測示意
移動檢測的目標是計算運動AABB碰撞到靜態AABB的時刻,因此需要計算出兩個AABB在所有維上的第一個點。為了簡化起見,可以把上述問題先歸結到某一維,然后再將三維結合到一起。假設把問題投影到x軸,如圖10-36所示。
圖10-36 AABB的動態檢測
黑色矩形代表沿坐標軸滑動的AABB,t=0時,運動AABB完全位於靜止AABB的左邊。當t=1時,運動AABB完全位於靜止AABB的右邊。當t=tenter時,兩個AABB剛剛相交,當t=tleave時,兩個AABB脫離碰撞。
對照上圖,可以推導出兩個AABB接觸和離開的時間:
,
AABB的動態檢測有3個要點。
n 如果速度為0,兩個包裝盒要么一直相交,要么一直分離。
n 不管物體從哪個方向運動,碰撞過程中,肯定是先入后出,所以有tenter<tleave。
n 如果tenter和tleave超出運動時間范圍,那么在此范圍內它們是不相交的。
檢測出某一維的碰撞還不夠,還需要進行其他兩維的檢測,然后取結果的交集。如果交集為空,那么兩AABB包裝盒沒有相交,如果區間范圍在時間段[0,1]之外,那么在此區間也不相交。對AABB進行動態檢測的方法定義如下:
float intersectMovingAABB(AABB stationaryBox,AABB movingBox,float []s)
{
float NoIntersection = 1e30f; //沒有碰撞則返回大數
float tEnter = 0.0f; //初始化碰撞時間
float tLeave = 1.0f; //初始化離開時間
float Swap = 0.0f; //交換操作中間變量
float [] sBoxmin= stationaryBox.getMin(); //靜止包裝盒的最小值頂點
float [] sBoxmax= stationaryBox.getMax(); //靜止包裝盒的最大值頂點
float [] mBoxmin= movingBox.getMin(); //運動包裝盒的最小值頂點
float [] mBoxmax= movingBox.getMax(); //運動包裝盒的最大值頂點
if (s[0] == 0.0f) { //如果x方向速度為0
if ((sBoxmin[0] >= mBoxmax[0]) ||(sBoxmax[0] <= mBoxmin[0])) {
return NoIntersection; //進行靜態檢測
}
} else {
float xEnter = (sBoxmin[0]-mBoxmax[0])/s[0]; //計算碰撞時間
float xLeave = (sBoxmax[0]-mBoxmin[0])/ s[0]; //計算離開時間
if (xEnter > xLeave) { //檢查順序
Swap = xEnter;
xEnter = xLeave;
xLeave = Swap;
}
if (xEnter > tEnter) tEnter = xEnter; //更新區間
if (xLeave < tLeave) tLeave = xLeave;
if (tEnter > tLeave) { //是否導致空重疊區
return NoIntersection; //沒有碰撞
}
}
if (s[1] == 0.0f) { //y軸速度為0
if ( (sBoxmin[1] >= mBoxmax[1]) || (sBoxmax[1] <= mBoxmin[1])) {
return NoIntersection; //沒有相交
}
} else {
float yEnter = (sBoxmin[1]-mBoxmax[1]) / s[1];
float yLeave = (sBoxmax[1]-mBoxmin[1]) / s[1];
if (yEnter > yLeave) {
Swap = yEnter;
yEnter = yLeave;
yLeave = Swap;
}
if (yEnter > tEnter) tEnter = yEnter; //更新區間
if (yLeave < tLeave) tLeave = yLeave;
if (tEnter > tLeave) {
return NoIntersection;
}
}
if (s[2] == 0.0f) { //z方向速度為0
if ((sBoxmin[2] >= mBoxmax[2]) ||(sBoxmax[2] <= mBoxmin[2])) {
return NoIntersection;
}
} else {
float oneOverD = 1.0f / s[2];
float zEnter = (sBoxmin[2]-mBoxmax[2]) / s[2];
float zLeave = (sBoxmax[2]- mBoxmin[2]) / s[2];
if (zEnter > zLeave) {
Swap = zEnter;
zEnter = zLeave;
zLeave = Swap;
}
if (zEnter > tEnter) tEnter = zEnter; //更新區間
if (zLeave < tLeave) tLeave = zLeave;
if (tEnter > tLeave) {
return NoIntersection;
}
}
return tEnter; //返回碰撞時間
}
為了對移動AABB進行檢測,創建兩個AABB如圖10-37所示。兩個包裝盒距離0.5,速度為3。
圖10-37 移動AABB檢測
檢測代碼如下:
float[] speed = new float []{3.0f,0.0f,0.0f};
float tEnter = intersectMovingAABB(box1,box2,speed);
輸出結果為0.16667,完全符合預期的猜測。
OBB
前面提到了長條物體在旋轉時AABB盒的變化,那么是否有能夠在任意方向都更為精確的檢測方式,答案是肯定的,這是一種基於OBB即定向包容盒子(Oriented Bounding Box,OBB)的技術,它已經廣泛用於光線追蹤和碰撞檢測中。
OBB這種方法是根據物體本身的幾何形狀來決定盒子的大小和方向,盒子無須和坐標軸垂直。這樣就可以選擇最合適的最緊湊的包容盒子。OBB盒子的生成比較復雜。一般是考慮物體所有的頂點在空間的分布,通過一定的算法找到最好的方向(OBB盒子的幾個軸)。
一個2D示意圖如圖10-38所示。
這種技術比AABB技術更精確而且更健壯,但OBB實現起來比較困難,執行速度慢,並且不太適合動態的或柔性的物體。特別注意的是,當把一個物體分得越來越小的時候,事實上是在創建一棵有層次的樹,如圖10-39所示。
圖10-39 OBB樹的生成(曲折線為物體)
為任意的網格模型創建OBB樹可能是算法里最難的一個部分,而且它還要調整以適合特定的引擎或游戲類型。從圖中可以看出,不得不找出包圍給定模型的最近似的包裝盒(或者其他3D體)。
現在得到了所有的包裝盒,下一步將構造一棵樹。
從最初的AABB包裝盒開始從上至下地反復分割它。另外,還可以用從下至上的方式,逐步地合並小包裝盒從而得到最大的包裝盒。把大的包裝盒分割成小的包裝盒,應該遵守以下幾條原則。
(1)用一個面(這個面垂直於包裝盒中的一條坐標軸)來分割包裝盒上最長的軸,然后根據多邊形處在分割軸的哪一邊把多邊形分離開來(如圖10-38所示)。
(2)如果不能沿着最長的軸進行分割,那就沿第二長的邊分割。持續地分割直到包裝盒不能再分割為止。
(3)依據需要的精度(比如,是否真的要判斷單個三角形的碰撞),可以按選擇的方式(是按樹的深度或是按包裝盒中多邊形的數目)以任意的條件停止分割。
正如讀者所看到的,創建階段相當復雜,其中包括了大量的運算,很明顯不能實時地創建樹,只能是事先創建。事先創建可以免去實時改變多邊形的可能。另一個缺點是OBB要求進行大量的矩陣運算,不得不把它們定位在適當的地方,並且每棵子樹必須與矩陣相乘。
現在假設已經有了OBB或者AABB樹。那么該怎么進行碰撞檢測呢?首先檢測最大的包裝盒是否相交(AABB級別),如果相交了,它們可能(注意,只是可能)發生了碰撞,接下來將進一步地遞歸處理它們(OBB級別,不斷地遞歸用下一級進行處理)。
如果沿着下一級,發現子樹並沒有發生相交,這時就可以停止,並得出結論沒有發生碰撞。如果發現子樹相交,那么要進一步處理它的子樹直到到達葉子節點,並最終得出結論。
碰撞檢測最直觀的想法是把一個OBB盒子的每個邊都和另一個盒子的所有面來比較,如果這個邊穿過了另一個OBB盒子的一個面,則兩個OBB盒子發生了碰撞。顯然這種方法的計算量是比較大的,因為要進行12×6×2=144次邊和面的比較。
但是,在考察兩個沒有碰撞的OBB盒子時,人們發現一些規律來簡化比較。
(1)如果兩個OBB盒子不互相接觸,則應該可以找到一個盒子上的一個面,這個面所在的平面可以把3D空間分為兩部分,兩個OBB盒子各在兩邊。
(2)如果沒有這樣的表面存在,則一定可以在兩個OBB盒子上各找出一條邊,這兩條邊所在的平面可以把兩個OBB盒子分在兩邊。有了這個平面,就可以找到垂直於它的分割軸(separating axis),如圖10-40所示。
(3)進行相交測試時,可以把包裝盒投影到分割軸上,並檢查它們是否線性相交。兩個OBB盒子在這個分割軸上的投影將是分離的。
如上所述,要判斷兩個OBB盒子是否碰撞,只需要看兩個OBB盒子之間是否有這樣的平面和分割軸存在。如果存在,則沒有碰撞。如果不存在,則碰撞。 對第一種情況,每個盒子有6個表面(其中每兩個平行),可以決定3個分割軸。兩個OBB盒子一共有6個可能的分割軸需要考慮。對第二種情況,兩個OBB盒 子之間的邊的組合可以有3×3=9種情況,也就是有9個可能的分割軸。這樣對任意兩個OBB盒子,只需要考察15個分割軸就可以了。如果在任一分割軸上的 陰影不重合,則OBB盒子之間沒有碰撞。
選擇AABB還是選擇OBB應該根據所需的精確程度而定。對一個需要快速反應的3D射擊游戲來說,可能用AABB來進行碰撞檢測更好些——可以犧牲一些精度來換取速度和實現的簡單化,因此總能在游戲中看到一些小疏漏。當然隨着硬件能力的提高,OBB處理會逐漸被重視起來。
在做碰撞檢測時應當遵循以下的優化理論,這樣可以改善檢測速度。
n 分兩步檢驗,距離遠時看作質點,距離近時采用包裝盒。
n 距離很遠的物體不會在短時間內相撞(可以采用BSP樹分割空間)。
n 一個物體不能隔着第二個物體和第三個物體相撞。
n 一旦一個物體檢測到和另一物體碰撞,另一物體對這個物體不再檢測。
n 靜止的物體不主動與其他物體碰撞。
以下是另一個博客的OBB解釋:
方向包圍盒(Oriented bounding box),簡稱OBB。方向包圍盒類似於AABB,但是具有方向性、可以旋轉,AABB不能旋轉。如圖3所示。
圖3 矩形和矩形投影檢測的四條軸
要計算兩個OBB是否碰撞,只需要計算他們在圖3上的4個坐標軸上的投影是否有重疊,如果有,則兩多邊形有接觸。這也可以擴展到任意多邊形,如圖4所示。
圖4 矩形和三角形投影檢測的五條軸
投影軸來自於多邊形自身邊的垂線。
判定方式:兩個多邊形在所有軸上的投影都發生重疊,則判定為碰撞;否則,沒有發生碰撞。
OBB存在多種的表達方式,這里使用最常用的一種:一個中心點、2個矩形的邊長、兩個旋轉軸(該軸垂直於多邊形自身的邊,用於投影計算)。代碼如下所示:
(function (window) {
var OBB = function (centerPoint, width, height, rotation) {
this.centerPoint = centerPoint;
this.extents = [width / 2, height / 2];
this.axes = [new Vector2(Math.cos(rotation), Math.sin(rotation)), new Vector2(-1 * Math.sin(rotation), Math.cos(rotation))];
this._width = width;
this._height = height;
this._rotation = rotation;
}
window.OBB = OBB;
})(window);
其所依賴的Vector2這個類如下所示:
(function (window) {
Vector2 = function (x, y) {
this.x = x || 0;
this.y = y || 0;
};
Vector2.prototype = {
sub: function (v) {
return new Vector2(this.x - v.x, this.y - v.y)
},
dot: function (v) {
return this.x * v.x + this.y * v.y;
}
};
window.Vector2 = Vector2;
} (window))
然后基於這個數據結構,進行OBB之間的相交測試。為OBB擴展一個方法,即或者在任意軸上的投影半徑:
OBB.prototype = {
getProjectionRadius: function (axis) {
returnthis.extents[0] * Math.abs(axis.dot(this.axes[0])) + this.extents[1] * Math.abs(axis.dot(this.axes[1]));
}
}
這里你可能需要讀者了解Vector2.dot的幾何意義:若b為單位矢量,則a與b的點積即為a在方向b的投影。
有了這些,就可以進行相交檢測。由上面的判定方式,可以得出,兩個矩形之間的碰撞檢測需要判斷四次(每個投影軸一次)。完整檢測代碼如下所示:
(function (window) {
var CollisionDetector = {
detectorOBBvsOBB: function (OBB1, OBB2) {
var nv = OBB1.centerPoint.sub(OBB2.centerPoint);
var axisA1 = OBB1.axes[0];
if (OBB1.getProjectionRadius(axisA1) + OBB2.getProjectionRadius(axisA1) <= Math.abs(nv.dot(axisA1))) return false;
var axisA2 = OBB1.axes[1];
if (OBB1.getProjectionRadius(axisA2) + OBB2.getProjectionRadius(axisA2) <= Math.abs(nv.dot(axisA2))) return false;
var axisB1 = OBB2.axes[0];
if (OBB1.getProjectionRadius(axisB1) + OBB2.getProjectionRadius(axisB1) <= Math.abs(nv.dot(axisB1))) return false;
var axisB2 = OBB2.axes[1];
if (OBB1.getProjectionRadius(axisB2) + OBB2.getProjectionRadius(axisB2) <= Math.abs(nv.dot(axisB2))) return false;
return true;
}
}
window.CollisionDetector = CollisionDetector;
})(window)
這里拿兩個OBB的中心點連線在坐標軸上的投影長度和兩個矩形投影半徑之和進行對比,如果半徑之后都小於或者等於中心連線之后才判定為碰撞,否則判定為分離狀態。
集成圖形化測試接口
為了更加直觀的測試OBB碰撞檢測方法,使用Easeljs輸出碰撞的狀態。當兩個矩形沒有發生碰撞的時候,兩矩形呈現藍色;當兩個矩形發生碰撞的時候,兩矩形呈現紅色。先引入相關的腳本庫以及用於顯示的canvas畫布:
<script src="Vector2.js" type="text/javascript"></script> <script src="OBB.js" type="text/javascript"></script> <script src="CollisionDetector.js" type="text/javascript"></script> <script src="easel.js" type="text/javascript"></script> <canvas id="testCanvas" width="980" height="580">
然后進行OBB初始化以及碰撞檢測:
var OBB1, OBB1x = 100, OBB1y = 150, OBB1w = 30, OBB1h = 140, OBB1r = 30;
var OBB2, OBB2x = 100, OBB2y = 70, OBB2w = 40, OBB2h = 110, OBB2r = 40;
var canvas;
var stage;
var color;
function init() {
canvas = document.getElementById("testCanvas");
stage = new Stage(canvas);
Ticker.addListener(window);
}
function tick() {
stage.removeAllChildren();
OBB1r += 2;
OBB2r += 1;
OBB1 = new OBB(new Vector2(OBB1x, OBB1y), OBB1w, OBB1h, OBB1r * Math.PI / 180);
OBB2 = new OBB(new Vector2(OBB2x, OBB2y), OBB2w, OBB2h, OBB2r * Math.PI / 180);
var r = CollisionDetector.detectorOBBvsOBB(OBB1, OBB2);
color=r?"red":"#00F";
OBB1 = new Container();
stage.addChild(OBB1);
OBB1.x = OBB1x;
OBB1.y = OBB1y;
var frame1 = new Shape();
frame1.graphics.beginFill(color).drawRect(0, 0, OBB1w, OBB1h);
frame1.rotation = OBB1r;
frame1.regX = OBB1w / 2;
frame1.regY = OBB1h / 2;
OBB1.addChild(frame1);
OBB2 = new Container();
stage.addChild(OBB2);
OBB2.x = OBB2x;
OBB2.y = OBB2y;
var frame2 = new Shape();
frame2.graphics.beginFill(color).drawRect(0, 0, OBB2w, OBB2h);
frame2.rotation = OBB2r;
frame2.regX = OBB2w / 2;
frame2.regY = OBB2h / 2;
OBB2.addChild(frame2);
stage.update();
}
init();
以上代碼定義了兩個旋轉的OBB包圍盒,當他們發生碰撞則改變繪制的顏色,使其成為紅色。運行代碼,效果圖5和6所示。
圖5 未發生碰撞
圖6 發生碰撞
這里是2D情況下的OBB碰撞檢測,對於3D OBB碰撞檢測,更為復雜。需要測試15個分離軸以確定OBB的相交狀態,兩個OBB的坐標軸各3個,以及垂直於每個軸的9個軸。除了坐標軸個數不一樣,其相交測試思路和本文一致,本文不再探討。