天天看點

AABB&OBB碰撞 內建圖形化測試接口

原文位址:http://www.cnblogs.com/lyggqm/p/5386174.html

包圍體是一個簡單的幾何空間,裡面包含着複雜形狀的物體。為物體添加包圍體的目的是快速的進行碰撞檢測或者進行精确的碰撞檢測之前進行過濾(即當包圍體碰撞,才進行精确碰撞檢測和處理)。包圍體類型包括球體、軸對齊包圍盒(AABB)、有向包圍盒(OBB)、8-DOP以及凸殼。如圖1所示。

AABB&OBB碰撞 內建圖形化測試接口

圖1 依次是球體、AABB、OBB

可以看到圖1是3D包圍體,在2D包圍體如圖2所示:
AABB&OBB碰撞 內建圖形化測試接口

圖2 依次是球體、AABB、OBB

包圍球

包圍球碰撞檢測方法是用球體包圍整個幾何體, 無論是幾何體還是相交測試都很簡單; 但是它的緊密性太差。因為除了在3 個坐标軸上分布得比較均勻的幾何體外, 幾乎都會留下較大的空隙, 需要花費大量的預處理時間, 以構造一個好的層次結構逼近對象。當物體變形之後,包圍球樹需要重新計算。是以,它是使用得比較少的一種包圍盒。當對象發生旋轉運動時, 包圍球不需作任何更新, 這是包圍球的較優秀特性; 當幾何對象進行頻繁的旋轉運動時, 采用包圍球可能得到較好結果。

AABB盒

AABB盒,一個3D的AABB就是一個簡單的六面體,每一邊都平行于一個坐标平面,矩形邊界框不一定都是立方體,它的長、寬、高可以彼此不同。

AABB的重要性質: Ymin <= Y <= Ymax Zmin <= Z <= Zmax 特别重要的兩個頂點為:Pmin = [Xmin Ymin Zmin],Pmax = [ Xmax Ymax Zmax].  

先介紹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所示。

AABB&amp;OBB碰撞 內建圖形化測試接口

圖3 矩形和矩形投影檢測的四條軸

要計算兩個OBB是否碰撞,隻需要計算他們在圖3上的4個坐标軸上的投影是否有重疊,如果有,則兩多邊形有接觸。這也可以擴充到任意多邊形,如圖4所示。

AABB&amp;OBB碰撞 內建圖形化測試接口

圖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所示。

AABB&amp;OBB碰撞 內建圖形化測試接口

圖5 未發生碰撞

AABB&amp;OBB碰撞 內建圖形化測試接口

圖6 發生碰撞

這裡是2D情況下的OBB碰撞檢測,對于3D OBB碰撞檢測,更為複雜。需要測試15個分離軸以确定OBB的相交狀态,兩個OBB的坐标軸各3個,以及垂直于每個軸的9個軸。除了坐标軸個數不一樣,其相交測試思路和本文一緻,本文不再探讨。

繼續閱讀