Ray intersection with 3D quads in XNA?

2019-02-24 10:34发布

问题:

So I have successfully made a ray the represents the mouse unprojected into the world, and now I need to check if that ray can intersect with a quad object, here is the code I use to get the ray:

public Ray GetMouseRay()
    {
        Vector2 mousePosition = new Vector2(cursor.getX(), cursor.getY());

        Vector3 nearPoint = new Vector3(mousePosition, 0);
        Vector3 farPoint = new Vector3(mousePosition, 1);

        nearPoint = viewport.Unproject(nearPoint, projectionMatrix, viewMatrix, Matrix.Identity);
        farPoint = viewport.Unproject(farPoint, projectionMatrix, viewMatrix, Matrix.Identity);

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

        return new Ray(nearPoint, direction);
    }

And similarly, this is my quad struct, which I use to draw a "cube world" in 3d space. public struct Quad { public Vector3 Origin; public Vector3 UpperLeft; public Vector3 LowerLeft; public Vector3 UpperRight; public Vector3 LowerRight; public Vector3 Normal; public Vector3 Up; public Vector3 Left;

    public VertexPositionNormalTexture[] Vertices;
      public int[] Indexes;
    public short[] Indexes;


    public Quad( Vector3 origin, Vector3 normal, Vector3 up, 
        float width, float height )
    {
        Vertices = new VertexPositionNormalTexture[4];
        Indexes = new short[6];
        Origin = origin;
        Normal = normal;
        Up = up;

        // Calculate the quad corners
        Left = Vector3.Cross( normal, Up );
        Vector3 uppercenter = (Up * height / 2) + origin;
        UpperLeft = uppercenter + (Left * width / 2);
        UpperRight = uppercenter - (Left * width / 2);
        LowerLeft = UpperLeft - (Up * height);
        LowerRight = UpperRight - (Up * height);

        FillVertices();
    }

    private void FillVertices()
    {
        // Fill in texture coordinates to display full texture
        // on quad
        Vector2 textureUpperLeft = new Vector2( 0.0f, 0.0f );
        Vector2 textureUpperRight = new Vector2( 1.0f, 0.0f );
        Vector2 textureLowerLeft = new Vector2( 0.0f, 1.0f );
        Vector2 textureLowerRight = new Vector2( 1.0f, 1.0f );

        // Provide a normal for each vertex
        for (int i = 0; i < Vertices.Length; i++)
        {
            Vertices[i].Normal = Normal;
        }

        // Set the position and texture coordinate for each
        // vertex
        Vertices[0].Position = LowerLeft;
        Vertices[0].TextureCoordinate = textureLowerLeft;
        Vertices[1].Position = UpperLeft;
        Vertices[1].TextureCoordinate = textureUpperLeft;
        Vertices[2].Position = LowerRight;
        Vertices[2].TextureCoordinate = textureLowerRight;
        Vertices[3].Position = UpperRight;
        Vertices[3].TextureCoordinate = textureUpperRight;

        // Set the index buffer for each vertex, using
        // clockwise winding
        Indexes[0] = 0;
        Indexes[1] = 1;
        Indexes[2] = 2;
        Indexes[3] = 2;
        Indexes[4] = 1;
        Indexes[5] = 3;
    }
}

I found that the ray class has a method intersects() which takes a plane struct as a parameter, and the plane struct takes a normal and distance from origin in the constructor, but my planes have a position and normal instead of just distance from origin, so I can't convert them. How can I detect if my ray is intersecting my quad struct?

Edit: I realize that I can't use the plane struct, as it is not of finite size and does not include corners, as my quad does. I need to find a way now to detect if this ray is intersecting the quad I have created...

Thanks for reading, I realize this question is a bit lengthy, and thanks in advance.

回答1:

I'm not sure exactly what they mean by "distance along the normal from the origin", but I would assume it just mean the distance from the origin. You can get that from the length property on a Vector3.

If that doesn't work, there is also a constructor for plane which takes three points on the plane:

public Plane (
     Vector3 point1,
     Vector3 point2,
     Vector3 point3
)

You can use this with any 3 points on the plane, such as the corners of your quad, to create a plane.



回答2:

Since you are using cubes, you can use the Ray.Intersects(Plane) method. You just need a few more planes.

Once you determine the first intersection point, then you can construct more planes for each perpendicular face of your cube. Then you can use the Plane.Distance(Vector3) (I'm pretty sure this or something like it exists) to make sure your point is between each pair of perpendicular planes. If it is not between them, then you can ignore the intersection.



回答3:

Not exactly a straight answer to your question, but perhaps you find this relevant.

Have you considered constructing a BoundingBox object and use the Intersects(Ray)? http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.boundingbox.intersects.aspx

Edit 1: It's how I usually do it. Depending on your data structure, it might also be easier to optimize (if you construct larger bounding boxes to bore into for example).

Edit 2: As per Niko's suggestion: I don't want to post the relevant code (because it's a couple of pages long in total to make the context make sense) from the samples, but I can point you to the parts that you'll be interested in.

  1. Download the sample from http://xbox.create.msdn.com/en-US/education/catalog/sample/picking_triangle
  2. The bottom of the Cursor.cs file contains what you already know, calculating the Ray
  3. Go to the Game.cs file. There you'll find UpdatePicking() which calls RayIntersectsModel(...) which calls RayIntersectsTriangle(...)

The transform of the Ray is this part:

Matrix inverseTransform = Matrix.Invert( modelTransform );

ray.Position = Vector3.Transform( ray.Position, inverseTransform );
ray.Direction = Vector3.TransformNormal( ray.Direction, inverseTransform );

How deep you want to go (how accurate your picking) is up to your needs of course, but there you have it.