/* * Farseer Physics Engine based on Box2D.XNA port: * Copyright (c) 2010 Ian Qvist * * Box2D.XNA port of Box2D: * Copyright (c) 2009 Brandon Furtwangler, Nathan Furtwangler * * Original source Box2D: * Copyright (c) 2006-2009 Erin Catto http://www.gphysics.com * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ using System; using System.Diagnostics; using System.Runtime.InteropServices; using FarseerPhysics.Collision.Shapes; using FarseerPhysics.Common; using Microsoft.Xna.Framework; namespace FarseerPhysics.Collision { internal enum ContactFeatureType : byte { Vertex = 0, Face = 1, } /// /// The features that intersect to form the contact point /// This must be 4 bytes or less. /// public struct ContactFeature { /// /// Feature index on ShapeA /// public byte IndexA; /// /// Feature index on ShapeB /// public byte IndexB; /// /// The feature type on ShapeA /// public byte TypeA; /// /// The feature type on ShapeB /// public byte TypeB; } /// /// Contact ids to facilitate warm starting. /// [StructLayout(LayoutKind.Explicit)] public struct ContactID { /// /// The features that intersect to form the contact point /// [FieldOffset(0)] public ContactFeature Features; /// /// Used to quickly compare contact ids. /// [FieldOffset(0)] public uint Key; } /// /// A manifold point is a contact point belonging to a contact /// manifold. It holds details related to the geometry and dynamics /// of the contact points. /// The local point usage depends on the manifold type: /// -ShapeType.Circles: the local center of circleB /// -SeparationFunction.FaceA: the local center of cirlceB or the clip point of polygonB /// -SeparationFunction.FaceB: the clip point of polygonA /// This structure is stored across time steps, so we keep it small. /// Note: the impulses are used for internal caching and may not /// provide reliable contact forces, especially for high speed collisions. /// public struct ManifoldPoint { /// /// Uniquely identifies a contact point between two Shapes /// public ContactID Id; public Vector2 LocalPoint; public float NormalImpulse; public float TangentImpulse; } public enum ManifoldType { Circles, FaceA, FaceB } /// /// A manifold for two touching convex Shapes. /// Box2D supports multiple types of contact: /// - clip point versus plane with radius /// - point versus point with radius (circles) /// The local point usage depends on the manifold type: /// -ShapeType.Circles: the local center of circleA /// -SeparationFunction.FaceA: the center of faceA /// -SeparationFunction.FaceB: the center of faceB /// Similarly the local normal usage: /// -ShapeType.Circles: not used /// -SeparationFunction.FaceA: the normal on polygonA /// -SeparationFunction.FaceB: the normal on polygonB /// We store contacts in this way so that position correction can /// account for movement, which is critical for continuous physics. /// All contact scenarios must be expressed in one of these types. /// This structure is stored across time steps, so we keep it small. /// public struct Manifold { /// /// Not use for Type.SeparationFunction.Points /// public Vector2 LocalNormal; /// /// Usage depends on manifold type /// public Vector2 LocalPoint; /// /// The number of manifold points /// public int PointCount; /// /// The points of contact /// public FixedArray2 Points; public ManifoldType Type; } /// /// This is used for determining the state of contact points. /// public enum PointState { /// /// Point does not exist /// Null, /// /// Point was added in the update /// Add, /// /// Point persisted across the update /// Persist, /// /// Point was removed in the update /// Remove, } /// /// Used for computing contact manifolds. /// public struct ClipVertex { public ContactID ID; public Vector2 V; } /// /// Ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). /// public struct RayCastInput { public float MaxFraction; public Vector2 Point1, Point2; } /// /// Ray-cast output data. The ray hits at p1 + fraction * (p2 - p1), where p1 and p2 /// come from RayCastInput. /// public struct RayCastOutput { public float Fraction; public Vector2 Normal; } /// /// An axis aligned bounding box. /// public struct AABB { private static DistanceInput _input = new DistanceInput(); /// /// The lower vertex /// public Vector2 LowerBound; /// /// The upper vertex /// public Vector2 UpperBound; public AABB(Vector2 min, Vector2 max) : this(ref min, ref max) { } public AABB(ref Vector2 min, ref Vector2 max) { LowerBound = min; UpperBound = max; } public AABB(Vector2 center, float width, float height) { LowerBound = center - new Vector2(width / 2, height / 2); UpperBound = center + new Vector2(width / 2, height / 2); } /// /// Get the center of the AABB. /// /// public Vector2 Center { get { return 0.5f * (LowerBound + UpperBound); } } /// /// Get the extents of the AABB (half-widths). /// /// public Vector2 Extents { get { return 0.5f * (UpperBound - LowerBound); } } /// /// Get the perimeter length /// /// public float Perimeter { get { float wx = UpperBound.X - LowerBound.X; float wy = UpperBound.Y - LowerBound.Y; return 2.0f * (wx + wy); } } /// /// Gets the vertices of the AABB. /// /// The corners of the AABB public Vertices Vertices { get { Vertices vertices = new Vertices(); vertices.Add(LowerBound); vertices.Add(new Vector2(LowerBound.X, UpperBound.Y)); vertices.Add(UpperBound); vertices.Add(new Vector2(UpperBound.X, LowerBound.Y)); return vertices; } } /// /// first quadrant /// public AABB Q1 { get { return new AABB(Center, UpperBound); } } public AABB Q2 { get { return new AABB(new Vector2(LowerBound.X, Center.Y), new Vector2(Center.X, UpperBound.Y)); ; } } public AABB Q3 { get { return new AABB(LowerBound, Center); } } public AABB Q4 { get { return new AABB(new Vector2(Center.X, LowerBound.Y), new Vector2(UpperBound.X, Center.Y)); } } public Vector2[] GetVertices() { Vector2 p1 = UpperBound; Vector2 p2 = new Vector2(UpperBound.X, LowerBound.Y); Vector2 p3 = LowerBound; Vector2 p4 = new Vector2(LowerBound.X, UpperBound.Y); return new[] { p1, p2, p3, p4 }; } /// /// Verify that the bounds are sorted. /// /// /// true if this instance is valid; otherwise, false. /// public bool IsValid() { Vector2 d = UpperBound - LowerBound; bool valid = d.X >= 0.0f && d.Y >= 0.0f; valid = valid && LowerBound.IsValid() && UpperBound.IsValid(); return valid; } /// /// Combine an AABB into this one. /// /// The aabb. public void Combine(ref AABB aabb) { LowerBound = Vector2.Min(LowerBound, aabb.LowerBound); UpperBound = Vector2.Max(UpperBound, aabb.UpperBound); } /// /// Combine two AABBs into this one. /// /// The aabb1. /// The aabb2. public void Combine(ref AABB aabb1, ref AABB aabb2) { LowerBound = Vector2.Min(aabb1.LowerBound, aabb2.LowerBound); UpperBound = Vector2.Max(aabb1.UpperBound, aabb2.UpperBound); } /// /// Does this aabb contain the provided AABB. /// /// The aabb. /// /// true if it contains the specified aabb; otherwise, false. /// public bool Contains(ref AABB aabb) { bool result = true; result = result && LowerBound.X <= aabb.LowerBound.X; result = result && LowerBound.Y <= aabb.LowerBound.Y; result = result && aabb.UpperBound.X <= UpperBound.X; result = result && aabb.UpperBound.Y <= UpperBound.Y; return result; } /// /// Determines whether the AAABB contains the specified point. /// /// The point. /// /// true if it contains the specified point; otherwise, false. /// public bool Contains(ref Vector2 point) { //using epsilon to try and gaurd against float rounding errors. if ((point.X > (LowerBound.X + Settings.Epsilon) && point.X < (UpperBound.X - Settings.Epsilon) && (point.Y > (LowerBound.Y + Settings.Epsilon) && point.Y < (UpperBound.Y - Settings.Epsilon)))) { return true; } return false; } public static bool TestOverlap(AABB a, AABB b) { return TestOverlap(ref a, ref b); } public static bool TestOverlap(ref AABB a, ref AABB b) { Vector2 d1 = b.LowerBound - a.UpperBound; Vector2 d2 = a.LowerBound - b.UpperBound; if (d1.X > 0.0f || d1.Y > 0.0f) return false; if (d2.X > 0.0f || d2.Y > 0.0f) return false; return true; } public static bool TestOverlap(Shape shapeA, int indexA, Shape shapeB, int indexB, ref Transform xfA, ref Transform xfB) { _input.ProxyA.Set(shapeA, indexA); _input.ProxyB.Set(shapeB, indexB); _input.TransformA = xfA; _input.TransformB = xfB; _input.UseRadii = true; SimplexCache cache; DistanceOutput output; Distance.ComputeDistance(out output, out cache, _input); return output.Distance < 10.0f * Settings.Epsilon; } // From Real-time Collision Detection, p179. public bool RayCast(out RayCastOutput output, ref RayCastInput input) { output = new RayCastOutput(); float tmin = -Settings.MaxFloat; float tmax = Settings.MaxFloat; Vector2 p = input.Point1; Vector2 d = input.Point2 - input.Point1; Vector2 absD = MathUtils.Abs(d); Vector2 normal = Vector2.Zero; for (int i = 0; i < 2; ++i) { float absD_i = i == 0 ? absD.X : absD.Y; float lowerBound_i = i == 0 ? LowerBound.X : LowerBound.Y; float upperBound_i = i == 0 ? UpperBound.X : UpperBound.Y; float p_i = i == 0 ? p.X : p.Y; if (absD_i < Settings.Epsilon) { // Parallel. if (p_i < lowerBound_i || upperBound_i < p_i) { return false; } } else { float d_i = i == 0 ? d.X : d.Y; float inv_d = 1.0f / d_i; float t1 = (lowerBound_i - p_i) * inv_d; float t2 = (upperBound_i - p_i) * inv_d; // Sign of the normal vector. float s = -1.0f; if (t1 > t2) { MathUtils.Swap(ref t1, ref t2); s = 1.0f; } // Push the min up if (t1 > tmin) { if (i == 0) { normal.X = s; } else { normal.Y = s; } tmin = t1; } // Pull the max down tmax = Math.Min(tmax, t2); if (tmin > tmax) { return false; } } } // Does the ray start inside the box? // Does the ray intersect beyond the max fraction? if (tmin < 0.0f || input.MaxFraction < tmin) { return false; } // Intersection. output.Fraction = tmin; output.Normal = normal; return true; } } /// /// Edge shape plus more stuff. /// public struct FatEdge { public bool HasVertex0, HasVertex3; public Vector2 Normal; public Vector2 V0, V1, V2, V3; } /// /// This lets us treate and edge shape and a polygon in the same /// way in the SAT collider. /// public class EPProxy { public Vector2 Centroid; public int Count; public Vector2[] Normals = new Vector2[Settings.MaxPolygonVertices]; public Vector2[] Vertices = new Vector2[Settings.MaxPolygonVertices]; } public struct EPAxis { public int Index; public float Separation; public EPAxisType Type; } public enum EPAxisType { Unknown, EdgeA, EdgeB, } public static class Collision { private static FatEdge _edgeA; private static EPProxy _proxyA = new EPProxy(); private static EPProxy _proxyB = new EPProxy(); private static Transform _xf; private static Vector2 _limit11, _limit12; private static Vector2 _limit21, _limit22; private static float _radius; private static Vector2[] _tmpNormals = new Vector2[2]; /// /// Evaluate the manifold with supplied transforms. This assumes /// modest motion from the original state. This does not change the /// point count, impulses, etc. The radii must come from the Shapes /// that generated the manifold. /// /// The manifold. /// The transform for A. /// The radius for A. /// The transform for B. /// The radius for B. /// World vector pointing from A to B /// Torld contact point (point of intersection). public static void GetWorldManifold(ref Manifold manifold, ref Transform transformA, float radiusA, ref Transform transformB, float radiusB, out Vector2 normal, out FixedArray2 points) { points = new FixedArray2(); normal = Vector2.Zero; if (manifold.PointCount == 0) { normal = Vector2.UnitY; return; } switch (manifold.Type) { case ManifoldType.Circles: { Vector2 tmp = manifold.Points[0].LocalPoint; float pointAx = transformA.Position.X + transformA.R.Col1.X * manifold.LocalPoint.X + transformA.R.Col2.X * manifold.LocalPoint.Y; float pointAy = transformA.Position.Y + transformA.R.Col1.Y * manifold.LocalPoint.X + transformA.R.Col2.Y * manifold.LocalPoint.Y; float pointBx = transformB.Position.X + transformB.R.Col1.X * tmp.X + transformB.R.Col2.X * tmp.Y; float pointBy = transformB.Position.Y + transformB.R.Col1.Y * tmp.X + transformB.R.Col2.Y * tmp.Y; normal.X = 1; normal.Y = 0; float result = (pointAx - pointBx) * (pointAx - pointBx) + (pointAy - pointBy) * (pointAy - pointBy); if (result > Settings.Epsilon * Settings.Epsilon) { float tmpNormalx = pointBx - pointAx; float tmpNormaly = pointBy - pointAy; float factor = 1f / (float)Math.Sqrt(tmpNormalx * tmpNormalx + tmpNormaly * tmpNormaly); normal.X = tmpNormalx * factor; normal.Y = tmpNormaly * factor; } Vector2 c = Vector2.Zero; c.X = (pointAx + radiusA * normal.X) + (pointBx - radiusB * normal.X); c.Y = (pointAy + radiusA * normal.Y) + (pointBy - radiusB * normal.Y); points[0] = 0.5f * c; } break; case ManifoldType.FaceA: { normal.X = transformA.R.Col1.X * manifold.LocalNormal.X + transformA.R.Col2.X * manifold.LocalNormal.Y; normal.Y = transformA.R.Col1.Y * manifold.LocalNormal.X + transformA.R.Col2.Y * manifold.LocalNormal.Y; float planePointx = transformA.Position.X + transformA.R.Col1.X * manifold.LocalPoint.X + transformA.R.Col2.X * manifold.LocalPoint.Y; float planePointy = transformA.Position.Y + transformA.R.Col1.Y * manifold.LocalPoint.X + transformA.R.Col2.Y * manifold.LocalPoint.Y; for (int i = 0; i < manifold.PointCount; ++i) { Vector2 tmp = manifold.Points[i].LocalPoint; float clipPointx = transformB.Position.X + transformB.R.Col1.X * tmp.X + transformB.R.Col2.X * tmp.Y; float clipPointy = transformB.Position.Y + transformB.R.Col1.Y * tmp.X + transformB.R.Col2.Y * tmp.Y; float value = (clipPointx - planePointx) * normal.X + (clipPointy - planePointy) * normal.Y; Vector2 c = Vector2.Zero; c.X = (clipPointx + (radiusA - value) * normal.X) + (clipPointx - radiusB * normal.X); c.Y = (clipPointy + (radiusA - value) * normal.Y) + (clipPointy - radiusB * normal.Y); points[i] = 0.5f * c; } } break; case ManifoldType.FaceB: { normal.X = transformB.R.Col1.X * manifold.LocalNormal.X + transformB.R.Col2.X * manifold.LocalNormal.Y; normal.Y = transformB.R.Col1.Y * manifold.LocalNormal.X + transformB.R.Col2.Y * manifold.LocalNormal.Y; float planePointx = transformB.Position.X + transformB.R.Col1.X * manifold.LocalPoint.X + transformB.R.Col2.X * manifold.LocalPoint.Y; float planePointy = transformB.Position.Y + transformB.R.Col1.Y * manifold.LocalPoint.X + transformB.R.Col2.Y * manifold.LocalPoint.Y; for (int i = 0; i < manifold.PointCount; ++i) { Vector2 tmp = manifold.Points[i].LocalPoint; float clipPointx = transformA.Position.X + transformA.R.Col1.X * tmp.X + transformA.R.Col2.X * tmp.Y; float clipPointy = transformA.Position.Y + transformA.R.Col1.Y * tmp.X + transformA.R.Col2.Y * tmp.Y; float value = (clipPointx - planePointx) * normal.X + (clipPointy - planePointy) * normal.Y; Vector2 c = Vector2.Zero; c.X = (clipPointx - radiusA * normal.X) + (clipPointx + (radiusB - value) * normal.X); c.Y = (clipPointy - radiusA * normal.Y) + (clipPointy + (radiusB - value) * normal.Y); points[i] = 0.5f * c; } // Ensure normal points from A to B. normal *= -1; } break; default: normal = Vector2.UnitY; break; } } public static void GetPointStates(out FixedArray2 state1, out FixedArray2 state2, ref Manifold manifold1, ref Manifold manifold2) { state1 = new FixedArray2(); state2 = new FixedArray2(); // Detect persists and removes. for (int i = 0; i < manifold1.PointCount; ++i) { ContactID id = manifold1.Points[i].Id; state1[i] = PointState.Remove; for (int j = 0; j < manifold2.PointCount; ++j) { if (manifold2.Points[j].Id.Key == id.Key) { state1[i] = PointState.Persist; break; } } } // Detect persists and adds. for (int i = 0; i < manifold2.PointCount; ++i) { ContactID id = manifold2.Points[i].Id; state2[i] = PointState.Add; for (int j = 0; j < manifold1.PointCount; ++j) { if (manifold1.Points[j].Id.Key == id.Key) { state2[i] = PointState.Persist; break; } } } } /// Compute the collision manifold between two circles. public static void CollideCircles(ref Manifold manifold, CircleShape circleA, ref Transform xfA, CircleShape circleB, ref Transform xfB) { manifold.PointCount = 0; float pAx = xfA.Position.X + xfA.R.Col1.X * circleA.Position.X + xfA.R.Col2.X * circleA.Position.Y; float pAy = xfA.Position.Y + xfA.R.Col1.Y * circleA.Position.X + xfA.R.Col2.Y * circleA.Position.Y; float pBx = xfB.Position.X + xfB.R.Col1.X * circleB.Position.X + xfB.R.Col2.X * circleB.Position.Y; float pBy = xfB.Position.Y + xfB.R.Col1.Y * circleB.Position.X + xfB.R.Col2.Y * circleB.Position.Y; float distSqr = (pBx - pAx) * (pBx - pAx) + (pBy - pAy) * (pBy - pAy); float radius = circleA.Radius + circleB.Radius; if (distSqr > radius * radius) { return; } manifold.Type = ManifoldType.Circles; manifold.LocalPoint = circleA.Position; manifold.LocalNormal = Vector2.Zero; manifold.PointCount = 1; ManifoldPoint p0 = manifold.Points[0]; p0.LocalPoint = circleB.Position; p0.Id.Key = 0; manifold.Points[0] = p0; } /// /// Compute the collision manifold between a polygon and a circle. /// /// The manifold. /// The polygon A. /// The transform of A. /// The circle B. /// The transform of B. public static void CollidePolygonAndCircle(ref Manifold manifold, PolygonShape polygonA, ref Transform transformA, CircleShape circleB, ref Transform transformB) { manifold.PointCount = 0; // Compute circle position in the frame of the polygon. Vector2 c = new Vector2( transformB.Position.X + transformB.R.Col1.X * circleB.Position.X + transformB.R.Col2.X * circleB.Position.Y, transformB.Position.Y + transformB.R.Col1.Y * circleB.Position.X + transformB.R.Col2.Y * circleB.Position.Y); Vector2 cLocal = new Vector2( (c.X - transformA.Position.X) * transformA.R.Col1.X + (c.Y - transformA.Position.Y) * transformA.R.Col1.Y, (c.X - transformA.Position.X) * transformA.R.Col2.X + (c.Y - transformA.Position.Y) * transformA.R.Col2.Y); // Find the min separating edge. int normalIndex = 0; float separation = -Settings.MaxFloat; float radius = polygonA.Radius + circleB.Radius; int vertexCount = polygonA.Vertices.Count; for (int i = 0; i < vertexCount; ++i) { Vector2 value1 = polygonA.Normals[i]; Vector2 value2 = cLocal - polygonA.Vertices[i]; float s = value1.X * value2.X + value1.Y * value2.Y; if (s > radius) { // Early out. return; } if (s > separation) { separation = s; normalIndex = i; } } // Vertices that subtend the incident face. int vertIndex1 = normalIndex; int vertIndex2 = vertIndex1 + 1 < vertexCount ? vertIndex1 + 1 : 0; Vector2 v1 = polygonA.Vertices[vertIndex1]; Vector2 v2 = polygonA.Vertices[vertIndex2]; // If the center is inside the polygon ... if (separation < Settings.Epsilon) { manifold.PointCount = 1; manifold.Type = ManifoldType.FaceA; manifold.LocalNormal = polygonA.Normals[normalIndex]; manifold.LocalPoint = 0.5f * (v1 + v2); ManifoldPoint p0 = manifold.Points[0]; p0.LocalPoint = circleB.Position; p0.Id.Key = 0; manifold.Points[0] = p0; return; } // Compute barycentric coordinates float u1 = (cLocal.X - v1.X) * (v2.X - v1.X) + (cLocal.Y - v1.Y) * (v2.Y - v1.Y); float u2 = (cLocal.X - v2.X) * (v1.X - v2.X) + (cLocal.Y - v2.Y) * (v1.Y - v2.Y); if (u1 <= 0.0f) { float r = (cLocal.X - v1.X) * (cLocal.X - v1.X) + (cLocal.Y - v1.Y) * (cLocal.Y - v1.Y); if (r > radius * radius) { return; } manifold.PointCount = 1; manifold.Type = ManifoldType.FaceA; manifold.LocalNormal = cLocal - v1; float factor = 1f / (float) Math.Sqrt(manifold.LocalNormal.X * manifold.LocalNormal.X + manifold.LocalNormal.Y * manifold.LocalNormal.Y); manifold.LocalNormal.X = manifold.LocalNormal.X * factor; manifold.LocalNormal.Y = manifold.LocalNormal.Y * factor; manifold.LocalPoint = v1; ManifoldPoint p0b = manifold.Points[0]; p0b.LocalPoint = circleB.Position; p0b.Id.Key = 0; manifold.Points[0] = p0b; } else if (u2 <= 0.0f) { float r = (cLocal.X - v2.X) * (cLocal.X - v2.X) + (cLocal.Y - v2.Y) * (cLocal.Y - v2.Y); if (r > radius * radius) { return; } manifold.PointCount = 1; manifold.Type = ManifoldType.FaceA; manifold.LocalNormal = cLocal - v2; float factor = 1f / (float) Math.Sqrt(manifold.LocalNormal.X * manifold.LocalNormal.X + manifold.LocalNormal.Y * manifold.LocalNormal.Y); manifold.LocalNormal.X = manifold.LocalNormal.X * factor; manifold.LocalNormal.Y = manifold.LocalNormal.Y * factor; manifold.LocalPoint = v2; ManifoldPoint p0c = manifold.Points[0]; p0c.LocalPoint = circleB.Position; p0c.Id.Key = 0; manifold.Points[0] = p0c; } else { Vector2 faceCenter = 0.5f * (v1 + v2); Vector2 value1 = cLocal - faceCenter; Vector2 value2 = polygonA.Normals[vertIndex1]; float separation2 = value1.X * value2.X + value1.Y * value2.Y; if (separation2 > radius) { return; } manifold.PointCount = 1; manifold.Type = ManifoldType.FaceA; manifold.LocalNormal = polygonA.Normals[vertIndex1]; manifold.LocalPoint = faceCenter; ManifoldPoint p0d = manifold.Points[0]; p0d.LocalPoint = circleB.Position; p0d.Id.Key = 0; manifold.Points[0] = p0d; } } /// /// Compute the collision manifold between two polygons. /// /// The manifold. /// The poly A. /// The transform A. /// The poly B. /// The transform B. public static void CollidePolygons(ref Manifold manifold, PolygonShape polyA, ref Transform transformA, PolygonShape polyB, ref Transform transformB) { manifold.PointCount = 0; float totalRadius = polyA.Radius + polyB.Radius; int edgeA = 0; float separationA = FindMaxSeparation(out edgeA, polyA, ref transformA, polyB, ref transformB); if (separationA > totalRadius) return; int edgeB = 0; float separationB = FindMaxSeparation(out edgeB, polyB, ref transformB, polyA, ref transformA); if (separationB > totalRadius) return; PolygonShape poly1; // reference polygon PolygonShape poly2; // incident polygon Transform xf1, xf2; int edge1; // reference edge bool flip; const float k_relativeTol = 0.98f; const float k_absoluteTol = 0.001f; if (separationB > k_relativeTol * separationA + k_absoluteTol) { poly1 = polyB; poly2 = polyA; xf1 = transformB; xf2 = transformA; edge1 = edgeB; manifold.Type = ManifoldType.FaceB; flip = true; } else { poly1 = polyA; poly2 = polyB; xf1 = transformA; xf2 = transformB; edge1 = edgeA; manifold.Type = ManifoldType.FaceA; flip = false; } FixedArray2 incidentEdge; FindIncidentEdge(out incidentEdge, poly1, ref xf1, edge1, poly2, ref xf2); int count1 = poly1.Vertices.Count; int iv1 = edge1; int iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0; Vector2 v11 = poly1.Vertices[iv1]; Vector2 v12 = poly1.Vertices[iv2]; float localTangentX = v12.X - v11.X; float localTangentY = v12.Y - v11.Y; float factor = 1f / (float)Math.Sqrt(localTangentX * localTangentX + localTangentY * localTangentY); localTangentX = localTangentX * factor; localTangentY = localTangentY * factor; Vector2 localNormal = new Vector2(localTangentY, -localTangentX); Vector2 planePoint = 0.5f * (v11 + v12); Vector2 tangent = new Vector2(xf1.R.Col1.X * localTangentX + xf1.R.Col2.X * localTangentY, xf1.R.Col1.Y * localTangentX + xf1.R.Col2.Y * localTangentY); float normalx = tangent.Y; float normaly = -tangent.X; v11 = new Vector2(xf1.Position.X + xf1.R.Col1.X * v11.X + xf1.R.Col2.X * v11.Y, xf1.Position.Y + xf1.R.Col1.Y * v11.X + xf1.R.Col2.Y * v11.Y); v12 = new Vector2(xf1.Position.X + xf1.R.Col1.X * v12.X + xf1.R.Col2.X * v12.Y, xf1.Position.Y + xf1.R.Col1.Y * v12.X + xf1.R.Col2.Y * v12.Y); // Face offset. float frontOffset = normalx * v11.X + normaly * v11.Y; // Side offsets, extended by polytope skin thickness. float sideOffset1 = -(tangent.X * v11.X + tangent.Y * v11.Y) + totalRadius; float sideOffset2 = tangent.X * v12.X + tangent.Y * v12.Y + totalRadius; // Clip incident edge against extruded edge1 side edges. FixedArray2 clipPoints1; FixedArray2 clipPoints2; // Clip to box side 1 int np = ClipSegmentToLine(out clipPoints1, ref incidentEdge, -tangent, sideOffset1, iv1); if (np < 2) return; // Clip to negative box side 1 np = ClipSegmentToLine(out clipPoints2, ref clipPoints1, tangent, sideOffset2, iv2); if (np < 2) { return; } // Now clipPoints2 contains the clipped points. manifold.LocalNormal = localNormal; manifold.LocalPoint = planePoint; int pointCount = 0; for (int i = 0; i < Settings.MaxManifoldPoints; ++i) { Vector2 value = clipPoints2[i].V; float separation = normalx * value.X + normaly * value.Y - frontOffset; if (separation <= totalRadius) { ManifoldPoint cp = manifold.Points[pointCount]; Vector2 tmp = clipPoints2[i].V; float tmp1X = tmp.X - xf2.Position.X; float tmp1Y = tmp.Y - xf2.Position.Y; cp.LocalPoint.X = tmp1X * xf2.R.Col1.X + tmp1Y * xf2.R.Col1.Y; cp.LocalPoint.Y = tmp1X * xf2.R.Col2.X + tmp1Y * xf2.R.Col2.Y; cp.Id = clipPoints2[i].ID; if (flip) { // Swap features ContactFeature cf = cp.Id.Features; cp.Id.Features.IndexA = cf.IndexB; cp.Id.Features.IndexB = cf.IndexA; cp.Id.Features.TypeA = cf.TypeB; cp.Id.Features.TypeB = cf.TypeA; } manifold.Points[pointCount] = cp; ++pointCount; } } manifold.PointCount = pointCount; } /// /// Compute contact points for edge versus circle. /// This accounts for edge connectivity. /// /// The manifold. /// The edge A. /// The transform A. /// The circle B. /// The transform B. public static void CollideEdgeAndCircle(ref Manifold manifold, EdgeShape edgeA, ref Transform transformA, CircleShape circleB, ref Transform transformB) { manifold.PointCount = 0; // Compute circle in frame of edge Vector2 Q = MathUtils.MultiplyT(ref transformA, MathUtils.Multiply(ref transformB, ref circleB._position)); Vector2 A = edgeA.Vertex1, B = edgeA.Vertex2; Vector2 e = B - A; // Barycentric coordinates float u = Vector2.Dot(e, B - Q); float v = Vector2.Dot(e, Q - A); float radius = edgeA.Radius + circleB.Radius; ContactFeature cf; cf.IndexB = 0; cf.TypeB = (byte)ContactFeatureType.Vertex; Vector2 P, d; // Region A if (v <= 0.0f) { P = A; d = Q - P; float dd; Vector2.Dot(ref d, ref d, out dd); if (dd > radius * radius) { return; } // Is there an edge connected to A? if (edgeA.HasVertex0) { Vector2 A1 = edgeA.Vertex0; Vector2 B1 = A; Vector2 e1 = B1 - A1; float u1 = Vector2.Dot(e1, B1 - Q); // Is the circle in Region AB of the previous edge? if (u1 > 0.0f) { return; } } cf.IndexA = 0; cf.TypeA = (byte)ContactFeatureType.Vertex; manifold.PointCount = 1; manifold.Type = ManifoldType.Circles; manifold.LocalNormal = Vector2.Zero; manifold.LocalPoint = P; ManifoldPoint mp = new ManifoldPoint(); mp.Id.Key = 0; mp.Id.Features = cf; mp.LocalPoint = circleB.Position; manifold.Points[0] = mp; return; } // Region B if (u <= 0.0f) { P = B; d = Q - P; float dd; Vector2.Dot(ref d, ref d, out dd); if (dd > radius * radius) { return; } // Is there an edge connected to B? if (edgeA.HasVertex3) { Vector2 B2 = edgeA.Vertex3; Vector2 A2 = B; Vector2 e2 = B2 - A2; float v2 = Vector2.Dot(e2, Q - A2); // Is the circle in Region AB of the next edge? if (v2 > 0.0f) { return; } } cf.IndexA = 1; cf.TypeA = (byte)ContactFeatureType.Vertex; manifold.PointCount = 1; manifold.Type = ManifoldType.Circles; manifold.LocalNormal = Vector2.Zero; manifold.LocalPoint = P; ManifoldPoint mp = new ManifoldPoint(); mp.Id.Key = 0; mp.Id.Features = cf; mp.LocalPoint = circleB.Position; manifold.Points[0] = mp; return; } // Region AB float den; Vector2.Dot(ref e, ref e, out den); Debug.Assert(den > 0.0f); P = (1.0f / den) * (u * A + v * B); d = Q - P; float dd2; Vector2.Dot(ref d, ref d, out dd2); if (dd2 > radius * radius) { return; } Vector2 n = new Vector2(-e.Y, e.X); if (Vector2.Dot(n, Q - A) < 0.0f) { n = new Vector2(-n.X, -n.Y); } n.Normalize(); cf.IndexA = 0; cf.TypeA = (byte)ContactFeatureType.Face; manifold.PointCount = 1; manifold.Type = ManifoldType.FaceA; manifold.LocalNormal = n; manifold.LocalPoint = A; ManifoldPoint mp2 = new ManifoldPoint(); mp2.Id.Key = 0; mp2.Id.Features = cf; mp2.LocalPoint = circleB.Position; manifold.Points[0] = mp2; } /// /// Collides and edge and a polygon, taking into account edge adjacency. /// /// The manifold. /// The edge A. /// The xf A. /// The polygon B. /// The xf B. public static void CollideEdgeAndPolygon(ref Manifold manifold, EdgeShape edgeA, ref Transform xfA, PolygonShape polygonB, ref Transform xfB) { MathUtils.MultiplyT(ref xfA, ref xfB, out _xf); // Edge geometry _edgeA.V0 = edgeA.Vertex0; _edgeA.V1 = edgeA.Vertex1; _edgeA.V2 = edgeA.Vertex2; _edgeA.V3 = edgeA.Vertex3; Vector2 e = _edgeA.V2 - _edgeA.V1; // Normal points outwards in CCW order. _edgeA.Normal = new Vector2(e.Y, -e.X); _edgeA.Normal.Normalize(); _edgeA.HasVertex0 = edgeA.HasVertex0; _edgeA.HasVertex3 = edgeA.HasVertex3; // Proxy for edge _proxyA.Vertices[0] = _edgeA.V1; _proxyA.Vertices[1] = _edgeA.V2; _proxyA.Normals[0] = _edgeA.Normal; _proxyA.Normals[1] = -_edgeA.Normal; _proxyA.Centroid = 0.5f * (_edgeA.V1 + _edgeA.V2); _proxyA.Count = 2; // Proxy for polygon _proxyB.Count = polygonB.Vertices.Count; _proxyB.Centroid = MathUtils.Multiply(ref _xf, ref polygonB.MassData.Centroid); for (int i = 0; i < polygonB.Vertices.Count; ++i) { _proxyB.Vertices[i] = MathUtils.Multiply(ref _xf, polygonB.Vertices[i]); _proxyB.Normals[i] = MathUtils.Multiply(ref _xf.R, polygonB.Normals[i]); } _radius = 2.0f * Settings.PolygonRadius; _limit11 = Vector2.Zero; _limit12 = Vector2.Zero; _limit21 = Vector2.Zero; _limit22 = Vector2.Zero; //Collide(ref manifold); inline start manifold.PointCount = 0; //ComputeAdjacency(); inline start Vector2 v0 = _edgeA.V0; Vector2 v1 = _edgeA.V1; Vector2 v2 = _edgeA.V2; Vector2 v3 = _edgeA.V3; // Determine allowable the normal regions based on adjacency. // Note: it may be possible that no normal is admissable. Vector2 centerB = _proxyB.Centroid; if (_edgeA.HasVertex0) { Vector2 e0 = v1 - v0; Vector2 e1 = v2 - v1; Vector2 n0 = new Vector2(e0.Y, -e0.X); Vector2 n1 = new Vector2(e1.Y, -e1.X); n0.Normalize(); n1.Normalize(); bool convex = MathUtils.Cross(n0, n1) >= 0.0f; bool front0 = Vector2.Dot(n0, centerB - v0) >= 0.0f; bool front1 = Vector2.Dot(n1, centerB - v1) >= 0.0f; if (convex) { if (front0 || front1) { _limit11 = n1; _limit12 = n0; } else { _limit11 = -n1; _limit12 = -n0; } } else { if (front0 && front1) { _limit11 = n0; _limit12 = n1; } else { _limit11 = -n0; _limit12 = -n1; } } } else { _limit11 = Vector2.Zero; _limit12 = Vector2.Zero; } if (_edgeA.HasVertex3) { Vector2 e1 = v2 - v1; Vector2 e2 = v3 - v2; Vector2 n1 = new Vector2(e1.Y, -e1.X); Vector2 n2 = new Vector2(e2.Y, -e2.X); n1.Normalize(); n2.Normalize(); bool convex = MathUtils.Cross(n1, n2) >= 0.0f; bool front1 = Vector2.Dot(n1, centerB - v1) >= 0.0f; bool front2 = Vector2.Dot(n2, centerB - v2) >= 0.0f; if (convex) { if (front1 || front2) { _limit21 = n2; _limit22 = n1; } else { _limit21 = -n2; _limit22 = -n1; } } else { if (front1 && front2) { _limit21 = n1; _limit22 = n2; } else { _limit21 = -n1; _limit22 = -n2; } } } else { _limit21 = Vector2.Zero; _limit22 = Vector2.Zero; } //ComputeAdjacency(); inline end //EPAxis edgeAxis = ComputeEdgeSeparation(); inline start EPAxis edgeAxis = ComputeEdgeSeparation(); // If no valid normal can be found than this edge should not collide. // This can happen on the middle edge of a 3-edge zig-zag chain. if (edgeAxis.Type == EPAxisType.Unknown) { return; } if (edgeAxis.Separation > _radius) { return; } EPAxis polygonAxis = ComputePolygonSeparation(); if (polygonAxis.Type != EPAxisType.Unknown && polygonAxis.Separation > _radius) { return; } // Use hysteresis for jitter reduction. const float k_relativeTol = 0.98f; const float k_absoluteTol = 0.001f; EPAxis primaryAxis; if (polygonAxis.Type == EPAxisType.Unknown) { primaryAxis = edgeAxis; } else if (polygonAxis.Separation > k_relativeTol * edgeAxis.Separation + k_absoluteTol) { primaryAxis = polygonAxis; } else { primaryAxis = edgeAxis; } EPProxy proxy1; EPProxy proxy2; FixedArray2 incidentEdge = new FixedArray2(); if (primaryAxis.Type == EPAxisType.EdgeA) { proxy1 = _proxyA; proxy2 = _proxyB; manifold.Type = ManifoldType.FaceA; } else { proxy1 = _proxyB; proxy2 = _proxyA; manifold.Type = ManifoldType.FaceB; } int edge1 = primaryAxis.Index; FindIncidentEdge(ref incidentEdge, proxy1, primaryAxis.Index, proxy2); int count1 = proxy1.Count; int iv1 = edge1; int iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0; Vector2 v11 = proxy1.Vertices[iv1]; Vector2 v12 = proxy1.Vertices[iv2]; Vector2 tangent = v12 - v11; tangent.Normalize(); Vector2 normal = MathUtils.Cross(tangent, 1.0f); Vector2 planePoint = 0.5f * (v11 + v12); // Face offset. float frontOffset = Vector2.Dot(normal, v11); // Side offsets, extended by polytope skin thickness. float sideOffset1 = -Vector2.Dot(tangent, v11) + _radius; float sideOffset2 = Vector2.Dot(tangent, v12) + _radius; // Clip incident edge against extruded edge1 side edges. FixedArray2 clipPoints1; FixedArray2 clipPoints2; int np; // Clip to box side 1 np = ClipSegmentToLine(out clipPoints1, ref incidentEdge, -tangent, sideOffset1, iv1); if (np < Settings.MaxManifoldPoints) { return; } // Clip to negative box side 1 np = ClipSegmentToLine(out clipPoints2, ref clipPoints1, tangent, sideOffset2, iv2); if (np < Settings.MaxManifoldPoints) { return; } // Now clipPoints2 contains the clipped points. if (primaryAxis.Type == EPAxisType.EdgeA) { manifold.LocalNormal = normal; manifold.LocalPoint = planePoint; } else { manifold.LocalNormal = MathUtils.MultiplyT(ref _xf.R, ref normal); manifold.LocalPoint = MathUtils.MultiplyT(ref _xf, ref planePoint); } int pointCount = 0; for (int i1 = 0; i1 < Settings.MaxManifoldPoints; ++i1) { float separation = Vector2.Dot(normal, clipPoints2[i1].V) - frontOffset; if (separation <= _radius) { ManifoldPoint cp = manifold.Points[pointCount]; if (primaryAxis.Type == EPAxisType.EdgeA) { cp.LocalPoint = MathUtils.MultiplyT(ref _xf, clipPoints2[i1].V); cp.Id = clipPoints2[i1].ID; } else { cp.LocalPoint = clipPoints2[i1].V; cp.Id.Features.TypeA = clipPoints2[i1].ID.Features.TypeB; cp.Id.Features.TypeB = clipPoints2[i1].ID.Features.TypeA; cp.Id.Features.IndexA = clipPoints2[i1].ID.Features.IndexB; cp.Id.Features.IndexB = clipPoints2[i1].ID.Features.IndexA; } manifold.Points[pointCount] = cp; ++pointCount; } } manifold.PointCount = pointCount; //Collide(ref manifold); inline end } private static EPAxis ComputeEdgeSeparation() { // EdgeA separation EPAxis bestAxis; bestAxis.Type = EPAxisType.Unknown; bestAxis.Index = -1; bestAxis.Separation = -Settings.MaxFloat; _tmpNormals[0] = _edgeA.Normal; _tmpNormals[1] = -_edgeA.Normal; for (int i = 0; i < 2; ++i) { Vector2 n = _tmpNormals[i]; // Adjacency bool valid1 = MathUtils.Cross(n, _limit11) >= -Settings.AngularSlop && MathUtils.Cross(_limit12, n) >= -Settings.AngularSlop; bool valid2 = MathUtils.Cross(n, _limit21) >= -Settings.AngularSlop && MathUtils.Cross(_limit22, n) >= -Settings.AngularSlop; if (valid1 == false || valid2 == false) { continue; } EPAxis axis; axis.Type = EPAxisType.EdgeA; axis.Index = i; axis.Separation = Settings.MaxFloat; for (int j = 0; j < _proxyB.Count; ++j) { float s = Vector2.Dot(n, _proxyB.Vertices[j] - _edgeA.V1); if (s < axis.Separation) { axis.Separation = s; } } if (axis.Separation > _radius) { return axis; } if (axis.Separation > bestAxis.Separation) { bestAxis = axis; } } return bestAxis; } private static EPAxis ComputePolygonSeparation() { EPAxis axis; axis.Type = EPAxisType.Unknown; axis.Index = -1; axis.Separation = -Settings.MaxFloat; for (int i = 0; i < _proxyB.Count; ++i) { Vector2 n = -_proxyB.Normals[i]; // Adjacency bool valid1 = MathUtils.Cross(n, _limit11) >= -Settings.AngularSlop && MathUtils.Cross(_limit12, n) >= -Settings.AngularSlop; bool valid2 = MathUtils.Cross(n, _limit21) >= -Settings.AngularSlop && MathUtils.Cross(_limit22, n) >= -Settings.AngularSlop; if (valid1 == false && valid2 == false) { continue; } float s1 = Vector2.Dot(n, _proxyB.Vertices[i] - _edgeA.V1); float s2 = Vector2.Dot(n, _proxyB.Vertices[i] - _edgeA.V2); float s = Math.Min(s1, s2); if (s > _radius) { axis.Type = EPAxisType.EdgeB; axis.Index = i; axis.Separation = s; } if (s > axis.Separation) { axis.Type = EPAxisType.EdgeB; axis.Index = i; axis.Separation = s; } } return axis; } private static void FindIncidentEdge(ref FixedArray2 c, EPProxy proxy1, int edge1, EPProxy proxy2) { int count2 = proxy2.Count; Debug.Assert(0 <= edge1 && edge1 < proxy1.Count); // Get the normal of the reference edge in proxy2's frame. Vector2 normal1 = proxy1.Normals[edge1]; // Find the incident edge on proxy2. int index = 0; float minDot = float.MaxValue; for (int i = 0; i < count2; ++i) { float dot = Vector2.Dot(normal1, proxy2.Normals[i]); if (dot < minDot) { minDot = dot; index = i; } } // Build the clip vertices for the incident edge. int i1 = index; int i2 = i1 + 1 < count2 ? i1 + 1 : 0; ClipVertex cTemp = new ClipVertex(); cTemp.V = proxy2.Vertices[i1]; cTemp.ID.Features.IndexA = (byte)edge1; cTemp.ID.Features.IndexB = (byte)i1; cTemp.ID.Features.TypeA = (byte)ContactFeatureType.Face; cTemp.ID.Features.TypeB = (byte)ContactFeatureType.Vertex; c[0] = cTemp; cTemp.V = proxy2.Vertices[i2]; cTemp.ID.Features.IndexA = (byte)edge1; cTemp.ID.Features.IndexB = (byte)i2; cTemp.ID.Features.TypeA = (byte)ContactFeatureType.Face; cTemp.ID.Features.TypeB = (byte)ContactFeatureType.Vertex; c[1] = cTemp; } /// /// Clipping for contact manifolds. /// /// The v out. /// The v in. /// The normal. /// The offset. /// The vertex index A. /// private static int ClipSegmentToLine(out FixedArray2 vOut, ref FixedArray2 vIn, Vector2 normal, float offset, int vertexIndexA) { vOut = new FixedArray2(); ClipVertex v0 = vIn[0]; ClipVertex v1 = vIn[1]; // Start with no output points int numOut = 0; // Calculate the distance of end points to the line float distance0 = normal.X * v0.V.X + normal.Y * v0.V.Y - offset; float distance1 = normal.X * v1.V.X + normal.Y * v1.V.Y - offset; // If the points are behind the plane if (distance0 <= 0.0f) vOut[numOut++] = v0; if (distance1 <= 0.0f) vOut[numOut++] = v1; // If the points are on different sides of the plane if (distance0 * distance1 < 0.0f) { // Find intersection point of edge and plane float interp = distance0 / (distance0 - distance1); ClipVertex cv = vOut[numOut]; cv.V.X = v0.V.X + interp * (v1.V.X - v0.V.X); cv.V.Y = v0.V.Y + interp * (v1.V.Y - v0.V.Y); // VertexA is hitting edgeB. cv.ID.Features.IndexA = (byte)vertexIndexA; cv.ID.Features.IndexB = v0.ID.Features.IndexB; cv.ID.Features.TypeA = (byte)ContactFeatureType.Vertex; cv.ID.Features.TypeB = (byte)ContactFeatureType.Face; vOut[numOut] = cv; ++numOut; } return numOut; } /// /// Find the separation between poly1 and poly2 for a give edge normal on poly1. /// /// The poly1. /// The XF1. /// The edge1. /// The poly2. /// The XF2. /// private static float EdgeSeparation(PolygonShape poly1, ref Transform xf1, int edge1, PolygonShape poly2, ref Transform xf2) { int count2 = poly2.Vertices.Count; Debug.Assert(0 <= edge1 && edge1 < poly1.Vertices.Count); // Convert normal from poly1's frame into poly2's frame. Vector2 p1n = poly1.Normals[edge1]; float normalWorldx = xf1.R.Col1.X * p1n.X + xf1.R.Col2.X * p1n.Y; float normalWorldy = xf1.R.Col1.Y * p1n.X + xf1.R.Col2.Y * p1n.Y; Vector2 normal = new Vector2(normalWorldx * xf2.R.Col1.X + normalWorldy * xf2.R.Col1.Y, normalWorldx * xf2.R.Col2.X + normalWorldy * xf2.R.Col2.Y); // Find support vertex on poly2 for -normal. int index = 0; float minDot = Settings.MaxFloat; for (int i = 0; i < count2; ++i) { float dot = Vector2.Dot(poly2.Vertices[i], normal); if (dot < minDot) { minDot = dot; index = i; } } Vector2 p1ve = poly1.Vertices[edge1]; Vector2 p2vi = poly2.Vertices[index]; return ((xf2.Position.X + xf2.R.Col1.X * p2vi.X + xf2.R.Col2.X * p2vi.Y) - (xf1.Position.X + xf1.R.Col1.X * p1ve.X + xf1.R.Col2.X * p1ve.Y)) * normalWorldx + ((xf2.Position.Y + xf2.R.Col1.Y * p2vi.X + xf2.R.Col2.Y * p2vi.Y) - (xf1.Position.Y + xf1.R.Col1.Y * p1ve.X + xf1.R.Col2.Y * p1ve.Y)) * normalWorldy; } /// /// Find the max separation between poly1 and poly2 using edge normals from poly1. /// /// Index of the edge. /// The poly1. /// The XF1. /// The poly2. /// The XF2. /// private static float FindMaxSeparation(out int edgeIndex, PolygonShape poly1, ref Transform xf1, PolygonShape poly2, ref Transform xf2) { int count1 = poly1.Vertices.Count; // Vector pointing from the centroid of poly1 to the centroid of poly2. float dx = (xf2.Position.X + xf2.R.Col1.X * poly2.MassData.Centroid.X + xf2.R.Col2.X * poly2.MassData.Centroid.Y) - (xf1.Position.X + xf1.R.Col1.X * poly1.MassData.Centroid.X + xf1.R.Col2.X * poly1.MassData.Centroid.Y); float dy = (xf2.Position.Y + xf2.R.Col1.Y * poly2.MassData.Centroid.X + xf2.R.Col2.Y * poly2.MassData.Centroid.Y) - (xf1.Position.Y + xf1.R.Col1.Y * poly1.MassData.Centroid.X + xf1.R.Col2.Y * poly1.MassData.Centroid.Y); Vector2 dLocal1 = new Vector2(dx * xf1.R.Col1.X + dy * xf1.R.Col1.Y, dx * xf1.R.Col2.X + dy * xf1.R.Col2.Y); // Find edge normal on poly1 that has the largest projection onto d. int edge = 0; float maxDot = -Settings.MaxFloat; for (int i = 0; i < count1; ++i) { float dot = Vector2.Dot(poly1.Normals[i], dLocal1); if (dot > maxDot) { maxDot = dot; edge = i; } } // Get the separation for the edge normal. float s = EdgeSeparation(poly1, ref xf1, edge, poly2, ref xf2); // Check the separation for the previous edge normal. int prevEdge = edge - 1 >= 0 ? edge - 1 : count1 - 1; float sPrev = EdgeSeparation(poly1, ref xf1, prevEdge, poly2, ref xf2); // Check the separation for the next edge normal. int nextEdge = edge + 1 < count1 ? edge + 1 : 0; float sNext = EdgeSeparation(poly1, ref xf1, nextEdge, poly2, ref xf2); // Find the best edge and the search direction. int bestEdge; float bestSeparation; int increment; if (sPrev > s && sPrev > sNext) { increment = -1; bestEdge = prevEdge; bestSeparation = sPrev; } else if (sNext > s) { increment = 1; bestEdge = nextEdge; bestSeparation = sNext; } else { edgeIndex = edge; return s; } // Perform a local search for the best edge normal. for (; ; ) { if (increment == -1) edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1; else edge = bestEdge + 1 < count1 ? bestEdge + 1 : 0; s = EdgeSeparation(poly1, ref xf1, edge, poly2, ref xf2); if (s > bestSeparation) { bestEdge = edge; bestSeparation = s; } else { break; } } edgeIndex = bestEdge; return bestSeparation; } private static void FindIncidentEdge(out FixedArray2 c, PolygonShape poly1, ref Transform xf1, int edge1, PolygonShape poly2, ref Transform xf2) { c = new FixedArray2(); int count2 = poly2.Vertices.Count; Debug.Assert(0 <= edge1 && edge1 < poly1.Vertices.Count); // Get the normal of the reference edge in poly2's frame. Vector2 v = poly1.Normals[edge1]; float tmpx = xf1.R.Col1.X * v.X + xf1.R.Col2.X * v.Y; float tmpy = xf1.R.Col1.Y * v.X + xf1.R.Col2.Y * v.Y; Vector2 normal1 = new Vector2(tmpx * xf2.R.Col1.X + tmpy * xf2.R.Col1.Y, tmpx * xf2.R.Col2.X + tmpy * xf2.R.Col2.Y); // Find the incident edge on poly2. int index = 0; float minDot = Settings.MaxFloat; for (int i = 0; i < count2; ++i) { float dot = Vector2.Dot(normal1, poly2.Normals[i]); if (dot < minDot) { minDot = dot; index = i; } } // Build the clip vertices for the incident edge. int i1 = index; int i2 = i1 + 1 < count2 ? i1 + 1 : 0; ClipVertex cv0 = c[0]; Vector2 v1 = poly2.Vertices[i1]; cv0.V.X = xf2.Position.X + xf2.R.Col1.X * v1.X + xf2.R.Col2.X * v1.Y; cv0.V.Y = xf2.Position.Y + xf2.R.Col1.Y * v1.X + xf2.R.Col2.Y * v1.Y; cv0.ID.Features.IndexA = (byte)edge1; cv0.ID.Features.IndexB = (byte)i1; cv0.ID.Features.TypeA = (byte)ContactFeatureType.Face; cv0.ID.Features.TypeB = (byte)ContactFeatureType.Vertex; c[0] = cv0; ClipVertex cv1 = c[1]; Vector2 v2 = poly2.Vertices[i2]; cv1.V.X = xf2.Position.X + xf2.R.Col1.X * v2.X + xf2.R.Col2.X * v2.Y; cv1.V.Y = xf2.Position.Y + xf2.R.Col1.Y * v2.X + xf2.R.Col2.Y * v2.Y; cv1.ID.Features.IndexA = (byte)edge1; cv1.ID.Features.IndexB = (byte)i2; cv1.ID.Features.TypeA = (byte)ContactFeatureType.Face; cv1.ID.Features.TypeB = (byte)ContactFeatureType.Vertex; c[1] = cv1; } } }