zoukankan      html  css  js  c++  java
  • 处理模型——检测光标是否在模型上

    问题

    你想检测光标是否在模型上。

    解决方案

    在XNA中,获取光标在屏幕上的2D位置是简单的。屏幕上的这个点对应3D空间中的一条射线Ray,如图4-28所示。

     

    1 图4-28 2D光标对应3D空间的一条射线

    因此,当你想检测光标在哪个模型上,需要检测射线与模型的碰撞,所以,这个教材会用到教程4-18的代码。

    很有可能射线与多个模型相交,这个教程还会教你如何获取离屏幕最*的一个模型。

    工作原理

    你需要创建一个3D射线并将它与模型一起传递到教程4-18创建的ModelRayCollision 方法中。

    只要知道了射线上的两个点就可以创建一条射线。你将使用的两个点如图4-28所示。第一个点是射线与*裁屏*面的交点;第二个点是与远裁*面的交点。

    如果知道了这两个点的3D位置,你将使用ViewProjection矩阵进行转换获取屏幕上的2D位置。但是,你转换一个Vector3结果仍是一个Vector3,在结果Vector3中,通过使用ViewProjection矩阵进行变换,X和Y分量就是2D屏幕位置,第三个坐标Z也包含有用的信息,即相机与初始点的距离,为0时表示点在*裁*面,为1时表示在远裁*面。在深度缓冲中存储的正是这个距离。所以,每个在2D屏幕上绘制的像素实际上都有3个坐标值。

    你要获取的两个点共享相同的像素,相同的2D位置,即它们的X和Y坐标是相同的。因为第一个点位于*裁*面,所以它的Z坐标为0。第二点位于远裁*面,所以Z坐标为1。这两个点在屏幕空间的三个坐标,在光标的情况中如下所示:

    • (mouseX, mouseY, 0)
    • (mouseX, mouseY, 1)

    下面是代码:

    MouseState mouseState = Mouse.GetState();   Vector3 nearScreenPoint = new Vector3(mouseState.X, mouseState.Y, 0);  Vector3 farScreenPoint = new Vector3(mouseState.X, mouseState.Y, 1); 

    如果从3D空间转换到屏幕空间,你要使用ViewProjection矩阵转换3D点。而这里你想讲这些点从屏幕空间转换到3D空间,所以使用的是ViewProjection矩阵的逆矩阵。你还需要将X和Y的光标坐标映射到[-1, 1]范围中,所以需要屏幕的像素大小的高和宽。

    幸运的是,XNA提供了UnProject方法实现了这个映射和反向变换:

    Vector3 near3DWorldPoint = device.Viewport.Unproject(nearScreenPoint, fpsCam.ProjectionMatrix, fpsCam.ViewMatrix, Matrix.Identity);  Vector3 far3DWorldPoint = device.Viewport.Unproject(farScreenPoint,fpsCam.ProjectionMatrix, fpsCam.ViewMatrix, Matrix.Identity); 

    你获得的这两个点如图4-28所示!

    注意:你想知道相对于3D初始位置(0,0,0)的位置,所以你将Matrix. Identity作为世界矩阵。ViewProjection矩阵可以通过将View和Projection矩阵相乘获得,这也是你将这两个矩阵作为第二第三个参数的原因。

    知道了射线的两个点,就可以创建一个Ray对象:

    Vector3 pointerRayDirection = far3DWorldPoint - near3DWorldPoint;   pointerRayDirection.Normalize();  Ray pointerRay = new Ray(near3DWorldPoint,pointerRayDirection); 

    创建了Ray之后,你就做好了使用上一个教程中的ModelRayCollision方法检测Ray和模型间碰撞的准备:

    selected = ModelRayCollision(myModel, modelWorld, pointerRay); 

    添加一个Crosshair

    前面的代码看起来很好,但如果你不能测试,代码再好也看不出来。所以让我们添加一个图像可以显示光标的位置,可见教程3-1学习绘制一个图像的简短介绍。首先将光标的2D位置存储在一个Vector2中:

    pointerPosition = new Vector2(mouseState.X, mouseState.Y); 

    在LoadContent方法中,添加一个SpriteBatch对象和一个Texture2D对象保存透明的crosshair图像:

    spriteBatch = new SpriteBatch(device); crosshair = content.Load("cross");

    然后在Draw方法中将这个图像绘制到屏幕:

    spriteBatch.Begin(SpriteBlendMode.AlphaBlend,SpriteSortMode.Deferred, SaveStateMode.SaveState);   spriteBatch.Draw(cross, mouseCoords, null, Color.White, 0, new Vector2(7, 7), 1, SpriteEffects.None, 0);   spriteBatch.End(); 

    这可以让你在屏幕上看到光标。图像的中心点(7,7)位于光标位置。

    检测多个对象

    如果在场景中有多个对象,那么可能有多个对象会与射线发生碰撞。在大多数情况中,你只关心离相机最*的那个对象,因为这个对象才占据屏幕的像素。

    要做到这点,你可以稍微调整一下ModelRayCollision方法,让它返回碰撞的距离而不是简单的true或false。类似于Intersect方法,你使用一个可空类型float?变量,这样如果没有碰撞那么返回null:

    private float? ModelRayCollision(Model model, Matrix modelWorld, Ray ray)   {      Matrix []modelTransforms = new Matrix[model.Bones.Count];       model.CopyAbsoluteBoneTransformsTo(modelTransforms);             float? collisionDistance = null;       foreach (ModelMesh mesh in model.Meshes)       {          Matrix absTransform = modelTransforms[mesh.ParentBone.Index]*modelWorld;           Triangle[] meshTriangles = (Triangle[])mesh.Tag;                     foreach (Triangle tri in meshTriangles)           {              Vector3 transP0 = Vector3.Transform(tri.P0, absTransform);               Vector3 transP1 = Vector3.Transform(tri.P1, absTransform);               Vector3 transP2 = Vector3.Transform(tri.P2, absTransform);                             Plane trianglePlane = new Plane(transP0, transP1, transP2);               float distanceOnRay = RayPlaneIntersection(ray, trianglePlane);               Vector3 intersectionPoint = ray.Position + distanceOnRay * ray.Direction;                             if (PointInsideTriangle(transP0, transP1, transP2, intersectionPoint))                   if ((collisionDistance == null) || (distanceOnRay < collisionDistance))                       collisionDistance = distanceOnRay;           }      }       return collisionDistance;   }

    每次发生碰撞时,你检查collisionDistance是否仍是null。这会显示是第一次检测到碰撞,所以你将这个距离存储到collisionDistance 中。从这时起,你检查这个距离是否小于已知的距离,如果是,则重写这个距离。

    从变量collisionDistance返回的结果将包含离相机最*的碰撞点,你可以使用这个结果检测哪个模型离相机最*。

    代码

    这个代码创建一个3D射线,这个射线描述所有属于通过光标显示的像素的点。这个射线传递到ModelRayCollision方法:

    Vector3 nearScreenPoint = new Vector3(mouseState.X, mouseState.Y, 0);  Vector3 farScreenPoint = new Vector3(mouseState.X, mouseState.Y, 1);   Vector3 near3DWorldPoint = device.Viewport.Unproject(nearScreenPoint, fpsCam.ProjectionMatrix, fpsCam.ViewMatrix, Matrix.Identity);   Vector3 far3DWorldPoint = device.Viewport.Unproject(farScreenPoint, fpsCam.ProjectionMatrix, fpsCam.ViewMatrix, Matrix.Identity);     Vector3 pointerRayDirection = far3DWorldPoint - near3DWorldPoint;   pointerRayDirection.Normalize();   Ray pointerRay = new Ray(near3DWorldPoint, pointerRayDirection);   selected = ModelRayCollision(myModel, worldMatrix, pointerRay);
  • 相关阅读:
    C# 观察者模式(Observer)
    CXGRID设置Selstart和SelLength
    Delphi 中相对路径与绝对路径、系统环境变量等相关函数说明
    用Delphi创建一个空的Access数据库
    非COM环境下的接口编程
    VCLZIP样例
    delphi 文件CRC32校验
    delphi中无类型文件读写
    Delphi调用MSSQL存储过程返回的多个数据集
    CXGRID,定位并高亮
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120124.html
Copyright © 2011-2022 走看看