1. 程式人生 > 其它 >JAVA智慧裝置基於OpenGL的3D開發技術 之AABB碰撞檢測演算法論述

JAVA智慧裝置基於OpenGL的3D開發技術 之AABB碰撞檢測演算法論述

摘要:無論是PC機的3D還是智慧裝置應用上,碰撞檢測始終是程式開發的難點,甚至可以用碰撞檢測作為衡量3D引擎是否完善的標準。現有許多3D碰撞檢測演算法,其中AABB碰撞檢測是一種卓有成效而又經典的檢測演算法,本文將為讀者詳細論述AABB碰撞檢測的各各技術點。

關鍵詞:J2ME;Open GL;JSR-184;M3G;CLDC2.0;3D引擎;Swerve引擎;AABB碰撞檢測;

第一部分、前述:

對於移動 終端有限的運算能力,幾乎不可能檢測每個物體的多邊形和頂點的穿透,那樣的運算量對手機等裝置來講是不可完成的,所以移動裝置上使用的碰撞檢測不可能使用 太精確的檢測,而且對於3D碰撞檢測問題,還沒有幾乎完美的解決方案。目前只能根據需要來取捨運算速度和精確性達到目地。

第二部分、J2ME技術:

1、體系結構

為 滿足消費者和嵌入式市場不斷髮展和多樣化的需求,SUN公司的J2ME平臺採用模組化、可擴充套件的設計。這種設計是通過三層軟體模型來實現的,這三層都構建 於智慧裝置的作業系統之上。J2ME體系結構依照各種裝置的特性,將架構分為簡表、配置、虛擬機器三層,這使J2ME可在每一類裝置的限制下工作。

2、J2ME 3D開發包

JSR184標準為java移動應用程式定義了一個簡潔的3D API介面,J2ME程式可以非常方便地使用它來實現3D應用,如遊戲等。此API為非常輕量級的,整個API的完整實現不超過150KB。

3、M3G開發包

M3G是J2ME的一個可選包,以Open GL為基礎的精簡版,一共有30 個類,只執行在CLDC1.1及CLDC2.0上(因為必須支援浮點運算,而CLCD1.0不支援),可以在MIDP1.0和MIDP2.0中使用。

4、開發環境

作業系統:Microsoft Windows XP

IDE: Ecplise  Latefrom Version 3.3.1.1

開發包:Java2 Platform Micor Edition、JSR184、M3G

三維圖形處理:3D MAX 設計3D環境

平面圖像處理:Photo Shop設計在3D MAX或其它用到的圖片

3D環境測試:M3G Tool Kit

模擬機:WTK2.5.2或NOKIA6131等

第三部分、需求分析

好 的碰撞檢測要求人物在場景中可以平滑移動,遇到一定高度的臺階可以自動上去,而過高的臺階則把人物擋住,遇到斜率較小的斜坡可以上去,斜率過高則會把人物 擋住,在各種前進方向被擋住的情況下都要儘可能地讓人物沿合理的方向滑動而不是被迫停下,在滿足這些要求的同時還要做到足夠精確和穩定,防止人物在特殊情 況下穿牆而入。

AABB碰撞檢測演算法對於以上要求都能達到比較理想的效果。

第四部分、演算法具體論述

一、AABB檢測前述

在遊戲中的大多數物體是方形的或者是長條形的,在進行碰撞檢測時應該用方盒來代表物體。一種常見的檢測模型是立方體邊界框,如圖1-1展示了一個AABB檢測盒和它裡面的物體。

圖1-1

在 此涉及到座標軸平行(Axially-aligned)這個概念,座標軸平行不僅指盒體與世界座標軸平行,同時也指盒體的每個面都和一條座標軸垂直,這樣 一個基本資訊就能減少轉換盒體時操作的次數。AABB技術在當今的許多遊戲中都得到了應用,開發者經常用它們作為模型的檢測模型,當然,提高精度的同時也 會降低速度。

因為AABB總是與座標軸平行,不能在旋轉物體時簡單地旋轉AABB盒體,而是應該在每一幀都重新計算。如果知道每個物件的內容,這個計算就不算困難了,也不降低遊戲的速度。然而,還面臨著精度的問題。

假如有一個3D的細直剛性直棒,並且要在每一幀動畫中都有重建它的AABB包裝盒。可以看到每一幀中的包裝盒都不一樣而且精度也會隨之改變,如圖1-2。

圖1-2

可 以注意到AABB對物體的方向很敏感,同一物體的不同方向,AABB也可能不同(由於球體只有一個自由度,所以檢測球對物體方向不敏感)。當物體在場景中 移動時,它的AABB也需要隨之移動,當物體發生旋轉選擇:用變換後的物體來重新計算AABB,或者對AABB做和物體同樣的變換。

如果物體沒有發生扭曲,可以通過“變換後的AABB”重新計算,因為該方法要比通過“變換後的物體”計算快得多,因為AABB只有8個頂點。變換AABB得出新的AABB要比變換物體的運算量小,但是也會帶來一定的誤差,如圖1-3。

圖1-3

比較圖中原AABB(藍色部分)和新AABB(右邊比較大的方框圖),它是通過旋轉後的AABB計算得到的,新AABB幾乎是原來AABB的兩倍,注意,如果從旋轉後的物體而不是旋轉後的AABB來計算新的AABB,它的大小將和原來的AABB相同。

二、AABB的表達方法

先介紹AABB的表達方法,AABB內的點滿足以下條件:

Xmin≤XXmax;

