XNA中的拾取与碰撞检

24 篇文章 0 订阅
15 篇文章 0 订阅

3D物体拾取及XNA实现


2009-10-19  来自:code84  字体大小:【    


  • 摘要:拾取主要用来表示能过鼠标在屏幕上单击来选中某个3D模型,然后就可以获取这个模型信息,同时也可以对这个模型进行编辑。 拾取算法的主要思想是:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交,则获取该相交三角形的信息。
  • -


拾取原理

    拾取主要用来表示能过鼠标在屏幕上单击来选中某个3D模型,然后就可以获取这个模型信息,同时也可以对这个模型进行编辑。

    拾取算法的主要思想是:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交,则获取该相交三角形的信息。

拾取的具体过程如下:

1.使用获取鼠标当前状态。

2.把屏幕坐标转换为屏幕空间坐标。

屏幕中心是(0, 0),屏幕右下角是 (1*(屏幕宽度/屏幕高度), 1)。屏幕空间的x坐标这样计算: ((鼠标x坐标) / (屏幕宽度/2))-1.0f) *(屏幕宽度/屏幕高度)

屏幕空间的y坐标这样计算: (1.0f − ((鼠标y坐标) / (屏幕高度/ 2 ) )

3.计算摄像机视图中的宽度和高度的截距比。如下计算:

view ratio = tangent(camera field of view / 2 )

通常,你只需要一次,然后保存这个结果供以后使用。在摄像机视野改变的时候,你需要重新计算。

4.把屏幕空间坐标转换成摄像机空间坐标系近景裁剪平面中的一个点。

Near point = ((屏幕空间x坐标)*(近景裁剪平面的Z值)*(view ratio ),
(屏幕空间y坐标)*(近景裁剪平面的Z值)*(view ratio ),
(-近景裁剪平面的Z值) )

5.把屏幕空间坐标转换成摄像机空间坐标系远景裁剪平面中的一个点。

Far point = ((屏幕空间x坐标)* (远景裁剪平面的Z值)*(view ratio ),
(屏幕空间y坐标)*(远景裁剪平面的Z值 )*(view ratio),
(-远景裁剪平面的Z值) )

6.使用Invert 获得摄像机视图矩阵的一个Invert的拷贝。使用摄像机视图矩阵(Matrix)来把世界坐标转化成摄像机坐标,使用这个反转的矩阵可以把摄像机坐标转化成世界坐标。

Matrix invView = Matrix.Invert(view);

7.使用反转的视图矩阵(view Matrix ) 和Transform方法把远景点和近景点转换成世界空间坐标。

Vector3 worldSpaceNear = Vector3.Transform(cameraSpaceNear, invView);

Vector3 worldSpaceFar = Vector3.Transform(cameraSpaceFar, invView);

8.创建一个射线(Ray)类的对象,起点是近景点,指向远景点。

Ray pickRay = new Ray(worldSpaceNear, worldSpaceFar - worldSpaceNear);

9.对世界空间中的所有物体循环调用 Intersects 方法来检测 Ray 是否与其相交。如果相交,则检测是不是目前为止距玩家最近的物体,如果是,记录下这个物体以及距离值,替换掉之前找到的最近的物体的记录。当完成对所有物体的检测后,最后一次记录的那个物体就是玩家用鼠标点击的距离玩家最近的物体。

XNA实现

     效果图如下:

 

主要方法如下:

 

001.publicclassGame1:Game
002. {
003.     GraphicsDeviceManager graphi;
004.     model[] models;
005.     Texture2D texture;
006.  
007.     Matrix view;
008.     Matrix projection;
009.  
010.     intselectIndex = -1;
011.  
012.     publicGame1()
013.     {
014.         graphi =newGraphicsDeviceManager(this);
015.         Content.RootDirectory ="Content";
016.         IsMouseVisible =true;
017.     }
018.  
019.     protectedoverridevoid Initialize()
020.     {
021.         models =newmodel[4];
022.         models[0] =newmodel();
023.         models[0].position = Vector3.Zero;
024.  
025.  
026.         models[1] =newmodel();
027.         models[1].position =newVector3(80,0,0);
028.  
029.         models[2] =newmodel();
030.         models[2].position =newVector3(-80, 0, 0);
031.  
032.         models[3] =newmodel();
033.         models[3].position =newVector3(80, 80, 0);
034.  
035.         //观察矩阵
036.         view = Matrix.CreateLookAt(newVector3(0, 0, 300), Vector3.Forward, Vector3.Up);
037.         //投影矩阵
038.         projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 10000);
039.         base.Initialize();
040.     }
041.  
042.  
043.     protectedoverridevoid LoadContent()
044.     {
045.         //载入模型文件
046.         models[0].mod = Content.Load<Model>("bsphere");
047.         models[1].mod = Content.Load<Model>("cub");
048.         models[2].mod = Content.Load<Model>("pyramid");
049.         models[3].mod = Content.Load<Model>("teaport");
050.  
051.         //载入选中物体的贴图纹理
052.         texture = Content.Load<Texture2D>("sp");
053.         base.LoadContent();
054.     }
055.  
056.     /**//// <summary>
057.     /// 更新
058.     /// </summary>
059.     /// <param name="gameTime"></param>
060.     protectedoverridevoid Update(GameTime gameTime)
061.     {
062.         CheckMousClick();
063.         base.Update(gameTime);
064.     }
065.  
066.     /**//// <summary>
067.     /// 绘制
068.     /// </summary>
069.     /// <param name="gameTime"></param>
070.     protectedoverridevoid Draw(GameTime gameTime)
071.     {
072.         GraphicsDevice.Clear(Color.CornflowerBlue);
073.  
074.         for(inti = 0; i < models.Length;i++ )
075.         {
076.             foreach(ModelMesh mesh in models[i].mod.Meshes)
077.             {
078.                 foreach(BasicEffect effect in mesh.Effects)
079.                 {
080.                     effect.TextureEnabled =true;
081.                     //根据是否选中设置物体的贴图
082.                     if(i != selectIndex)
083.                     {
084.                         //如果没有选中,不贴图
085.                         effect.Texture =null;
086.                     }
087.                     else
088.                         effect.Texture = texture;
089.  
090.                     effect.World = Matrix.CreateTranslation(models[i].position);
091.                     effect.View = view;
092.                     effect.Projection = projection;
093.  
094.                     effect.EnableDefaultLighting();
095.                     effect.LightingEnabled =true;
096.                 }
097.  
098.                 mesh.Draw();
099.             }
100.               
101.         }
102.  
103.         base.Draw(gameTime);
104.     }
105.  
106.     /**//// <summary>
107.     /// 取得射线
108.     /// </summary>
109.     /// <returns></returns>
110.     privateRay GetRay()
111.     {
112.         MouseState ms = Mouse.GetState();
113.         Vector3 neerSource =newVector3(ms.X, ms.Y, 0);
114.         Vector3 farSource =newVector3(ms.X, ms.Y, 1);
115.  
116.         Vector3 neerPosi = GraphicsDevice.Viewport.Unproject(neerSource, projection, view, Matrix.Identity);
117.         Vector3 farPosi = GraphicsDevice.Viewport.Unproject(farSource, projection, view, Matrix.Identity);
118.         Vector3 direction=farPosi-neerPosi;
119.         direction.Normalize();
120.         returnnewRay(neerPosi, direction);
121.     }
122.  
123.     /**//// <summary>
124.     /// 鼠标单击
125.     /// </summary>
126.     privatevoidCheckMousClick()
127.     {
128.         if(Mouse.GetState().LeftButton==ButtonState.Pressed)
129.         {
130.             Ray ray = GetRay();
131.             for(inti=0;i<models.Length;i++)
132.             {
133.                 BoundingSphere bs=models[i].mod.Meshes[0].BoundingSphere;
134.                 bs.Center = models[i].position;
135.                 Nullable<float> result = ray.Intersects(bs);
136.                 if(result.HasValue)
137.                 {
138.                     selectIndex = i;
139.                 }
140.             }
141.         }
142.     }
143. }
144.  
145. /**//// <summary>
146. /// 自定义模型结构
147. /// </summary>
148. publicstruct model
149. {
150.     publicModel mod;
151.     publicTexture2D text;
152.     publicVector3 position;
153. }

 

