你现在知道如何根据一个已知速度矢量去检测是否与你的世界中任意一个三角面之间的碰撞了。但是碰撞发生后我们要做什么?最简单的方法是碰撞发生时,让他停止不再前进。但这并不是专业的做法,我们理想的碰撞响应应该是沿着墙体滑动、自动爬梯子和自动撞击地表上小的障碍物。
很幸运的是我将告诉你如何自动地实现滑动。你理解的滑动是什么呢?我们在这里想做的是当物体移动到起冲突的一个世界三角面附近时,会自动改变移动方向并移动到新的位置。看下图4.1:
图 4.1: 当碰撞到一个三角面时我们自动滑开
4.1 滑动面
滑动面,顾名思义,它是一个让我们沿着它继续移动的面。从图4.1中你也可以了解到这个滑动面同样是碰撞面所在的那个面,在一些例子里并不这样的,就像图4.2那样:
图4.2滑动板的概念
让我们归纳一下滑动面的概念,在图4.2中两个滑动面什么是共通的?他们都是与三角形碰撞的那个点上的球体的切面,所以我们要找到一个根据球体表面上的某个点来计算切面的方法。
记得我们如何描述面的?我们为它定义了一个点和一个法线。对吗?我们已经有了这个点,名字叫做碰撞点(intersection point),它是我们从碰撞检测中得到的,我们这里要做的是得到碰撞点上的这个切面的法线。
再次声明:我们之所以选择椭球体是因为它可以为我们省去很多的计算,因为一个单位球体表面上任一点与球体的切面 的法线 都是该点到这个单位球体的球心的 矢量。其实未必适于大多数椭球体。看图4.3:
图4.3:一个单位球体的切面
容易计算出滑动面,并浓缩为下面几行代码
VECTOR planeOrigin = intersectionPoint;
VECTOR planeNormal = newPosition - intersectionPoint;
planeNormal.normalize();
PLANE slidingPlane(planeOrigin, planeNormal);
不知道这是不是个令人满意的解释呢?如果我们没有选择椭球体呢?我们是否可以通过其他方法来实现滑动?当然如果你感兴趣的话可以参考我提供的附录A,它提供了任何规则曲面的法线计算方法,看了它你会明白为什么一个单位椭球体会省去很多的计算了。
4.2 滑动这个球体
现在我们知道如何去计算滑动面接下来我们要对它做什么?
下面是一些步骤:
1、 将我们的球体近可能的移动碰撞面边上,我们叫这个位置叫“newPosition”。
2、 计算基于这个位置的滑动面。
3、 利用原始的速度向量与滑动面来取得新的目标。
4、 通过新目标点减去多边形碰撞点来取得新的速度向量。
5、 提供新的位置和新的速度向量来重新执行(递归)碰撞检测代码
最后一步令人吃惊,但是是必要的,碰撞检测是一个递归函数,并且每次递归都将又一遍检测所有三角形。我们一直递归直到:
1、 我们没有碰到任何东西,所以我们更新新的位置。
2、 速度向量太小了。
重点是什么?它只是刚好帮我们正确地做了想要做的事情。它获得了一个我们可以用来继续移动的新速度向量,而且直接碰撞越多我们滑动越少。看图4.4:
图 4.4: 相交角度影响滑动数
唯一要做的事情就是提供一个位深度来表示我们将提供速度向量给滑动面到什么程度。我们的滑动面是由一个多边形交点和一个面法线计算而来,我们将把我们原来目标位置沿着滑动面法线方向变换到滑动面上。为了做这个我们需要从起始点到滑动面的有向位移,做法如下:
float distance = slidingPlane.distanceTo(destination);
VECTOR newDestinationPoint = destination-distance*planeNormal;
上面所提的东西就是一些关于如何去获得新的速度向量和递归新的位置和新的速度向量到碰撞检测及响应的算法。
4.3重力
如果在我们的世界中有重力呢?它会困难吗?根本不,在每一帧我们使用调用两次碰撞检测递归:一次是使用玩家速度向量,一次是使用重力。我们可以把两个向量混合起来调用一次碰撞检测递归,vector v = velocity + gravity,但这会使得我们去实现爬梯子变得更为复杂,因为当你碰撞到梯子的边缘时,速度向量必须长到可以提供足够的向上滑动速度。在大多数应用中,都把两个调用分开,并不会影响调用函数的总数,因为当你用混合的向量在平坦的地面上行走时就会有有两个递归,如图4.5所示,如果你第一次用速度沿着表面行走时并无碰撞,而第二次使用重力调用时,你不需要再进行递归因为重力是和地表垂直的。
图4.5:在这个例子中两次调用和一次调用有同样的重复次数
4.4摘要