///
/// This class stores a single triangular face in a CollisionMesh. This class performs the
/// actual mathematics of detecting and describing collisions between points or boxes and a triangle.
///
public class CollisionTriangle : CollideableElement
{
/*
* CONSTANTS
*/
///
/// cosine of 135 degrees
///
public const float COS_135 = -0.707106781f;
///
/// cosine of 45 degrees
///
public const float COS_45 = 0.707106781f;
///
/// the buffer added to the triangle's bounding box
///
public const float TRIANGLE_BOUNDS_BUFFER = 0.001f;
/*
* CLASS PROPERTIES
*/
///
/// index of the vertices in the accesible mesh structure
///
private int[] vertexIndeces;
///
/// the positions of the vertices in world coordinates
///
private Vector3[] pos;
///
/// the area of the triangle
///
public float Area;
///
/// double the area of the triangle
///
public float DoubleArea;
///
/// the normal of the triangle surface
///
public Vector3 FaceNormal;
///
/// the centroid of the face
///
public Vector3 Centroid;
///
/// the accessible mesh structure this traingle is associated with
///
private CollisionMesh Mesh;
///
/// the array of 3 collideable edges of this triangle
///
private CollisionEdge[] Edges;
/*
* CONSTRUCTOR
*/
///
/// constructor for a new mesh triangles
///
/// the index in the mesh object of the first vertex of the triangle
/// the index in the mesh object of the second vertex of the triangle
/// the index in the mesh object of the third vertex of the triangle
/// the accessible mesh object to which this triangle belongs
/// vertices should be defined in counter-clockwise order
public CollisionTriangle(TwelveCylinderGame newGame, int newIndex1, int newIndex2, int newIndex3, CollisionMesh newMesh)
: base(newGame)
{
Mesh = newMesh;
pos = new Vector3[3];
vertexIndeces = new int[3] { newIndex1, newIndex2, newIndex3 };
updateVertexPositions();
updateFaceNormal();
updateCollisionBox();
//edge definitions
Edges = new CollisionEdge[3];
Edges[0] = new CollisionEdge(Mesh, pos[0], pos[1]);
Edges[1] = new CollisionEdge(Mesh, pos[1], pos[2]);
Edges[2] = new CollisionEdge(Mesh, pos[2], pos[0]);
}
/*
* PARAMETER UPDATES
*/
///
/// updates vertex positions, face normal
/// and collision box definitions
///
public void updateAll()
{
updateVertexPositions();
updateEdges();
updateFaceNormal();
updateCollisionBox();
}
///
/// updates the vertex world positions
/// from the collision mesh object
///
public void updateVertexPositions()
{
pos[0] = Mesh.getWorldPosition(vertexIndeces[0]);
pos[1] = Mesh.getWorldPosition(vertexIndeces[1]);
pos[2] = Mesh.getWorldPosition(vertexIndeces[2]);
}
public void updateEdges()
{
Edges[0].WorldPosition1 = pos[0];
Edges[0].WorldPosition2 = pos[1];
Edges[0].updateEdge();
Edges[1].WorldPosition1 = pos[1];
Edges[1].WorldPosition2 = pos[2];
Edges[1].updateEdge();
Edges[2].WorldPosition1 = pos[2];
Edges[2].WorldPosition2 = pos[0];
Edges[2].updateEdge();
}
///
/// updates the triangles face normal direction
/// based on its vertex positions
///
public void updateFaceNormal()
{
Vector3 v2 = (pos[2] - pos[0]);
Vector3 v1 = (pos[1] - pos[0]);
Vector3 cross = Vector3.Cross(v2, v1);
DoubleArea = cross.Length();
Area = DoubleArea / 2.0f;
Centroid = (pos[0] + pos[1] + pos[2]) / 3.0f;
FaceNormal = cross / DoubleArea;
}
///
/// updates the object oriented
/// bounding box of this triangle
///
public override void updateCollisionBox()
{
box = Bounds.FromPoints(curGame, CollisionTriangle.TRIANGLE_BOUNDS_BUFFER, pos[0], pos[1], pos[2]);
}
/*
* EDGE-EDGE INTERSECTION
*/
///
/// intersect BoxEdge moving in direction (dir) colliding with edge (p3,p4)
/// return true on a collision with collision distance (dist) and intersection point (ip)
///
/// the moving box edge definition
/// the static edge of the triangle
/// the output distance from the box start position at which the collision occured
/// the output position at which the intersection occured
/// true if a collision occured, false otherwise
public static bool EdgeIntersect(BoxEdge boxEdge, CollisionEdge faceEdge,
out float paramT, out Vector3 intersectPos)
{
paramT = 0;
intersectPos = Vector3.Zero;
// if colliding edge (p3,p4) does not cross plane return no collision
// same as if p3 and p4 on same side of plane return 0
float temp = (Vector3.Dot(boxEdge.PlaneDir, faceEdge.WorldPosition1) - boxEdge.PlaneW)
* (Vector3.Dot(boxEdge.PlaneDir, faceEdge.WorldPosition2) - boxEdge.PlaneW);
if (temp > 0)
return false;
// if colliding edge (p3,p4) and plane are paralell return no collision
//v2.Normalize();
temp = Vector3.Dot(boxEdge.PlaneDir, faceEdge.EdgeDir);
if (temp == 0)
return false;
// compute intersection point of plane and colliding edge (p3,p4)
intersectPos = faceEdge.WorldPosition1 + (faceEdge.EdgeDir *
((boxEdge.PlaneW - Vector3.Dot(boxEdge.PlaneDir, faceEdge.WorldPosition1)) / temp));
uint i = XNAHelper.GetLargestAbsComponent(boxEdge.PlaneDir);
// remove a component from each vector to simply to a 2D problem
Vector2 p12d = XNAHelper.Vector3RemoveComponent(boxEdge.WorldPosition1, i);
Vector2 v12d = XNAHelper.Vector3RemoveComponent(boxEdge.EdgeVector, i);
Vector2 ip2d = XNAHelper.Vector3RemoveComponent(intersectPos, i);
Vector2 dir2d = XNAHelper.Vector3RemoveComponent(boxEdge.EdgeMotion, i);
// compute distance of intersection from line to line
paramT = (v12d.X * (ip2d.Y - p12d.Y) - v12d.Y * (ip2d.X - p12d.X)) / (v12d.X * dir2d.Y - v12d.Y * dir2d.X);
if (paramT < 0)
return false;
// compute intesection point along edge path
intersectPos -= paramT * boxEdge.EdgeMotion;
// check if intersection point is between egde vertices
temp = Vector3.Dot(boxEdge.WorldPosition1 - intersectPos, boxEdge.WorldPosition2 - intersectPos);
if (temp >= 0)
return false; // no collision
return true; // collision found!
}
/*
* RAY-TRIANGLE INTERSECT
*/
///
/// ray-triangle intersect from
/// http://www.graphics.cornell.edu/pubs/1997/MT97.pdf
///
/// origin of the ray
/// direction of the ray
/// output distance to intersection
///
public bool RayTriangleIntersect(Vector3 rayOrigin, Vector3 rayDirection, out float dist)
{
Vector3 edge1 = pos[1] - pos[0];
Vector3 edge2 = pos[2] - pos[0];
Vector3 tVector, pVector, qVector;
float det, invDet;
dist = 0; //parametric distance
pVector = Vector3.Cross(rayDirection, edge2);
det = Vector3.Dot(edge1, pVector);
if (det > -0.00001f)
//ray is parallel to triangle
return false
invDet = 1.0f / det;
tVector = rayOrigin - pos[0];
float u = Vector3.Dot(tVector, pVector) * invDet;
if (u < -0.0001f || u > 1.0001f)
//intersection with plane of triangle is off the face
return false;
qVector = Vector3.Cross(tVector, edge1);
float v = Vector3.Dot(rayDirection, qVector) * invDet;
if (v < -0.0001f || u + v > 1.0001f)
//intersection with plane of triangle is off the face
return false;
//intersect distance
dist = Vector3.Dot(edge2, qVector) * invDet;
if (dist <= 0)
//intersection is behind ray origin
return false;
return true; //intersection occured
}
/*
* COLLIDEABLE ELEMENT INTERSECTION TESTS
*/
///
/// ray intersect face and return
/// intersection distance, point and normal
///
/// point movement data that
/// describes movement of point to test for intersection
public override void PointIntersect(ref MovingPointData pointData)
{
float dist;
if (this.RayTriangleIntersect(pointData.StartPosition, pointData.MoveDirection, out dist))
{
if (dist < pointData.CollisionDistance)
pointData.CollisionOccured(this, pointData.StartPosition +
(pointData.MoveDirection * dist), this.FaceNormal, dist);
}
}
///
/// box intersect face and return intersection distance, point and normal
///
///
/// output distance to intersection
/// output position of intersection
/// output normal of intersection
/// true if an intersection occured
public override bool BoxIntersect(MovingBoxData boxData, out float collisionDistance,
out Vector3 collisionPosition, out Vector3 collisionNormal)
{
collisionDistance = float.MaxValue;
collisionPosition = boxData.WorldStart;
collisionNormal = Vector3.Zero;
bool collided = false;
//cull triangle if we're moving in the same direction as the triangle's normal
if (Vector3.Dot(this.FaceNormal, boxData.OffsetDirection) > 0)
return false;
uint i, j;
float distance;
Vector3 position;
float tnear, tfar;
/*
* EDGE TO EDGE
*/
for (i = 0; i < 12; i++)
{
//for each edge of the box
// skip edges with normal more than 135 degrees away from moving direction
if (Vector3.Dot(Bounds.edgeNormals[i], boxData.OffsetDirection)
< CollisionTriangle.COS_135)
continue;
for (j = 0; j < Edges.Length; j++)
{
//for each edge of the triangle test for intersection with the current box edge
if (CollisionTriangle.EdgeIntersect(boxData.BoxEdges[i], Edges[j],
out distance, out position))
{
//intersection occured
if (distance < collisionDistance)
{
//intersection is closer than previous distance
collisionDistance = distance;
collisionPosition = position;
collisionNormal = Vector3.Normalize(Vector3.Cross(boxData.BoxEdges[i].WorldPosition2 -
boxData.BoxEdges[i].WorldPosition1, -Edges[j].EdgeVector));
if (Vector3.Dot(boxData.OffsetDirection, collisionNormal) > 0)
collisionNormal = -collisionNormal;
collided = true;
}
}
}
}
/*
* TRIANGLE VERTICES WITH BOX
*/
BoxFace boxFace;
for (i = 0; i < 3; i++)
{
//for each triangle vertex...
//intersect with collision box
boxFace = boxData.WorldBox.RayIntersect(new OptimizedRay(pos[i], -boxData.OffsetDirection),
out tnear, out tfar);
if (boxFace != BoxFace.None)
{
//ray hits the bounds
if (tnear < collisionDistance)
{
//closest distance
collisionDistance = tnear;
collisionPosition = pos[i];
collisionNormal = -Bounds.faceNormals[(int)boxFace];
collided = true;
}
}
}
/*
* BOX VERTICES WITH TRIANGLE
*/
float dist;
for (i = 0; i < 8; i++)
{
// cull vertices with normal more than 135 degree from moving direction
if (Vector3.Dot(Bounds.vertexNormals[i], boxData.OffsetDirection) < CollisionTriangle.COS_135)
continue;
if (this.RayTriangleIntersect(boxData.WorldBoxVerts[i],
boxData.OffsetDirection, out dist))
{
if (dist < collisionDistance)
{
collisionDistance = dist;
collisionPosition = (boxData.OffsetDirection * dist) +
boxData.WorldBoxVerts[i];
collisionNormal = this.FaceNormal; //normal of triangle
collided = true;
}
}
}
return collided;
}
}