using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame1
{
static class Picker
{
// CalculateCursorRay Calculates a world space ray starting at the camera's
// "eye" and pointing in the direction of the cursor. Viewport.Unproject is used
// to accomplish this. see the accompanying documentation for more explanation
// of the math behind this function.
public static Ray CalculateCursorRay(Vector2 Position, Matrix projectionMatrix, Matrix viewMatrix, GraphicsDevice graphics)
{
// create 2 positions in screenspace using the cursor position. 0 is as
// close as possible to the camera, 1 is as far away as possible.
Vector3 nearSource = new Vector3(Position, 0f);
Vector3 farSource = new Vector3(Position, 1f);
// use Viewport.Unproject to tell what those two screen space positions
// would be in world space. we'll need the projection matrix and view
// matrix, which we have saved as member variables. We also need a world
// matrix, which can just be identity.
Vector3 nearPoint = graphics.Viewport.Unproject(nearSource,
projectionMatrix, viewMatrix, Matrix.Identity);
Vector3 farPoint = graphics.Viewport.Unproject(farSource,
projectionMatrix, viewMatrix, Matrix.Identity);
// find the direction vector that goes from the nearPoint to the farPoint
// and normalize it....
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
// and then create a new ray using nearPoint as the source.
return new Ray(nearPoint, direction);
}
///
/// This helper function takes a BoundingSphere and a transform matrix, and
/// returns a transformed version of that BoundingSphere.
///
/// the BoundingSphere to transform
/// how to transform the BoundingSphere.
/// the transformed BoundingSphere/
private static BoundingSphere TransformBoundingSphere(BoundingSphere sphere,
Matrix transform)
{
BoundingSphere transformedSphere;
// the transform can contain different scales on the x, y, and z components.
// this has the effect of stretching and squishing our bounding sphere along
// different axes. Obviously, this is no good: a bounding sphere has to be a
// SPHERE. so, the transformed sphere's radius must be the maximum of the
// scaled x, y, and z radii.
// to calculate how the transform matrix will affect the x, y, and z
// components of the sphere, we'll create a vector3 with x y and z equal
// to the sphere's radius...
Vector3 scale3 = new Vector3(sphere.Radius, sphere.Radius, sphere.Radius);
// then transform that vector using the transform matrix. we use
// TransformNormal because we don't want to take translation into account.
scale3 = Vector3.TransformNormal(scale3, transform);
// scale3 contains the x, y, and z radii of a squished and stretched sphere.
// we'll set the finished sphere's radius to the maximum of the x y and z
// radii, creating a sphere that is large enough to contain the original
// squished sphere.
transformedSphere.Radius = Math.Max(scale3.X, Math.Max(scale3.Y, scale3.Z));
// transforming the center of the sphere is much easier. we can just use
// Vector3.Transform to transform the center vector. notice that we're using
// Transform instead of TransformNormal because in this case we DO want to
// take translation into account.
transformedSphere.Center = Vector3.Transform(sphere.Center, transform);
return transformedSphere;
}
//public static float? RayIntersectsModel(Ray p_Ray, EntityModel p_Model)
//{
// float? distance = null;
// foreach (ModelMesh mesh in p_Model.Model.Meshes)
// {
// BoundingSphere boundingSphere = TransformBoundingSphere(mesh.BoundingSphere, p_Model.TransformationMatrix);
// float? temp_distance = p_Ray.Intersects(boundingSphere);
// if (temp_distance != null)
// {
// if (temp_distance < distance || distance == null)
// distance = temp_distance;
// }
// }
// return distance;
//}
///
/// Checks whether a ray intersects a model. This method needs to access
/// the model vertex data, so the model must have been built using the
/// custom TrianglePickingProcessor provided as part of this sample.
/// Returns the distance along the ray to the point of intersection, or null
/// if there is no intersection.
///
public static float? RayIntersectsModel(Ray ray, EntityModel model,
out bool insideBoundingSphere,
out Vector3 vertex1, out Vector3 vertex2,
out Vector3 vertex3, out Vector3? pointInWorldSpace)
{
pointInWorldSpace = null;
vertex1 = vertex2 = vertex3 = Vector3.Zero;
// The input ray is in world space, but our model data is stored in object
// space. We would normally have to transform all the model data by the
// modelTransform matrix, moving it into world space before we test it
// against the ray. That transform can be slow if there are a lot of
// triangles in the model, however, so instead we do the opposite.
// Transforming our ray by the inverse modelTransform moves it into object
// space, where we can test it directly against our model data. Since there
// is only one ray but typically many triangles, doing things this way
// around can be much faster.
Matrix inverseTransform = Matrix.Invert(model.TransformationMatrix);
ray.Position = Vector3.Transform(ray.Position, inverseTransform);
ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransform);
// Look up our custom collision data from the Tag property of the model.
Dictionary tagData = (Dictionary)model.Model.Tag;
if (tagData == null)
{
throw new InvalidOperationException(
"Model.Tag is not set correctly. Make sure your model " +
"was built using the custom TrianglePickingProcessor.");
}
// Start off with a fast bounding sphere test.
BoundingSphere boundingSphere = (BoundingSphere)tagData["BoundingSphere"];
if (boundingSphere.Intersects(ray) == null)
{
// If the ray does not intersect the bounding sphere, we cannot
// possibly have picked this model, so there is no need to even
// bother looking at the individual triangle data.
insideBoundingSphere = false;
return null;
}
else
{
// The bounding sphere test passed, so we need to do a full
// triangle picking test.
insideBoundingSphere = true;
// Keep track of the closest triangle we found so far,
// so we can always return the closest one.
float? closestIntersection = null;
// Loop over the vertex data, 3 at a time (3 vertices = 1 triangle).
Vector3[] vertices = (Vector3[])tagData["Vertices"];
for (int i = 0; i < vertices.Length; i += 3)
{
// Perform a ray to triangle intersection test.
float? intersection;
RayIntersectsTriangle(ref ray,
ref vertices[i],
ref vertices[i + 1],
ref vertices[i + 2],
out intersection);
// Does the ray intersect this triangle?
if (intersection != null)
{
// If so, is it closer than any other previous triangle?
if ((closestIntersection == null) ||
(intersection < closestIntersection))
{
// Store the distance to this triangle.
closestIntersection = intersection;
// Transform the three vertex positions into world space,
// and store them into the output vertex parameters.
Matrix transformationMatrix = model.TransformationMatrix;
Vector3.Transform(ref vertices[i],
ref transformationMatrix, out vertex1);
Vector3.Transform(ref vertices[i + 1],
ref transformationMatrix, out vertex2);
Vector3.Transform(ref vertices[i + 2],
ref transformationMatrix, out vertex3);
Vector3 pointInModelSpace = (Vector3)(ray.Position + (ray.Direction * closestIntersection));
pointInWorldSpace = Vector3.Transform((Vector3)pointInModelSpace, transformationMatrix);
}
}
}
return closestIntersection;
}
}
///
/// Checks whether a ray intersects a triangle. This uses the algorithm
/// developed by Tomas Moller and Ben Trumbore, which was published in the
/// Journal of Graphics Tools, volume 2, "Fast, Minimum Storage Ray-Triangle
/// Intersection".
///
/// This method is implemented using the pass-by-reference versions of the
/// XNA math functions. Using these overloads is generally not recommended,
/// because they make the code less readable than the normal pass-by-value
/// versions. This method can be called very frequently in a tight inner loop,
/// however, so in this particular case the performance benefits from passing
/// everything by reference outweigh the loss of readability.
///
static void RayIntersectsTriangle(ref Ray ray,
ref Vector3 vertex1,
ref Vector3 vertex2,
ref Vector3 vertex3, out float? result)
{
// Compute vectors along two edges of the triangle.
Vector3 edge1, edge2;
Vector3.Subtract(ref vertex2, ref vertex1, out edge1);
Vector3.Subtract(ref vertex3, ref vertex1, out edge2);
// Compute the determinant.
Vector3 directionCrossEdge2;
Vector3.Cross(ref ray.Direction, ref edge2, out directionCrossEdge2);
float determinant;
Vector3.Dot(ref edge1, ref directionCrossEdge2, out determinant);
// If the ray is parallel to the triangle plane, there is no collision.
if (determinant > -float.Epsilon && determinant < float.Epsilon)
{
result = null;
return;
}
float inverseDeterminant = 1.0f / determinant;
// Calculate the U parameter of the intersection point.
Vector3 distanceVector;
Vector3.Subtract(ref ray.Position, ref vertex1, out distanceVector);
float triangleU;
Vector3.Dot(ref distanceVector, ref directionCrossEdge2, out triangleU);
triangleU *= inverseDeterminant;
// Make sure it is inside the triangle.
if (triangleU < 0 || triangleU > 1)
{
result = null;
return;
}
// Calculate the V parameter of the intersection point.
Vector3 distanceCrossEdge1;
Vector3.Cross(ref distanceVector, ref edge1, out distanceCrossEdge1);
float triangleV;
Vector3.Dot(ref ray.Direction, ref distanceCrossEdge1, out triangleV);
triangleV *= inverseDeterminant;
// Make sure it is inside the triangle.
if (triangleV < 0 || triangleU + triangleV > 1)
{
result = null;
return;
}
// Compute the distance along the ray to the triangle.
float rayDistance;
Vector3.Dot(ref edge2, ref distanceCrossEdge1, out rayDistance);
rayDistance *= inverseDeterminant;
// Is the triangle behind the ray origin?
if (rayDistance < 0)
{
result = null;
return;
}
result = rayDistance;
}
}
}