[翻译]XNA 3.0 Game Programming Recipes之twenty-six
PS:自己翻译的,转载请著明出处格
4-19 检测Pointer是否已经超出了模型
问题
你想检测用户的Pointer是否已经超出一个模型在你的3D场景里。
解决方案
在XNA中,它很容易得到Pointer的2D位置在场景中。虽然,这个单独的点在你的场景中相应到在你程序的3D空间的整条射线,如图4-28所示。
作为结果,如果你想检查模型,pointer是否已经超出了,你需要检查射线是否与任何模型相撞。因此,这节将使用4-18节的代码。
这是完全有可能的,射线与多个模型相交。这节同样显示如何检测哪个模型太接*屏幕。
它如何工作的
所有你应该做的是创建一个3Dpointer射线和模型一起传到ModelRayCollision方法(4-18节创建)
你可以定义一条射线仅用你知道在射线上的两个点。如图4-28所定义你准备使用的两个点。第一个点是pointer射线与最*的剪辑*面想碰撞;第二个点是与最远的剪辑*面相碰撞的(2-1节有更多的关于剪辑*面的知识)。围绕这一问题你可以找到它们的3D位置。
如果你知道两个点的3D位置,你转换它们用视景矩阵来获得它们在屏幕上的2D位置。尽管,当你转换一个Vector3,结果是另一个Vector3,用视景矩阵转换的,X和Y组成2D屏幕上的位置。第三个坐标,Z同样包含有用的信息,因为它包含相机和初始点之间的距离,0表示一个点在*景剪辑*面,1表示点在远景剪辑*面。这个距离被保存在深度缓冲中,事实上,每一个象素被XNA绘制在2D*面有三个坐标。
这节可以被用来作为这样的一个例子。这两个点你正在寻找共享同样的象素在屏幕上,同样的2D位置,所以他们共享X和Y。因为第一个点是在*剪辑*面,所以它的Z为0。第二个点在远剪辑*面,它的Z为1。在屏幕空间上这两个点的三个坐标,在鼠标指针情况下,如下代码:
(mouseX,mouseY,0)
(mouseX,mouseY,1)
这里是代码:
幸运的是,XNA提供UnProject方法,它准确的执行映射和逆矩阵变换。
注意:你想知道相对于(0,0,0)3D的初始位置,所以你指定Matrix.Identity作为世界矩阵。视景投影矩阵用视景和投影矩阵相乘得到。这就是为什么你指定两个矩阵作为第二和第三个参数。
一旦你知道射线的两个点,你可以创建一条射线对象:
检查多对象
如果你有多个对象在你的场景中,很可能很多的对象与射线相碰撞。在大数情况下,你的兴趣仅仅在接*摄象机的,因为这将是一个实际上是占用了的屏幕上的像素。
你可以轻微调整你的ModelRayCollision以至于它返回到碰撞的距离,而不是简单的true或者false.类似于Intersect方法,你可以使用一个空的float?变量,这样你可以返回null如果没有碰撞发生:
结果值在collisionDistance变量中将包含到摄象机最*的碰撞,这样被返回。这可以用这个结果去检测哪个模型离摄象机最*。
代码
这段代码创建一个3D射线描述所有点属于这个象素所显示的指针。
这射线被传递到ModelRayCollision方法中
4-19 检测Pointer是否已经超出了模型
问题
你想检测用户的Pointer是否已经超出一个模型在你的3D场景里。
解决方案
在XNA中,它很容易得到Pointer的2D位置在场景中。虽然,这个单独的点在你的场景中相应到在你程序的3D空间的整条射线,如图4-28所示。
作为结果,如果你想检查模型,pointer是否已经超出了,你需要检查射线是否与任何模型相撞。因此,这节将使用4-18节的代码。
这是完全有可能的,射线与多个模型相交。这节同样显示如何检测哪个模型太接*屏幕。
它如何工作的
所有你应该做的是创建一个3Dpointer射线和模型一起传到ModelRayCollision方法(4-18节创建)
你可以定义一条射线仅用你知道在射线上的两个点。如图4-28所定义你准备使用的两个点。第一个点是pointer射线与最*的剪辑*面想碰撞;第二个点是与最远的剪辑*面相碰撞的(2-1节有更多的关于剪辑*面的知识)。围绕这一问题你可以找到它们的3D位置。
如果你知道两个点的3D位置,你转换它们用视景矩阵来获得它们在屏幕上的2D位置。尽管,当你转换一个Vector3,结果是另一个Vector3,用视景矩阵转换的,X和Y组成2D屏幕上的位置。第三个坐标,Z同样包含有用的信息,因为它包含相机和初始点之间的距离,0表示一个点在*景剪辑*面,1表示点在远景剪辑*面。这个距离被保存在深度缓冲中,事实上,每一个象素被XNA绘制在2D*面有三个坐标。
这节可以被用来作为这样的一个例子。这两个点你正在寻找共享同样的象素在屏幕上,同样的2D位置,所以他们共享X和Y。因为第一个点是在*剪辑*面,所以它的Z为0。第二个点在远剪辑*面,它的Z为1。在屏幕空间上这两个点的三个坐标,在鼠标指针情况下,如下代码:
(mouseX,mouseY,0)
(mouseX,mouseY,1)
这里是代码:
1 MouseState mouseState=Mouse.GetState();
2 Vector3 nearScreenPoint=new Vector3(mouseState.X,mouseState.Y,0);
3 Vector3 farScreenPoint=new Vector3(mouseState.X,mouseState.Y,1);
如果你从3D空间到屏幕空间,你转变3D点用视景矩阵。这里你想转变这些点从屏幕空间到3D空间,所以你要变换的逆矩阵的ViewProjection 。您还需要地图的X 和Y坐标指针到[-1,1]区域,所以你也需要屏幕的高度和宽度像素。2 Vector3 nearScreenPoint=new Vector3(mouseState.X,mouseState.Y,0);
3 Vector3 farScreenPoint=new Vector3(mouseState.X,mouseState.Y,1);
幸运的是,XNA提供UnProject方法,它准确的执行映射和逆矩阵变换。
1 Vector3 near3DWorldPoint=device.Viewport.Unproject(nearScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
2 Vector3 far3DWorldPoint=device.Viewport.Unproject(farScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
在图4-28中,这两个点你包含两个点的3D位置。2 Vector3 far3DWorldPoint=device.Viewport.Unproject(farScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
注意:你想知道相对于(0,0,0)3D的初始位置,所以你指定Matrix.Identity作为世界矩阵。视景投影矩阵用视景和投影矩阵相乘得到。这就是为什么你指定两个矩阵作为第二和第三个参数。
一旦你知道射线的两个点,你可以创建一条射线对象:
1 Vector3 pointerRayDirection=far3DWorldPoint-near3DWorldPoint;
2 pointerRayDirection.Normalize();
3 Ray pointerRay=new Ray(near3WorldPoint,pointerRayDirection);
随着射线创建,你可以检测射线和你模型的碰撞了,使用在前面所创建的ModeRayCollision方法:
2 pointerRayDirection.Normalize();
3 Ray pointerRay=new Ray(near3WorldPoint,pointerRayDirection);
1 selected=ModelRayCollision(myModel,modelWorld,pointerRay);
2 Adding a Crosshair to Verify Accuracy
早些时候提交的代码看起来非常不错,但有什么好处,如果你不能对其进行测试?让我们添加一个图像显示指针的位置。为简要介绍了渲染图像到屏幕,如3-1。开始存储指针的2D屏幕位置用一个Vector2:2 Adding a Crosshair to Verify Accuracy
1 pointerPosition=new Vector2(mouseState.X,mouseState.Y);
在你的LoadContent方法,添加一个SpriteBatch对象和一个Texture2D保持一十字图片
1 spriteBatch=new SpriteBatch(device);
2 crosshair=content.Load<Texture2D>("cross");
用Draw方法绘制这个图象到屏幕:
2 crosshair=content.Load<Texture2D>("cross");
1 spriteBatch.Begin(SpriteBlendMode.AlphaBlend,SpriteSortMode.Deferred,SaveStateMode.SaveState);
2 spriteBatch.Draw(cross,mouseCoords,null,Color.White,0,new Vector2(7,7),SpriteEffects.None,0);
3 spriteBatch.End();
这个允许你可视化你的指针在屏幕上。图象的中心点(7,7)将会处于位置指针。2 spriteBatch.Draw(cross,mouseCoords,null,Color.White,0,new Vector2(7,7),SpriteEffects.None,0);
3 spriteBatch.End();
检查多对象
如果你有多个对象在你的场景中,很可能很多的对象与射线相碰撞。在大数情况下,你的兴趣仅仅在接*摄象机的,因为这将是一个实际上是占用了的屏幕上的像素。
你可以轻微调整你的ModelRayCollision以至于它返回到碰撞的距离,而不是简单的true或者false.类似于Intersect方法,你可以使用一个空的float?变量,这样你可以返回null如果没有碰撞发生:
1 private float? ModelRayCollision(Model model,Matrix modelWorld,Ray ray)
2 {
3 Matrix[] modelTransforms=new Matrix[mode.Bones.Count];
4 model.CopyAbsoluteBoneTransformsTo(modelTransforms);
5 float? collisionDistance=null;
6 foreach(ModelMesh mesh in model.Meshes)
7 {
8 Matrix absTransform=modelTransforms[mesh.ParentBone.Index]*modelWorld;
9 Triangle[] meshTriangles=(Triangle[])mesh.Tag;
10 foreach(Triangle tri in meshTriangles)
11 {
12 Vector3 transP0=Vector3.Transform(tri.p0,absTransform);
13 Vector3 transP1=Vector3.Transform(tri.p1,absTransform);
14 Vector3 transP2=Vector3.Transform(tri.p2,absTransform);
15 Plane trianglePlane=new Plane(transP0,transP1,transP2);
16 float distanceOnRay=RayPlaneIntersection(ray,trianglePlane);
17 Vector3 intersectionPoint=ray.Position+distanceOnRay*ray.Direction;
18 if(PointInsideTriangle(transP0,transP1,transP2,intersectionPoint))
19 if((collisionDistance==null)||(distanceOnRay<collisionDistance))
20 collisionDistance=distanceOnRay;
21 }
22 }
23 return collisionDistance;
24 }
每一次一个碰撞被检测,检查collisionDistance是否为null.这表示第一碰撞是否被检测,这样你可以保存碰撞的距离。从这以后,检查后面的距离是否比已经知道的距离短。如果短,覆盖最小的距离。2 {
3 Matrix[] modelTransforms=new Matrix[mode.Bones.Count];
4 model.CopyAbsoluteBoneTransformsTo(modelTransforms);
5 float? collisionDistance=null;
6 foreach(ModelMesh mesh in model.Meshes)
7 {
8 Matrix absTransform=modelTransforms[mesh.ParentBone.Index]*modelWorld;
9 Triangle[] meshTriangles=(Triangle[])mesh.Tag;
10 foreach(Triangle tri in meshTriangles)
11 {
12 Vector3 transP0=Vector3.Transform(tri.p0,absTransform);
13 Vector3 transP1=Vector3.Transform(tri.p1,absTransform);
14 Vector3 transP2=Vector3.Transform(tri.p2,absTransform);
15 Plane trianglePlane=new Plane(transP0,transP1,transP2);
16 float distanceOnRay=RayPlaneIntersection(ray,trianglePlane);
17 Vector3 intersectionPoint=ray.Position+distanceOnRay*ray.Direction;
18 if(PointInsideTriangle(transP0,transP1,transP2,intersectionPoint))
19 if((collisionDistance==null)||(distanceOnRay<collisionDistance))
20 collisionDistance=distanceOnRay;
21 }
22 }
23 return collisionDistance;
24 }
结果值在collisionDistance变量中将包含到摄象机最*的碰撞,这样被返回。这可以用这个结果去检测哪个模型离摄象机最*。
代码
这段代码创建一个3D射线描述所有点属于这个象素所显示的指针。
这射线被传递到ModelRayCollision方法中
1 Vector3 nearScreenPoint=new Vector3(mouseState.X,mouseState.Y,0);
2 Vector3 farScreenPoint=new Vector3(mouseState.X,mouseState.Y,1);
3 Vector3 near3DWorldPoint=device.Viewport.Unproject(nearScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
4 Vector3 far3DWorldPoint=device.Viewport.Unproject(farScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
5 Vector3 pointerRayDirection=far3DWorldPoint-near3DWorldPoint;
6 pointerRayDirection.Normalize();
7 Ray pointerRay=new Ray(near3DWorldPoint,pointerRayDirection);
8 selected=ModelRayCollision(myModel,worldMatrix,pointerRay);
2 Vector3 farScreenPoint=new Vector3(mouseState.X,mouseState.Y,1);
3 Vector3 near3DWorldPoint=device.Viewport.Unproject(nearScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
4 Vector3 far3DWorldPoint=device.Viewport.Unproject(farScreenPoint,fpsCam.ProjectionMatrix,fpsCam.ViewMatrix,Matrix.Identity);
5 Vector3 pointerRayDirection=far3DWorldPoint-near3DWorldPoint;
6 pointerRayDirection.Normalize();
7 Ray pointerRay=new Ray(near3DWorldPoint,pointerRayDirection);
8 selected=ModelRayCollision(myModel,worldMatrix,pointerRay);