[翻译]XNA 3.0 Game Programming Recipes之twenty-three
PS:自己翻译的,转载请著明出处格
4-11 使用光线跟踪的碰撞检测 小/快速物体
问题
大部分碰撞的定义了一个方法,就是检测碰撞仅仅是如果两个对象在物理上接触碰撞。但是,如果你有一个小物体非常迅速通过一个对象,没准您的程序更新速度不够快不能检测出碰撞。
作为一个详细的例子,考虑子弹射击一瓶子。子弹会以5000公里每小时的速度朝着瓶子飞去,瓶子只有15公分宽(将已经是一个1.5 L 瓶子) 。您的XNA程序更新是每秒60次,所以你的程序每一次更新子弹已经走过23米,因为你可以从这个公式了解:
这意味着几乎没有任何机会子弹和一瓶将碰撞在你Update被调用的这一刻,即使子弹径直期间通过您的瓶在最后一祯。
解决方案
可以建立一个射线在过去和当前的子弹之间。接着,检测这个射线与你的对象的BoundingSphere是否碰撞调用射线或者BoundingSphere的Intersect方法实现。
如果发生碰撞,该方法返回在碰撞和你射线的点之间的距离(您可以使用先前子弹的位置)。您可以使用此距离是去检查碰撞的实际是否发生在子弹的先前和当前的位置。
它是如何工作的
这个方法需要一个模型的球体BoundingSphere,它被存储在Tag属性中,如4-5节所解释:
注意:如果你想缩放一个模型在它被绘制到屏幕上时,缩放转换同样被包含在世界矩阵。这将导致先前的代码去测量BoundingSphere的正确性。
接下来,您要查找快速物体准备移动的方向,以及涵盖的距离自上次的祯:
现在你知道射线的一个点和它的方向,你可以创建一个射线了。你可以这样做,使用它的Intersect方法来检测慢速度模型的BoundingSphere是否与它相碰撞。
你调用Intersect方法在射线上,传递它改变慢速度模型的BoundingSphere.
这个交叉方法有点特别。在这个交叉的情况下,它返回交叉点和用来射线定义的点之间的距离。虽然,如果射线和球体没有碰撞,这个方法返回null.从此,你需要一个float?变量代替一个float.因为它需要一个存储null值的能力。
注意:如果碰撞点的位置在您用于界定射线的点的后面,该方法也返回null。由于您所指定的方向,同时创造了射线,XNA知道射线哪一方向的在前面或后面的点。
如果这有一个碰撞在射线和球体之间,碰撞点在lastPosition点的前面,这个交叉点就不会为null。去确定碰撞发生在当前和前面的祯之间,检测在碰撞点和先前的快速度物体的位置之间的距离,是否比在最后一祯移动的距离要小。如果是这种情况,快速度物体与模型在最后一祯发生了碰撞。
更准确的方法
正如前面的章节,由于使用BoundingSphere,这种方法可以检测碰撞,即使这里实际上没有碰撞。
在次,你可以增加准确性用执行射线-球体碰撞在模型的不同ModelMeshes上的小BoundingSphere。
Large and Fast Objects
前面介绍的方法检查是否有碰撞,假定快速对象是非常小的, 就像一个单一的点在三维空间。这通常是好的,但在当时情况下的快速对象太大,无法当作为一个点,您需要快速对象代表作为一个BoundingSphere 。 一种解决办法是执行多个射线检查,根据快速对象的不同BoundingSphere的点,但是这会相当复杂。
更快更简单的方法是添加快速对象的半径到慢速度物体的半径,如图4-15所示。上图显示的是两个物体的边缘碰撞。 如果您添加小半径到大半径,其结果将是相同的。使用此方法,您可以再次表示快速对象用单点和使用的较早的方法讨论。
代码
在加载一个慢速度模型后,你需要保存它的BoundingSphere在Tag属性里面:
4-11 使用光线跟踪的碰撞检测 小/快速物体
问题
大部分碰撞的定义了一个方法,就是检测碰撞仅仅是如果两个对象在物理上接触碰撞。但是,如果你有一个小物体非常迅速通过一个对象,没准您的程序更新速度不够快不能检测出碰撞。
作为一个详细的例子,考虑子弹射击一瓶子。子弹会以5000公里每小时的速度朝着瓶子飞去,瓶子只有15公分宽(将已经是一个1.5 L 瓶子) 。您的XNA程序更新是每秒60次,所以你的程序每一次更新子弹已经走过23米,因为你可以从这个公式了解:
这意味着几乎没有任何机会子弹和一瓶将碰撞在你Update被调用的这一刻,即使子弹径直期间通过您的瓶在最后一祯。
解决方案
可以建立一个射线在过去和当前的子弹之间。接着,检测这个射线与你的对象的BoundingSphere是否碰撞调用射线或者BoundingSphere的Intersect方法实现。
如果发生碰撞,该方法返回在碰撞和你射线的点之间的距离(您可以使用先前子弹的位置)。您可以使用此距离是去检查碰撞的实际是否发生在子弹的先前和当前的位置。
它是如何工作的
这个方法需要一个模型的球体BoundingSphere,它被存储在Tag属性中,如4-5节所解释:
1 myModel=XNAUtils.LoadModelWithBoundingSphere(ref modelTransforms,"tank",Content);
您将创建一个方法,它接收一个模型 ,其世界矩阵,和快速对象的以前的和目前的位置。该模型将返回物体是否相撞与当在以前和现在的祯中
1 private bool RayCollision(Model model,Matrix world,Vector3 lastPosition,Vector3 currentPosition)
2 {
3 BoundingSphere modeSphere=(BoundingSphere)model.Tag;
4 BoundingSphere transPhere=TransformBoundingSphere(modelSphere,world);
5 }
首先你需要移动模型的BoundingSphere到模型在3D空间的当前位置。使用模型的世界矩阵来实现转化BoundingSphere(如先前的章节介绍).2 {
3 BoundingSphere modeSphere=(BoundingSphere)model.Tag;
4 BoundingSphere transPhere=TransformBoundingSphere(modelSphere,world);
5 }
注意:如果你想缩放一个模型在它被绘制到屏幕上时,缩放转换同样被包含在世界矩阵。这将导致先前的代码去测量BoundingSphere的正确性。
接下来,您要查找快速物体准备移动的方向,以及涵盖的距离自上次的祯:
1 Vector3 direction=currentPosition-lastPosition;
2 float distanceCovered=direction.Length();
3 direction.Normalize();
一如往常,你可以找到从A点到B点的方向,用B减去A.您找到它们之间的距离在它们被调用Length方法在这个方向之间。当随着方向运动,您通常会恢复正常,从而成为其长度1 。由于这个原因,您需要确认您正常化后,您存储的长度,否则,长度将永远是1 。2 float distanceCovered=direction.Length();
3 direction.Normalize();
现在你知道射线的一个点和它的方向,你可以创建一个射线了。你可以这样做,使用它的Intersect方法来检测慢速度模型的BoundingSphere是否与它相碰撞。
1 Ray ray=new Ray(lastPosition,direction);
现在你有了子弹的射线了,你已经完成了这个方法:
1 bool collision=false;
2 float? intersection=ray.Intersects(tranSphere);
3 if(intersection!=null)
4 if(intersection<=distanceCovered)
5 collision=true;
6 return collision;
首先,你定义一个collision变量,这将保持false除非快速和慢速物体碰撞。方法结束,此变量返回。2 float? intersection=ray.Intersects(tranSphere);
3 if(intersection!=null)
4 if(intersection<=distanceCovered)
5 collision=true;
6 return collision;
你调用Intersect方法在射线上,传递它改变慢速度模型的BoundingSphere.
这个交叉方法有点特别。在这个交叉的情况下,它返回交叉点和用来射线定义的点之间的距离。虽然,如果射线和球体没有碰撞,这个方法返回null.从此,你需要一个float?变量代替一个float.因为它需要一个存储null值的能力。
注意:如果碰撞点的位置在您用于界定射线的点的后面,该方法也返回null。由于您所指定的方向,同时创造了射线,XNA知道射线哪一方向的在前面或后面的点。
如果这有一个碰撞在射线和球体之间,碰撞点在lastPosition点的前面,这个交叉点就不会为null。去确定碰撞发生在当前和前面的祯之间,检测在碰撞点和先前的快速度物体的位置之间的距离,是否比在最后一祯移动的距离要小。如果是这种情况,快速度物体与模型在最后一祯发生了碰撞。
更准确的方法
正如前面的章节,由于使用BoundingSphere,这种方法可以检测碰撞,即使这里实际上没有碰撞。
在次,你可以增加准确性用执行射线-球体碰撞在模型的不同ModelMeshes上的小BoundingSphere。
Large and Fast Objects
前面介绍的方法检查是否有碰撞,假定快速对象是非常小的, 就像一个单一的点在三维空间。这通常是好的,但在当时情况下的快速对象太大,无法当作为一个点,您需要快速对象代表作为一个BoundingSphere 。 一种解决办法是执行多个射线检查,根据快速对象的不同BoundingSphere的点,但是这会相当复杂。
更快更简单的方法是添加快速对象的半径到慢速度物体的半径,如图4-15所示。上图显示的是两个物体的边缘碰撞。 如果您添加小半径到大半径,其结果将是相同的。使用此方法,您可以再次表示快速对象用单点和使用的较早的方法讨论。
代码
在加载一个慢速度模型后,你需要保存它的BoundingSphere在Tag属性里面:
1 protected override void LoadContent()
2 {
3 device=graphics.GraphicsDevice;
4 basicEffect=new BasicEffect(device,null);
5 cCross=new CoordCross(device);
6 myModel=XNAUtils.LoadModelWithBoundingSphere(ref modelTransforms,"tank",Content);
7 }
8 //现在你有了球体,可以调用这个方法了,表名一个碰撞发生在最后一祯
9 private bool RayCollision(Model model,Matrix world,Vector3 lastPosition,Vector3 currentPosition)
10 {
11 BoundingSphere modelSpere=(BoundingSphere)model.Tag;
12 BoundingSphere transSphere=XNAUtils.TransformBoundingSphere(modelSphere,world);
13 Vector3 direction=currentPosition-lastPosition;
14 float distanceCovered=direction.Length();
15 direction.Normalize();
16 Ray ray=new Ray(lastPosition,direction);
17 bool collision=false;
18 float? intersection=ray.Intersects(transSphere);
19 if(intersection!=null)
20 if(intersection<=distanceCovered)
21 collision=true;
22 return collision;
23 }
2 {
3 device=graphics.GraphicsDevice;
4 basicEffect=new BasicEffect(device,null);
5 cCross=new CoordCross(device);
6 myModel=XNAUtils.LoadModelWithBoundingSphere(ref modelTransforms,"tank",Content);
7 }
8 //现在你有了球体,可以调用这个方法了,表名一个碰撞发生在最后一祯
9 private bool RayCollision(Model model,Matrix world,Vector3 lastPosition,Vector3 currentPosition)
10 {
11 BoundingSphere modelSpere=(BoundingSphere)model.Tag;
12 BoundingSphere transSphere=XNAUtils.TransformBoundingSphere(modelSphere,world);
13 Vector3 direction=currentPosition-lastPosition;
14 float distanceCovered=direction.Length();
15 direction.Normalize();
16 Ray ray=new Ray(lastPosition,direction);
17 bool collision=false;
18 float? intersection=ray.Intersects(transSphere);
19 if(intersection!=null)
20 if(intersection<=distanceCovered)
21 collision=true;
22 return collision;
23 }