转自http://www.cnblogs.com/lyggqm/p/5386174.html
AABB盒,一个3D的AABB就是一个简单的六面体,每一边都平行于一个坐标平面,矩形边界框不一定都是立方体,它的长、宽、高可以彼此不同。
先介绍AABB的表达方法,AABB内的点满足以下条件:
xmin≤x≤xmax
ymin≤y≤ymax
zmin≤z≤zmax
因此只需要知道两个特别重要的顶点(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]); }
float ySize() { return (max[1]-min[1]); }
float zSize() { return (max[2]-min[2]); }
float size(){ return (max[0]-min[0])*(max[1]-min[1])*(max[2]-min[2]);}
当添加一个顶点到包装盒时,需要先与这两个顶点进行比较。
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的静态检测比较简单,检测两个静止包装盒是否相交,它是一种布尔测试,测试结果只有相交或者不相交。这里我们还提供了获取相交范围信息的方法,一般来说,这种测试的目的是为了返回一个布尔值。碰撞的示意如图10-34所示。
图10-34 包装盒的碰撞
检测静态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的计算速度,而不用变换8个顶点,再从这8个顶点中计算新AABB。下面简单地回顾4×4矩阵变换一个3D点的过程。
通过原边界框(xmin,ymin,zmin,xmax,ymax,zmax)计算新边界框(,,,,,),现在的任务是计算的速度。换句话说,希望找到m11x+m12y+m13z+m14的最小值。其中[x,y,z]是原8个顶点中的任意一个。
变换的目的是找出这些点经过变换后哪一个的x坐标最小。看第一个乘积m11x,为了最小化乘积,必须决定是用xmin还是xmax来替换其中的x。显然,如果m11>0,用xmin能得到最小化的乘积;如果m11<0,则用xmax能得到最小化乘积。
比较方便的是,不管xmin还是xmax中哪一个被用来计算,都可以用另外一个来计算。可以对矩阵中的9个元素中的每一个都应用这个计算过程(其他元素不影响大小)。
根据变换矩阵和原有的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]; //x方向上平移
maxx += m[3]; //x方向上平移
miny += m[7]; //y方向上平移
maxy += m[7]; //y方向上平移
minz += m[11]; //z方向上平移
maxz += m[11]; //z方向上平移
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; //用新的AABB坐标替换原有坐标
max[0] = maxx; max[1] = maxy; max[2] = maxz; //用新的AABB坐标替换原有坐标
}
为了使用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;
}
}
//其他方法同上
}
为了检验碰撞检测的使用构造了两个立方体,并各自绑定了一个包装盒。
mesh1 = createCube(); //创建立方体1
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); //设置包装盒1的最小顶点
box1.setMax(1.0f,1.0f,1.0f); //设置包装盒1的最大顶点
mesh1.getCompositeTransform(cubeTransform); //获取立方体1的混合矩阵
box1.setToTransformedBox(cubeTransform); //将变换矩阵应用到包装盒中
world.addChild(mesh1); //将立方体1添加到场景中
mesh2 = createCube(); //创建立方体2
mesh2.setTranslation(-0.5f, 0.0f,0.0f) ; //平移
mesh2.setScale(0.5f,0.5f,0.5f); //缩放
box2 = new AABB(); //包装盒
box2.setMin(-1.0f,-1.0f,-1.0f); //设置包装盒2的最小顶点
box2.setMax(1.0f,1.0f,1.0f); //设置包装盒2的最大顶点
mesh2.getCompositeTransform(cubeTransform); //获取立方体2的混合矩阵
box2.setToTransformedBox(cubeTransform); //将变换矩阵应用到包装盒2中
world.addChild(mesh2); //将立方体2添加到场景中
检测包装盒1和包装盒2是否碰撞的代码如下:
isCollided = box1.intersectAABBs(box2,null); //检测两个AABB包装盒是否碰撞
编译运行程序,设置两个立方体不同的位置和角度,可以比较精确地检测出它们的碰撞情况,如图10-35所示。
检测两个静止AABB的碰撞情况比较简单,只需要在每一维上单独检查它们的重合程度即可。如果在所有维上都没有重合,那么这两个AABB就不会相交。
AABB间的动态检测稍微复杂一些,考虑一个由顶点smin和smax指定的静态包装盒和一个由顶点mmin和mmax指定的动态包装盒(如果两个都是动态的,可以根据相对运动视作如此)。运动的速度由向量s给出,运动时间t假定为0~1。
图10-35 静态物体碰撞检测示意
移动检测的目标是计算运动AABB碰撞到静态AABB的时刻,因此需要计算出两个AABB在所有维上的第一个点。为了简化起见,可以把上述问题先归结到某一维,然后再将三维结合到一起。假设把问题投影到x轴,如图10-36所示。
图10-36 AABB的动态检测
黑色矩形代表沿坐标轴滑动的AABB,t=0时,运动AABB完全位于静止AABB的左边。当t=1时,运动AABB完全位于静止AABB的右边。当t=tenter时,两个AABB刚刚相交,当t=tleave时,两个AABB脱离碰撞。
对照上图,可以推导出两个AABB接触和离开的时间:
,
AABB的动态检测有3个要点。
n 如果速度为0,两个包装盒要么一直相交,要么一直分离。
n 不管物体从哪个方向运动,碰撞过程中,肯定是先入后出,所以有tenter<tleave。
n 如果tenter和tleave超出运动时间范围,那么在此范围内它们是不相交的。
检测出某一维的碰撞还不够,还需要进行其他两维的检测,然后取结果的交集。如果交集为空,那么两AABB包装盒没有相交,如果区间范围在时间段[0,1]之外,那么在此区间也不相交。对AABB进行动态检测的方法定义如下:
float intersectMovingAABB(AABB stationaryBox,AABB movingBox,float []s)
{
float NoIntersection = 1e30f; //没有碰撞则返回大数
float tEnter = 0.0f; //初始化碰撞时间
float tLeave = 1.0f; //初始化离开时间
float Swap = 0.0f; //交换操作中间变量
float [] sBoxmin= stationaryBox.getMin(); //静止包装盒的最小值顶点
float [] sBoxmax= stationaryBox.getMax(); //静止包装盒的最大值顶点
float [] mBoxmin= movingBox.getMin(); //运动包装盒的最小值顶点
float [] mBoxmax= movingBox.getMax(); //运动包装盒的最大值顶点
if (s[0] == 0.0f) { //如果x方向速度为0
if ((sBoxmin[0] >= mBoxmax[0]) ||(sBoxmax[0] <= mBoxmin[0])) {
return NoIntersection; //进行静态检测
}
} else {
float xEnter = (sBoxmin[0]-mBoxmax[0])/s[0]; //计算碰撞时间
float xLeave = (sBoxmax[0]-mBoxmin[0])/ s[0]; //计算离开时间
if (xEnter > xLeave) { //检查顺序
Swap = xEnter;
xEnter = xLeave;
xLeave = Swap;
}
if (xEnter > tEnter) tEnter = xEnter; //更新区间
if (xLeave < tLeave) tLeave = xLeave;
if (tEnter > tLeave) { //是否导致空重叠区
return NoIntersection; //没有碰撞
}
}
if (s[1] == 0.0f) { //y轴速度为0
if ( (sBoxmin[1] >= mBoxmax[1]) || (sBoxmax[1] <= mBoxmin[1])) {
return NoIntersection; //没有相交
}
} else {
float yEnter = (sBoxmin[1]-mBoxmax[1]) / s[1];
float yLeave = (sBoxmax[1]-mBoxmin[1]) / s[1];
if (yEnter > yLeave) {
Swap = yEnter;
yEnter = yLeave;
yLeave = Swap;
}
if (yEnter > tEnter) tEnter = yEnter; //更新区间
if (yLeave < tLeave) tLeave = yLeave;
if (tEnter > tLeave) {
return NoIntersection;
}
}
if (s[2] == 0.0f) { //z方向速度为0
if ((sBoxmin[2] >= mBoxmax[2]) ||(sBoxmax[2] <= mBoxmin[2])) {
return NoIntersection;
}
} else {
float oneOverD = 1.0f / s[2];
float zEnter = (sBoxmin[2]-mBoxmax[2]) / s[2];
float zLeave = (sBoxmax[2]- mBoxmin[2]) / s[2];
if (zEnter > zLeave) {
Swap = zEnter;
zEnter = zLeave;
zLeave = Swap;
}
if (zEnter > tEnter) tEnter = zEnter; //更新区间
if (zLeave < tLeave) tLeave = zLeave;
if (tEnter > tLeave) {
return NoIntersection;
}
}
return tEnter; //返回碰撞时间
}
为了对移动AABB进行检测,创建两个AABB如图10-37所示。两个包装盒距离0.5,速度为3。
图10-37 移动AABB检测
检测代码如下:
float[] speed = new float []{3.0f,0.0f,0.0f};
float tEnter = intersectMovingAABB(box1,box2,speed);
输出结果为0.16667,完全符合预期的猜测。