1. 程式人生 > >《實時碰撞檢測演算法技術》讀書筆記(一):包圍體(BV)

《實時碰撞檢測演算法技術》讀書筆記(一):包圍體(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個彼此正交的邊向量,以及中心點、一個旋轉矩陣和31/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處於分離狀態。如下圖:


    對於OBB2D最多測試四個分離軸(A的兩個座標軸,B的兩個座標軸),3D最多測試15個分離軸(A的三個、B的三個,以及垂直於每個軸的9個軸)。若在所有軸上均不重疊,則不相交,否則在任一軸上重疊則判定為相交且退出測試。

    可將B轉換至A的座標系統中從而減少運算元量。設tBA的位移向量,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的計算在此不進行詳細說明。