Ymin≤YYmax;

Zmin≤ZZmax;

因此只需要知道兩個特別重要的頂點(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]);   }返回X軸座標點

   float ySize() {     return (max[1] - min[1]);}返回Y軸座標點

   float zSize() {     return (max[2] - min[2]);} 返回Z軸座標點

當新增一個頂點到包裝盒時,需要先與這兩個頂點進行比較。

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對不可移動物體的靜態檢測

AABB的靜態檢測比較簡單,檢測兩靜止包裝盒是否相交,它是一種布林測試,測試結果只在相交或者不相交。這裡我們提供了獲取相交範圍資訊的方法,一般來說,這種測試的目的是為了返回一個布林什。碰撞的示意如圖1-4

圖1-4

可以利用AABB的結構來加快新的AABB的計算速度,而不用變換8個頂點,再從這8個頂點中計算新AABB。下面簡單地回顧4×4矩陣變換一個3D點的過程。

圖1-5

通過原邊界框(Xmin, Ymin,Zmin,Xmax ,Ymax ,Zmax)計算新邊界框(Xmin, Ymin, Zmin,Xmax ,Ymax ,Zmax),現在的任務是計算X1min的速度。換句話說,希望找到m11x+m12y+m13z+m14的最小值。其中[XYZ]是原8個頂點的任意一個。

變換的目的是找出這些點經過變換後哪一個的X座標最小。看第一個乘積m11x,為了最小化乘積,必須決定是用Xmin還是Xmax來替換其中的X。顯然,如果m11>0,用Xmin能得到最小化的乘積;如果說m11<0,則用Xmax能得到最小化乘積。

比較方便的是,不管Xmin還是Xmax中哪一個都應用這個計算過程(其它元素不影響大小)。

根據變換矩陣和原有的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];              maxx += m[3];
                  miny += m[7];              maxy += m[7];
                  minz += m[11]; maxz += m[11];
                  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; max[0] = maxx;
                  max[1] = maxy;            max[2] = maxz;
      }

為了使用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;} }
}

為了檢驗碰撞檢測的使用構造了兩個立方體,並各自綁定了一個包裝盒。

/**************************立方體1************************/
Mesh1= createCube();
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};
Box1.setMax{1.0f,1.0f,1.0};
Mesh1.getCompositeTransform(cubeTransform);
World.addChild(mesh1);
/**************************立方體2************************/
Mesh2= createCube();
Mesh2.setTranslation{1.0f,0.0f,0.0f};
Mesh2.setOrientation{90,0.0f,1.0f,0.0f};
Mesh2.setScale{0.5f,0.5f,0.5f};
Box2 = new AABB();
Box2.setMin{-1.0f,-1.0f,-1.0f};
Box2.setMax{1.0f,1.0f,1.0};
Mesh2.getCompositeTransform(cubeTransform);
World.addChild(mesh2);

檢測包裝盒1和包裝盒2是否碰撞的程式碼如下:

isCollided = box1.intersectAABB(box2,null);

編譯執行程式,設定兩個立方體不同的位置和角度,可以比較精確地檢測出它們的碰撞情況,如圖1-6與1-7所示。

圖1-6                                 圖1-7

四、AABB對可移動物體的動態檢測

移動檢測的目標是計算運動AABB碰撞到靜態AABB的時刻,因此需要計算出兩個AABB在所有維上的第一個點。為了簡化起見,可以把上述問題先歸結到某一維,然後再將三維結合到一起。假設把問題投影到X軸,如圖1-8所示。

圖1-8

綠色矩形代表沿座標軸滑動的AABB,t=0時,運動AABB完全位於靜止AABB的左邊。當t=1時,運動AABB完全位於靜止AABB的右邊。當t=tenter時,兩個AABB剛剛相交,當t=tleave時,兩個AABB脫離碰撞。

對照相館上圖,可以推匯出兩個AABB接觸和離開的時間:

AABB的動態檢測有3個要點。

(1)   如果速度為0,兩個包裝盒要麼一直相交,要麼一直分離。

(2)   不管物體從哪個方向運動,碰撞過程中,肯定是先入後出,所以有tenter< tleave。

(3)   如tenter和tleave超出運動時間範圍,那麼在此範圍內它們是不相交的。

檢測出某一維碰撞還不夠,還需要進行其它兩維的檢測,然後取結果的交集。如果交集為空,那麼AABB包裝盒沒有相交,如果區間範圍在時間段[0,1]之外,那麼在此區間也不相交。對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如圖1-9所示。兩個包裝盒距離0。5,速度為3。

檢測程式碼如下:

float [] speed = new float [] {3.0f,0.0f,0.0f};

float [] tEnter = intersectAABB(box1,box2,speed);

輸出結果為0.16667,完全符合預期的猜測。

第五部分、總結

做 碰撞檢測時,該技術的重要性容易被人忽視,顯然這符合日常生活中的常識。有時出現BUG,也很不容易被發現,例如人物無緣無故被卡住不能動或人物穿越了障 礙等,所以像AABB這樣有效的演算法在碰撞檢測中是起極重要作用的,由上所述正確使用AABB可並不是件容易的事,這就需要讀者下一番功夫。

參考資料

《3D 圖形學》,電子工業出版社

《Open GL 開發技術》,電子工業出版社

《J2ME 3D手機遊戲開發詳解》,人民郵電出版社,2007.11

《J2ME手機遊戲開發詳解》,清華大學出版社,2005.8

《Java手機/PDA程式設計入門》電子工業出版社出版 2004.3