00001 using System;
00002 using System.Collections.Generic;
00003 using Microsoft.Xna.Framework;
00004 using Microsoft.Xna.Framework.Graphics;
00005
00006 namespace XnaCollisionLib
00007 {
00008 public struct CollisionData
00009 {
00010 public Vector3 ERadius;
00011 public Vector3 R3Velocity;
00012 public Vector3 R3Position;
00013 public Vector3 Velocity;
00014 public Vector3 NormalizedVelocity;
00015 public Vector3 BasePoint;
00016 public bool FoundCollision;
00017 public float NearestDistance;
00018 public Vector3 IntersectionPoint;
00019 public Triangle IntersectionTriangle;
00020 public int TriangleIndex;
00021 public int TriangleHits;
00022 public float SlidingSpeed;
00023 }
00024
00025 public class CollisionManager
00026 {
00027 private CollisionSource _Source;
00028 private List<Triangle> _Triangles;
00029
00030 public CollisionManager()
00031 {
00032 _Source = new CollisionSource();
00033 _Triangles = new List<Triangle>();
00034 }
00035
00036 public CollisionSource Source
00037 {
00038 get { return _Source; }
00039 }
00040
00041 public Nullable<float> GetRayCollisionResult(Ray ray, out Triangle hitTriangle)
00042 {
00043 return _Source.ISect(ray, out hitTriangle);
00044 }
00045
00046 public Vector3 GetCollisionResult(
00047 Vector3 position,
00048 Vector3 radius,
00049 Vector3 velocity,
00050 Vector3 gravity,
00051 float slidingSpeed,
00052 out Triangle hitTriangle,
00053 out Vector3 hitPosition,
00054 out bool falling
00055 )
00056 {
00057 hitTriangle = new Triangle();
00058 hitPosition = new Vector3();
00059 falling = false;
00060
00061 return CollideWithSource(position, radius, velocity, gravity, slidingSpeed, ref hitTriangle, ref hitPosition, ref falling);
00062 }
00063
00064 private Vector3 CollideWithSource(
00065 Vector3 position,
00066 Vector3 radius,
00067 Vector3 velocity,
00068 Vector3 gravity,
00069 float slidingSpeed,
00070 ref Triangle hitTriangle,
00071 ref Vector3 hitPosition,
00072 ref bool falling
00073 )
00074 {
00075 if (Source.Count == 0 || radius == Vector3.Zero)
00076 return position + velocity;
00077
00078 _Triangles.Capacity = Source.Count;
00079
00080 CollisionData colData = new CollisionData();
00081
00082 colData.R3Position = position;
00083 colData.R3Velocity = velocity;
00084 colData.ERadius = radius;
00085 colData.NearestDistance = float.MaxValue;
00086 colData.SlidingSpeed = slidingSpeed;
00087 colData.TriangleHits = 0;
00088 colData.TriangleIndex = -1;
00089
00090 Vector3 eSpacePosition = colData.R3Position / colData.ERadius;
00091 Vector3 eSpaceVelocity = colData.R3Velocity / colData.ERadius;
00092 Vector3 finalPosition = CollideWithSource(0, ref colData, eSpacePosition, eSpaceVelocity);
00093
00094 falling = false;
00095 if (gravity != Vector3.Zero)
00096 {
00097 colData.R3Position = finalPosition * colData.ERadius;
00098 colData.R3Velocity = gravity;
00099 colData.TriangleHits = 0;
00100
00101 eSpaceVelocity = gravity / colData.ERadius;
00102 finalPosition = CollideWithSource(0, ref colData, finalPosition, eSpaceVelocity);
00103 falling = colData.TriangleHits == 0;
00104 }
00105 if (colData.TriangleHits != 0)
00106 {
00107 hitTriangle = colData.IntersectionTriangle;
00108 hitTriangle.A *= colData.ERadius;
00109 hitTriangle.B *= colData.ERadius;
00110 hitTriangle.C *= colData.ERadius;
00111 }
00112 finalPosition *= colData.ERadius;
00113 hitPosition = colData.IntersectionPoint * colData.ERadius;
00114
00115 return finalPosition;
00116 }
00117
00118 private Vector3 CollideWithSource(int recursionDepth, ref CollisionData colData, Vector3 position, Vector3 velocity)
00119 {
00120 float veryCloseDistance = colData.SlidingSpeed;
00121
00122 if (recursionDepth > 5)
00123 return position;
00124
00125 colData.Velocity = velocity;
00126 colData.NormalizedVelocity = Vector3.Normalize(velocity);
00127 colData.BasePoint = position;
00128 colData.FoundCollision = false;
00129 colData.NearestDistance = float.MaxValue;
00130
00131 BoundingBox box = new BoundingBox(colData.R3Position, colData.R3Position);
00132
00133 CollisionHelper.AddToBox(ref box, colData.R3Position + colData.R3Velocity);
00134 box.Min -= colData.ERadius;
00135 box.Max += colData.ERadius;
00136
00137 Matrix scaleMatrix = Matrix.CreateScale(new Vector3(1, 1, 1) / colData.ERadius);
00138
00139 _Triangles.Clear();
00140
00141 Source.Query(box, _Triangles, scaleMatrix);
00142
00143 for (int i = 0; i != _Triangles.Count; i++)
00144 if (TestTriangleIntersection(ref colData, _Triangles[i]))
00145 colData.TriangleIndex = i;
00146
00147 if (!colData.FoundCollision)
00148 return position + velocity;
00149
00150 Vector3 destinationPoint = position + velocity;
00151 Vector3 newBasePoint = position;
00152
00153 if (colData.NearestDistance >= veryCloseDistance)
00154 {
00155 Vector3 v = Vector3.Normalize(velocity) * (colData.NearestDistance - veryCloseDistance);
00156
00157 newBasePoint = colData.BasePoint + v;
00158 v.Normalize();
00159 colData.IntersectionPoint -= (v * veryCloseDistance);
00160 }
00161
00162 Vector3 slidePlaneOrigin = colData.IntersectionPoint;
00163 Vector3 slidePlaneNormal = Vector3.Normalize(newBasePoint - colData.IntersectionPoint);
00164 Plane slidingPlane = CollisionHelper.CreatePlane(slidePlaneNormal, slidePlaneOrigin);
00165 Vector3 newDestinationPoint = destinationPoint - (slidePlaneNormal * CollisionHelper.DistanceTo(slidingPlane, destinationPoint));
00166 Vector3 newVelocity = newDestinationPoint - colData.IntersectionPoint;
00167
00168 if (newVelocity.Length() < veryCloseDistance)
00169 return newBasePoint;
00170
00171 return CollideWithSource(recursionDepth + 1, ref colData, newBasePoint, newVelocity);
00172 }
00173
00174 private bool TestTriangleIntersection(ref CollisionData colData, Triangle triangle)
00175 {
00176 Plane trianglePlane = triangle.Plane;
00177
00178 if (!triangle.IsFronFacing(colData.NormalizedVelocity))
00179 return false;
00180
00181 float t0, t1;
00182 bool embeddedInPlane = false;
00183 float signedDistToTrianglePlane = CollisionHelper.DistanceTo(trianglePlane, colData.BasePoint);
00184 float normalDotVelocity = Vector3.Dot(trianglePlane.Normal, colData.Velocity);
00185
00186 if (CollisionHelper.IsZero(normalDotVelocity))
00187 {
00188 if (signedDistToTrianglePlane >= 1)
00189 return false;
00190 else
00191 {
00192 embeddedInPlane = true;
00193 t0 = 0;
00194 t1 = 1;
00195 }
00196 }
00197 else
00198 {
00199 normalDotVelocity = CollisionHelper.Reciprocal(normalDotVelocity);
00200
00201 t0 = (-1.0f - signedDistToTrianglePlane) * normalDotVelocity;
00202 t1 = (1.0f - signedDistToTrianglePlane) * normalDotVelocity;
00203
00204 if (t0 > t1)
00205 {
00206 float temp = t1;
00207
00208 t1 = t0;
00209 t0 = temp;
00210 }
00211
00212 if (t0 > 1.0f || t1 < 0.0f)
00213 return false;
00214
00215 t0 = CollisionHelper.Clamp(t0, 0.0f, 1.0f);
00216 t1 = CollisionHelper.Clamp(t1, 0.0f, 1.0f);
00217 }
00218
00219 Vector3 collisionPoint = new Vector3(0, 0, 0);
00220 bool foundCollision = false;
00221 float t = 1.0f;
00222
00223 if (!embeddedInPlane)
00224 {
00225 Vector3 planeIntersectionPoint = (colData.BasePoint - trianglePlane.Normal) + colData.Velocity * t0;
00226
00227 if (triangle.IsPointInside(planeIntersectionPoint))
00228 {
00229 foundCollision = true;
00230 t = t0;
00231 collisionPoint = planeIntersectionPoint;
00232 }
00233 }
00234
00235 if (!foundCollision)
00236 {
00237 Vector3 velocity = colData.Velocity;
00238 Vector3 basePt = colData.BasePoint;
00239 float velocityLenSq = velocity.LengthSquared();
00240 float a, b, c, newT;
00241
00242 a = velocityLenSq;
00243 b = 2.0f * Vector3.Dot(velocity, basePt - triangle.A);
00244 c = (triangle.A - basePt).LengthSquared() - 1.0f;
00245 if (GetLowestRoot(a, b, c, t, out newT))
00246 {
00247 t = newT;
00248 foundCollision = true;
00249 collisionPoint = triangle.A;
00250 }
00251
00252 if (!foundCollision)
00253 {
00254 b = 2.0f * Vector3.Dot(velocity, basePt - triangle.B);
00255 c = (triangle.B - basePt).LengthSquared() - 1.0f;
00256 if (GetLowestRoot(a, b, c, t, out newT))
00257 {
00258 t = newT;
00259 foundCollision = true;
00260 collisionPoint = triangle.B;
00261 }
00262 }
00263
00264 if (!foundCollision)
00265 {
00266 b = 2.0f * Vector3.Dot(velocity, basePt - triangle.C);
00267 c = (triangle.C - basePt).LengthSquared() - 1.0f;
00268 if (GetLowestRoot(a, b, c, t, out newT))
00269 {
00270 t = newT;
00271 foundCollision = true;
00272 collisionPoint = triangle.C;
00273 }
00274 }
00275
00276 Vector3 edge = triangle.B - triangle.A;
00277 Vector3 baseToVertex = triangle.A - basePt;
00278 float edgeLenSq = edge.LengthSquared();
00279 float edgeDotVelocity = Vector3.Dot(edge, velocity);
00280 float edgeDotBaseToVertex = Vector3.Dot(edge, baseToVertex);
00281
00282 a = edgeLenSq * -velocityLenSq + edgeDotVelocity * edgeDotVelocity;
00283 b = edgeLenSq * (2.0f * Vector3.Dot(velocity, baseToVertex)) - 2.0f * edgeDotVelocity * edgeDotBaseToVertex;
00284 c = edgeLenSq * (1.0f - baseToVertex.LengthSquared()) + edgeDotBaseToVertex * edgeDotBaseToVertex;
00285
00286 if (GetLowestRoot(a, b, c, t, out newT))
00287 {
00288 float f = (edgeDotVelocity * newT - edgeDotBaseToVertex) / edgeLenSq;
00289 if (f >= 0.0f && f <= 1.0f)
00290 {
00291 t = newT;
00292 foundCollision = true;
00293 collisionPoint = triangle.A + (edge * f);
00294 }
00295 }
00296
00297 edge = triangle.C - triangle.B;
00298 baseToVertex = triangle.B - basePt;
00299 edgeLenSq = edge.LengthSquared();
00300 edgeDotVelocity = Vector3.Dot(edge,velocity);
00301 edgeDotBaseToVertex = Vector3.Dot(edge,baseToVertex);
00302
00303 a = edgeLenSq * -velocityLenSq + edgeDotVelocity * edgeDotVelocity;
00304 b = edgeLenSq * (2.0f * Vector3.Dot(velocity, baseToVertex)) - 2.0f * edgeDotVelocity * edgeDotBaseToVertex;
00305 c = edgeLenSq * (1.0f - baseToVertex.LengthSquared()) + edgeDotBaseToVertex * edgeDotBaseToVertex;
00306
00307 if (GetLowestRoot(a, b, c, t, out newT))
00308 {
00309 float f = (edgeDotVelocity * newT - edgeDotBaseToVertex) / edgeLenSq;
00310 if (f >= 0.0f && f <= 1.0f)
00311 {
00312 t = newT;
00313 foundCollision = true;
00314 collisionPoint = triangle.B + (edge * f);
00315 }
00316 }
00317
00318 edge = triangle.A - triangle.C;
00319 baseToVertex = triangle.C - basePt;
00320 edgeLenSq = edge.LengthSquared();
00321 edgeDotVelocity = Vector3.Dot(edge, velocity);
00322 edgeDotBaseToVertex = Vector3.Dot(edge, baseToVertex);
00323
00324 a = edgeLenSq * -velocityLenSq + edgeDotVelocity * edgeDotVelocity;
00325 b = edgeLenSq * (2.0f * Vector3.Dot(velocity, baseToVertex)) - 2.0f * edgeDotVelocity * edgeDotBaseToVertex;
00326 c = edgeLenSq * (1.0f - baseToVertex.LengthSquared()) + edgeDotBaseToVertex * edgeDotBaseToVertex;
00327
00328 if (GetLowestRoot(a, b, c, t, out newT))
00329 {
00330 float f = (edgeDotVelocity * newT - edgeDotBaseToVertex) / edgeLenSq;
00331 if (f >= 0.0f && f <= 1.0f)
00332 {
00333 t = newT;
00334 foundCollision = true;
00335 collisionPoint = triangle.C + (edge * f);
00336 }
00337 }
00338 }
00339
00340 if (foundCollision)
00341 {
00342 float distToCollision = t * colData.Velocity.Length();
00343
00344 if (!colData.FoundCollision || distToCollision < colData.NearestDistance)
00345 {
00346 colData.NearestDistance = distToCollision;
00347 colData.IntersectionPoint = collisionPoint;
00348 colData.FoundCollision = true;
00349 colData.IntersectionTriangle = triangle;
00350 colData.TriangleHits++;
00351
00352 return true;
00353 }
00354 }
00355 return false;
00356 }
00357
00358 private bool GetLowestRoot(float a, float b, float c, float t, out float newT)
00359 {
00360 float determinant = b * b - 4.0f * a * c;
00361
00362 newT = -1;
00363
00364 if (determinant < 0.0f) return false;
00365
00366 float sqrtD = (float)Math.Sqrt(determinant);
00367
00368 float r1 = (-b - sqrtD) / (2 * a);
00369 float r2 = (-b + sqrtD) / (2 * a);
00370
00371 if (r1 > r2)
00372 {
00373 float temp = r2;
00374
00375 r2 = r1;
00376 r1 = temp;
00377 }
00378
00379 if (r1 > 0 && r1 < t)
00380 {
00381 newT = r1;
00382
00383 return true;
00384 }
00385
00386 if (r2 > 0 && r2 < t)
00387 {
00388 newT = r2;
00389
00390 return true;
00391 }
00392 return false;
00393 }
00394 }
00395 }