您的位置:首页 > 其它

XNA中的拾取与碰撞检

2012-10-24 09:05 225 查看

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.计算摄像机视图中的宽度和高度的截距比。如下计算:

viewratio=tangent(camerafieldofview/2)

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

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

Nearpoint=((屏幕空间x坐标)*(近景裁剪平面的Z值)*(viewratio),

(屏幕空间y坐标)*(近景裁剪平面的Z值)*(viewratio),

(-近景裁剪平面的Z值))

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

Farpoint=((屏幕空间x坐标)*(远景裁剪平面的Z值)*(viewratio),

(屏幕空间y坐标)*(远景裁剪平面的Z值)*(viewratio),

(-远景裁剪平面的Z值))

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

MatrixinvView=Matrix.Invert(view);

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

Vector3worldSpaceNear=Vector3.Transform(cameraSpaceNear,invView);

Vector3worldSpaceFar=Vector3.Transform(cameraSpaceFar,invView);

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

RaypickRay=newRay(worldSpaceNear,worldSpaceFar-worldSpaceNear);

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

XNA实现

效果图如下:





主要方法如下:

001.
public
class
Game1:Game


002.
{


003.
GraphicsDeviceManagergraphi;


004.
model[]models;


005.
Texture2Dtexture;


006.


007.
Matrixview;


008.
Matrixprojection;


009.


010.
int
selectIndex
=-1;


011.


012.
public
Game1()


013.
{


014.
graphi=
new
GraphicsDeviceManager(
this
);


015.
Content.RootDirectory=
"Content"
;


016.
IsMouseVisible=
true
;


017.
}


018.


019.
protected
override
void

Initialize()


020.
{


021.
models=
new
model[4];


022.
models[0]=
new
model();


023.
models[0].position=Vector3.Zero;


024.


025.


026.
models[1]=
new
model();


027.
models[1].position=
new
Vector3(80,0,0);


028.


029.
models[2]=
new
model();


030.
models[2].position=
new
Vector3(-80,
0,0);


031.


032.
models[3]=
new
model();


033.
models[3].position=
new
Vector3(80,
80,0);


034.


035.
//观察矩阵


036.
view=Matrix.CreateLookAt(
new
Vector3(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.
protected
override
void

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.
///<paramname="gameTime"></param>


060.
protected
override
void

Update(GameTimegameTime)


061.
{


062.
CheckMousClick();


063.
base
.Update(gameTime);


064.
}


065.


066.
/**/
///<summary>


067.
///绘制


068.
///</summary>


069.
///<paramname="gameTime"></param>


070.
protected
override
void

Draw(GameTimegameTime)


071.
{


072.
GraphicsDevice.Clear(Color.CornflowerBlue);


073.


074.
for
(
int
i
=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.
private
RayGetRay()


111.
{


112.
MouseStatems=Mouse.GetState();


113.
Vector3neerSource=
new
Vector3(ms.X,
ms.Y,0);


114.
Vector3farSource=
new
Vector3(ms.X,
ms.Y,1);


115.


116.
Vector3neerPosi=GraphicsDevice.Viewport.Unproject(neerSource,
projection,view,Matrix.Identity);


117.
Vector3farPosi=GraphicsDevice.Viewport.Unproject(farSource,
projection,view,Matrix.Identity);


118.
Vector3direction=farPosi-neerPosi;


119.
direction.Normalize();


120.
return
new
Ray(neerPosi,
direction);


121.
}


122.


123.
/**/
///<summary>


124.
///鼠标单击


125.
///</summary>


126.
private
void
CheckMousClick()


127.
{


128.
if
(Mouse.GetState().LeftButton==ButtonState.Pressed)


129.
{


130.
Rayray=GetRay();


131.
for
(
int
i=0;i<models.Length;i++)


132.
{


133.
BoundingSpherebs=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.
public
struct
model


149.
{


150.
public
Modelmod;


151.
public
Texture2D
text;


152.
public
Vector3
position;


153.
}


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

01.
public
void
Intersects(
ref
Ray
ray,
out
float
?result)


02.
{


03.
result=0;


04.
float
num=0f;


05.
float
maxValue=
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.
float
num11=
1f/ray.Direction.X;


16.
float
num8=
(
this
.Min.X-ray.Position.X)*num11;


17.
float
num7=
(
this
.Max.X-ray.Position.X)*num11;


18.
if
(num8>num7)


19.
{


20.
float
num14
=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.
float
num10=
1f/ray.Direction.Y;


41.
float
num6=
(
this
.Min.Y-ray.Position.Y)*num10;


42.
float
num5=
(
this
.Max.Y-ray.Position.Y)*num10;


43.
if
(num6>num5)


44.
{


45.
float
num13
=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.
float
num9=
1f/ray.Direction.Z;


66.
float
num4=
(
this
.Min.Z-ray.Position.Z)*num9;


67.
float
num3=
(
this
.Max.Z-ray.Position.Z)*num9;


68.
if
(num4>num3)


69.
{


70.
float
num12
=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=
new
float
?(num);


82.
}


一、二维图片的拾取与碰撞检测1、拾取二维图片的拾取是通过判断鼠标的当前坐标是否落在图片的现实区域中来实现的(1)、获取顶点信息(图片的显示位置)(2)、获取纹理的宽度与高度myTexture.Width

myTexture.Height

(3)、获取鼠标位置信息

MouseStatestate=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)、构造矩形

/*构造第一个矩形*/

RectanglemoveRect=newRectangle();moveRect.X=(int)movePosition.X;moveRect.Y=(int)movePosition.Y;moveRect.Width=myTexture.Width;moveRect.Height=myTexture.Height;

/*构造第二个矩形*/

RectanglerectPos=newRectangle();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)、获取包围球

BoundingSpherec1BoundingSphere=model1.Meshes[i].BoundingSphere;BoundingSpherec2BoundingSphere=model2.Meshes[j].BoundingSphere;(2)、判断两个包围球是否相交

if(c1BoundingSphere.Intersects(c2BoundingSphere))

返回值为true表示两模型相交,返回值为false表示两模型没有相交

2、拾取

拾取是指通过鼠标来选中某个模型。(1)、获取当前鼠标状态

MouseStatemouseState=Mouse.GetState();(2)、获取鼠标的位置信息

intmouseX=mouseState.X;intmouseY=mouseState.Y;

(3)、构造摄像机坐标系下的两个点,分别代表了近点和远点

Vector3nearsource=newVector3((float)mouseX,(float)mouseY,0f);Vector3farsource=newVector3((float)mouseX,(float)mouseY,1f);

(4)、将两个点通过逆投影矩阵转换为世界坐标系下的两个点

Matrixworld=Matrix.CreateTranslation(0,0,0);

Vector3nearPoint=graphics.GraphicsDevice.Viewport.Unproject(nearsource,

proj,view,world);

Vector3farPoint=graphics.GraphicsDevice.Viewport.Unproject(farsource,

proj,view,world);

(5)、构造拾取射线

Vector3direction=farPoint-nearPoint;direction.Normalize();

RaypickRay=newRay(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平台开发。Xbox360不支持Mouse和MouseState对象。

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

检查鼠标是否位于一个3D对象之上

使用GetState获取鼠标当前状态。

MouseStatemouseState=Mouse.GetState();


从X和Y中获取鼠标的屏幕坐标。

intmouseX=mouseState.X;intmouseY=mouseState.Y;


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

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

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

Vector3nearsource=newVector3((float)mouseX,(float)mouseY,0f);
Vector3farsource=newVector3((float)mouseX,(float)mouseY,1f);Matrixworld=Matrix.CreateTranslation(0,0,0);

Vector3nearPoint=graphics.GraphicsDevice.Viewport.Unproject(nearsource,proj,view,world);

VectVector3farPoint=graphics.GraphicsDevice.Viewport.Unproject(farsource,proj,view,world);


创建一个从nearPoint指向farPoint的Ray。

//Createarayfromthenearclipplanetothefarclipplane.
Vector3direction=farPoint-nearPoint;rection.Normalize();
RayRaypickRay=newRay(nearPoint,direction);


使用Intersects循环检测场景中的所有对象是否和Ray相交。

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

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




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: