AABB包围盒详解

转自http://www.cnblogs.com/lyggqm/p/5386174.html


AABB盒,一个3D的AABB就是一个简单的六面体,每一边都平行于一个坐标平面,矩形边界框不一定都是立方体,它的长、宽、高可以彼此不同。

AABB的重要性质:
Ymin <= Y <= Ymax
Zmin <= Z <= Zmax
特别重要的两个顶点为:Pmin = [Xmin Ymin Zmin],Pmax = [ Xmax Ymax Zmax].
 

先介绍AABB的表达方法,AABB内的点满足以下条件:

xmin≤xxmax

ymin≤yymax

zmin≤zzmax

因此只需要知道两个特别重要的顶点(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的最小值。其中[xyz]是原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     不管物体从哪个方向运动,碰撞过程中,肯定是先入后出,所以有tentertleave

n     如果tentertleave超出运动时间范围,那么在此范围内它们是不相交的。

检测出某一维的碰撞还不够,还需要进行其他两维的检测,然后取结果的交集。如果交集为空,那么两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,完全符合预期的猜测。


  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值