《碰撞检测技术》word版.docx
- 文档编号:10688145
- 上传时间:2023-02-22
- 格式:DOCX
- 页数:19
- 大小:25.04KB
《碰撞检测技术》word版.docx
《《碰撞检测技术》word版.docx》由会员分享,可在线阅读,更多相关《《碰撞检测技术》word版.docx(19页珍藏版)》请在冰豆网上搜索。
《碰撞检测技术》word版
碰撞检测技术
碰撞检测技术2011-05-0623:
00
技术--引擎2008-09-0519:
50:
55阅读25110.3碰撞检测技术
到目前为止,构造的各种对象都是相互独立的,在场景中漫游各种物体,墙壁、树木对玩家(视点)好像是虚设,可以任意从其中穿越。
为了使场景人物更加完善,还需要使用碰撞检测技术。
10.3.1碰撞检测技术简介
无论是PC游戏,还是移动应用,碰撞检测始终是程序开发的难点,甚至可以用碰撞检测作为衡量游戏引擎是否完善的标准。
好的碰撞检测要求人物在场景中可以平滑移动,遇到一定高度的台阶可以自动上去,而过高的台阶则把人物挡住,遇到斜率较小的斜坡可以上去,斜率过大则会把人物挡住,在各种前进方向被挡住的情况下都要尽可能地让人物沿合理的方向滑动而不是被迫停下。
在满足这些要求的同时还要做到足够精确和稳定,防止人物在特殊情况下穿墙而掉出场景。
做碰撞检测时,该技术的重要性容易被人忽视,因为这符合日常生活中的常识。
如果出现Bug,很容易被人发现,例如人物无缘无故被卡住不能前进或者人物穿越了障碍。
所以,碰撞检测是让很多程序员头疼的算法,算法复杂,容易出错。
对于移动终端有限的运算能力,几乎不可能检测每个物体的多边形和顶点的穿透,那样的运算量对手机等设备来讲是不可完成的,所以移动游戏上使用的碰撞检测不可能使用太精确的检测,而且对于3D碰撞检测问题,还没有几乎完美的解决方案。
目前只能根据需要来取舍运算速度和精确性。
目前成功商业3D游戏普遍采用的碰撞检测是BSP树及AABB(axiallyalignedboundingbox)包装盒(球)方式。
简单地讲,AABB检测法就是采用一个描述用的立方体或者球形体包裹住3D物体对象的整体(或者是主要部分),之后根据包装盒的距离、位置等信息来计算是否发生碰撞,如图10-24所示。
除了球体和正方体以外,其他形状也可以作包装盒,但是相比计算量和方便性来讲还是立方体和球体更方便些,所以其他形状的包装只用在一些特殊场合使用。
BSP树是用来控制检测顺序和方向的数据描述。
在一个游戏场景中可能存在很多物体,它们之间大多属于较远位置或者相对无关的状态,一个物体的碰撞运算没必要遍历这些物体,同时还可以节省重要的时间。
如果使用单步碰撞检测,需要注意当时间步长较大时会发生两个物体完全穿透而算法却未检测出来的问题,如图10-25所示。
其解决方案是产生一个4D空间,在物体运动的开始和结束时间之间产生一个4D超多面体,用于穿透测试。
图10-24AABB包装盒图10-25碰撞检测的单步失控和4D测试
读者在程序开发初期有必要对碰撞检测有一个初步的估计,以免最后把大量精力消耗在碰撞检测问题上,从而降低了在基础的图形编程之上的注意力。
10.3.2球体碰撞检测
真实的物理模拟系统需要非常精确的碰撞检测算法,但是游戏中常常只需要较为简单的碰撞检测,因为只需要知道物体什么时候发生碰撞,而不用知道模型的哪个多边形发生了碰撞,因此可以将不规则的物体投影成较规则的物体进行碰撞检测。
球体只有一个自由度,其碰撞检测是最简单的数学模型,我们只需要知道两个球体的球心和半径就能进行检测。
那么球体碰撞是如何工作的?
主要过程如下。
n计算两个物体中心之间的距离,并且将其与两个球体的半径和进行比较。
n如果距离大于半径和,则没有发生碰撞。
n否则,如果距离小于半径和,则发生了物体碰撞。
考虑由球心c1、c2和半径r1、r2定义的两个球,如图10-26所示。
设d为球心间的距离。
很明显,当d 对两个运动的球进行碰撞检测要麻烦一些,假设两个球的运动向量为d1和d2,球与位移向量是一一对应的,它们描述了所讨论时间段中的运动方式。 事实上,物体的运动是相对的,例如两列在两条平行轨道上相向行驶的火车,在其中一列中观察,对方的速度是两车速度之和。 同样,也可以从第一个球的角度来简化问题,假设第一个球是"静止"的,另一个是"运动"的,那么该运动向量等于原向量d1和d2之差,如图10-27所示。 图10-27动态球的检测过程 球体碰撞的优点是非常适用于需要快速检测的游戏,因为它不需要精确的碰撞检测算法。 执行速度相对较快,不会给CPU带来过大的计算负担。 球体碰撞的另一个劣势是只适用于近似球形物体,如果物体非常窄或者非常宽,该碰撞检测算法将会失效,因为会在物体实际发生碰撞之前,碰撞检测系统就发出碰撞信号,如图10-28所示是球体碰撞检测中可能出现的坏情况,其解决方法是缩小检测半径,或者使用其他检测模型,如图10-29所示。 图10-28球体碰撞的坏情况图10-29缩小检测半径 为了解决包容球精确度不高的问题,人们又提出了球体树的方法。 球体树实际上是一种表达3D物体的层次结构。 对一个形状复杂的3D物体,先用一个大球体包容整个物体,然后对物体的各个主要部分用小一点的球体来表示,然后对更小的细节用更小的包容球体,这些球体和它们之间的层次关系就形成了一个球体树。 举例来说,对一个游戏中的人物角色,可以用一个大球来表示整个人,然后用中等大小的球体来表示四肢和躯干,然后用更小的球体来表示手脚等。 这样在对两个物体进行碰撞检测时,先比较两个最大的球体。 如果有重叠,则沿树结构向下遍历,对小一点的球体进行比较,直到没有任何球体重叠,或者到了最小的球体,这个最小的球体所包含的部分就是碰撞的部分,如图10-30所示。 10.3.3AABB立方体边界框检测 用球体去近似地代表物体运算量很小,但在游戏中的大多数物体是方的或者长条形的,应该用方盒来代表物体。 另一种常见的检测模型是立方体边界框,如图10-31展示了一个AABB检测盒和它里面的物体。 坐标轴平行(Axially-aligned)不仅指盒体与世界坐标轴平行,同时也指盒体的每个面都和一条坐标轴垂直,这样一个基本信息就能减少转换盒体时操作的次数。 AABB技术在当今的许多游戏中都得到了应用,开发者经常用它们作为模型的检测模型,再次指出,提高精度的同时也会降低速度。 因为AABB总是与坐标轴平行,不能在旋转物体时简单地旋转AABB,而是应该在每一帧都重新计算。 如果知道每个对象的内容,这个计算就不算困难,也不会降低游戏的速度。 然而,还面临着精度的问题。 假如有一个3D的细长刚性直棒,并且要在每一帧动画中都重建它的AABB。 可以看到每一帧中的包装盒都不一样而且精度也会随之改变,如图10-32所示。 图10-313D模型与AABB检测盒图10-32不同方向的AABB 可以注意到AABB对物体的方向很敏感,同一物体的不同方向,AABB也可能不同(由于球体只有一个自由度,所以检测球对物体方向不敏感)。 当物体在场景中移动时,它的AABB也需要随之移动,当物体发生旋转时,有两种选择: 用变换后的物体来重新计算AABB,或者对AABB做和物体同样的变换。 如果物体没有发生扭曲,可以通过"变换后的AABB"重新计算,因为该方法要比通过"变换后的物体"计算快得多,因为AABB只有8个顶点。 变换AABB得出新的AABB要比变换物体的运算量小,但是也会带来一定的误差,如图10-33所示。 比较图中原AABB(灰色部分)和新AABB(右边比较大的方框),它是通过旋转后的AABB计算得到的,新AABB几乎是原来AABB的两倍,注意,如果从旋转后的物体而不是旋转后的AABB来计算新AABB,它的大小将和原来的AABB相同。 先介绍AABB的表达方法,AABB内的点满足以下条件: xmin≤x≤xmaxymin≤y≤ymaxzmin≤z≤zmax 因此只需要知道两个特别重要的顶点(xmin,ymin,zmin)、(xmax,ymax,zmax),记作: floatmin=newfloat{0.0f,0.0f,0.0f}; floatmax=newfloat{0.0f,0.0f,0.0f}; 中心点是两个顶点的中点,代表了包装盒的质点。 floatcenter=newfloat{0.0f,0.0f,0.0f}; 中心点的计算方法如下: floatcenter(){ center[0]=(min[0]+max[0])*0.5f; center[1]=(min[1]+max[1])*0.5f; center[2]=(min[2]+max[2])*0.5f; returncenter; 通过这两个顶点可以知道以下属性。 floatxSize(){return(max[0]-min[0]);} floatySize(){return(max[1]-min[1]);} floatzSize(){return(max[2]-min[2]);} floatsize(){return(max[0]-min[0])*(max[1]-min[1])*(max[2]-min[2]);} 当添加一个顶点到包装盒时,需要先与这两个顶点进行比较。 voidadd(floatp){ 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]; 检测包装盒是否为空,可以将这两个顶点进行比较。 booleanisEmpty(){ return(min[0]max[0])||(min[1]max[1])||(min[2]max[2]); 检测某个点是否属于AABB范围之内的代码如下: booleancontains(floatp){ 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碰撞的方法如下: booleanintersectAABBs(AABBbox2,AABBboxIntersect) floatbox2_min=box2.getMin(); floatbox2_max=box2.getMax(); if(min[0]box2_max[0])returnfalse; if(max[0]box2_min[0])returnfalse; if(min[1]box2_max[1])returnfalse; if(max[1]box2_min[1])returnfalse; if(min[2]box2_max[2])returnfalse; if(max[2]box2_min[2])returnfalse; if(boxIntersect! =null){ floatbox_intersect_min=newfloat[3]; floatbox_intersect_max=newfloat[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]); returntrue; 可以利用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包装盒的代码如下: voidsetToTransformedBox(Transformt) if(isEmpty()){//判断包装盒是否为空 return; floatm=newfloat[16]; t.get(m);//将变换矩阵存入数组 floatminx=0,miny=0,minz=0; floatmaxx=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类,代码如下: importjava.lang.Math; importjavax.microedition.m3g.Transform; classAABB{ publicAABB(){} floatgetMin(){returnmin;} floatgetMax(){returnmax;} voidsetMin(floatx,floaty,floatz){min[0]=x;min[1]=y;min[2]=z;} voidsetMax(floatx,floaty,floatz){max[0]=x;max[1]=y;max[2]=z;} voidreset(){ for(inti=0;i3;i++) min[i]=0; max[i]=0; //其他方法同上 为了检验碰撞检测的使用构造了两个立方体,并各自绑定了一个包装盒。 /*立方体1*/ mesh1=createCube();//创建立方体1mesh1.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=newAABB();//包装盒 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添加到场景中 /*立方体2*/ mesh2=createCube();//创建立方体2mesh2.setTranslation(-0.5f,0.0f,0.0f);//平移 mesh2.setScale(0.5f,0.5f,0.5f);//缩放 box2=newAABB();//包装盒 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-36AABB的动态检测 黑色矩形代表沿坐标轴滑动的AABB,t=0时,运动AABB完全位于静止AABB的左边。 当t=1时,运动AABB完全位于静止AABB的右边。 当t=tenter时,两个AABB刚刚相交,当t=tleave时,两个AABB脱离碰撞。 对照上图,可以推导出两个AABB接触和离开的时间: AABB的动态检测有3个要点。 n如果速度为0,两个包装盒要么一直相交,要么一直分离。 n不管物体从哪个方向运动,碰撞过程中,肯定是先入后出,所以有tenter n如果tenter和tleave超出运动时间范围,那么在此范围内它们是不相交的。 检测出某一维的碰撞还不够,还需要进行其他两维的检测,然后取结果的交集。 如果交集为空,那么两AABB包装盒没有相交,如果区间范围在时间段[0,1]之外,那么在此区间也不相交。 对AABB进行动态检测的方法定义如下: floatintersectMovingAABB(AABBstationaryBox,AABBmovingBox,floats) floatNoIntersection=1e30f;//没有碰撞则返回大数 floattEnter=0.0f;//初始化碰撞时间 floattLeave=1.0f;//初始化离开时间 floatSwap=0.0f;//交换操作中间变量 floatsBoxmin=stationaryBox.getMin();//静止包装盒的最小值顶点 floatsBoxmax=stationaryBox.getMax();//静止包装盒的最大值顶点 floatmBoxmin=movingBox.getMin();//运动包装盒的最小值顶点 floatmBoxmax=movingBox.getMax();//运动包装盒的最大值顶点 if(s[0]==0.0f){//如果x方向速度为0if((sBoxmin[0]=mBoxmax[0])||(sBoxmax[0]=mBoxmin[0])){ returnNoIntersection;//进行静态检测 }else{ floatxEnter=(sBoxmin[0]-mBoxmax[0])/s[0];//计算碰撞时间 floatxLeave=(sBoxmax[0]-mBoxmin[0])/s[0];//计算离开时间 if(xEnterxLeave){//检查顺序 Swap=xEnter; xEnter=xLeave; xLeave=Swap; if(xEntertEnter)tEnter=xEnter;//更新区间 if(xLeavetLeave)tLeave=xLeave; if(tEntertLeave){//是否导致空重叠区 returnNoIntersection;//没有碰撞 if(s[1]==0.0f){//y轴速度为0if((sBoxmin[1]=mBoxmax[1])||(sBoxmax[1]=mBoxmin[1])){ returnNoIntersection;//没有相交 }else{ floatyEnter=(sBoxmin[1]-mBoxmax[1])/s[1]; floatyLeave=(sB
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 碰撞检测技术 碰撞 检测 技术 word