《实时碰撞检测算法技术》读书笔记(一):包围体(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的计算在此不进行详细说明。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页