/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="startPos"></param> /// <param name="endPos"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public override bool Collide(Vector3 startPos, Vector3 endPos, float radius, ref CollInfo collPrim) { bool hit = false; float combinedRadius = WorldSphere.Radius + radius + Vector3.Distance(startPos, endPos) * 0.5f; float distance = Vector3.Distance(WorldSphere.Center, (startPos + endPos) * 0.5f); if (distance <= combinedRadius) { CollInfo test = new CollInfo(); test.DistSq = Single.MaxValue; for (int i = 0; i < children.Count; ++i) { if (children[i].Collide(startPos, endPos, radius, ref test)) { if (test.DistSq <= collPrim.DistSq) { collPrim = test; collPrim.TopPrimitive = this; hit = true; } } } } else { /// Just a spot to put a breakpoint. hit = false; } return(hit); }
/// <summary> /// Check for initial condition of contact. If so, fill in the collPrim /// and return true, else return false and leave collPrim alone. /// </summary> /// <param name="p0"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> protected bool CheckTouching(Vector3 p0, float radius, ref CollInfo collPrim) { Vector3 p0Local = Vector3.Transform(p0, WorldToLocal); Vector3 closestLocal = p0Local; float lengthSq = p0Local.X * p0Local.X / (Radii.X * Radii.X) + p0Local.Y * p0Local.Y / (Radii.Y * Radii.Y) + p0Local.Z * p0Local.Z / (Radii.Z * Radii.Z); if (lengthSq > 1.0f) { float invLength = (float)(1.0 / Math.Sqrt(lengthSq)); closestLocal *= invLength; } Vector3 closest = Vector3.Transform(closestLocal, LocalToWorld); if (Vector3.DistanceSquared(closest, p0) <= radius * radius) { Vector3 normal = lengthSq < 1.0f ? p0 - LocalToWorld.Translation : p0 - closest; return(SetCollPrimTouching( p0, p0, closest, normal, Vector3.Zero, ref collPrim)); } return(false); }
/// <summary> /// Helper func to set up a collision reporting struct. /// </summary> /// <param name="from"></param> /// <param name="center"></param> /// <param name="contact"></param> /// <param name="normal"></param> /// <param name="struck"></param> /// <param name="collPrim"></param> /// <returns></returns> public bool SetCollPrim( Vector3 from, Vector3 center, Vector3 contact, Vector3 normal, Vector3 struck, ref CollInfo collPrim) { collPrim.DistSq = Vector3.DistanceSquared(from, contact); collPrim.Center = center; collPrim.Contact = contact; if (normal.LengthSquared() == 0.0f) { collPrim.Normal = Vector3.UnitZ; } else { collPrim.Normal = normal; } collPrim.Struck = struck; collPrim.Touching = false; collPrim.LowPrimitive = this; collPrim.TopPrimitive = this; return(true); }
/// <summary> /// Check for initial condition of contact. If so, fill in the collPrim /// and return true, else return false and leave collPrim alone. /// </summary> /// <param name="p0"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> protected bool CheckTouching(Vector3 p0, float radius, ref CollInfo collPrim) { Vector3 p0Local = Vector3.Transform(p0, WorldToLocal); Vector3 closestLocal = ClosestPointLocal(p0Local); Vector3 closest = Vector3.Transform(closestLocal, LocalToWorld); if (Vector3.DistanceSquared(closest, p0) <= radius * radius) { Vector3 localNorm = Vector3.Zero; if (p0Local.Z >= Length) { localNorm = Vector3.UnitZ; } else if (p0Local.Z <= 0) { localNorm = -Vector3.UnitZ; } else { localNorm.X = p0Local.X; localNorm.Y = p0Local.Y; } Vector3 norm = Vector3.TransformNormal(localNorm, LocalToWorld); /// Make sure the contact point is on the surface. if ((closestLocal.Z < Length) && (closestLocal.Z > 0)) { /// It's not on an end cap, check if it needs pushing out to the sides. Vector2 xy = new Vector2(closestLocal.X, closestLocal.Y); if (xy.LengthSquared() < Radius * Radius) { /// Yep, push it out. xy.Normalize(); xy *= Radius; closestLocal.X = xy.X; closestLocal.Y = xy.Y; closest = Vector3.Transform(closestLocal, LocalToWorld); } } else { // This case happens when we're within the infinite cylinder // but outside of either end cap. return(false); } return(SetCollPrimTouching( p0, p0, closest, p0 - closest, Vector3.Zero, ref collPrim)); } return(false); }
/// <summary> /// Fill in a collPrim for initial condition already in contact. /// </summary> /// <param name="p0"></param> /// <param name="del"></param> /// <param name="collPrim"></param> /// <returns></returns> private bool AlreadyTouching(Vector3 p0, Vector3 del, ref CollInfo collPrim) { Vector3 contact = Center + del; return(SetCollPrimTouching( p0, p0, contact, p0 - contact, Center, ref collPrim)); }
/// <summary> /// Helper func to set up a collision reporting struct. /// For use when two bodies already touching at beginning of frame. /// </summary> /// <param name="from"></param> /// <param name="center"></param> /// <param name="contact"></param> /// <param name="normal"></param> /// <param name="collPrim"></param> /// <returns></returns> public bool SetCollPrimTouching( Vector3 from, Vector3 center, Vector3 contact, Vector3 normal, Vector3 struck, ref CollInfo collPrim) { SetCollPrim(from, center, contact, normal, struck, ref collPrim); collPrim.Touching = true; return(true); }
} // end of Collide() /// <summary> /// Specialized check against other Movers /// </summary> /// <param name="other"></param> /// <param name="collPrim"></param> /// <returns></returns> public bool Collide(Mover other, ref CollInfo collPrim) { float dt = Time.GameTimeFrameSeconds; Vector3 p0 = other.Owner.Movement.Position; Vector3 p1 = p0 - Owner.Movement.Velocity * dt + other.Owner.Movement.Velocity * dt; return(Collide( p0, p1, other.Radius, ref collPrim)); }
/// <summary> /// Helper func to set up a collision reporting struct. /// </summary> /// <param name="from"></param> /// <param name="center"></param> /// <param name="contact"></param> /// <param name="normal"></param> /// <param name="collPrim"></param> /// <returns></returns> protected bool SetCollPrim( Vector3 from, Vector3 center, Vector3 contact, Vector3 normal, ref CollInfo collPrim) { return(SetCollPrim( from, center, contact, normal, Vector3.Zero, ref collPrim)); }
/// <summary> /// Fill in a hitInfo struct from the input. /// </summary> /// <param name="best"></param> /// <param name="radius"></param> /// <returns></returns> private HitInfo MakeHitScratch(CollInfo best, float radius) { GameActor other = best.TopPrimitive.Owner; HitInfo hitScratch = new HitInfo(); hitScratch.Contact = best.Contact; hitScratch.Center = best.Center; hitScratch.Normal = Vector3.Normalize(best.Normal); hitScratch.DistSq = best.DistSq; hitScratch.Other = other; hitScratch.Struck = best.Struck; hitScratch.OtherMover = best.TopPrimitive as Mover; hitScratch.Offset = MakeOffset(hitScratch, radius); hitScratch.Touching = best.Touching; hitScratch.Handled = false; hitScratch.TimeStamp = Time.GameTimeTotalSeconds; return(hitScratch); }
/// <summary> /// Check for initial condition of contact. If so, fill in the collPrim /// and return true, else return false and leave collPrim alone. /// </summary> /// <param name="p0"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> private bool CheckTouching(Vector3 worldPos, float radius, ref CollInfo collPrim) { Vector3 closePos = ClosestPoint(worldPos); if (Vector3.DistanceSquared(worldPos, closePos) <= radius * radius) { Vector3 normal = worldPos - closePos; if (Vector3.Dot(normal, WorldNormal) < 0.0f) { normal = WorldNormal; } return(SetCollPrimTouching( worldPos, worldPos, closePos, normal, Vector3.Zero, ref collPrim)); } return(false); }
/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="p0"></param> /// <param name="p1"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public override bool Collide(Vector3 p0, Vector3 p1, float radius, ref CollInfo collPrim) { ///Check sphere first. CollInfo testPrim = new CollInfo(); testPrim.DistSq = Single.MaxValue; bool hit = false; foreach (Rectangle rect in sides) { if (rect.Collide(p0, p1, radius, ref testPrim)) { if (testPrim.DistSq < collPrim.DistSq) { hit = true; collPrim = testPrim; collPrim.TopPrimitive = this; } } } return(hit); }
/// <summary> /// Start, end and radii are passed in from a mover. This method tests /// this mover's swept ellipsoid against all the non-moving things as /// well as all the other movers which appear after it in the list. /// The "first" param is the index of the next item in the list. /// /// The listAll param seems to determine whether we just keep the nearest hit /// or keep them all. It appears that listAll=false is used for physics collision /// testing while listAll=true is used for WHEN Bumped testing. But, of course, /// this is just a guess on my part since it's not documented. Argh. /// </summary> /// <param name="startPos0">start position of swept sphere</param> /// <param name="endPos0">end position of swept sphere</param> /// <param name="radii0">Radii of swept ellipsoid.</param> /// <param name="first">This is the index of the first mover to check for collisions with. Start, end, and radii come from the previous mover. TODO (****) Change numbering so this is more clear.</param> /// <param name="listAll">When true, all hits are listed. When false, only the nearest hit is listed.</param> /// <param name="hits"></param> /// <returns></returns> private bool CollisionCheck(Vector3 startPos0, Vector3 endPos0, Vector3 radii0, int first, bool listAll, List <HitInfo> hits) { /// These are structs, no real allocation done here. CollInfo curCollPrim = new CollInfo(); curCollPrim.DistSq = Single.MaxValue; CollInfo best = new CollInfo(); best.DistSq = Single.MaxValue; bool hit = false; // Test the current swept ellipsoid against all the non-moving things. List <CollisionPrimitive> relevants = things; for (int ithing = 0; ithing < relevants.Count; ++ithing) { CollisionPrimitive thing = relevants[ithing]; if (thing.Owner.Ignored) { continue; } // Note we used the Z part of the radii. So, on squashed movers we // might get some intersection. // TODO (****) Actually figure out how to test a swept ellipsoid against // the Primitive based collision shapes. if (thing.Collide(startPos0, endPos0, radii0.Z, ref curCollPrim)) { if (listAll) { HitInfo hitScratch = MakeHitScratch(curCollPrim, radii0.X); hits.Add(hitScratch); curCollPrim.DistSq = Single.MaxValue; } else if (curCollPrim.DistSq <= best.DistSq) { hit = true; best = curCollPrim; endPos0 = best.Center; if (best.Touching) { HitInfo hitScratch = MakeHitScratch(best, radii0.X); hits.Add(hitScratch); } } } } // Test the current mover against the remaining movers in the list. // Note that in the case of hit testing blips this index may be -1. int curMoverIndex = first - 1; // If listAll is true, then we're testing for bump so test the full // list since we have to worry about TouchCushions. if (listAll) { first = 0; } for (int second = first; second < movers.Count; ++second) { Mover sphere = movers[second]; // Don't test against self. if (second == curMoverIndex) { continue; } if (sphere.Owner.Ignored) { continue; } // Don't collide missiles with their launcher. Note that this assumes // that the launcher is always in the list _ahead_ of the missile. MissileChassis mc = movers[second].Owner.Chassis as MissileChassis; if (mc != null && curMoverIndex >= 0 && mc.Launcher == movers[curMoverIndex].Owner) { continue; } Vector3 radii1 = sphere.Radius * sphere.Owner.SquashScale; Vector3 hitContactPosition = Vector3.Zero; Vector3 hitNormal = Vector3.UnitZ; float hitT = -1; bool hitTest = SweptPrims.SweptEllipsoidSweptEllipsoid(startPos0, endPos0, radii0, sphere.Center, sphere.Center + sphere.Delta, sphere.Radius * sphere.Owner.SquashScale, ref hitContactPosition, ref hitNormal, ref hitT); if (hitTest) { Vector3 posAtCollision = MyMath.Lerp(startPos0, endPos0, hitT); // Already touching? if (hitT <= 0) { sphere.SetCollPrimTouching(startPos0, posAtCollision, hitContactPosition, -hitNormal, sphere.Center + hitT * sphere.Delta, ref curCollPrim); } else { sphere.SetCollPrim(startPos0, posAtCollision, hitContactPosition, -hitNormal, sphere.Center + hitT * sphere.Delta, ref curCollPrim); } if (listAll) { HitInfo hitScratch = MakeHitScratch(curCollPrim, radii0.X); hits.Add(hitScratch); curCollPrim.DistSq = Single.MaxValue; } else if (curCollPrim.DistSq <= best.DistSq) { best = curCollPrim; hit = true; if (best.Touching) { HitInfo hitScratch = MakeHitScratch(best, radii0.X); hits.Add(hitScratch); } } } // end if hitTest } // end of loop over other movers. if (hit) { Debug.Assert(!listAll, "Hit shouldn't get set when listing all hits"); /// If it's touching, we've already applied, otherwise /// do it here. if (!best.Touching) { HitInfo hitScratch = MakeHitScratch(best, radii0.X); hits.Add(hitScratch); } } if (listAll && (hits.Count > 1)) { hits.Sort(comparer); } return(hits.Count > 0); }
/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="startPositionOther"></param> /// <param name="p1"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public override bool Collide(Vector3 startPositionOther, Vector3 endPositionOther, float radius, ref CollInfo collPrim) { Vector3 myRadii = Radius * Owner.SquashScale; /// Compensate for our motion. // We can then use this for "this" motion while "other" is now motionless. Vector3 endPositionThis = endPositionOther - Delta; Vector3 centerCenterDelta = startPositionOther - Center; float sumOfRadiiSquared = radius + Radius; sumOfRadiiSquared *= sumOfRadiiSquared; float centerCenterDistSquared = centerCenterDelta.LengthSquared(); double C = centerCenterDistSquared - sumOfRadiiSquared; if (C <= 0.0) { /// Already touching return(AlreadyTouching(startPositionOther, centerCenterDelta * Radius / (radius + Radius), ref collPrim)); } /// The aren't touching. The only degeneracies to worry about /// are that they aren't moving, they miss each other, or /// they hit outside range p0->p1. /// They aren't moving shows up as p1==p0. /// A miss will show up as a lack of real roots to the quadratic. /// Outside time range shows up as t < 0 || t > 1. Vector3 dir = endPositionThis - startPositionOther; double A = dir.LengthSquared(); if (A < Single.Epsilon) { /// Not moving and not already touching. return(false); } double B = 2.0 * Vector3.Dot(dir, centerCenterDelta); double determ = B * B - 4.0 * A * C; if (determ < 0.0) { /// Missed. return(false); } double t = -B - Math.Sqrt(determ); t /= 2.0 * A; if ((t < 0) || (t > 1)) { /// Outside interval return(false); } Vector3 centerOld = startPositionOther + ((float)t) * (endPositionOther - startPositionOther); Vector3 normalOld = centerOld - Center; Vector3 contactOld = Center + normalOld * Radius / (Radius + radius); return(SetCollPrim( startPositionOther, centerOld, contactOld, normalOld, Center + delta * (float)t, ref collPrim)); } // end of Collide()
/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="startPos"></param> /// <param name="endPos"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public override bool Collide(Vector3 startPos, Vector3 endPos, float radius, ref CollInfo collPrim) { /// Three possibilities of a collision here. /// One is that the sphere hit our top or bottom cap /// Second is the sphere hits the side. /// Third is sphere hits edge between side and cap. /// if (disabled) { return(false); } if (CheckTouching(startPos, radius, ref collPrim)) { return(true); } Vector3 p0loc = Vector3.Transform(startPos, WorldToLocal); Vector3 p1loc = Vector3.Transform(endPos, WorldToLocal); float localRadius = WorldToLocalRadius(radius); /// Check the end caps first. /// Note that the cap checks look only for the sphere's tangent /// hitting on the cap, not for further misses. float t = CheckCapTop(p0loc, p1loc, localRadius); if (IsHit(t)) { Vector3 center = startPos + t * (endPos - startPos); Vector3 norm = Vector3.TransformNormal(Vector3.UnitZ, LocalToWorld); return(SetCollPrim( startPos, center, center - norm * radius, norm, ref collPrim)); } t = CheckCapBot(p0loc, p1loc, localRadius); if (IsHit(t)) { Vector3 center = startPos + t * (endPos - startPos); Vector3 norm = Vector3.TransformNormal(-Vector3.UnitZ, LocalToWorld); return(SetCollPrim( startPos, center, center - norm * radius, norm, ref collPrim)); } /// Now check the sides. t = CheckSides(p0loc, p1loc, localRadius); if (IsHit(t)) { Vector3 localCenter = p0loc + t * (p1loc - p0loc); if ((localCenter.Z >= 0) && (localCenter.Z <= Length)) { Vector3 center = startPos + t * (endPos - startPos); Vector3 localNorm = new Vector3(localCenter.X, localCenter.Y, 0.0f); Vector3 norm = Vector3.TransformNormal(localNorm, LocalToWorld); return(SetCollPrim( startPos, center, center - norm * radius, norm, ref collPrim)); } } return(false); }
/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="p0"></param> /// <param name="p1"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public override bool Collide(Vector3 p0, Vector3 p1, float radius, ref CollInfo collPrim) { /// Bounding sphere test here!!! if (disabled) { return(false); } if (CheckTouching(p0, radius, ref collPrim)) { return(true); } /// Find point on sphere tangent to plane parallel to this. Vector3 worldNormal = WorldNormal; Vector3 pTan0 = p0 - worldNormal * radius; Vector3 pTan1 = p1 - worldNormal * radius; /// Project it along velocity onto plane float stepDist = Vector3.Dot(pTan1 - pTan0, worldNormal); if (stepDist >= -Single.Epsilon) /// We're moving away from it { return(false); } Vector3 center = planeToWorld.Translation; /// Check if we're already inside the plane float centerDist = Vector3.Dot(center, worldNormal) - Vector3.Dot(pTan0, worldNormal); if (centerDist > 0) { if (centerDist > radius) { /// We're more than halfway embedded from the start, skip out return(false); } pTan0 += centerDist * worldNormal; pTan1 += centerDist * worldNormal; centerDist = 0.0f; } float t = centerDist / stepDist; if ((t < 0.0f) || (t > 1.0f)) { return(false); } Vector3 pProj = pTan0 + t * (pTan1 - pTan0); Vector3 pProjPlane = Vector3.Transform(pProj, worldToPlane); if (Inside(pProjPlane)) { /// The tangent on the sphere smacked into a side. Vector3 centerAtT = p0 + t * (p1 - p0); return(SetCollPrim(p0, centerAtT, pProj, centerAtT - pProj, ref collPrim)); } /// pProj is where the sphere hits the plane. /// Find the closest point on the rect to pProj pProjPlane.X = MathHelper.Clamp(pProjPlane.X, -HalfWidth, HalfWidth); pProjPlane.Y = MathHelper.Clamp(pProjPlane.Y, -HalfHeight, HalfHeight); Vector3 pHit = Vector3.Transform(pProjPlane, planeToWorld); /// Try for an early out. if (Vector3.DistanceSquared(pHit, pProj) > radius * radius) { return(false); } /// pHit now the point on the rect the sphere will make first contact, /// if indeed it's going to make contact. /// Now cast a ray back from pHit to see where on the sphere makes contact. if (RaySphere(p0, radius, pHit, pHit - (p1 - p0), ref t)) { Vector3 centerAtT = p0 + t * (p1 - p0); return(SetCollPrim(p0, centerAtT, pHit, centerAtT - pHit, ref collPrim)); } return(false); }
/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="p0"></param> /// <param name="p1"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public override bool Collide(Vector3 p0, Vector3 p1, float radius, ref CollInfo collPrim) { if (disabled) { return(false); } //p1 = p0; //p1.Y -= 1.0f; /// Check against bounding sphere. /// Check degenerate case where we are already in contact if (CheckTouching(p0, radius, ref collPrim)) { return(true); } /// Move the problem to the space in which the expanded ellipse is a unit sphere /// centered at the origin. /// /// Expand our radii by radius float localRadius = WorldToLocalRadius(radius); Matrix ellipseToSphere = Matrix.CreateScale( 1.0f / (Radii.X + localRadius), 1.0f / (Radii.X + localRadius), 1.0f / (Radii.Z + localRadius)); Matrix worldToSphere = WorldToLocal * ellipseToSphere; /// Convert ray test to expanded ellipsoid space Vector3 p0sph = Vector3.Transform(p0, worldToSphere); Vector3 p1sph = Vector3.Transform(p1, worldToSphere); /// If we have a hit... float t = 0.0f; bool hit = RaySphere(Vector3.Zero, 1.0f, p0sph, p1sph, ref t); if (hit) { Matrix sphereToEllipse = Matrix.CreateScale( Radii.X + localRadius, Radii.Y + localRadius, Radii.Z + localRadius); Matrix sphereToWorld = sphereToEllipse * LocalToWorld; Vector3 hitWorld = p0 + t * (p1 - p0); /// Get the hit point and normal in sphere space Vector3 hitsph = Vector3.Transform(hitWorld, worldToSphere); Matrix normSphereToLocal = Matrix.CreateScale( ellipseToSphere.M11 * ellipseToSphere.M11, ellipseToSphere.M22 * ellipseToSphere.M22, ellipseToSphere.M33 * ellipseToSphere.M33); Vector3 localNormal = Vector3.TransformNormal(hitsph, normSphereToLocal); /// Transform the hit point and normal back to world space Vector3 normWorld = Vector3.TransformNormal(localNormal, LocalToWorld); normWorld.Normalize(); /// Move the hit point into the ellipse by the normalized hit normal /// times the radius. SetCollPrim( p0, hitWorld, hitWorld - normWorld * radius, normWorld, ref collPrim); return(true); } return(false); }
/// <summary> /// Evaluate collisions with the moving sphere. /// If returns true, there was a collision as described in collPrim, /// else no collision and collPrim untouched. /// </summary> /// <param name="startPos"></param> /// <param name="endPos"></param> /// <param name="radius"></param> /// <param name="collPrim"></param> /// <returns></returns> public abstract bool Collide(Vector3 startPos, Vector3 endPos, float radius, ref CollInfo collPrim);