在Cocos2d-x 3.x版本添加了對3D物體的支持后,3D物體的碰撞檢測方法也隨之更新,其中一種最簡單的碰撞檢測方法就是AABB碰撞檢測。
1. AABB包圍盒
在游戲中,為了簡化物體之間的碰撞檢測運算,通常會對物體創建一個規則的幾何外形將其包圍。
其中,AABB(axis-aligned bounding box)包圍盒被稱為軸對其包圍盒。
二維場景中的AABB包圍盒具備特點:(注:由於Cocos2d-x是基於Opengl ES的,所以下圖中的所有坐標系均采用右手直角坐標系)
(1) 表現形式為四邊形,即用四邊形包圍物體。
(2) 四邊形的每一條邊,都會與坐標系的軸垂直。
如圖 1-1 所示:
圖1-1
三維場景中的AABB包圍盒特點:
(1) 表現形式為六面體。
(2) 六面體中的每條邊都平行於一個坐標平面。
如圖 1-2 所示:
圖 1-2
在圖1-2中,為了更明顯的展示AABB包圍盒的特點,在最右側展示了一個OBB(Oriented Bounding Box)包圍盒,也稱作有向包圍盒。
可以看出,AABB包圍盒與OBB包圍盒的最直接的區別就是,AABB包圍盒是不可以旋轉的,而OBB包圍盒是可以旋轉的,也就是有向的。
2. 二維場景中的AABB碰撞檢測原理
首先來看一張二維場景中的物體碰撞圖:
圖 2-1
在圖 2-1中,分別做物體A與物體B在X,Y軸方向的投影,物體A的Y軸方向最大點坐標為Y1,最小點坐標Y2,X軸方向最小點坐標X1,最大點坐標X2,物體B同理。
圖中紅色區域為物體A與物體B投影的重疊部分。
可以看出,AABB碰撞檢測具有如下規則:
物體A與物體B分別沿兩個坐標軸做投影,只有在兩個坐標軸都發生重疊的情況下,兩個物體才意味着發生了碰撞。
所以,在程序中做二維游戲的AABB碰撞檢測時,只需驗證物體A與物體B是否滿足如下條件:
(1)物體A的Y軸方向最小值大於物體B的Y軸方向最大值;
(2)物體A的X軸方向最小值大於物體B的X軸方向最大值;
(3)物體B的Y軸方向最小值大於物體A的Y軸方向最大值;
(4)物體B的X軸方向最小值大於物體A的X軸方向最大值;
若滿足上述條件,則證明物體A與物體B並未發生重合,反之,則證明物體A與物體B重合。
3. 三維場景中的AABB碰撞檢測原理
首先,再來看一下圖2-1中的二維物體A和物體B的包圍盒,可以發現實際上判斷物體A與物體B是否發生重合只需要知道兩個信息:
(1) 物體A的最小點的信息,即圖2-1中A的左下角點;以及物體A的最大點的信息,即圖2-1中A的右上角點。
(2) 物體B的最小點的信息,物體B的最大點的信息。
也就是說在二維場景的碰撞檢測中,每個物體的頂點坐標信息都可以由兩個坐標來確定,即兩個坐標就可以標識一個物體了,所以兩個物體的碰撞檢測只需要獲得到四個點坐標就可以了。
之前在圖1-2中已經看到,三維場景中物體的AABB包圍盒是一個六面體,其坐標系對於二維坐標系來講只是多了一個Z軸,所以實際上在三維場景中物體的AABB碰撞檢測依然可以采用四個點信息的判定來實現。即從物體A的八個頂點與物體B的八個頂點分別選出兩個最大與最小的頂點進行對比。三維物體的AABB包圍盒的八個頂點依舊可以用兩個頂點來標識,如圖 3-1 所示:
圖3-1
只要確定了圖中黑色點部分的坐標,就可以確定八個頂點的全部信息了。
在Cocos2d-x 3.x版本中,為開發者提供了AABB類,用於保存包圍盒的最大頂點與最小頂點的信息,並且為每個Sprite3D對象提供了獲取AABB包圍盒的接口,在AABB類同時提供了判斷相應的碰撞檢測的方法。有一點需要注意的是,CCAABB類中一開始保存的最大頂點與最小頂點的信息實際上是物體坐標系中的信息,而實際上在碰撞檢測時需要將其轉換成世界坐標系中的點,這一過程在Sprite3D中的getAABB()方法中實現,可通過CCAABB中的
transform()方法來完成。
下面對AABB的源碼進行分析:
class CC_3D_DLL AABB { public: /** * 構造函數 */ AABB(); /** * 構造函數 參數:最小頂點坐標,最大頂點坐標 */ AABB(const Vec3& min, const Vec3& max); /** * 構造函數 參數:AABB包圍盒 */ AABB(const AABB& box); /** * 獲取包圍盒中心點坐標 */ Vec3 getCenter(); /* 獲取包圍盒八個頂點信息 * Z軸正方向的面 * verts[0] : 左上頂點 * verts[1] : 左下頂點 * verts[2] : 右下頂點 * verts[3] : 右上頂點 * * Z軸負方向的面 * verts[4] : 右上頂點 * verts[5] : 右下頂點 * verts[6] : 左下頂點 * verts[7] : 左上頂點 */ void getCorners(Vec3 *dst) const; /** * 判斷兩個包圍盒是否重合 */ bool intersects(const AABB& aabb) const; /** * 判斷一個點是否在包圍盒內 */ bool containPoint(const Vec3& point) const; /** 由兩個包圍盒生成一個能同時包圍這兩個包圍盒的最小包圍盒 */ void merge(const AABB& box); /** * 設置包圍盒的最大頂點與最小頂點 */ void set(const Vec3& min, const Vec3& max); /** * 復位函數 初始化最大最小頂點信息 */ void reset(); bool isEmpty() const; /** * 更新最大頂點與最小頂點信息 */ void updateMinMax(const Vec3* point, ssize_t num); /** * 由一個矩陣對對包圍盒進行頂點變換 */ void transform(const Mat4& mat); public: Vec3 _min; //三維向量 保存最小點坐標 Vec3 _max; //三維向量 保存最大點坐標 };
CCAABB.cpp 文件
#include "3d/CCAABB.h" NS_CC_BEGIN //構造函數 AABB::AABB() { reset(); //初始化最大頂點與最小頂點 } AABB::AABB(const Vec3& min, const Vec3& max) { set(min, max); //設置最大頂點與最小頂點 } AABB::AABB(const AABB& box) { set(box._min,box._max); //設置最大頂點與最小頂點 } //獲取包圍盒中心點坐標 Vec3 AABB::getCenter() { Vec3 center; center.x = 0.5f*(_min.x+_max.x); center.y = 0.5f*(_min.y+_max.y); center.z = 0.5f*(_min.z+_max.z); return center; } //獲取包圍盒八個頂點信息 void AABB::getCorners(Vec3 *dst) const { assert(dst); // 朝着Z軸正方向的面 // 左上頂點坐標 dst[0].set(_min.x, _max.y, _max.z); // 左下頂點坐標 dst[1].set(_min.x, _min.y, _max.z); // 右下頂點坐標 dst[2].set(_max.x, _min.y, _max.z); // 右上頂點坐標 dst[3].set(_max.x, _max.y, _max.z); // 朝着Z軸負方向的面 // 右上頂點坐標 dst[4].set(_max.x, _max.y, _min.z); // 右下頂點坐標 dst[5].set(_max.x, _min.y, _min.z); // 左下頂點坐標 dst[6].set(_min.x, _min.y, _min.z); // 左上頂點坐標 dst[7].set(_min.x, _max.y, _min.z); } //判斷兩個包圍盒是否碰撞 bool AABB::intersects(const AABB& aabb) const { return ((_min.x >= aabb._min.x && _min.x <= aabb._max.x) || (aabb._min.x >= _min.x && aabb._min.x <= _max.x)) && ((_min.y >= aabb._min.y && _min.y <= aabb._max.y) || (aabb._min.y >= _min.y && aabb._min.y <= _max.y)) && ((_min.z >= aabb._min.z && _min.z <= aabb._max.z) || (aabb._min.z >= _min.z && aabb._min.z <= _max.z)); } //判斷點和包圍盒是否碰撞 bool AABB::containPoint(const Vec3& point) const { if (point.x < _min.x) return false; if (point.y < _min.y) return false; if (point.z < _min.z) return false; if (point.x > _max.x) return false; if (point.y > _max.y) return false; if (point.z > _max.z) return false; return true; } //生成一個新的包圍盒 同時容納兩個包圍盒 void AABB::merge(const AABB& box) { // 計算新的最小點坐標 _min.x = std::min(_min.x, box._min.x); _min.y = std::min(_min.y, box._min.y); _min.z = std::min(_min.z, box._min.z); // 計算新的最大點坐標 _max.x = std::max(_max.x, box._max.x); _max.y = std::max(_max.y, box._max.y); _max.z = std::max(_max.z, box._max.z); } //設置最大頂點與最小頂點 void AABB::set(const Vec3& min, const Vec3& max) { this->_min = min; this->_max = max; } //頂點復位 初始化信息 void AABB::reset() { _min.set(99999.0f, 99999.0f, 99999.0f); _max.set(-99999.0f, -99999.0f, -99999.0f); } //檢測坐標信息是否有誤 bool AABB::isEmpty() const { return _min.x > _max.x || _min.y > _max.y || _min.z > _max.z; } //由給定點坐標點重新確定最大最小的坐標向量 void AABB::updateMinMax(const Vec3* point, ssize_t num) { for (ssize_t i = 0; i < num; i++) { // 最小x坐標 if (point[i].x < _min.x) _min.x = point[i].x; // 最小y坐標 if (point[i].y < _min.y) _min.y = point[i].y; // 最小z坐標 if (point[i].z < _min.z) _min.z = point[i].z; // 最大x坐標 if (point[i].x > _max.x) _max.x = point[i].x; // 最大y坐標 if (point[i].y > _max.y) _max.y = point[i].y; // 最大z坐標 if (point[i].z > _max.z) _max.z = point[i].z; } } //通過給定的變換矩陣對包圍盒進行變換 void AABB::transform(const Mat4& mat) { Vec3 corners[8]; //保存包圍盒八個頂點 //朝向z軸正方向的面 //左上頂點坐標 corners[0].set(_min.x, _max.y, _max.z); //左下頂點坐標 corners[1].set(_min.x, _min.y, _max.z); //右下頂點坐標 corners[2].set(_max.x, _min.y, _max.z); //右上頂點坐標 corners[3].set(_max.x, _max.y, _max.z); //朝向z軸負方向的面 //右上頂點坐標 corners[4].set(_max.x, _max.y, _min.z); //右下頂點坐標 corners[5].set(_max.x, _min.y, _min.z); //左下頂點坐標 corners[6].set(_min.x, _min.y, _min.z); //左上頂點坐標 corners[7].set(_min.x, _max.y, _min.z); //頂點變換 for (int i = 0; i < 8; i++) mat.transformPoint(&corners[i]); //復位最大頂點最小頂點 reset(); //重新計算最大最小點信息 updateMinMax(corners, 8); }
4. 總結