其中用到了XNA中的Viewport.Unproject方法和Ray.Intersects方法,它们的内部结构分别如下:

 
01.publicvoidIntersects(refRay ray,out float? result)
02.{
03.    result = 0;
04.    floatnum = 0f;
05.    floatmaxValue =float.MaxValue;
06.    if(Math.Abs(ray.Direction.X) < 1E-06f)
07.    {
08.        if((ray.Position.X < this.Min.X) || (ray.Position.X >this.Max.X))
09.        {
10.            return;
11.        }
12.    }
13.    else
14.    {
15.        floatnum11 = 1f / ray.Direction.X;
16.        floatnum8 = (this.Min.X - ray.Position.X) * num11;
17.        floatnum7 = (this.Max.X - ray.Position.X) * num11;
18.        if(num8 > num7)
19.        {
20.            floatnum14 = num8;
21.            num8 = num7;
22.            num7 = num14;
23.        }
24.        num = MathHelper.Max(num8, num);
25.        maxValue = MathHelper.Min(num7, maxValue);
26.        if(num > maxValue)
27.        {
28.            return;
29.        }
30.    }
31.    if(Math.Abs(ray.Direction.Y) < 1E-06f)
32.    {
33.        if((ray.Position.Y < this.Min.Y) || (ray.Position.Y >this.Max.Y))
34.        {
35.            return;
36.        }
37.    }
38.    else
39.    {
40.        floatnum10 = 1f / ray.Direction.Y;
41.        floatnum6 = (this.Min.Y - ray.Position.Y) * num10;
42.        floatnum5 = (this.Max.Y - ray.Position.Y) * num10;
43.        if(num6 > num5)
44.        {
45.            floatnum13 = num6;
46.            num6 = num5;
47.            num5 = num13;
48.        }
49.        num = MathHelper.Max(num6, num);
50.        maxValue = MathHelper.Min(num5, maxValue);
51.        if(num > maxValue)
52.        {
53.            return;
54.        }
55.    }
56.    if(Math.Abs(ray.Direction.Z) < 1E-06f)
57.    {
58.        if((ray.Position.Z < this.Min.Z) || (ray.Position.Z >this.Max.Z))
59.        {
60.            return;
61.        }
62.    }
63.    else
64.    {
65.        floatnum9 = 1f / ray.Direction.Z;
66.        floatnum4 = (this.Min.Z - ray.Position.Z) * num9;
67.        floatnum3 = (this.Max.Z - ray.Position.Z) * num9;
68.        if(num4 > num3)
69.        {
70.            floatnum12 = num4;
71.            num4 = num3;
72.            num3 = num12;
73.        }
74.        num = MathHelper.Max(num4, num);
75.        maxValue = MathHelper.Min(num3, maxValue);
76.        if(num > maxValue)
77.        {
78.            return;
79.        }
80.    }
81.    result =newfloat?(num);
82.}

  



