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 }