碰撞检测是在3D开发中不可避免的问题, XNA中本身提供了三种碰撞模型供开发者所用:

1.BoundingSphere 包围球

3D模型的每个Mesh都拥有自己的BoundingSphere,只要调用Intersects方法即可获得两个球是否碰撞.

也可以自己创建BoundingSphere,只需指定球心坐标和半径长度

至于包围球的检测算法, 我本以为是用两个球心的距离和半径之和相比, 但反编译XNA framework的代码后才发现并不是,因为计算两个球心的距离需要开方计算,这对每秒需要渲染几十帧的3D程序的性能还是有些影响的,所以XNA是用两球心的距离平方与半径之和的平方来比的,这样就避免了开方计算,提高了性能.

public bool Intersects(BoundingSphere sphere)
{
float num3;
Vector3.DistanceSquared(
ref this.Center, ref sphere.Center, out num3);
float radius = this.Radius;
float num = sphere.Radius;
if ((((radius * radius) + ((2f * radius) * num)) + (num * num)) <= num3)
{
return false;
}
return true;
}
public static void DistanceSquared(ref Vector3 value1, ref Vector3 value2, out float result)
{
float num3 = value1.X - value2.X;
float num2 = value1.Y - value2.Y;
float num = value1.Z - value2.Z;
result
= ((num3 * num3) + (num2 * num2)) + (num * num);
}

2.BoundingBox 包围盒 类型为AABB(Axis Aligned Bounding Box),即只能平行于x,y,z三条轴平移,而不能旋转

BoundingBox需要自己手工创建实例,只需指定Min和Max两个坐标,即该包围盒两个对角顶点的坐标,同样调用Intersects方法获取碰撞结果

它的检测原理为:两个包围盒在x,y,z三个平面上的2D投影全都没有相交,则两者没有碰撞,反之碰撞,看反编译后的源代码,非常简洁明了,只有比较和与或运算,

所以个人认为AABB包围盒的碰撞检测效率应该是高于包围球的,下一篇可以再写个性能测试的sample :)

public bool Intersects(BoundingBox box)
{
if ((this.Max.X < box.Min.X) || (this.Min.X > box.Max.X))
{
return false;
}
if ((this.Max.Y < box.Min.Y) || (this.Min.Y > box.Max.Y))
{
return false;
}
return ((this.Max.Z >= box.Min.Z) && (this.Min.Z <= box.Max.Z));
}

 

3.BoundingFrustum 包围截椎体(不知道是不是这么翻译的,用的比较少)

 即一头大,一头小的包围盒,形状类型于透视投影的平截体,创建时通过Matrix来定义,一般使用透视矩阵PerspectiveFieldOfView

其检测原理就比较复杂了,因为我没用到此类型,贴上代码供感兴趣的朋友研究

BoundingFrustum
public bool Intersects(BoundingFrustum frustum)
{
Vector3 closestPoint;
if (frustum == null)
{
throw new ArgumentNullException("frustum");
}
if (this.gjk == null)
{
this.gjk = new Gjk();
}
this.gjk.Reset();
Vector3.Subtract(
ref this.cornerArray[0], ref frustum.cornerArray[0], out closestPoint);
if (closestPoint.LengthSquared() < 1E-05f)
{
Vector3.Subtract(
ref this.cornerArray[0], ref frustum.cornerArray[1], out closestPoint);
}
float maxValue = float.MaxValue;
float num3 = 0f;
do
{
Vector3 vector2;
Vector3 vector3;
Vector3 vector4;
Vector3 vector5;
vector5.X
= -closestPoint.X;
vector5.Y
= -closestPoint.Y;
vector5.Z
= -closestPoint.Z;
this.SupportMapping(ref vector5, out vector4);
frustum.SupportMapping(
ref closestPoint, out vector3);
Vector3.Subtract(
ref vector4, ref vector3, out vector2);
float num4 = ((closestPoint.X * vector2.X) + (closestPoint.Y * vector2.Y)) + (closestPoint.Z * vector2.Z);
if (num4 > 0f)
{
return false;
}
this.gjk.AddSupportPoint(ref vector2);
closestPoint
= this.gjk.ClosestPoint;
float num2 = maxValue;
maxValue
= closestPoint.LengthSquared();
num3
= 4E-05f * this.gjk.MaxLengthSquared;
if ((num2 - maxValue) <= (1E-05f * num2))
{
return false;
}
}
while (!this.gjk.FullSimplex && (maxValue >= num3));
return true;
}

以上三种碰撞模型都是XNA自带的, 开发时直接调用Intersects即可得知碰撞结果,并且它们之间也可以互相检测碰撞,如 BoundingSphere.Intersects(BoundingBox),使用简单效率高

但缺点也很明显,就是不精确, 对复杂形状物体的包围会有大量空白存在,产生两物体还有一段距离就报告碰撞的不真实感

另外还有OBB(Oriented Bounding Box) 即可以旋转的包围盒, BSP树等

其中BSP树是使用最广泛,精确度也相当高的碰撞检测方法,大名鼎鼎的Quake就是使用BSP树进行的碰撞检测,而Half-life又使用了Quake引擎

不过BSP树不再此文的讨论范围内,感兴趣的朋友可以自己搜索相关资料.

本文主要讨论如何用简单基础的碰撞方法实现复杂形状物体的碰撞检测,如下图所示:

其中卡车为直来直去的运动,使用一大一小两个BoundingBox即可

而挖掘机所有组件的移动都涉及到旋转,所以使用若干个BoundingSphere, 尽可能精确的紧密包围住挖掘机的所有部件

这样整个碰撞检测只是用了BoundingSphere和BoundingBox,对于碰撞精确度不高的程序效果还是很好的,即使在配置很低的上网本上也可以跑的起来,当然会有些卡

到此为止,碰撞检测已经不再是什么难题了,难的在后面的碰撞处理上

当检测到两物体碰撞之后,即使没有物理碰撞效果,最起码也要让其中一个物体停止下来,而又不能完全停止,因为用户操纵它往反方向移动时,该物体又可以移动

最好的方法是使用3D物理引擎,网上有一些基于XNA平台的开源物理引擎

如Opps http://oopsframework.codeplex.com/

不过本人的这个程序只是个3D模拟器的小项目,使用物理引擎实在有点杀鸡用牛刀的感觉,而且为了模拟真实驾驶的感觉,挖掘机的操作是由两个飞行摇杆来操纵的,与物理引擎的交互方面也会有些问题

所以我写了个简陋的碰撞处理方法,当检测到碰撞时,获取碰撞BoungdingSphere中心点与被碰BoundingBox中心点之间的方向

计算方向只需要两个Vector3向量相减,再调用Normalize方法,即可获得一个x,y,z都在-1~1之间的Vector3向量,从中可以知道碰撞物体在被碰物体的哪个方向

然后根据碰撞方向的不同禁用输入设备上相应的按键,如大臂不能伸展,铲斗不能收缩,驾驶室不能左右旋转等.

不过此方法仅适用于简单情况,对于复杂的游戏,物理引擎还是王道,继续研究物理引擎去鸟~~~

作者: 张磊_larry.zhang 发表于 2011-08-30 21:06 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"