一、二维图片的拾取与碰撞检测 1、拾取  二维图片的拾取是通过判断鼠标的当前坐标是否落在图片的现实区域中来实现的  (1)、获取顶点信息(图片的显示位置)  (2)、获取纹理的宽度与高度   myTexture.Width  
myTexture.Height
 (3)、获取鼠标位置信息  
MouseState state = Mouse.GetState();
 (4)、判断鼠标坐标是否在纹理的显示范围之内 
 
if (state.X >= pos.X && state.X <= pos.X + myTexture.Width && state.Y >= pos.Y 
&& state.Y <= pos.Y + myTexture.Height)
2、碰撞检测  二维图片的碰撞检测可以通过判断图片的两个显示矩形是否相交来判断。 
(1)、构造矩形
            /*构造第一个矩形*/
            Rectangle moveRect = new Rectangle();             moveRect.X = (int)movePosition.X;             moveRect.Y = (int)movePosition.Y;             moveRect.Width = myTexture.Width;             moveRect.Height = myTexture.Height; 
  
            /*构造第二个矩形*/
            Rectangle rectPos = new Rectangle();             rectPos.X = (int)pos.X;             rectPos.Y = (int)pos.Y;
            rectPos.Width = myTexture.Width;             rectPos.Height = myTexture.Height;  (2)、判断矩形是否相交 
 
