4-10 使用BoundingSpheres的基本模式:碰撞检测
问题
要检查两个模型是否碰撞了。如果您有许多模型在你的场景中,你不能再进行详细,每三角检查。要启动一个快速的方法来检查可能的碰撞和检查后精细的运行。
解决方案
在进行碰撞检查,你会发现自己总是作出在速度和准确性之间的权衡。在大多数情况下,你需要解决的综合检查。在第一次执行中,您要扫描所有的物体的可能的碰撞使用非常快速检查以及以后进行详细的测试只适用于物体上的确定的响应在快速检查中。
这节例子表明两个快速的方法检查存在的两模型之间可能的碰撞。
最快的方法是找出模型的两个球体BoundingSpheres,并且检查他们是否相撞用调用Intersect方法在其中一个球体上。
你可以延长一点以提高准确度。模型由几个ModelMeshes组成,它保存这几何信息关于模型的不同部分。ModelMeshes中的每一能产生它自己的BoundingSphere,所以你可以执行一个碰撞检测在第一个模型的每一个BoundingSpheres和第二个模型的每一个BoundingSpheres之间。显然,这更高的精度达到一个更高的计算成本。
它是如何工作的
Fastest Check
这个方法将使用BoundingSphere包围整个模型。如果两个模型的这些BoundingSpheres碰撞,它可能会是两个模型碰撞。
在某些情况下,这可以是非常糟糕的结果,如在一个滑雪游戏要检测两个滑雪板的球员在哪里发生了碰撞。滑雪场的范围是巨大的与滑雪撬本身相比;滑雪撬将占用不到百分之一的体积范围。此检查将所有在球体里对象分类(如其他滑雪撬!)作为碰撞所示, 图4-13 。
然而,使用这种方法往往是作为第一个检查,因为它的速度快。 这将是一个耻辱,废物处理能力,以检查两个模型是否有碰撞,它其中球BoundingSphere甚至不发生碰撞。
执行最快的检查,开始加载模型和产生它们的球状BoundingSphere作为说明在4-5节。这个BoundingSphere将会被存储在模型的Tag属性中:
1 myModel=XNAUtils.LoadModelWithBoundingSphere(ref modelTransforms,"tank",Content);
这个BoundingSphere将会包含模型的中心(它不需要在模型的(0,0,0)点)和它的半径。当你绘制两个模型时,你需要一个不同的世界矩阵为每个模型,使它们在3D世界里移动(缩放/旋转)到正确的位置。当你移动(缩放/旋转)一个模型时,你同样需要移动BoundingSphere的中心(缩放半径)。你可以使用我的XNAUtils文件的TransformBoundingSphere来实现这个。这个方法花费一个BoundingSphere和一个世界矩阵。它移动中心和缩放球体的半径依照这个矩阵。结果球体被返回。
所有你需要做的是检查两个模型之间的碰撞,它用相应模型的世界矩阵转化为每一个BoundingSpheres,并且检查转化成的BoundingSpheres是否相撞。
1 private bool ModelsCollode1(Model model1,Matrix world1,Model model2,Matrix world2)
2 {
3 BoundingSphere origShpere1=(BoundingSphere)model1.Tag;
4 BoundingSphere sphere1=XNAUtils.TransformBoundingSphere(origSphere1,world1);
5 BoundingSphere origShpere2=(BoundingSphere)model2.Tag;
6 BoundingSphere sphere2=XNAUtils.TransformBoundingSphere(origSphere2,world2);
7 bool collision=sphere1.Intersects(sphere2);
8 return collision;
9 }
因为这个一个模型的Tag属性能真实的包含任何东西(对象类是C#的根类),你需要告诉你的编译器在这种情况下,它包含的一个BoundingSphere被转化成一个cast.如果这个被转化的BoundingSpheres碰撞了,这个方法返回一个true。2 {
3 BoundingSphere origShpere1=(BoundingSphere)model1.Tag;
4 BoundingSphere sphere1=XNAUtils.TransformBoundingSphere(origSphere1,world1);
5 BoundingSphere origShpere2=(BoundingSphere)model2.Tag;
6 BoundingSphere sphere2=XNAUtils.TransformBoundingSphere(origSphere2,world2);
7 bool collision=sphere1.Intersects(sphere2);
8 return collision;
9 }
More Accurate, a Bit Slower
每一个模型由多个成员组成,它的几何数据被存储在模型的Meshes集合中,在4-1节有解释。每一个如ModeMesh能生成它自己的BoundingSphere。所有这些小的BoundingSpheres的总量将会比模型的球体的BoundingSphere还会小。如图4-14所显示那样。在图象的右部,你可以看见分割开的BoundingSphere为模型的所有部分。
图象的左侧,两个BoundingSpheres碰撞,所以第一检查表明了两个模型之间的碰撞。换句话说,没有一个BoundingShperes是正确的碰撞,所以这个检查将会正确的表明两个模型并没有碰撞。第二种方法经常给一个好的结果;尽管,第一种情况只需要一次检查,同时第二种情况要求(模型1的内部元素)*(模型2的内部元素)检查。
下面的方法是根据第二种情况的检测碰撞:
1 private bool FinerCheck(Model model1,Matrix world1,Model model2,Matrix world2)
2 {
3 if(CoarseCheck(model1,world1,model2,world2)==false)
4 return false;
5 bool collision=false;
6 Matrix[] model1Transforms=new Matrix[model1.Bones.Count];
7 Matrix[] model2Transforms=new Matrix[model2.Bones.Count];
8 model1.CopyAbsoluteBoneTransformsTo(model1Transforms);
9 model2.CopyAbsoluteBoneTransformsTo(model2Transforms);
10 foreach(ModeMesh mesh1 in model1.Meshes)
11 {
12 BoundingSphere origSphere1=mesh1.BoundingSphere;
13 Matrix trans1=model1Transforms[mesh1.ParentBone.Index]*world1;
14 BoundingSphere transSphere1=XNAUtils.TransformBoundingSphere(origSphere1,trans1);
15 foreach(ModeMesh mesh2 in model2.Meshes)
16 {
17 BoundingSphere origSphere2=mesh2.BoundingSphere;
18 Matrix trans2=model2Transforms[mesh2.ParentBone.Index]*world2;
19 BoundingSphere transSphere2=XNAUtils.TransformBoundingSphere(origSphere2,trans2);
20 if(transSphere1.Intersects(transSphere2))
21 collision=true;
22 }
23 }
24 return collision;
25 }
你开始执行快速检查。如果这个检查返回false,但肯定没有需要执行的精细检查。2 {
3 if(CoarseCheck(model1,world1,model2,world2)==false)
4 return false;
5 bool collision=false;
6 Matrix[] model1Transforms=new Matrix[model1.Bones.Count];
7 Matrix[] model2Transforms=new Matrix[model2.Bones.Count];
8 model1.CopyAbsoluteBoneTransformsTo(model1Transforms);
9 model2.CopyAbsoluteBoneTransformsTo(model2Transforms);
10 foreach(ModeMesh mesh1 in model1.Meshes)
11 {
12 BoundingSphere origSphere1=mesh1.BoundingSphere;
13 Matrix trans1=model1Transforms[mesh1.ParentBone.Index]*world1;
14 BoundingSphere transSphere1=XNAUtils.TransformBoundingSphere(origSphere1,trans1);
15 foreach(ModeMesh mesh2 in model2.Meshes)
16 {
17 BoundingSphere origSphere2=mesh2.BoundingSphere;
18 Matrix trans2=model2Transforms[mesh2.ParentBone.Index]*world2;
19 BoundingSphere transSphere2=XNAUtils.TransformBoundingSphere(origSphere2,trans2);
20 if(transSphere1.Intersects(transSphere2))
21 collision=true;
22 }
23 }
24 return collision;
25 }
如果粗糙的检查表明有碰撞的可能,你继续前移。你首先设置碰撞变量为false,它会继续这样直到你遇到一个碰撞。因为你需要移动每一个小的BoundingSphere到它正确的位置在模型中,你需要绝对的Bone矩阵为两个模型。
为了执行这个检查,第一个模型的每一个ModelMesh,你转化它的BoundingSphere到它在3D世界的绝对位置。为此,你需要考虑在这个模型中的BoundingSphere的位置在3D世界里。
下一步,您翻阅模型2的所有部分,并把这些BoundingSphere转化成三维世界的绝对位置。
对于模型1的每一个BoundingSphere,检查模型2的任何BoundingSphere是否与它发生碰撞,如果发生了,设置碰撞变量为true.
最后,碰撞变量将会表示模型的最后一个BoundingSpheres和模型2的其中一个BoundingSpheres发生了碰撞,然后返回这个变量。
Optimization
由于模型的每一部分,模型2的所有BoundingSpheres每一次都会被用相同的方法转变,这种方法能明显得到优化。例如,您可以做到这一点,靠首先改变了模型1的BoundingSpheres和模型2的BoundingSpheres,在这个方法的开始,把它们保存进一个数组。在此之后,你可以简单的检查这些转变过的BoundingSpheres之间的碰撞。
这是FinerCheck方法下一个版本所做的事情:
1 private bool FinerCheck(Model model1,Matrix world1,Model model2,Matrix world2)
2 {
3 if(CoarseCheck(model1,world1,model2,world2)==false)
4 return false;
5 Matrix[] model1Transforms=new Matrix[model1.Bones.Count];
6 model1.CopyAbsoluteBoneTransformsTo(model1Transforms);
7 BoundingSphere[] model1Spheres=new BoundingSphere[model1.Meshes.Count];
8 for(int i=0;i<model1.Meshes.Count;i++)
9 {
10 ModelMesh mesh=model1.Meshes[i];
11 BoundingSphere origSphere=mesh.BoundingSphere;
12 Matrix trans=model1Transform[mesh.ParentBone.Index]*world1;
13 BoundingSphere transSphere=XNAUtils.TransformBoundingSphere(origSphere,trans);
14 model1Spheres[i]=transSphere;
15 }
16 Matrix[] model2Transforms=new Matrix[model2.Bones.Count];
17 model2.CopyAbsoluteBoneTransformsTo(model2Transforms);
18 BoundingSphere[] model2Spheres=new BoundingSphere[model2.Meshes.Count];
19 for(int i=0;i<model1.Meshes.Count;i++)
20 {
21 ModelMesh mesh=model2.Meshes[i];
22 BoundingSphere origSphere=mesh.BoundingSphere;
23 Matrix trans=model2Transform[mesh.ParentBone.Index]*world2;
24 BoundingSphere transSphere=XNAUtils.TransformBoundingSphere(origSphere,trans);
25 model2Spheres[i]=transSphere;
26 }
27 bool collision=false;
28 for(int i=0;i<model1Sphere.Length;i++)
29 for(int j=0;j<model2Spheres.Length;j++)
30 if(model1Spheres[i].Intersects(model2Spheres[j]
2 {
3 if(CoarseCheck(model1,world1,model2,world2)==false)
4 return false;
5 Matrix[] model1Transforms=new Matrix[model1.Bones.Count];
6 model1.CopyAbsoluteBoneTransformsTo(model1Transforms);
7 BoundingSphere[] model1Spheres=new BoundingSphere[model1.Meshes.Count];
8 for(int i=0;i<model1.Meshes.Count;i++)
9 {
10 ModelMesh mesh=model1.Meshes[i];
11 BoundingSphere origSphere=mesh.BoundingSphere;
12 Matrix trans=model1Transform[mesh.ParentBone.Index]*world1;
13 BoundingSphere transSphere=XNAUtils.TransformBoundingSphere(origSphere,trans);
14 model1Spheres[i]=transSphere;
15 }
16 Matrix[] model2Transforms=new Matrix[model2.Bones.Count];
17 model2.CopyAbsoluteBoneTransformsTo(model2Transforms);
18 BoundingSphere[] model2Spheres=new BoundingSphere[model2.Meshes.Count];
19 for(int i=0;i<model1.Meshes.Count;i++)
20 {
21 ModelMesh mesh=model2.Meshes[i];
22 BoundingSphere origSphere=mesh.BoundingSphere;
23 Matrix trans=model2Transform[mesh.ParentBone.Index]*world2;
24 BoundingSphere transSphere=XNAUtils.TransformBoundingSphere(origSphere,trans);
25 model2Spheres[i]=transSphere;
26 }
27 bool collision=false;
28 for(int i=0;i<model1Sphere.Length;i++)
29 for(int j=0;j<model2Spheres.Length;j++)
30 if(model1Spheres[i].Intersects(model2Spheres[j]
))
31 return true;
32 return collision;
33 }
注意 CopyAbsoluteBoneTransformsTo方法最好是在模型的Bone矩阵已经被更新是被调用。31 return true;
32 return collision;
33 }
代码
CoarseCheck和FinerCheck两个方法在早期都会被列出来。下面的Draw方法定义两个世界矩阵到两个模型的位置在不同的地方。随着时间的过去,一个模型接近另一个模型。在它们之间检测出一个碰撞,背景闪烁红色:
1 protected override void Draw(GameTime gameTime)
2 {
3 //define World matrices
4 float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 1000.0f;
5 Matrix worldM1 = Matrix.CreateScale(0.01f) * Matrix.CreateRotationY(MathHelper.PiOver2) * Matrix.CreateTranslation(-10, 0, 0);
6 Matrix worldM2 = Matrix.CreateScale(0.01f) * Matrix.CreateRotationY(-MathHelper.PiOver2) * Matrix.CreateTranslation(10, 3, 0);
7 worldM2 = worldM2 * Matrix.CreateTranslation(-time * 3.0f, 0, 0);
8 //check for collision
9 if (FinerCheck(myModel, worldM1, myModel, worldM2))
10 {
11 Window.Title = "Collision!!";
12 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Red, 1, 0);
13 }
14 else
15 {
16 Window.Title = "No collision ";
17 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.CornflowerBlue, 1, 0);
18 }
19 //render scene
20 cCross.Draw(fpsCam.ViewMatrix, fpsCam.ProjectionMatrix);
21 DrawModel(myModel, worldM1, modelTransforms);
22 DrawModel(myModel, worldM2, modelTransforms);
23 base.Draw(gameTime);
24 }
2 {
3 //define World matrices
4 float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 1000.0f;
5 Matrix worldM1 = Matrix.CreateScale(0.01f) * Matrix.CreateRotationY(MathHelper.PiOver2) * Matrix.CreateTranslation(-10, 0, 0);
6 Matrix worldM2 = Matrix.CreateScale(0.01f) * Matrix.CreateRotationY(-MathHelper.PiOver2) * Matrix.CreateTranslation(10, 3, 0);
7 worldM2 = worldM2 * Matrix.CreateTranslation(-time * 3.0f, 0, 0);
8 //check for collision
9 if (FinerCheck(myModel, worldM1, myModel, worldM2))
10 {
11 Window.Title = "Collision!!";
12 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Red, 1, 0);
13 }
14 else
15 {
16 Window.Title = "No collision ";
17 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.CornflowerBlue, 1, 0);
18 }
19 //render scene
20 cCross.Draw(fpsCam.ViewMatrix, fpsCam.ProjectionMatrix);
21 DrawModel(myModel, worldM1, modelTransforms);
22 DrawModel(myModel, worldM2, modelTransforms);
23 base.Draw(gameTime);
24 }