Jeff Molofee(NeHe)的OpenGL教程
- 碰撞检测与模型运动
原 文:Lesson 31: Collision Detection and Physically Based Modeling Tutorial
译 者:Wguzgg
下面我们要讨论的是如何快速有效的检测物体的碰撞和合乎物理法则的物体运动,先看一下我们要学的:
1)碰撞检测
·移动的范围 — 平面
·移动的范围 — 圆柱
·移动的范围 — 运动的物体
2)符合物理规则的物体运动
·碰撞后的响应
·在具有重力影响的环境下应用Euler公式运动物体。
3)特别的效果
·使用A Fin-Tree Billboard方法实现爆炸效果
·使用Windows Multimedia Library实现声音(仅限于Windows平台)
4)源代码的说明
源代码由5个文件组成 |
|
Lesson31.cpp |
该实例程序的主程序 |
Image.cpp、Image.h |
读入位图文件 |
Tmatrix.cpp、Tmatrix.h |
处理旋转 |
Tray.cpp、Tray.h |
处理光线 |
Tvector.cpp、Tvector.h |
矢量类 |
Vector,Ray和Matrix类是很有用的,我在个人的项目中常使用它们。那么下面就让我们马上开始这段学习的历程吧。
31.1、碰撞检测
为了实现碰撞检测我们将使用一套经常在光线跟踪算法中使用的规则。先让我们定义一下什么是光线。
一条通过矢量描述的光线,意味着规定了起点,并且有一个矢量(通常已被归一化),描述了该光线 通过的方向。基本上该光线从起点出发并沿着该矢量规定的方向前进。所以我们的光线可被一下公式所表达:
PointOnRay = Raystart + t * Raydirection |
t是一个浮点数,取值从0到无穷大。
t=0时获得起始点的位置;为其它值时获得相应的位置,当然是在该光线所经过的路线上。
变量PointOnRay,Raystart和Raydirection都是3D的矢量,取值(x,y,z)。现在我们可以使用该光线公式计算平面或圆柱的横截面。
31.1.1 光线 — 平面相交的检测
一个平面由以下的矢量来描述:
Xn dot X = d |
Xn与X是矢量而d是一个浮点数。Xn是它的法线 X是它表面的一个点。d是一个浮点数,描述了从坐标系的原点到法线平面的距离。
本质上一个平面将空间分成了两个部分。所以我们要做的就是定义一个平面。由一个点以及一条法线(经过该点且垂直于该平面),这两个矢量描述了该平面。也就是,如果我们有一个点(0,0,0)和一条法线(0,1,0),我们实际上就已经定义了一个平面,也即x,z平面。因此通过一个点和一个法线已经足够定义一个平面的矢量方程式了。
使用平面的矢量方程式,法线被Xn所代替,那个点(也即法线的起点)被X所代替。d是唯一还未知的变量,不过很容易计算出来(通过点乘运算,是基本的矢量运算公式)。
注意:这种矢量表示法与通常的参数表达式方法是等价的,参数表达式描述一个平面公式如下:Ax+By+Cz+D=0只需简单的将法线的矢量(x,y,z)代替A,B,C,将D = -d即可。
迄今为止我们已有了两个公式:
PointOnRay = Raystart + t * Raydirection |
Xn dot X = d |
如果一条光线与一个平面相交,那么必定有该光线上的几个点满足该平面的公式,也就是:
Xn dot PointOnRay = d OR (Xn dot Raystart) + t * (Xn dot Raydirection) = d |
求得t:
t = (d - Xn dot Raystart) / (Xn dot Raydirection) |
将d替换后得到:
t = (Xn dot PointOnRay - Xn dot Raystart) / (Xn dot Raydirection) |
运用结合率得到:
t = (Xn dot (PointOnRay - Raystart)) / (Xn dot Raydirection) |
t是从该光线的起点沿着光线的方向到该平面的距离。因此将t代入光线公式即可算出撞击点。但是还有几个特殊情况需要考虑:如果Xn dot Raydirection = 0,表明光线和平面是平行的,将不会有撞击点。如果t是负数,那么表明撞击点是在光线的起始点的后面,也就是沿着光线后退的方向才能撞到平面,这只能说明光线和平面没有交点。
int TestIntersionPlane(const Plane& plane,const TVector& position,const TVector& direction, double& lamda, TVector& pNormal)
{
double DotProduct=direction.dot(plane._Normal); // 求得平面法线和光线方向的点积
// (也即求Xn dot Raydirection)
double l2;
// 判断光线是否和平面平行
if ((DotProduct< ZERO)&&(DotProduct>-ZERO)) // 判断一个浮点数是否为0,也即在一个很小的数的正负区间内即可认为该浮点数为0
return 0;
// 求得从光线的起点到撞击点的距离
l2=(plane._Normal.dot(plane._Position-position))/DotProduct;
if (l2<-ZERO) // 如果l2小于0表明撞击点在光线的反方向上,
// 这只能表明两者没有相撞
return 0;
pNormal=plane._Normal;
lamda=l2;
return 1;
}
上面这段代码计算并返回光线和平面的撞击点。如果有撞击点函数返回1否则返回0。函数的参数依次是平面,光线的起点,光线的方向,一个浮点数记录了撞击点的距离(如果有的话),最后一个参数记录了平面的法线。
31.1.2 光线 — 圆柱体相交的检测
计算一条光线和一个无限大的圆柱体的相撞是一件很复杂的事,所以我在这里没有解释它。有太多的过于复杂的数学方法以至于不容易解释,我的目标首先是提供给你一个工具,不需知道过多的细节你就可以使用它(这并不是一个几何的类)。如果有人对下面检测碰撞的代码感兴趣的话,请看《Graphic Gems II Book》(pp 35, intersection of a with a cylinder)。一个圆柱体的描述类似于光线,有一个起点和方向, 该方向描述了圆柱体的轴,还有一个半径。相关的函数是:
int TestIntersionCylinder(
const Cylinder& cylinder,
const TVector& position,
const TVector& direction,
double& lamda,
TVector& pNormal,
TVector& newposition
)
如果光线和圆柱体相撞则返回1否则返回0。
函数的参数依次是圆柱体,光线的起点,光线的方向,一个浮点数记录了撞击点的距离(如果有的话),一个参数记录了撞击点的法线,最后一个参数记录了撞击点。
31.1.3 球体 — 球体撞击的检测
一个球体通过圆心和半径来描述。判断两个球体是否相撞十分简单,只要算一下这两个球体的圆心的距离,如果小于这两个球体半径的和,即表明该两个球体已经相撞。
问题是该如何判断两个运动球体的碰撞。两个球体的运动轨迹相交并不能表明它们会相撞,因为它们可能是在不同的时间经过相交点的。
</ |