if(moveRect.Intersects(rectPos))
二、SD模型的拾取与碰撞检测 1、碰撞检测
XNA中的碰撞检测是通过测试两个物体的包围盒或者包围球是否相交来实现的。XNA为模型的每个Meshe建立一个包围盒和包围球。




(1)、获取包围球
BoundingSphere c1BoundingSphere = model1.Meshes[i].BoundingSphere; BoundingSphere c2BoundingSphere = model2.Meshes[j].BoundingSphere; (2)、判断两个包围球是否相交
if (c1BoundingSphere.Intersects(c2BoundingSphere))
返回值为true表示两模型相交,返回值为false表示两模型没有相交
2、拾取
拾取是指通过鼠标来选中某个模型。 (1)、获取当前鼠标状态 
     MouseState mouseState = Mouse.GetState(); (2)、获取鼠标的位置信息
int mouseX = mouseState.X; int mouseY = mouseState.Y;
 
(3)、构造摄像机坐标系下的两个点,分别代表了近点和远点
Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f); Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 1f);
 
(4)、将两个点通过逆投影矩阵转换为世界坐标系下的两个点
Matrix world = Matrix.CreateTranslation(0, 0, 0);
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearsource, 
proj, view, world);
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farsource, 
proj, view, world);
(5)、构造拾取射线
Vector3 direction = farPoint - nearPoint; direction.Normalize();
Ray pickRay = new Ray(nearPoint, direction);
 (6)、判断射线是否与模型Mesh的包围盒相交 
 
Nullable<float> result = pickRay.Intersects(sphere);
如果result.HasValue 为 true并且result.Value < selectedDistance则认为物体被拾取。当射线与多个物体相交时可通过result.Value来判断谁在前面,值小的更靠近屏幕。在初始阶段我们将selectedDistance设置为最大值(float.MaxValue;)。
 
注:XNA为我们提供的拾取仅仅能够知道是否与某模型相交,无法提供与哪一点相交的信息,




为此,我们可以通过让拾取射线与模型的某个三角片相交并求其交点的方法来实现。



检查用户是否点击了一个3D对象

这个例子讲述了通过创建一个从摄像机近景裁剪面指向远景裁剪面的射线怎样检测鼠标是否放在一个3D物体上。

示例截图

本示例仅适用于Windows 平台开发。 Xbox 360不支持Mouse和MouseState对象。

检查用户是否点击了一个3D对象

检查鼠标是否位于一个3D对象之上
  1. 使用GetState获取鼠标当前状态。

    MouseState mouseState = Mouse.GetState();
  2. 从X和Y中获取鼠标的屏幕坐标。

    int mouseX = mouseState.X;int mouseY = mouseState.Y;
  3. 使用Viewport.Unproject得出在近景裁剪面和远景裁剪面上的点在世界空间中的位置。对于近景裁剪面的点,传递一个Vector3向量,此向量的x和y设置为鼠标位置,而z设置为0。

  4. 对于远景裁剪面的点,传递一个Vector3向量,此向量的x和y设置为鼠标位置,而z设置为1。

  5. 对于这两个点,使用Unproject传递给当前投影矩阵,观察矩阵和点(0,0,0)的平移矩阵。

    Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
    Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 1f);Matrix world = Matrix.CreateTranslation(0, 0, 0);
    
    Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearsource, proj, view, world);
    
    VectVector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farsource, proj, view, world);
  6. 创建一个从nearPoint指向farPoint的Ray。

    // Create a ray from the near clip plane to the far clip plane.
    Vector3 direction = farPoint - nearPoint;rection.Normalize();
    Ray Ray pickRay = new Ray(nearPoint, direction);
  7. 使用Intersects循环检测场景中的所有对象是否和Ray相交。

  8. 如果Ray与一个对象相交,检查这个对象是否是相交最近的。如果是,储存这个对象和相交的距离,并替换前面储存的对象。

  9. 对你完成对象的循环,存储的最后一个对象将会是用户点击区域内最近的那个对象。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值