《實時碰撞檢測演算法技術》讀書筆記(一):包圍體(BV)
概述:
在碰撞檢測中,為減少計算消耗,在進行相交測試前,可以先進行粗略的包圍體(BV)測試。對於某些應用程式,包圍體測試足以提供碰撞檢測依據。
一般情況下,包圍體計算須採用預處理而非實時計算。當包圍體所包含的物件移動時,一些包圍體需要實現空間重對齊。因此,若包圍體的計算代價高昂,重對齊包圍體就是一個更可取的方法。
包圍體的期望特徵:
A)低消耗的相交測試。
B)實現緊密擬合。
C)計算耗費較少。
D)易於旋轉和變換。
E)記憶體佔用較少。
包圍體型別:
球體、軸對齊包圍盒(AABB)、有向包圍盒(OBB)、8-DOP以及凸殼
以下僅對球體,AABB和OBB進行簡單介紹。
軸對齊包圍盒(AABB):
軸對齊包圍盒(AABB)是應用最廣泛的包圍體之一。最大特點是能實現快速的相交測試,即僅執行相應的座標值之間的比較。
軸對齊包圍盒(AABB)有3種常規表達方式
1.採用各座標軸上的最小值和最大值
//region R = (x,y,z) | min.x <= x <= max.x, min.y <= y <= max.y, min.z <= z <= max.z struct AABB { Point min; Point max; };
AABB間的相交測試
bool Collision(AABB a, AABB b) { //Exit with no intersection if separated along an axis if(a.max[0] < b.min[0] || a.min[0] > b.max[0]) return false; if(a.max[1] < b.min[1] || a.min[1] > b.max[1]) return false; if(a.max[2] < b.min[2] || a.min[2] > b.max[2]) return false; //Overlapping on all axes means AABs are intersecting return true; }
2.採用一個最小頂點值和直徑範圍dx,dy,dz
//region R = (x,y,z) | min.x <= x <= min.x + dx, min.y <= y <= min.y + dy, min.z <= z <= min.z + z
struct AABB {
Point min;
float d[3];
};
AABB間的相交測試
bool Collision(AABB a, AABB b) {
float t;
if((t = a.min[0] - b.min[0]) > b.d[0] || -t > a.d[0]) return false;
if((t = a.min[1] - b.min[1]) > b.d[1] || -t > a.d[0]) return false;
if((t = a.min[2] - b.min[2]) > b.d[2] || -t > a.d[0]) return false;
return true;
}
3.給定AABB的中心點C,以及各軸向半徑rx,ry,rz
//region R = (x, y, z) | |c.x - x| <= rx, |c.y - y| <= ry, |c.z - z| <= rz
struct AABB {
Point c;
float r[3];
};
AABB間的相交測試
bool Collision(AABB a, AABB b) {
if(Abs(a.c[0] - b.c[0]) > (a.r[0] + b.r[0])) return false;
if(Abs(a.c[1] - b.c[1]) > (a.r[1] + b.r[1])) return false;
if(Abs(a.c[2] - b.c[2]) > (a.r[2] + b.r[2])) return false;
return true;
}
對於需要執行大量相交測試的碰撞檢測系統,可根據狀態的相似性對測試進行排序。例如,如果相應操作基本出現在xz平面,則y座標測試應最後執行,從而使狀態的變化降低到最小程度。
若物體只是以平移方式運動,與“最大-最小值”方式相比,另外兩種更加方便——只需要更新6個引數中的3個(即Point中的x,y,z)。“中心-半徑”的另一個比較有用的性質是:可作為一個包圍球加以檢測。
對於AABB的計算與更新,在此僅列出以下4種基本方法,不進行詳細說明:
A)使用固定尺寸且較為鬆散的AABB包圍物體物件。
B)基於原點確定動態且緊湊的重構方式。
C)利用爬山法確定動態且緊湊的重構方式。
D)基於旋轉後的AABB,確定近似且動態的重構方式。
Spheres球體:
與包圍盒相比,球體是另一類較常用的包圍體。如同包圍盒,球體也具備快速相交測試這一特徵。同時,球體基本不受旋轉變換的影響。
//region R = (x, y, z) | (x – c.x) ^ 2 + (y – c.y) ^ 2 + (z – c.z) ^ 2 <= r ^2
struct Sphere {
Point c; //Sphere center
float r; //Sphere radius
};
球體間的相交測試:
計算兩球體間的幾何距離,並與半徑和比較。可以採用距離的平方進行相關計算,從而避免計算成本較高的平方根運算。
bool Collision(Sphere a, Sphere a) {
Vector d = a.c – b.c;
float dist = Dot(d, d);
float radiusSum = a,r + b.r;
return dist <= radiusSum * radiusSum;
}
這裡你可能需要了解Dot的幾何意義,Dot(Vector a, Vector b)即a與b的點積,若b為單位向量,則是a在b方向上的投影。
關於包圍球的計算在此不進行詳細說明。
方向包圍盒(OBB)
方向包圍盒是一個長方體,類似於AABB,但是具有方向性。存在多種OBB表達方式:8個頂點的點集、6個面的面集、三個平行面集合、一個頂點和3個彼此正交的邊向量,以及中心點、一個旋轉矩陣和3個1/2邊長。通常最後一種表達方式最為常用,其測試演算法基於分離軸理論。
結構顯示如下:
//region R = x | x = c + r * u[0] + s * r[1] + t * u[2], |r| <= e[0], |s| <= e[1], |t| <= e[2]
struct OBB {
Point c; //OBB center point
Vector u[3]; //Local x-, y-, z-axes
Vector e; //Positive halfwidth extents of OBB along each axis
};
OBB無疑是包圍體中記憶體消耗較大的一類。為節省記憶體開銷,可以只儲存旋轉矩陣中的兩個軸,第三軸僅在測試時利用向量叉積進行計算。相對來講,這能夠節省CPU的操作開銷,同時還能節省3個浮點數分量。
OBB間的相交測試;
OBB相交測試可以採用“分離軸測試”加以實現。對於某一軸L,若兩盒投影半徑之和小於中心點間投影距離,則OBB處於分離狀態。如下圖:
對於OBB,2D最多測試四個分離軸(A的兩個座標軸,B的兩個座標軸),3D最多測試15個分離軸(A的三個、B的三個,以及垂直於每個軸的9個軸)。若在所有軸上均不重疊,則不相交,否則在任一軸上重疊則判定為相交且退出測試。
可將B轉換至A的座標系統中從而減少運算元量。設t為B至A的位移向量,R為將B轉換(投影)至A座標系中的旋轉矩陣,則在L軸上測試如下:
需注意的是,為使測試更加高效,各軸測試順序應按表中內容執行。
bool Collision(OBB &a, OBB &b) {
float ra, rb;
Matrix33 R, AbsR;
//Compute rotation matrix expressing b in a’s coordinate frame
for(int i = 0; j < 3; j++) {
for(int j = 0; j < 3; j++)
R[i][j] = Dot(a.u[i], b.u[j]);
}
//Compute translation vector t
Vector t = b.c - a.c;
//Bring translation into a’s coordinate frame
t = Vector(Dot(t, a.u[0]), Dot(t, a.u[1]), Dot(t, a.u[2]));
//Compute common subexpressions. Add in an epsilon term to
//counteract arithmetic errors when tow edges are parallel and
//their cross product is (near) null
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++)
AbsR[i][j] = Abs(R[i][j]) + EPSILON;
}
//Test axes L = A0, L = A1, L = A2
for(int i = 0; i < 3; i++) {
ra = a.e[i];
rb = b.e[0] * AbsR[i][0] + b.e[1] * AbsR[i][1] + b.e[2] * AbsR[i][2];
if(Abs(t[i]) > ra + rb) return false;
}
//Test axes L = B0, L = B1, L = B2
for(int i = 0; i < 3; i++) {
ra = a.e[0] * AbsR[i][0] + a.e[1] * AbsR[i][1] + a.e[2] * AbsR[i][2];
rb = b.e[i];
if(Abs(t[0] * R[0][i] + t[1] * R[1][i] + t[2] * R[2][i]) > ra + rb) return false;
}
//Test axis L = A0 x B0
ra = a.e[1] * AbsR[2][0] + a.e[2] * AbsR[1][0];
rb = b.e[1] * AbsR[0][2] + b.e[2] * AbsR[0][1];
if(Abs(t[2] * R[1][0] - t[1] * R[2][0]) > ra + rb) return false;
//Test aixs L = A0 x B1
ra = a.e[1] * AbsR[2][1] + a.e[2] * AbsR[1][1];
rb = b.e[0] * AbsR[0][2] + b.e[2] * AbsR[0][0];
if(Abs(t[2] * R[1][1] - t[1] * R[2][1]) > ra + rb) return false;
//Test aixs L = A0 x B2
ra = a.e[1] * AbsR[2][2] + a.e[2] * AbsR[1][2];
rb = b.e[0] * AbsR[0][1] + b.e[1] * AbsR[0][0];
if(Abs(t[2] * R[1][2] - t[1] * R[2][2]) > ra + rb) return false;
//Test aixs L = A1 x B0
ra = a.e[0] * AbsR[2][0] + a.e[2] * AbsR[0][0];
rb = b.e[1] * AbsR[1][2] + b.e[2] * AbsR[1][1];
if(Abs(t[0] * R[2][0] - t[2] * R[0][0]) > ra + rb) return false;
//Test aixs L = A1 x B1
ra = a.e[0] * AbsR[2][1] + a.e[2] * AbsR[0][1];
rb = b.e[0] * AbsR[1][2] + b.e[2] * AbsR[1][0];
if(Abs(t[0] * R[2][1] - t[2] * R[0][1]) > ra + rb) return false;
//Test aixs L = A1 x B2
ra = a.e[0] * AbsR[2][2] + a.e[2] * AbsR[0][2];
rb = b.e[0] * AbsR[1][1] + b.e[1] * AbsR[1][0];
if(Abs(t[0] * R[2][2] - t[2] * R[0][2]) > ra + rb) return false;
//Test aixs L = A2 x B0
ra = a.e[0] * AbsR[1][0] + a.e[1] * AbsR[0][0];
rb = b.e[1] * AbsR[2][2] + b.e[2] * AbsR[2][1];
if(Abs(t[2] * R[0][0] - t[0] * R[1][0]) > ra + rb) return false;
//Test aixs L = A2 x B1
ra = a.e[0] * AbsR[1][1] + a.e[1] * AbsR[0][1];
rb = b.e[0] * AbsR[2][2] + b.e[2] * AbsR[2][0];
if(Abs(t[1] * R[0][1] - t[0] * R[1][1]) > ra + rb) return false;
//Test aixs L = A2 x B2
ra = a.e[0] * AbsR[1][2] + a.e[1] * AbsR[0][2];
rb = b.e[0] * AbsR[2][1] + b.e[1] * AbsR[2][0];
if(Abs(t[1] * R[0][2] - t[0] * R[1][2]) > ra + rb) return false;
//Since no separating axis is found, the OBBs must be intersecting
return true;
}
關於OBB的計算在此不進行詳細說明。