高级碰撞检测及响应算法——碰撞响应
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
高级碰撞检测及响应算法——碰撞响应
2010-11-23 16:32
对运动的球体作碰撞检测,查明它将与三角片发生何种形式的碰撞是检测算法完成的任务。
但是当碰撞真的发生了,接下来应该怎么做呢?最简单的作法是让球体停下来,或者一旦发现可能的碰撞,就禁止球体继续运动。
但是这些作法都不是真正意义上的碰撞响应,我们应该得到更加自然的响应过程。
比如在游戏开发中,我们应该在发生碰撞后将物体沿墙壁滑动,自动地爬上楼梯抑或自动地翻过地面上的障碍物,又比如在计算机过程仿真中,让碰撞的物体按物理规律进一步做出诸如反弹等的反应……下面,我们以基本的滑移响应为例,说明如何利用由检测算法得到的碰撞数据。
那么到底什么是滑移呢?在场景世界中,如果我们检测到角色将与一面三角片碰撞,我们接下来要做的事情就是尽量靠近这块三角片,然后改变角色的速度方向,最后在新的方向上继续运动。
见图4.1。
图4.1:碰撞三角片后的滑移
1.滑移平面
滑移平面,顾名思意,就是发生碰撞后物体将沿此平面滑动。
简单地从图4.1可以看出,滑移平面应该就是被碰撞的三角片所在的平面。
事实上,在某些情况下这个结论是对的,但是在图4.2中所描述的情况下,这种说法显然不太妥当。
图4.2:滑移平面的定义
下面我们概括一下什么是滑移平面。
在图4.2中的两个滑移平面有什么共同点呢?它们都是扫掠球上接触点的切平面。
所以,我们必须知道过球体表面上给定点的切平面的方法。
通常我们描述一张平面的方法是通过平面上一点(origin点)和平面的法向量(normal向量)来定义。
在碰撞检测部分,我们已经得到了碰撞点,所以当前的任务就是计算在球体碰撞点处的切平面的法向量。
很幸运,我们选择在e空间中进行所有的操作的确为我们节省了大量的计算工作,因为过单位球体表面上任意一点的切平面的法向量其实就是由表面上定点到球体中心的向量!见图4.3。
图4.3:单位球体的切平面
于是在e空间中,通过以下几行代码我们就能求得滑移平面的表达式。
VECTOR planeOrigin = intersectionPoint ;
VECTOR planeNormal = newPosition-intersectionPoint ;
planeNormal. normalize( ) ;
PLANE slidingPlane( planeOrigin, planeNormal ) ;
2.滑动单位球
现在我们已经知道了如何计算滑移平面,接下来呢?
***使单位球不断逼近将要碰撞的三角片。
不妨将此时的球体位置叫作“新点”
***计算新点所对应的滑移平面
***将原始的速度向量投影到滑移平面,得到新的速度向量的终点
***用终点减去三角片上的碰撞点得到新的速度向量
***然后使用新的位置basePoint和新的速度向量velocity递归调用碰撞检测函数进行新一轮检测
在这里,最后一步出现得比较突然,碰撞检测被递归调用,对于每一次循环,我们都要用更新了参数的单位球重新检测所有的三角片。
循环的结束条件是下面条件之一:
***单位球没有撞上任何东西,于是我们只用更新位置
***速度向量变得非常小(经过多次投影后,可能会变得非常小,比如说垂直碰撞三角片平面时)
那么,上面提到的“投影”是怎么回事儿?这个对我们很重要,通过投影我们可以得到新的速度向量,从而可以在滑移平面上继续运动,并且球体撞击三角片的
入射角越小,球体产生的滑移量就越小。
见图4.4。
图4.4:碰撞入射角影响滑移量
接下来,我们更深入地解释如何将一个速度向量投影到滑移平面上。
我们通过三角片上的碰撞点和法向量来求得滑移平面。
那么首先必须求出原始速度向量的终点到滑移平面的有符号距离(singed distance),然后将终点沿滑移平面法线方向投影到滑移平面上,于是我们可以得到投影点:
float distance = slidingPlane.distanceTo( destination ) ;
VECTOR newDestinationPoint = destination - distance * planeNormal ;(这里
planeNormal是单位向量)
总之,我们的工作是计算新的速度向量,然后用新的位置(也就是碰撞的位置)和新的速度递归调用碰撞检测和响应算法。
3.重力
如何对场景中的物体施加重力呢?难吗?一点都不难。
在碰撞检测算法中,我们都插入两个函数调用,一个用于设置角色的速度,另一个则设置重力(也可以将其当作一个速度施加)。
你可以将这两个向量合并起来,那么每次循环时只需调用一次函数用于设置综合速度向量v = velocity + gravity ,但是这种合并会使诸如爬楼梯的情形变得更加困难,因为当角色撞击了楼梯的边缘,速度向量必须足够长才能使投影向量向上滑移。
因为重力向量使原始的速度向量产生了垂直向下的分量,从而使碰撞入射角减小,当然沿切平面向上滑移变得困难。
一般,如果你将速度向量与重力向量合并,由于角色沿平地移动,你应该进行两次循环,就像图4.5显示的那样,所以就算不合并两者,也不会额外增加调用检测函数的总的次数。
图4.5:合并速度和重力向量并不会额外增加调用检测函数的总次数
如果你一开始沿速度向量在地面上移动不会碰到障碍,那么使用重力向量的第二次调用将会发生碰撞,于是不会重复循环下去,因为碰撞始终是垂直于平面的(即一定会发生碰撞)。
4.总结
总结一下碰撞检测与响应的算法:
***首先我们将输入的速度和位置变量从R3空间转换到e空间
***然后我们调用碰撞检测程序,找出可能最先发生的碰撞点和速度方向上到发生碰撞位置之间的距离
***移动球体,无限接近于碰撞点,于是得到一个新的球体位置
***从第二步返回的碰撞数据中,我们可以求得一个滑移平面
***将速度向量投影到滑移平面于是得到一个新的速度向量终点(起点当然是碰撞点)
***计算新的速度向量,然后递归调用以上算法直到满足循环结束条件
***最后,我们将得到的结果转换到R3空间中,并更新角色的位置
***或者我们也可以将重力向量与速度向量合并起来当成一个速度向量加入到以上流程中
上面的响应过程,我们使用速度向量的投影使得物体在发生碰撞之后自动地滑动。
这是一种方法,也是一种启示,你可以在此基础上进行调整以满足自己的需用。
可以通过自己定义使碰撞的响应与众不同。
最后谈一下该算法的稳定性。
事实上,如果能正确地实现,上面的算法应该是很稳定的。
在ERA游戏引擎上运行我自己的程序时,还没有出现一例卡死或者穿越几何体的异常情况。
也许有些人注意到这一版本的单三角片碰撞检测函数有点长,可能会因此怀疑算法的运行速度。
个人来讲,使用这个算法我至今没有出现过任何的运行问题。
同时你应该记得在程序中有两个“提前退出”循环的地方,其实在很多情况下,它们为我们提前剔除了很多不必要的检测。
这里有一些数据:有一张ERA测试地图,它是由陆地纹理,比如说房子、楼梯等多边形网格组成的。
我将角色置于地图中,走了走。
在整个测试期间,总共有110万个三角片被检测函数检测,其中有差不多40%的三角片由于处于背对角色的状态而提早退出碰撞检测。
(有人可能推断应该有50%的三角片处于背面状态,但是在我的测试中地形网格中的大多数三角片沿角色速度方向是正对角色的,所以40%的数据是合理的)。
余下约70万三角片中的65%,在检测t0和t1的时候就被提前判出。
剩下的继续接受检测,不过有很多碰撞实际上发生于三角片内,所以也没有进行扫掠测试(检测是否与边缘或者顶点碰撞),满打满算,仅有20%的三角片进行了扫掠测试,所以检测函数的绝大部分计算负荷来自于这20%的三角片。
有人可能会质疑,这些数据取决于处理数据的方法,也取决于地图中哪些三角片被选择出来作为函数的输入参数。
也许所选的三角片刚好有利于检测函数的检测,比如那些与球体的碰撞发生在三角片内部的三角片被刻意挑选出来等等。
但是我想我上面的数据能够代表一般情况,因为三角片的选择是通过八叉树来进行的:规定只有那些在运动角色附近的三角片才被输入到函数中。