#region File Description //----------------------------------------------------------------------------- // Helper3D.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; #endregion namespace RobotGameData.Helper { /// /// a triangle structure /// public struct Triangle { public Vector3 v1; public Vector3 v2; public Vector3 v3; public Triangle(Vector3 point1, Vector3 point2, Vector3 point3) { v1 = point1; v2 = point2; v3 = point3; } public void Clear() { v1 = Vector3.Zero; v2 = Vector3.Zero; v3 = Vector3.Zero; } } /// /// Useful functions about 3D dimension. /// public static class Helper3D { public const float Epsilon = 1E-5f; //for numerical imprecision /// /// Checks whether a ray intersects a triangle /// /// closest intersection point public static float? RayIntersectTriangle(Ray ray, Vector3[] vertices, Matrix transform, out Triangle triangle) { triangle.v1 = triangle.v2 = triangle.v3 = Vector3.Zero; // Keep track of the closest triangle we found so far, // so we can always return the closest one. float? closestIntersection = null; Vector3 v1 = Vector3.Zero; Vector3 v2 = Vector3.Zero; Vector3 v3 = Vector3.Zero; // Loop over the vertex data, 3 at a time (3 vertices = 1 triangle). for (int i = 0; i < vertices.Length; i += 3) { // Perform a ray to triangle intersection test. float? intersection; // Transform the three vertex positions into world space v1 = Vector3.Transform(vertices[i], transform); v2 = Vector3.Transform(vertices[i + 1], transform); v3 = Vector3.Transform(vertices[i + 2], transform); RayIntersectsTriangle(ray, v1, v2, v3, 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; // Store the three vertex positions into the vertex parameters. triangle.v1 = v1; triangle.v2 = v2; triangle.v3 = v3; } } } return closestIntersection; } /// /// Checks whether a ray intersects a model. This method needs to access /// the model vertex data, /// Returns the distance along the ray to the point of intersection, or null /// if there is no intersection. /// /// closest intersection point public static float? RayIntersectModel(Ray ray, Model model, Matrix worldTransform, out bool insideBoundingSphere, out Triangle triangle) { triangle.v1 = triangle.v2 = triangle.v3 = Vector3.Zero; // Look up our custom collision data from the Tag property of the model. Dictionary tagData = (Dictionary)model.Tag; if (tagData == null) { throw new InvalidOperationException( "Model.Tag is not set correctly. Make sure your model " + "was built using the custom CollideProcessor."); } // 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(ray, vertices[i], vertices[i + 1], 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. triangle.v1 = Vector3.Transform(vertices[i], worldTransform); triangle.v2 = Vector3.Transform(vertices[i + 1], worldTransform); triangle.v3 = Vector3.Transform(vertices[i + 2], worldTransform); } } } return closestIntersection; } } /// /// Checks whether a ray intersects a triangle. /// 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. /// public static void RayIntersectsTriangle( Ray ray, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, out float? result) { // Compute vectors along two edges of the triangle. Vector3 edge1 = Vector3.Subtract(vertex2, vertex1); Vector3 edge2 = Vector3.Subtract(vertex3, vertex1); // Compute the determinant. Vector3 directionCrossEdge2 = Vector3.Cross(ray.Direction, edge2); float determinant = Vector3.Dot(edge1, directionCrossEdge2); // 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(ray.Position, vertex1); float triangleU = Vector3.Dot(distanceVector, directionCrossEdge2); 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(distanceVector, edge1); float triangleV = Vector3.Dot(ray.Direction, distanceCrossEdge1); 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(edge2, distanceCrossEdge1); rayDistance *= inverseDeterminant; // Is the triangle behind the ray origin? if (rayDistance < 0) { result = null; return; } result = rayDistance; } public static Plane TransformPlane(ref Plane plane, ref Matrix matrix) { Vector3 normal = plane.Normal; normal.Normalize(); Vector3 planeCenter = Vector3.Zero + plane.D * normal; Vector3 newCenter = Vector3.Transform(planeCenter, matrix); Vector3 centerToOrigin = Vector3.Zero - newCenter; float newDistance = Math.Abs(centerToOrigin.Length()); Vector3 newNormal = Vector3.TransformNormal(normal, matrix); newNormal.Normalize(); return new Plane(newNormal, newDistance); } /// /// Intersect point inside triangle. /// /// vertex 1 /// vertex 2 /// vertex 3 /// point /// public static bool PointInsidePoly(Vector3 vectorA, Vector3 vectorB, Vector3 vectorC, Vector3 point) { int positives = 0; int negatives = 0; Vector3[] verts = { vectorA, vectorB, vectorC }; Plane plane = new Plane(vectorA, vectorB, vectorC); uint v0 = 3 - 1; for (uint v1 = 0; v1 < 3; v0 = v1, ++v1) { Vector3 point0 = verts[v0]; Vector3 point1 = verts[v1]; // Generate a normal for this edge Vector3 normal = Vector3.Cross(point1 - point0, plane.Normal); // Which side of this edge-plane is the point? float halfPlane = Vector3.Dot(point, normal) - Vector3.Dot(point0, normal); // Keep track of positives and negatives //(but not zeros -- which means it's on the edge) if (halfPlane > Epsilon) positives++; else if (halfPlane < -Epsilon) negatives++; // Early-out if ((positives | negatives) != 0) return false; } // If they're ALL positive, or ALL negative, then it's inside if ((positives | negatives) == 0) return true; // Must not be inside, because some were pos and some were neg return false; } /// /// It calculates the perpendicular point of the specified point and the line. /// public static bool GetPerpendicularPoint(Vector3 vector1, Vector3 vector2, Vector3 point, out Vector3 perpendicular, out float t) { Vector3 vector12 = vector2 - vector1; Vector3 vector10 = point - vector1; Vector3 norm = Vector3.Normalize(vector12); perpendicular = Vector3.Zero; t = 0.0f; float num = Vector3.Dot(norm, vector10); float den = Vector3.Dot(norm, vector12); if (den * den < 1E-15f * num * num) return false; t = num / den; perpendicular = vector1 + (vector12 * t); if (t > 0.0f && t < 1.0f) return true; return false; } /// /// Calculates whether a line intersects a triangle /// public static bool LineIntersectSphere(Vector3 vector1, Vector3 vector2, Vector3 center, float radius, out Vector3 intersect, out float t) { Vector3 len = Vector3.Zero; if (GetPerpendicularPoint(vector1, vector2, center, out intersect, out t)) { len = intersect - center; if (len.Length() < radius) return true; return false; } len = vector2 - center; if (len.Length() < radius) { intersect = vector2; t = 1.0f; return true; } len = vector1 - center; if (len.Length() < radius) { intersect = vector1; t = 0.0f; return true; } return false; } /// /// Calculates whether a sphere intersects a triangle /// public static bool SphereIntersectTriangle(Vector3 center, float radius, Vector3 vector1, Vector3 vector2, Vector3 vector3, out Vector3 interset, out float t) { Vector3 normal = Vector3.Normalize(Vector3.Cross(vector3 - vector1, vector2 - vector1)); float dot = Math.Abs(Vector3.Dot(center - vector1, normal)); interset = Vector3.Zero; t = dot; if (dot < radius) { Vector3 projCenter = normal * -dot; projCenter += center; // Colliding center if (PointInsidePoly(vector1, vector2, vector3, center)) { interset = projCenter; t = dot - radius; return true; } // Colliding edge Vector3 intersect1 = Vector3.Zero; Vector3 intersect2 = Vector3.Zero; Vector3 intersect3 = Vector3.Zero; float t1 = 0.0f; float t2 = 0.0f; float t3 = 0.0f; bool bHit1 = LineIntersectSphere(vector1, vector2, center, radius, out intersect1, out t1); bool bHit2 = LineIntersectSphere(vector1, vector3, center, radius, out intersect2, out t2); bool bHit3 = LineIntersectSphere(vector2, vector3, center, radius, out intersect3, out t3); if (bHit1 || bHit2 || bHit3) { float min = (t1 < t2 ? (t1 < t3 ? t1 : t3) : (t2 < t3 ? t2 : t3)); if (t1 == min) interset = intersect1; else if (t2 == min) interset = intersect2; else if (t3 == min) interset = intersect3; Vector3 len = center - interset; t = len.Length(); return true; } } return false; } /// /// Calculates whether a ray intersects a triangle /// public static bool RayTriangleIntersect(Vector3 rayOrigin, Vector3 rayDirection, Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, out float t, out float u, out float v) { t = 0; u = 0; v = 0; Vector3 edge1 = vertex1 - vertex0; Vector3 edge2 = vertex2 - vertex0; Vector3 tvec, pvec, qvec; float det, inv_det; Vector3.Cross(ref rayDirection, ref edge2, out pvec); det = Vector3.Dot(edge1, pvec); if (det > -0.00001f) return false; inv_det = 1.0f / det; tvec = rayOrigin - vertex0; u = Vector3.Dot(tvec, pvec) * inv_det; if (u < -0.001f || u > 1.001f) return false; qvec = Vector3.Cross(tvec, edge1); v = Vector3.Dot(rayDirection, qvec) * inv_det; if (v < -0.001f || u + v > 1.001f) return false; t = Vector3.Dot(edge2, qvec) * inv_det; if (t <= 0) return false; return true; } /// /// Calculates whether a ray intersects a triangle /// public static bool RayTriangleIntersect(Vector3 origin, Vector3 direction, Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, out float t, out float u, out float v, bool testCull) { // Make sure the "out" params are set. t = 0; u = 0; v = 0; // Get vectors for the two edges that share vert0 Vector3 edge1 = vertex1 - vertex0; Vector3 edge2 = vertex2 - vertex0; Vector3 tvec, pvec, qvec; float det, inv_det; // Begin calculating determinant pvec = Vector3.Cross(direction, edge2); // If the determinant is near zero, ray lies in plane of triangle det = Vector3.Dot(edge1, pvec); if (testCull) { if (det < Helper3D.Epsilon) return false; tvec = origin - vertex0; u = Vector3.Dot(tvec, pvec); if (u < 0.0 || u > det) return false; qvec = Vector3.Cross(tvec, edge1); v = Vector3.Dot(direction, qvec); if (v < 0.0f || u + v > det) return false; t = Vector3.Dot(edge2, qvec); inv_det = 1.0f / det; t *= inv_det; u *= inv_det; v *= inv_det; } else { // Account for Float rounding errors / inaccuracies. if (det > -Helper3D.Epsilon && det < Helper3D.Epsilon) return false; // Get the inverse determinant inv_det = 1.0f / det; // Calculate distance from vert0 to ray origin tvec = origin - vertex0; // Calculate U parameter and test bounds u = Vector3.Dot(tvec, pvec) * inv_det; if (u < 0.0f || u > 1.0f) return false; // Prepare for v qvec = Vector3.Cross(tvec, edge1); // Calculate V parameter and test bounds v = Vector3.Dot(direction, qvec) * inv_det; if (v < 0.0f || u + v > 1.0f) return false; // Calculate t, ray intersects triangle. t = Vector3.Dot(edge2, qvec) * inv_det; } return true; } /// /// ray intersect face and return intersection distance, point and normal. /// public static bool PointIntersect(Vector3 rayOrigin, Vector3 rayDirection, Triangle triangle, out float intersectDistance, out Vector3 intersectPosition, out Vector3 intersectNormal) { intersectDistance = 0.0f; intersectPosition = rayOrigin; intersectNormal = Vector3.Zero; Vector3 uvt = Vector3.Zero; if (RayTriangleIntersect(rayOrigin, rayDirection, triangle.v1, triangle.v2, triangle.v3, out uvt.Z, out uvt.X, out uvt.Y)) { intersectDistance = uvt.Z; intersectPosition = (1.0f - uvt.X - uvt.Y) * triangle.v1 + uvt.X * triangle.v2 + uvt.Y * triangle.v3; intersectNormal = Vector3.Normalize( Vector3.Cross(triangle.v3 - triangle.v1, triangle.v2 - triangle.v1)); return true; } return false; } /// /// Calculates a world space ray starting at the camera's /// "eye" and pointing in the direction of 2D screen position. /// Viewport.Unproject is used to accomplish this. /// public static Ray ScreenPositionToRay(Vector2 screenPosition, Matrix projectionMatrix, Matrix viewMatrix, GraphicsDevice device) { // 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(screenPosition, 0f); Vector3 farSource = new Vector3(screenPosition, 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 = device.Viewport.Unproject(nearSource, projectionMatrix, viewMatrix, Matrix.Identity); Vector3 farPoint = device.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); } static Vector3[] axis3x3 = new Vector3[3] { new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f), }; public static Matrix MakeMatrixWithUp(Vector3 up, Vector3 at) { Matrix matrix = Matrix.Identity; matrix.Up = Vector3.Normalize(up); float dot = Vector3.Dot(matrix.Up, at); if (Math.Abs(dot) >= 1.0f - Epsilon) { for (int i = 0; (i < 3) && (Math.Abs(dot) >= 1.0f - Epsilon); i++) { dot = Vector3.Dot(matrix.Up, axis3x3[i]); if (Math.Abs(dot) < 1.0f - Epsilon) { matrix.Right = Vector3.Cross(axis3x3[i], matrix.Up); matrix.Right.Normalize(); break; } } } else { matrix.Right = Vector3.Cross(at, matrix.Up); matrix.Right.Normalize(); } matrix.Forward = Vector3.Cross(matrix.Right, matrix.Up); matrix.Forward.Normalize(); matrix.Translation = Vector3.Zero; return matrix; } public static Matrix MakeMatrixWithAt(Vector3 at, Vector3 up) { Matrix matrix = Matrix.Identity; matrix.Forward = Vector3.Normalize(at); float dot = Vector3.Dot(matrix.Forward, up); if (Math.Abs(dot) >= 1.0f - Epsilon) { for (int i = 0; (i < 3) && (Math.Abs(dot) >= 1.0f - Epsilon); i++) { dot = Vector3.Dot(matrix.Forward, axis3x3[i]); if (Math.Abs(dot) < 1.0f - Epsilon) { matrix.Right = Vector3.Cross(matrix.Forward, axis3x3[i]); matrix.Right.Normalize(); break; } } } else { matrix.Right = Vector3.Cross(matrix.Forward, up); matrix.Right.Normalize(); } matrix.Up = Vector3.Cross(matrix.Forward, matrix.Right); matrix.Up.Normalize(); matrix.Translation = Vector3.Zero; return matrix; } public static Vector3 TransformCoord(Vector3 vector, Matrix matrix) { float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f; x = (matrix.M11 * vector.X) + (matrix.M21 * vector.Y) + (matrix.M31 * vector.Z) + matrix.M41; y = (matrix.M12 * vector.X) + (matrix.M22 * vector.Y) + (matrix.M32 * vector.Z) + matrix.M42; z = (matrix.M13 * vector.X) + (matrix.M23 * vector.Y) + (matrix.M33 * vector.Z) + matrix.M43; w = (matrix.M14 * vector.X) + (matrix.M24 * vector.Y) + (matrix.M34 * vector.Z) + matrix.M44; return new Vector3((x / w), (y / w), (z / w)); } /// /// Transposes the rows and columns of a matrix. /// It only has the rotation value. /// public static Matrix Transpose(Matrix matrix) { Matrix temp = matrix; Matrix transpose = Matrix.Transpose(matrix); temp.Right = transpose.Right; temp.Up = transpose.Up; temp.Forward = transpose.Forward; return temp; } public static Matrix PreScale(Matrix matrix, Vector3 vector) { matrix.M11 = matrix.M11 * vector.X; matrix.M12 = matrix.M12 * vector.X; matrix.M13 = matrix.M13 * vector.X; matrix.M14 = matrix.M14 * vector.X; matrix.M21 = matrix.M21 * vector.Y; matrix.M22 = matrix.M22 * vector.Y; matrix.M23 = matrix.M23 * vector.Y; matrix.M24 = matrix.M24 * vector.Y; matrix.M31 = matrix.M31 * vector.Z; matrix.M32 = matrix.M32 * vector.Z; matrix.M33 = matrix.M33 * vector.Z; matrix.M34 = matrix.M34 * vector.Z; return matrix; } public static Matrix PostScale(Matrix matrix, Vector3 vector) { matrix.M11 = matrix.M11 * vector.X; matrix.M21 = matrix.M21 * vector.X; matrix.M31 = matrix.M31 * vector.X; matrix.M41 = matrix.M41 * vector.X; matrix.M12 = matrix.M12 * vector.Y; matrix.M22 = matrix.M22 * vector.Y; matrix.M32 = matrix.M32 * vector.Y; matrix.M42 = matrix.M42 * vector.Y; matrix.M13 = matrix.M13 * vector.Z; matrix.M23 = matrix.M23 * vector.Z; matrix.M33 = matrix.M33 * vector.Z; matrix.M43 = matrix.M43 * vector.Z; return matrix; } } }