diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37a1e68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.vs/ae-physics/v14/.suo +/ae-physics.csproj.user diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8ff6341 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/FNA"] + path = lib/FNA + url = ssh://git@srchub.org/fna-workbench.git diff --git a/Collision/Collision.cs b/Collision/Collision.cs new file mode 100644 index 0000000..4e08de7 --- /dev/null +++ b/Collision/Collision.cs @@ -0,0 +1,1945 @@ +/* +* 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; + } + } +} \ No newline at end of file diff --git a/Collision/Distance.cs b/Collision/Distance.cs new file mode 100644 index 0000000..c19723b --- /dev/null +++ b/Collision/Distance.cs @@ -0,0 +1,780 @@ +/* +* 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 FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision +{ + /// + /// A distance proxy is used by the GJK algorithm. + /// It encapsulates any shape. + /// + public class DistanceProxy + { + internal float Radius; + internal Vertices Vertices = new Vertices(); + + /// + /// Initialize the proxy using the given shape. The shape + /// must remain in scope while the proxy is in use. + /// + /// The shape. + /// The index. + public void Set(Shape shape, int index) + { + switch (shape.ShapeType) + { + case ShapeType.Circle: + { + CircleShape circle = (CircleShape)shape; + Vertices.Clear(); + Vertices.Add(circle.Position); + Radius = circle.Radius; + } + break; + + case ShapeType.Polygon: + { + PolygonShape polygon = (PolygonShape)shape; + Vertices.Clear(); + for (int i = 0; i < polygon.Vertices.Count; i++) + { + Vertices.Add(polygon.Vertices[i]); + } + Radius = polygon.Radius; + } + break; + + case ShapeType.Loop: + { + LoopShape loop = (LoopShape)shape; + Debug.Assert(0 <= index && index < loop.Vertices.Count); + Vertices.Clear(); + Vertices.Add(loop.Vertices[index]); + Vertices.Add(index + 1 < loop.Vertices.Count ? loop.Vertices[index + 1] : loop.Vertices[0]); + + Radius = loop.Radius; + } + break; + + case ShapeType.Edge: + { + EdgeShape edge = (EdgeShape)shape; + Vertices.Clear(); + Vertices.Add(edge.Vertex1); + Vertices.Add(edge.Vertex2); + Radius = edge.Radius; + } + break; + + default: + Debug.Assert(false); + break; + } + } + + /// + /// Get the supporting vertex index in the given direction. + /// + /// The direction. + /// + public int GetSupport(Vector2 direction) + { + int bestIndex = 0; + float bestValue = Vector2.Dot(Vertices[0], direction); + for (int i = 1; i < Vertices.Count; ++i) + { + float value = Vector2.Dot(Vertices[i], direction); + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + + return bestIndex; + } + + /// + /// Get the supporting vertex in the given direction. + /// + /// The direction. + /// + public Vector2 GetSupportVertex(Vector2 direction) + { + int bestIndex = 0; + float bestValue = Vector2.Dot(Vertices[0], direction); + for (int i = 1; i < Vertices.Count; ++i) + { + float value = Vector2.Dot(Vertices[i], direction); + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + + return Vertices[bestIndex]; + } + } + + /// + /// Used to warm start ComputeDistance. + /// Set count to zero on first call. + /// + public struct SimplexCache + { + /// + /// Length or area + /// + public ushort Count; + + /// + /// Vertices on shape A + /// + public FixedArray3 IndexA; + + /// + /// Vertices on shape B + /// + public FixedArray3 IndexB; + + public float Metric; + } + + /// + /// Input for ComputeDistance. + /// You have to option to use the shape radii + /// in the computation. + /// + public class DistanceInput + { + public DistanceProxy ProxyA = new DistanceProxy(); + public DistanceProxy ProxyB = new DistanceProxy(); + public Transform TransformA; + public Transform TransformB; + public bool UseRadii; + } + + /// + /// Output for ComputeDistance. + /// + public struct DistanceOutput + { + public float Distance; + + /// + /// Number of GJK iterations used + /// + public int Iterations; + + /// + /// Closest point on shapeA + /// + public Vector2 PointA; + + /// + /// Closest point on shapeB + /// + public Vector2 PointB; + } + + internal struct SimplexVertex + { + /// + /// Barycentric coordinate for closest point + /// + public float A; + + /// + /// wA index + /// + public int IndexA; + + /// + /// wB index + /// + public int IndexB; + + /// + /// wB - wA + /// + public Vector2 W; + + /// + /// Support point in proxyA + /// + public Vector2 WA; + + /// + /// Support point in proxyB + /// + public Vector2 WB; + } + + internal struct Simplex + { + internal int Count; + internal FixedArray3 V; + + internal void ReadCache(ref SimplexCache cache, + DistanceProxy proxyA, ref Transform transformA, + DistanceProxy proxyB, ref Transform transformB) + { + Debug.Assert(cache.Count <= 3); + + // Copy data from cache. + Count = cache.Count; + for (int i = 0; i < Count; ++i) + { + SimplexVertex v = V[i]; + v.IndexA = cache.IndexA[i]; + v.IndexB = cache.IndexB[i]; + Vector2 wALocal = proxyA.Vertices[v.IndexA]; + Vector2 wBLocal = proxyB.Vertices[v.IndexB]; + v.WA = MathUtils.Multiply(ref transformA, wALocal); + v.WB = MathUtils.Multiply(ref transformB, wBLocal); + v.W = v.WB - v.WA; + v.A = 0.0f; + V[i] = v; + } + + // Compute the new simplex metric, if it is substantially different than + // old metric then flush the simplex. + if (Count > 1) + { + float metric1 = cache.Metric; + float metric2 = GetMetric(); + if (metric2 < 0.5f * metric1 || 2.0f * metric1 < metric2 || metric2 < Settings.Epsilon) + { + // Reset the simplex. + Count = 0; + } + } + + // If the cache is empty or invalid ... + if (Count == 0) + { + SimplexVertex v = V[0]; + v.IndexA = 0; + v.IndexB = 0; + Vector2 wALocal = proxyA.Vertices[0]; + Vector2 wBLocal = proxyB.Vertices[0]; + v.WA = MathUtils.Multiply(ref transformA, wALocal); + v.WB = MathUtils.Multiply(ref transformB, wBLocal); + v.W = v.WB - v.WA; + V[0] = v; + Count = 1; + } + } + + internal void WriteCache(ref SimplexCache cache) + { + cache.Metric = GetMetric(); + cache.Count = (UInt16)Count; + for (int i = 0; i < Count; ++i) + { + cache.IndexA[i] = (byte)(V[i].IndexA); + cache.IndexB[i] = (byte)(V[i].IndexB); + } + } + + internal Vector2 GetSearchDirection() + { + switch (Count) + { + case 1: + return -V[0].W; + + case 2: + { + Vector2 e12 = V[1].W - V[0].W; + float sgn = MathUtils.Cross(e12, -V[0].W); + if (sgn > 0.0f) + { + // Origin is left of e12. + return new Vector2(-e12.Y, e12.X); + } + else + { + // Origin is right of e12. + return new Vector2(e12.Y, -e12.X); + } + } + + default: + Debug.Assert(false); + return Vector2.Zero; + } + } + + internal Vector2 GetClosestPoint() + { + switch (Count) + { + case 0: + Debug.Assert(false); + return Vector2.Zero; + + case 1: + return V[0].W; + + case 2: + return V[0].A * V[0].W + V[1].A * V[1].W; + + case 3: + return Vector2.Zero; + + default: + Debug.Assert(false); + return Vector2.Zero; + } + } + + internal void GetWitnessPoints(out Vector2 pA, out Vector2 pB) + { + switch (Count) + { + case 0: + pA = Vector2.Zero; + pB = Vector2.Zero; + Debug.Assert(false); + break; + + case 1: + pA = V[0].WA; + pB = V[0].WB; + break; + + case 2: + pA = V[0].A * V[0].WA + V[1].A * V[1].WA; + pB = V[0].A * V[0].WB + V[1].A * V[1].WB; + break; + + case 3: + pA = V[0].A * V[0].WA + V[1].A * V[1].WA + V[2].A * V[2].WA; + pB = pA; + break; + + default: + throw new Exception(); + } + } + + internal float GetMetric() + { + switch (Count) + { + case 0: + Debug.Assert(false); + return 0.0f; + + case 1: + return 0.0f; + + case 2: + return (V[0].W - V[1].W).Length(); + + case 3: + return MathUtils.Cross(V[1].W - V[0].W, V[2].W - V[0].W); + + default: + Debug.Assert(false); + return 0.0f; + } + } + + // Solve a line segment using barycentric coordinates. + // + // p = a1 * w1 + a2 * w2 + // a1 + a2 = 1 + // + // The vector from the origin to the closest point on the line is + // perpendicular to the line. + // e12 = w2 - w1 + // dot(p, e) = 0 + // a1 * dot(w1, e) + a2 * dot(w2, e) = 0 + // + // 2-by-2 linear system + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // + // Define + // d12_1 = dot(w2, e12) + // d12_2 = -dot(w1, e12) + // d12 = d12_1 + d12_2 + // + // Solution + // a1 = d12_1 / d12 + // a2 = d12_2 / d12 + + internal void Solve2() + { + Vector2 w1 = V[0].W; + Vector2 w2 = V[1].W; + Vector2 e12 = w2 - w1; + + // w1 region + float d12_2 = -Vector2.Dot(w1, e12); + if (d12_2 <= 0.0f) + { + // a2 <= 0, so we clamp it to 0 + SimplexVertex v0 = V[0]; + v0.A = 1.0f; + V[0] = v0; + Count = 1; + return; + } + + // w2 region + float d12_1 = Vector2.Dot(w2, e12); + if (d12_1 <= 0.0f) + { + // a1 <= 0, so we clamp it to 0 + SimplexVertex v1 = V[1]; + v1.A = 1.0f; + V[1] = v1; + Count = 1; + V[0] = V[1]; + return; + } + + // Must be in e12 region. + float inv_d12 = 1.0f / (d12_1 + d12_2); + SimplexVertex v0_2 = V[0]; + SimplexVertex v1_2 = V[1]; + v0_2.A = d12_1 * inv_d12; + v1_2.A = d12_2 * inv_d12; + V[0] = v0_2; + V[1] = v1_2; + Count = 2; + } + + // Possible regions: + // - points[2] + // - edge points[0]-points[2] + // - edge points[1]-points[2] + // - inside the triangle + internal void Solve3() + { + Vector2 w1 = V[0].W; + Vector2 w2 = V[1].W; + Vector2 w3 = V[2].W; + + // Edge12 + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // a3 = 0 + Vector2 e12 = w2 - w1; + float w1e12 = Vector2.Dot(w1, e12); + float w2e12 = Vector2.Dot(w2, e12); + float d12_1 = w2e12; + float d12_2 = -w1e12; + + // Edge13 + // [1 1 ][a1] = [1] + // [w1.e13 w3.e13][a3] = [0] + // a2 = 0 + Vector2 e13 = w3 - w1; + float w1e13 = Vector2.Dot(w1, e13); + float w3e13 = Vector2.Dot(w3, e13); + float d13_1 = w3e13; + float d13_2 = -w1e13; + + // Edge23 + // [1 1 ][a2] = [1] + // [w2.e23 w3.e23][a3] = [0] + // a1 = 0 + Vector2 e23 = w3 - w2; + float w2e23 = Vector2.Dot(w2, e23); + float w3e23 = Vector2.Dot(w3, e23); + float d23_1 = w3e23; + float d23_2 = -w2e23; + + // Triangle123 + float n123 = MathUtils.Cross(e12, e13); + + float d123_1 = n123 * MathUtils.Cross(w2, w3); + float d123_2 = n123 * MathUtils.Cross(w3, w1); + float d123_3 = n123 * MathUtils.Cross(w1, w2); + + // w1 region + if (d12_2 <= 0.0f && d13_2 <= 0.0f) + { + SimplexVertex v0_1 = V[0]; + v0_1.A = 1.0f; + V[0] = v0_1; + Count = 1; + return; + } + + // e12 + if (d12_1 > 0.0f && d12_2 > 0.0f && d123_3 <= 0.0f) + { + float inv_d12 = 1.0f / (d12_1 + d12_2); + SimplexVertex v0_2 = V[0]; + SimplexVertex v1_2 = V[1]; + v0_2.A = d12_1 * inv_d12; + v1_2.A = d12_2 * inv_d12; + V[0] = v0_2; + V[1] = v1_2; + Count = 2; + return; + } + + // e13 + if (d13_1 > 0.0f && d13_2 > 0.0f && d123_2 <= 0.0f) + { + float inv_d13 = 1.0f / (d13_1 + d13_2); + SimplexVertex v0_3 = V[0]; + SimplexVertex v2_3 = V[2]; + v0_3.A = d13_1 * inv_d13; + v2_3.A = d13_2 * inv_d13; + V[0] = v0_3; + V[2] = v2_3; + Count = 2; + V[1] = V[2]; + return; + } + + // w2 region + if (d12_1 <= 0.0f && d23_2 <= 0.0f) + { + SimplexVertex v1_4 = V[1]; + v1_4.A = 1.0f; + V[1] = v1_4; + Count = 1; + V[0] = V[1]; + return; + } + + // w3 region + if (d13_1 <= 0.0f && d23_1 <= 0.0f) + { + SimplexVertex v2_5 = V[2]; + v2_5.A = 1.0f; + V[2] = v2_5; + Count = 1; + V[0] = V[2]; + return; + } + + // e23 + if (d23_1 > 0.0f && d23_2 > 0.0f && d123_1 <= 0.0f) + { + float inv_d23 = 1.0f / (d23_1 + d23_2); + SimplexVertex v1_6 = V[1]; + SimplexVertex v2_6 = V[2]; + v1_6.A = d23_1 * inv_d23; + v2_6.A = d23_2 * inv_d23; + V[1] = v1_6; + V[2] = v2_6; + Count = 2; + V[0] = V[2]; + return; + } + + // Must be in triangle123 + float inv_d123 = 1.0f / (d123_1 + d123_2 + d123_3); + SimplexVertex v0_7 = V[0]; + SimplexVertex v1_7 = V[1]; + SimplexVertex v2_7 = V[2]; + v0_7.A = d123_1 * inv_d123; + v1_7.A = d123_2 * inv_d123; + v2_7.A = d123_3 * inv_d123; + V[0] = v0_7; + V[1] = v1_7; + V[2] = v2_7; + Count = 3; + } + } + + public static class Distance + { + public static int GJKCalls, GJKIters, GJKMaxIters; + + public static void ComputeDistance(out DistanceOutput output, + out SimplexCache cache, + DistanceInput input) + { + cache = new SimplexCache(); + ++GJKCalls; + + // Initialize the simplex. + Simplex simplex = new Simplex(); + simplex.ReadCache(ref cache, input.ProxyA, ref input.TransformA, input.ProxyB, ref input.TransformB); + + // Get simplex vertices as an array. + const int k_maxIters = 20; + + // These store the vertices of the last simplex so that we + // can check for duplicates and prevent cycling. + FixedArray3 saveA = new FixedArray3(); + FixedArray3 saveB = new FixedArray3(); + + Vector2 closestPoint = simplex.GetClosestPoint(); + float distanceSqr1 = closestPoint.LengthSquared(); + float distanceSqr2 = distanceSqr1; + + // Main iteration loop. + int iter = 0; + while (iter < k_maxIters) + { + // Copy simplex so we can identify duplicates. + int saveCount = simplex.Count; + for (int i = 0; i < saveCount; ++i) + { + saveA[i] = simplex.V[i].IndexA; + saveB[i] = simplex.V[i].IndexB; + } + + switch (simplex.Count) + { + case 1: + break; + + case 2: + simplex.Solve2(); + break; + + case 3: + simplex.Solve3(); + break; + + default: + Debug.Assert(false); + break; + } + + // If we have 3 points, then the origin is in the corresponding triangle. + if (simplex.Count == 3) + { + break; + } + + // Compute closest point. + Vector2 p = simplex.GetClosestPoint(); + distanceSqr2 = p.LengthSquared(); + + // Ensure progress + if (distanceSqr2 >= distanceSqr1) + { + //break; + } + distanceSqr1 = distanceSqr2; + + // Get search direction. + Vector2 d = simplex.GetSearchDirection(); + + // Ensure the search direction is numerically fit. + if (d.LengthSquared() < Settings.Epsilon * Settings.Epsilon) + { + // The origin is probably contained by a line segment + // or triangle. Thus the shapes are overlapped. + + // We can't return zero here even though there may be overlap. + // In case the simplex is a point, segment, or triangle it is difficult + // to determine if the origin is contained in the CSO or very close to it. + break; + } + + // Compute a tentative new simplex vertex using support points. + SimplexVertex vertex = simplex.V[simplex.Count]; + vertex.IndexA = input.ProxyA.GetSupport(MathUtils.MultiplyT(ref input.TransformA.R, -d)); + vertex.WA = MathUtils.Multiply(ref input.TransformA, input.ProxyA.Vertices[vertex.IndexA]); + + vertex.IndexB = input.ProxyB.GetSupport(MathUtils.MultiplyT(ref input.TransformB.R, d)); + vertex.WB = MathUtils.Multiply(ref input.TransformB, input.ProxyB.Vertices[vertex.IndexB]); + vertex.W = vertex.WB - vertex.WA; + simplex.V[simplex.Count] = vertex; + + // Iteration count is equated to the number of support point calls. + ++iter; + ++GJKIters; + + // Check for duplicate support points. This is the main termination criteria. + bool duplicate = false; + for (int i = 0; i < saveCount; ++i) + { + if (vertex.IndexA == saveA[i] && vertex.IndexB == saveB[i]) + { + duplicate = true; + break; + } + } + + // If we found a duplicate support point we must exit to avoid cycling. + if (duplicate) + { + break; + } + + // New vertex is ok and needed. + ++simplex.Count; + } + + GJKMaxIters = Math.Max(GJKMaxIters, iter); + + // Prepare output. + simplex.GetWitnessPoints(out output.PointA, out output.PointB); + output.Distance = (output.PointA - output.PointB).Length(); + output.Iterations = iter; + + // Cache the simplex. + simplex.WriteCache(ref cache); + + // Apply radii if requested. + if (input.UseRadii) + { + float rA = input.ProxyA.Radius; + float rB = input.ProxyB.Radius; + + if (output.Distance > rA + rB && output.Distance > Settings.Epsilon) + { + // Shapes are still no overlapped. + // Move the witness points to the outer surface. + output.Distance -= rA + rB; + Vector2 normal = output.PointB - output.PointA; + normal.Normalize(); + output.PointA += rA * normal; + output.PointB -= rB * normal; + } + else + { + // Shapes are overlapped when radii are considered. + // Move the witness points to the middle. + Vector2 p = 0.5f * (output.PointA + output.PointB); + output.PointA = p; + output.PointB = p; + output.Distance = 0.0f; + } + } + } + } +} \ No newline at end of file diff --git a/Collision/DynamicTree.cs b/Collision/DynamicTree.cs new file mode 100644 index 0000000..1141ecc --- /dev/null +++ b/Collision/DynamicTree.cs @@ -0,0 +1,654 @@ +/* +* 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.Collections.Generic; +using System.Diagnostics; +using FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision +{ + /// + /// A node in the dynamic tree. The client does not interact with this directly. + /// + internal struct DynamicTreeNode + { + /// + /// This is the fattened AABB. + /// + internal AABB AABB; + + internal int Child1; + internal int Child2; + + internal int LeafCount; + internal int ParentOrNext; + internal T UserData; + + internal bool IsLeaf() + { + return Child1 == DynamicTree.NullNode; + } + } + + /// + /// A dynamic tree arranges data in a binary tree to accelerate + /// queries such as volume queries and ray casts. Leafs are proxies + /// with an AABB. In the tree we expand the proxy AABB by Settings.b2_fatAABBFactor + /// so that the proxy AABB is bigger than the client object. This allows the client + /// object to move by small amounts without triggering a tree update. + /// + /// Nodes are pooled and relocatable, so we use node indices rather than pointers. + /// + public class DynamicTree + { + internal const int NullNode = -1; + private static Stack _stack = new Stack(256); + private int _freeList; + private int _insertionCount; + private int _nodeCapacity; + private int _nodeCount; + private DynamicTreeNode[] _nodes; + + /// + /// This is used incrementally traverse the tree for re-balancing. + /// + private int _path; + + private int _root; + + /// + /// Constructing the tree initializes the node pool. + /// + public DynamicTree() + { + _root = NullNode; + + _nodeCapacity = 16; + _nodes = new DynamicTreeNode[_nodeCapacity]; + + // Build a linked list for the free list. + for (int i = 0; i < _nodeCapacity - 1; ++i) + { + _nodes[i].ParentOrNext = i + 1; + } + _nodes[_nodeCapacity - 1].ParentOrNext = NullNode; + } + + /// + /// Create a proxy in the tree as a leaf node. We return the index + /// of the node instead of a pointer so that we can grow + /// the node pool. + /// /// + /// The aabb. + /// The user data. + /// Index of the created proxy + public int AddProxy(ref AABB aabb, T userData) + { + int proxyId = AllocateNode(); + + // Fatten the aabb. + Vector2 r = new Vector2(Settings.AABBExtension, Settings.AABBExtension); + _nodes[proxyId].AABB.LowerBound = aabb.LowerBound - r; + _nodes[proxyId].AABB.UpperBound = aabb.UpperBound + r; + _nodes[proxyId].UserData = userData; + _nodes[proxyId].LeafCount = 1; + + InsertLeaf(proxyId); + + return proxyId; + } + + /// + /// Destroy a proxy. This asserts if the id is invalid. + /// + /// The proxy id. + public void RemoveProxy(int proxyId) + { + Debug.Assert(0 <= proxyId && proxyId < _nodeCapacity); + Debug.Assert(_nodes[proxyId].IsLeaf()); + + RemoveLeaf(proxyId); + FreeNode(proxyId); + } + + /// + /// Move a proxy with a swepted AABB. If the proxy has moved outside of its fattened AABB, + /// then the proxy is removed from the tree and re-inserted. Otherwise + /// the function returns immediately. + /// + /// The proxy id. + /// The aabb. + /// The displacement. + /// true if the proxy was re-inserted. + public bool MoveProxy(int proxyId, ref AABB aabb, Vector2 displacement) + { + Debug.Assert(0 <= proxyId && proxyId < _nodeCapacity); + + Debug.Assert(_nodes[proxyId].IsLeaf()); + + if (_nodes[proxyId].AABB.Contains(ref aabb)) + { + return false; + } + + RemoveLeaf(proxyId); + + // Extend AABB. + AABB b = aabb; + Vector2 r = new Vector2(Settings.AABBExtension, Settings.AABBExtension); + b.LowerBound = b.LowerBound - r; + b.UpperBound = b.UpperBound + r; + + // Predict AABB displacement. + Vector2 d = Settings.AABBMultiplier * displacement; + + if (d.X < 0.0f) + { + b.LowerBound.X += d.X; + } + else + { + b.UpperBound.X += d.X; + } + + if (d.Y < 0.0f) + { + b.LowerBound.Y += d.Y; + } + else + { + b.UpperBound.Y += d.Y; + } + + _nodes[proxyId].AABB = b; + + InsertLeaf(proxyId); + return true; + } + + /// + /// Perform some iterations to re-balance the tree. + /// + /// The iterations. + public void Rebalance(int iterations) + { + if (_root == NullNode) + { + return; + } + + // Rebalance the tree by removing and re-inserting leaves. + for (int i = 0; i < iterations; ++i) + { + int node = _root; + + int bit = 0; + while (_nodes[node].IsLeaf() == false) + { + // Child selector based on a bit in the path + int selector = (_path >> bit) & 1; + + // Select the child nod + node = (selector == 0) ? _nodes[node].Child1 : _nodes[node].Child2; + + // Keep bit between 0 and 31 because _path has 32 bits + // bit = (bit + 1) % 31 + bit = (bit + 1) & 0x1F; + } + ++_path; + + RemoveLeaf(node); + InsertLeaf(node); + } + } + + /// + /// Get proxy user data. + /// + /// + /// The proxy id. + /// the proxy user data or 0 if the id is invalid. + public T GetUserData(int proxyId) + { + Debug.Assert(0 <= proxyId && proxyId < _nodeCapacity); + return _nodes[proxyId].UserData; + } + + /// + /// Get the fat AABB for a proxy. + /// + /// The proxy id. + /// The fat AABB. + public void GetFatAABB(int proxyId, out AABB fatAABB) + { + Debug.Assert(0 <= proxyId && proxyId < _nodeCapacity); + fatAABB = _nodes[proxyId].AABB; + } + + /// + /// Compute the height of the binary tree in O(N) time. Should not be + /// called often. + /// + /// + public int ComputeHeight() + { + return ComputeHeight(_root); + } + + /// + /// Query an AABB for overlapping proxies. The callback class + /// is called for each proxy that overlaps the supplied AABB. + /// + /// The callback. + /// The aabb. + public void Query(Func callback, ref AABB aabb) + { + _stack.Clear(); + _stack.Push(_root); + + while (_stack.Count > 0) + { + int nodeId = _stack.Pop(); + if (nodeId == NullNode) + { + continue; + } + + DynamicTreeNode node = _nodes[nodeId]; + + if (AABB.TestOverlap(ref node.AABB, ref aabb)) + { + if (node.IsLeaf()) + { + bool proceed = callback(nodeId); + if (proceed == false) + { + return; + } + } + else + { + _stack.Push(node.Child1); + _stack.Push(node.Child2); + } + } + } + } + + /// + /// Ray-cast against the proxies in the tree. This relies on the callback + /// to perform a exact ray-cast in the case were the proxy contains a Shape. + /// The callback also performs the any collision filtering. This has performance + /// roughly equal to k * log(n), where k is the number of collisions and n is the + /// number of proxies in the tree. + /// + /// A callback class that is called for each proxy that is hit by the ray. + /// The ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). + public void RayCast(Func callback, ref RayCastInput input) + { + Vector2 p1 = input.Point1; + Vector2 p2 = input.Point2; + Vector2 r = p2 - p1; + Debug.Assert(r.LengthSquared() > 0.0f); + r.Normalize(); + + // v is perpendicular to the segment. + Vector2 absV = MathUtils.Abs(new Vector2(-r.Y, r.X)); + + // Separating axis for segment (Gino, p80). + // |dot(v, p1 - c)| > dot(|v|, h) + + float maxFraction = input.MaxFraction; + + // Build a bounding box for the segment. + AABB segmentAABB = new AABB(); + { + Vector2 t = p1 + maxFraction * (p2 - p1); + Vector2.Min(ref p1, ref t, out segmentAABB.LowerBound); + Vector2.Max(ref p1, ref t, out segmentAABB.UpperBound); + } + + _stack.Clear(); + _stack.Push(_root); + + while (_stack.Count > 0) + { + int nodeId = _stack.Pop(); + if (nodeId == NullNode) + { + continue; + } + + DynamicTreeNode node = _nodes[nodeId]; + + if (AABB.TestOverlap(ref node.AABB, ref segmentAABB) == false) + { + continue; + } + + // Separating axis for segment (Gino, p80). + // |dot(v, p1 - c)| > dot(|v|, h) + Vector2 c = node.AABB.Center; + Vector2 h = node.AABB.Extents; + float separation = Math.Abs(Vector2.Dot(new Vector2(-r.Y, r.X), p1 - c)) - Vector2.Dot(absV, h); + if (separation > 0.0f) + { + continue; + } + + if (node.IsLeaf()) + { + RayCastInput subInput; + subInput.Point1 = input.Point1; + subInput.Point2 = input.Point2; + subInput.MaxFraction = maxFraction; + + float value = callback(subInput, nodeId); + + if (value == 0.0f) + { + // the client has terminated the raycast. + return; + } + + if (value > 0.0f) + { + // Update segment bounding box. + maxFraction = value; + Vector2 t = p1 + maxFraction * (p2 - p1); + segmentAABB.LowerBound = Vector2.Min(p1, t); + segmentAABB.UpperBound = Vector2.Max(p1, t); + } + } + else + { + _stack.Push(node.Child1); + _stack.Push(node.Child2); + } + } + } + + private int CountLeaves(int nodeId) + { + if (nodeId == NullNode) + { + return 0; + } + + Debug.Assert(0 <= nodeId && nodeId < _nodeCapacity); + DynamicTreeNode node = _nodes[nodeId]; + + if (node.IsLeaf()) + { + Debug.Assert(node.LeafCount == 1); + return 1; + } + + int count1 = CountLeaves(node.Child1); + int count2 = CountLeaves(node.Child2); + int count = count1 + count2; + Debug.Assert(count == node.LeafCount); + return count; + } + + private void Validate() + { + CountLeaves(_root); + } + + private int AllocateNode() + { + // Expand the node pool as needed. + if (_freeList == NullNode) + { + Debug.Assert(_nodeCount == _nodeCapacity); + + // The free list is empty. Rebuild a bigger pool. + DynamicTreeNode[] oldNodes = _nodes; + _nodeCapacity *= 2; + _nodes = new DynamicTreeNode[_nodeCapacity]; + Array.Copy(oldNodes, _nodes, _nodeCount); + + // Build a linked list for the free list. The parent + // pointer becomes the "next" pointer. + for (int i = _nodeCount; i < _nodeCapacity - 1; ++i) + { + _nodes[i].ParentOrNext = i + 1; + } + _nodes[_nodeCapacity - 1].ParentOrNext = NullNode; + _freeList = _nodeCount; + } + + // Peel a node off the free list. + int nodeId = _freeList; + _freeList = _nodes[nodeId].ParentOrNext; + _nodes[nodeId].ParentOrNext = NullNode; + _nodes[nodeId].Child1 = NullNode; + _nodes[nodeId].Child2 = NullNode; + _nodes[nodeId].LeafCount = 0; + ++_nodeCount; + return nodeId; + } + + private void FreeNode(int nodeId) + { + Debug.Assert(0 <= nodeId && nodeId < _nodeCapacity); + Debug.Assert(0 < _nodeCount); + _nodes[nodeId].ParentOrNext = _freeList; + _freeList = nodeId; + --_nodeCount; + } + + private void InsertLeaf(int leaf) + { + ++_insertionCount; + + if (_root == NullNode) + { + _root = leaf; + _nodes[_root].ParentOrNext = NullNode; + return; + } + + // Find the best sibling for this node + AABB leafAABB = _nodes[leaf].AABB; + int sibling = _root; + while (_nodes[sibling].IsLeaf() == false) + { + int child1 = _nodes[sibling].Child1; + int child2 = _nodes[sibling].Child2; + + // Expand the node's AABB. + _nodes[sibling].AABB.Combine(ref leafAABB); + _nodes[sibling].LeafCount += 1; + + float siblingArea = _nodes[sibling].AABB.Perimeter; + AABB parentAABB = new AABB(); + parentAABB.Combine(ref _nodes[sibling].AABB, ref leafAABB); + float parentArea = parentAABB.Perimeter; + float cost1 = 2.0f * parentArea; + + float inheritanceCost = 2.0f * (parentArea - siblingArea); + + float cost2; + if (_nodes[child1].IsLeaf()) + { + AABB aabb = new AABB(); + aabb.Combine(ref leafAABB, ref _nodes[child1].AABB); + cost2 = aabb.Perimeter + inheritanceCost; + } + else + { + AABB aabb = new AABB(); + aabb.Combine(ref leafAABB, ref _nodes[child1].AABB); + float oldArea = _nodes[child1].AABB.Perimeter; + float newArea = aabb.Perimeter; + cost2 = (newArea - oldArea) + inheritanceCost; + } + + float cost3; + if (_nodes[child2].IsLeaf()) + { + AABB aabb = new AABB(); + aabb.Combine(ref leafAABB, ref _nodes[child2].AABB); + cost3 = aabb.Perimeter + inheritanceCost; + } + else + { + AABB aabb = new AABB(); + aabb.Combine(ref leafAABB, ref _nodes[child2].AABB); + float oldArea = _nodes[child2].AABB.Perimeter; + float newArea = aabb.Perimeter; + cost3 = newArea - oldArea + inheritanceCost; + } + + // Descend according to the minimum cost. + if (cost1 < cost2 && cost1 < cost3) + { + break; + } + + // Expand the node's AABB to account for the new leaf. + _nodes[sibling].AABB.Combine(ref leafAABB); + + // Descend + if (cost2 < cost3) + { + sibling = child1; + } + else + { + sibling = child2; + } + } + + // Create a new parent for the siblings. + int oldParent = _nodes[sibling].ParentOrNext; + int newParent = AllocateNode(); + _nodes[newParent].ParentOrNext = oldParent; + _nodes[newParent].UserData = default(T); + _nodes[newParent].AABB.Combine(ref leafAABB, ref _nodes[sibling].AABB); + _nodes[newParent].LeafCount = _nodes[sibling].LeafCount + 1; + + if (oldParent != NullNode) + { + // The sibling was not the root. + if (_nodes[oldParent].Child1 == sibling) + { + _nodes[oldParent].Child1 = newParent; + } + else + { + _nodes[oldParent].Child2 = newParent; + } + + _nodes[newParent].Child1 = sibling; + _nodes[newParent].Child2 = leaf; + _nodes[sibling].ParentOrNext = newParent; + _nodes[leaf].ParentOrNext = newParent; + } + else + { + // The sibling was the root. + _nodes[newParent].Child1 = sibling; + _nodes[newParent].Child2 = leaf; + _nodes[sibling].ParentOrNext = newParent; + _nodes[leaf].ParentOrNext = newParent; + _root = newParent; + } + } + + private void RemoveLeaf(int leaf) + { + if (leaf == _root) + { + _root = NullNode; + return; + } + + int parent = _nodes[leaf].ParentOrNext; + int grandParent = _nodes[parent].ParentOrNext; + int sibling; + if (_nodes[parent].Child1 == leaf) + { + sibling = _nodes[parent].Child2; + } + else + { + sibling = _nodes[parent].Child1; + } + + if (grandParent != NullNode) + { + // Destroy parent and connect sibling to grandParent. + if (_nodes[grandParent].Child1 == parent) + { + _nodes[grandParent].Child1 = sibling; + } + else + { + _nodes[grandParent].Child2 = sibling; + } + _nodes[sibling].ParentOrNext = grandParent; + FreeNode(parent); + + // Adjust ancestor bounds. + parent = grandParent; + while (parent != NullNode) + { + _nodes[parent].AABB.Combine(ref _nodes[_nodes[parent].Child1].AABB, + ref _nodes[_nodes[parent].Child2].AABB); + + Debug.Assert(_nodes[parent].LeafCount > 0); + _nodes[parent].LeafCount -= 1; + + parent = _nodes[parent].ParentOrNext; + } + } + else + { + _root = sibling; + _nodes[sibling].ParentOrNext = NullNode; + FreeNode(parent); + } + } + + private int ComputeHeight(int nodeId) + { + if (nodeId == NullNode) + { + return 0; + } + + Debug.Assert(0 <= nodeId && nodeId < _nodeCapacity); + DynamicTreeNode node = _nodes[nodeId]; + int height1 = ComputeHeight(node.Child1); + int height2 = ComputeHeight(node.Child2); + return 1 + Math.Max(height1, height2); + } + } +} \ No newline at end of file diff --git a/Collision/DynamicTreeBroadPhase.cs b/Collision/DynamicTreeBroadPhase.cs new file mode 100644 index 0000000..7e1e391 --- /dev/null +++ b/Collision/DynamicTreeBroadPhase.cs @@ -0,0 +1,324 @@ +/* +* 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 FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision +{ + internal struct Pair : IComparable + { + public int ProxyIdA; + public int ProxyIdB; + + #region IComparable Members + + public int CompareTo(Pair other) + { + if (ProxyIdA < other.ProxyIdA) + { + return -1; + } + if (ProxyIdA == other.ProxyIdA) + { + if (ProxyIdB < other.ProxyIdB) + { + return -1; + } + if (ProxyIdB == other.ProxyIdB) + { + return 0; + } + } + + return 1; + } + + #endregion + } + + /// + /// The broad-phase is used for computing pairs and performing volume queries and ray casts. + /// This broad-phase does not persist pairs. Instead, this reports potentially new pairs. + /// It is up to the client to consume the new pairs and to track subsequent overlap. + /// + public class DynamicTreeBroadPhase : IBroadPhase + { + private int[] _moveBuffer; + private int _moveCapacity; + private int _moveCount; + + private Pair[] _pairBuffer; + private int _pairCapacity; + private int _pairCount; + private int _proxyCount; + private Func _queryCallback; + private int _queryProxyId; + private DynamicTree _tree = new DynamicTree(); + + public DynamicTreeBroadPhase() + { + _queryCallback = new Func(QueryCallback); + + _pairCapacity = 16; + _pairBuffer = new Pair[_pairCapacity]; + + _moveCapacity = 16; + _moveBuffer = new int[_moveCapacity]; + } + + #region IBroadPhase Members + + /// + /// Get the number of proxies. + /// + /// The proxy count. + public int ProxyCount + { + get { return _proxyCount; } + } + + /// + /// Create a proxy with an initial AABB. Pairs are not reported until + /// UpdatePairs is called. + /// + /// The aabb. + /// The user data. + /// + public int AddProxy(ref FixtureProxy proxy) + { + int proxyId = _tree.AddProxy(ref proxy.AABB, proxy); + ++_proxyCount; + BufferMove(proxyId); + return proxyId; + } + + /// + /// Destroy a proxy. It is up to the client to remove any pairs. + /// + /// The proxy id. + public void RemoveProxy(int proxyId) + { + UnBufferMove(proxyId); + --_proxyCount; + _tree.RemoveProxy(proxyId); + } + + public void MoveProxy(int proxyId, ref AABB aabb, Vector2 displacement) + { + bool buffer = _tree.MoveProxy(proxyId, ref aabb, displacement); + if (buffer) + { + BufferMove(proxyId); + } + } + + /// + /// Get the AABB for a proxy. + /// + /// The proxy id. + /// The aabb. + public void GetFatAABB(int proxyId, out AABB aabb) + { + _tree.GetFatAABB(proxyId, out aabb); + } + + /// + /// Get user data from a proxy. Returns null if the id is invalid. + /// + /// The proxy id. + /// + public FixtureProxy GetProxy(int proxyId) + { + return _tree.GetUserData(proxyId); + } + + /// + /// Test overlap of fat AABBs. + /// + /// The proxy id A. + /// The proxy id B. + /// + public bool TestOverlap(int proxyIdA, int proxyIdB) + { + AABB aabbA, aabbB; + _tree.GetFatAABB(proxyIdA, out aabbA); + _tree.GetFatAABB(proxyIdB, out aabbB); + return AABB.TestOverlap(ref aabbA, ref aabbB); + } + + /// + /// Update the pairs. This results in pair callbacks. This can only add pairs. + /// + /// The callback. + public void UpdatePairs(BroadphaseDelegate callback) + { + // Reset pair buffer + _pairCount = 0; + + // Perform tree queries for all moving proxies. + for (int j = 0; j < _moveCount; ++j) + { + _queryProxyId = _moveBuffer[j]; + if (_queryProxyId == -1) + { + continue; + } + + // We have to query the tree with the fat AABB so that + // we don't fail to create a pair that may touch later. + AABB fatAABB; + _tree.GetFatAABB(_queryProxyId, out fatAABB); + + // Query tree, create pairs and add them pair buffer. + _tree.Query(_queryCallback, ref fatAABB); + } + + // Reset move buffer + _moveCount = 0; + + // Sort the pair buffer to expose duplicates. + Array.Sort(_pairBuffer, 0, _pairCount); + + // Send the pairs back to the client. + int i = 0; + while (i < _pairCount) + { + Pair primaryPair = _pairBuffer[i]; + FixtureProxy userDataA = _tree.GetUserData(primaryPair.ProxyIdA); + FixtureProxy userDataB = _tree.GetUserData(primaryPair.ProxyIdB); + + callback(ref userDataA, ref userDataB); + ++i; + + // Skip any duplicate pairs. + while (i < _pairCount) + { + Pair pair = _pairBuffer[i]; + if (pair.ProxyIdA != primaryPair.ProxyIdA || pair.ProxyIdB != primaryPair.ProxyIdB) + { + break; + } + ++i; + } + } + + // Try to keep the tree balanced. + _tree.Rebalance(4); + } + + /// + /// Query an AABB for overlapping proxies. The callback class + /// is called for each proxy that overlaps the supplied AABB. + /// + /// The callback. + /// The aabb. + public void Query(Func callback, ref AABB aabb) + { + _tree.Query(callback, ref aabb); + } + + /// + /// Ray-cast against the proxies in the tree. This relies on the callback + /// to perform a exact ray-cast in the case were the proxy contains a shape. + /// The callback also performs the any collision filtering. This has performance + /// roughly equal to k * log(n), where k is the number of collisions and n is the + /// number of proxies in the tree. + /// + /// A callback class that is called for each proxy that is hit by the ray. + /// The ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). + public void RayCast(Func callback, ref RayCastInput input) + { + _tree.RayCast(callback, ref input); + } + + public void TouchProxy(int proxyId) + { + BufferMove(proxyId); + } + + #endregion + + /// + /// Compute the height of the embedded tree. + /// + /// + public int ComputeHeight() + { + return _tree.ComputeHeight(); + } + + private void BufferMove(int proxyId) + { + if (_moveCount == _moveCapacity) + { + int[] oldBuffer = _moveBuffer; + _moveCapacity *= 2; + _moveBuffer = new int[_moveCapacity]; + Array.Copy(oldBuffer, _moveBuffer, _moveCount); + } + + _moveBuffer[_moveCount] = proxyId; + ++_moveCount; + } + + private void UnBufferMove(int proxyId) + { + for (int i = 0; i < _moveCount; ++i) + { + if (_moveBuffer[i] == proxyId) + { + _moveBuffer[i] = -1; + return; + } + } + } + + private bool QueryCallback(int proxyId) + { + // A proxy cannot form a pair with itself. + if (proxyId == _queryProxyId) + { + return true; + } + + // Grow the pair buffer as needed. + if (_pairCount == _pairCapacity) + { + Pair[] oldBuffer = _pairBuffer; + _pairCapacity *= 2; + _pairBuffer = new Pair[_pairCapacity]; + Array.Copy(oldBuffer, _pairBuffer, _pairCount); + } + + _pairBuffer[_pairCount].ProxyIdA = Math.Min(proxyId, _queryProxyId); + _pairBuffer[_pairCount].ProxyIdB = Math.Max(proxyId, _queryProxyId); + ++_pairCount; + + return true; + } + } +} \ No newline at end of file diff --git a/Collision/IBroadPhase.cs b/Collision/IBroadPhase.cs new file mode 100644 index 0000000..400d53e --- /dev/null +++ b/Collision/IBroadPhase.cs @@ -0,0 +1,30 @@ +using System; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision +{ + public interface IBroadPhase + { + int ProxyCount { get; } + void UpdatePairs(BroadphaseDelegate callback); + + bool TestOverlap(int proxyIdA, int proxyIdB); + + int AddProxy(ref FixtureProxy proxy); + + void RemoveProxy(int proxyId); + + void MoveProxy(int proxyId, ref AABB aabb, Vector2 displacement); + + FixtureProxy GetProxy(int proxyId); + + void TouchProxy(int proxyId); + + void GetFatAABB(int proxyId, out AABB aabb); + + void Query(Func callback, ref AABB aabb); + + void RayCast(Func callback, ref RayCastInput input); + } +} \ No newline at end of file diff --git a/Collision/QuadTree.cs b/Collision/QuadTree.cs new file mode 100644 index 0000000..f6c67a8 --- /dev/null +++ b/Collision/QuadTree.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Collision; +using Microsoft.Xna.Framework; + +public class Element +{ + public QuadTree Parent; + public AABB Span; + public T Value; + + public Element(T value, AABB span) + { + Span = span; + Value = value; + Parent = null; + } +} + +public class QuadTree +{ + public int MaxBucket; + public int MaxDepth; + public List> Nodes; + public AABB Span; + public QuadTree[] SubTrees; + + public QuadTree(AABB span, int maxbucket, int maxdepth) + { + Span = span; + Nodes = new List>(); + + MaxBucket = maxbucket; + MaxDepth = maxdepth; + } + + public bool IsPartitioned + { + get { return SubTrees != null; } + } + + /// + /// returns the quadrant of span that entirely contains test. if none, return 0. + /// + /// + /// + /// + private int Partition(AABB span, AABB test) + { + if (span.Q1.Contains(ref test)) return 1; + if (span.Q2.Contains(ref test)) return 2; + if (span.Q3.Contains(ref test)) return 3; + if (span.Q4.Contains(ref test)) return 4; + + return 0; + } + + public void AddNode(Element node) + { + if (!IsPartitioned) + { + if (Nodes.Count >= MaxBucket && MaxDepth > 0) //bin is full and can still subdivide + { + // + //partition into quadrants and sort existing nodes amonst quads. + // + Nodes.Add(node); //treat new node just like other nodes for partitioning + + SubTrees = new QuadTree[4]; + SubTrees[0] = new QuadTree(Span.Q1, MaxBucket, MaxDepth - 1); + SubTrees[1] = new QuadTree(Span.Q2, MaxBucket, MaxDepth - 1); + SubTrees[2] = new QuadTree(Span.Q3, MaxBucket, MaxDepth - 1); + SubTrees[3] = new QuadTree(Span.Q4, MaxBucket, MaxDepth - 1); + + List> remNodes = new List>(); + //nodes that are not fully contained by any quadrant + + foreach (Element n in Nodes) + { + switch (Partition(Span, n.Span)) + { + case 1: //quadrant 1 + SubTrees[0].AddNode(n); + break; + case 2: + SubTrees[1].AddNode(n); + break; + case 3: + SubTrees[2].AddNode(n); + break; + case 4: + SubTrees[3].AddNode(n); + break; + default: + n.Parent = this; + remNodes.Add(n); + break; + } + } + + Nodes = remNodes; + } + else + { + node.Parent = this; + Nodes.Add(node); + //if bin is not yet full or max depth has been reached, just add the node without subdividing + } + } + else //we already have children nodes + { + // + //add node to specific sub-tree + // + switch (Partition(Span, node.Span)) + { + case 1: //quadrant 1 + SubTrees[0].AddNode(node); + break; + case 2: + SubTrees[1].AddNode(node); + break; + case 3: + SubTrees[2].AddNode(node); + break; + case 4: + SubTrees[3].AddNode(node); + break; + default: + node.Parent = this; + Nodes.Add(node); + break; + } + } + } + + /// + /// tests if ray intersects AABB + /// + /// + /// + public static bool RayCastAABB(AABB aabb, Vector2 p1, Vector2 p2) + { + AABB segmentAABB = new AABB(); + { + Vector2.Min(ref p1, ref p2, out segmentAABB.LowerBound); + Vector2.Max(ref p1, ref p2, out segmentAABB.UpperBound); + } + if (!AABB.TestOverlap(aabb, segmentAABB)) return false; + + Vector2 rayDir = p2 - p1; + Vector2 rayPos = p1; + + Vector2 norm = new Vector2(-rayDir.Y, rayDir.X); //normal to ray + if (norm.Length() == 0.0) + return true; //if ray is just a point, return true (iff point is within aabb, as tested earlier) + norm.Normalize(); + + float dPos = Vector2.Dot(rayPos, norm); + + Vector2[] verts = aabb.GetVertices(); + float d0 = Vector2.Dot(verts[0], norm) - dPos; + for (int i = 1; i < 4; i++) + { + float d = Vector2.Dot(verts[i], norm) - dPos; + if (Math.Sign(d) != Math.Sign(d0)) + //return true if the ray splits the vertices (ie: sign of dot products with normal are not all same) + return true; + } + + return false; + } + + public void QueryAABB(Func, bool> callback, ref AABB searchR) + { + Stack> stack = new Stack>(); + stack.Push(this); + + while (stack.Count > 0) + { + QuadTree qt = stack.Pop(); + if (!AABB.TestOverlap(ref searchR, ref qt.Span)) + continue; + + foreach (Element n in qt.Nodes) + if (AABB.TestOverlap(ref searchR, ref n.Span)) + { + if (!callback(n)) return; + } + + if (qt.IsPartitioned) + foreach (QuadTree st in qt.SubTrees) + stack.Push(st); + } + } + + public void RayCast(Func, float> callback, ref RayCastInput input) + { + Stack> stack = new Stack>(); + stack.Push(this); + + float maxFraction = input.MaxFraction; + Vector2 p1 = input.Point1; + Vector2 p2 = p1 + (input.Point2 - input.Point1) * maxFraction; + + while (stack.Count > 0) + { + QuadTree qt = stack.Pop(); + + if (!RayCastAABB(qt.Span, p1, p2)) + continue; + + foreach (Element n in qt.Nodes) + { + if (!RayCastAABB(n.Span, p1, p2)) + continue; + + RayCastInput subInput; + subInput.Point1 = input.Point1; + subInput.Point2 = input.Point2; + subInput.MaxFraction = maxFraction; + + float value = callback(subInput, n); + if (value == 0.0f) + return; // the client has terminated the raycast. + + if (value <= 0.0f) + continue; + + maxFraction = value; + p2 = p1 + (input.Point2 - input.Point1) * maxFraction; //update segment endpoint + } + if (IsPartitioned) + foreach (QuadTree st in qt.SubTrees) + stack.Push(st); + } + } + + public void GetAllNodesR(ref List> nodes) + { + nodes.AddRange(Nodes); + + if (IsPartitioned) + foreach (QuadTree st in SubTrees) st.GetAllNodesR(ref nodes); + } + + public void RemoveNode(Element node) + { + node.Parent.Nodes.Remove(node); + } + + public void Reconstruct() + { + List> allNodes = new List>(); + GetAllNodesR(ref allNodes); + + Clear(); + + allNodes.ForEach(AddNode); + } + + public void Clear() + { + Nodes.Clear(); + SubTrees = null; + } +} \ No newline at end of file diff --git a/Collision/QuadTreeBroadPhase.cs b/Collision/QuadTreeBroadPhase.cs new file mode 100644 index 0000000..60105db --- /dev/null +++ b/Collision/QuadTreeBroadPhase.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics; +using FarseerPhysics.Collision; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +public class QuadTreeBroadPhase : IBroadPhase +{ + private const int TreeUpdateThresh = 10000; + private int _currID; + private Dictionary> _idRegister; + private List> _moveBuffer; + private List _pairBuffer; + private QuadTree _quadTree; + private int _treeMoveNum; + + /// + /// Creates a new quad tree broadphase with the specified span. + /// + /// the maximum span of the tree (world size) + public QuadTreeBroadPhase(AABB span) + { + _quadTree = new QuadTree(span, 5, 10); + _idRegister = new Dictionary>(); + _moveBuffer = new List>(); + _pairBuffer = new List(); + } + + #region IBroadPhase Members + + /// + /// The number of proxies + /// + public int ProxyCount + { + get { return _idRegister.Count; } + } + + public void GetFatAABB(int proxyID, out AABB aabb) + { + if (_idRegister.ContainsKey(proxyID)) + aabb = _idRegister[proxyID].Span; + else + throw new KeyNotFoundException("proxyID not found in register"); + } + + public void UpdatePairs(BroadphaseDelegate callback) + { + _pairBuffer.Clear(); + foreach (Element qtnode in _moveBuffer) + { + // Query tree, create pairs and add them pair buffer. + Query(proxyID => PairBufferQueryCallback(proxyID, qtnode.Value.ProxyId), ref qtnode.Span); + } + _moveBuffer.Clear(); + + // Sort the pair buffer to expose duplicates. + _pairBuffer.Sort(); + + // Send the pairs back to the client. + int i = 0; + while (i < _pairBuffer.Count) + { + Pair primaryPair = _pairBuffer[i]; + FixtureProxy userDataA = GetProxy(primaryPair.ProxyIdA); + FixtureProxy userDataB = GetProxy(primaryPair.ProxyIdB); + + callback(ref userDataA, ref userDataB); + ++i; + + // Skip any duplicate pairs. + while (i < _pairBuffer.Count && _pairBuffer[i].ProxyIdA == primaryPair.ProxyIdA && + _pairBuffer[i].ProxyIdB == primaryPair.ProxyIdB) + ++i; + } + } + + /// + /// Test overlap of fat AABBs. + /// + /// The proxy id A. + /// The proxy id B. + /// + public bool TestOverlap(int proxyIdA, int proxyIdB) + { + AABB aabb1; + AABB aabb2; + GetFatAABB(proxyIdA, out aabb1); + GetFatAABB(proxyIdB, out aabb2); + return AABB.TestOverlap(ref aabb1, ref aabb2); + } + + public int AddProxy(ref FixtureProxy proxy) + { + int proxyID = _currID++; + proxy.ProxyId = proxyID; + AABB aabb = Fatten(ref proxy.AABB); + Element qtnode = new Element(proxy, aabb); + + _idRegister.Add(proxyID, qtnode); + _quadTree.AddNode(qtnode); + + return proxyID; + } + + public void RemoveProxy(int proxyId) + { + if (_idRegister.ContainsKey(proxyId)) + { + Element qtnode = _idRegister[proxyId]; + UnbufferMove(qtnode); + _idRegister.Remove(proxyId); + _quadTree.RemoveNode(qtnode); + } + else + throw new KeyNotFoundException("proxyID not found in register"); + } + + public void MoveProxy(int proxyId, ref AABB aabb, Vector2 displacement) + { + AABB fatAABB; + GetFatAABB(proxyId, out fatAABB); + + //exit if movement is within fat aabb + if (fatAABB.Contains(ref aabb)) + return; + + // Extend AABB. + AABB b = aabb; + Vector2 r = new Vector2(Settings.AABBExtension, Settings.AABBExtension); + b.LowerBound = b.LowerBound - r; + b.UpperBound = b.UpperBound + r; + + // Predict AABB displacement. + Vector2 d = Settings.AABBMultiplier * displacement; + + if (d.X < 0.0f) + b.LowerBound.X += d.X; + else + b.UpperBound.X += d.X; + + if (d.Y < 0.0f) + b.LowerBound.Y += d.Y; + else + b.UpperBound.Y += d.Y; + + + Element qtnode = _idRegister[proxyId]; + qtnode.Value.AABB = b; //not neccesary for QTree, but might be accessed externally + qtnode.Span = b; + + ReinsertNode(qtnode); + + BufferMove(qtnode); + } + + public FixtureProxy GetProxy(int proxyId) + { + if (_idRegister.ContainsKey(proxyId)) + return _idRegister[proxyId].Value; + else + throw new KeyNotFoundException("proxyID not found in register"); + } + + public void TouchProxy(int proxyId) + { + if (_idRegister.ContainsKey(proxyId)) + BufferMove(_idRegister[proxyId]); + else + throw new KeyNotFoundException("proxyID not found in register"); + } + + public void Query(Func callback, ref AABB query) + { + _quadTree.QueryAABB(TransformPredicate(callback), ref query); + } + + public void RayCast(Func callback, ref RayCastInput input) + { + _quadTree.RayCast(TransformRayCallback(callback), ref input); + } + + #endregion + + private AABB Fatten(ref AABB aabb) + { + Vector2 r = new Vector2(Settings.AABBExtension, Settings.AABBExtension); + return new AABB(aabb.LowerBound - r, aabb.UpperBound + r); + } + + private Func, bool> TransformPredicate(Func idPredicate) + { + Func, bool> qtPred = qtnode => idPredicate(qtnode.Value.ProxyId); + return qtPred; + } + + private Func, float> TransformRayCallback( + Func callback) + { + Func, float> newCallback = + (input, qtnode) => callback(input, qtnode.Value.ProxyId); + return newCallback; + } + + private bool PairBufferQueryCallback(int proxyID, int baseID) + { + // A proxy cannot form a pair with itself. + if (proxyID == baseID) + return true; + + Pair p = new Pair(); + p.ProxyIdA = Math.Min(proxyID, baseID); + p.ProxyIdB = Math.Max(proxyID, baseID); + _pairBuffer.Add(p); + + return true; + } + + private void ReconstructTree() + { + //this is faster than _quadTree.Reconstruct(), since the quadtree method runs a recusive query to find all nodes. + _quadTree.Clear(); + foreach (Element elem in _idRegister.Values) + _quadTree.AddNode(elem); + } + + private void ReinsertNode(Element qtnode) + { + _quadTree.RemoveNode(qtnode); + _quadTree.AddNode(qtnode); + + if (++_treeMoveNum > TreeUpdateThresh) + { + ReconstructTree(); + _treeMoveNum = 0; + } + } + + private void BufferMove(Element proxy) + { + _moveBuffer.Add(proxy); + } + + private void UnbufferMove(Element proxy) + { + _moveBuffer.Remove(proxy); + } +} \ No newline at end of file diff --git a/Collision/Shapes/CircleShape.cs b/Collision/Shapes/CircleShape.cs new file mode 100644 index 0000000..a52250b --- /dev/null +++ b/Collision/Shapes/CircleShape.cs @@ -0,0 +1,207 @@ +/* +* 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 FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision.Shapes +{ + public class CircleShape : Shape + { + internal Vector2 _position; + + public CircleShape(float radius, float density) + : base(density) + { + ShapeType = ShapeType.Circle; + _radius = radius; + _position = Vector2.Zero; + ComputeProperties(); + } + + internal CircleShape() + : base(0) + { + ShapeType = ShapeType.Circle; + _radius = 0.0f; + _position = Vector2.Zero; + } + + public override int ChildCount + { + get { return 1; } + } + + public Vector2 Position + { + get { return _position; } + set + { + _position = value; + ComputeProperties(); + } + } + + public override Shape Clone() + { + CircleShape shape = new CircleShape(); + shape._radius = Radius; + shape._density = _density; + shape._position = _position; + shape.ShapeType = ShapeType; + shape.MassData = MassData; + return shape; + } + + /// + /// Test a point for containment in this shape. This only works for convex shapes. + /// + /// The shape world transform. + /// a point in world coordinates. + /// True if the point is inside the shape + public override bool TestPoint(ref Transform transform, ref Vector2 point) + { + Vector2 center = transform.Position + MathUtils.Multiply(ref transform.R, Position); + Vector2 d = point - center; + return Vector2.Dot(d, d) <= Radius * Radius; + } + + /// + /// Cast a ray against a child shape. + /// + /// The ray-cast results. + /// The ray-cast input parameters. + /// The transform to be applied to the shape. + /// The child shape index. + /// True if the ray-cast hits the shape + public override bool RayCast(out RayCastOutput output, ref RayCastInput input, ref Transform transform, + int childIndex) + { + // Collision Detection in Interactive 3D Environments by Gino van den Bergen + // From Section 3.1.2 + // x = s + a * r + // norm(x) = radius + + output = new RayCastOutput(); + + Vector2 position = transform.Position + MathUtils.Multiply(ref transform.R, Position); + Vector2 s = input.Point1 - position; + float b = Vector2.Dot(s, s) - Radius * Radius; + + // Solve quadratic equation. + Vector2 r = input.Point2 - input.Point1; + float c = Vector2.Dot(s, r); + float rr = Vector2.Dot(r, r); + float sigma = c * c - rr * b; + + // Check for negative discriminant and short segment. + if (sigma < 0.0f || rr < Settings.Epsilon) + { + return false; + } + + // Find the point of intersection of the line with the circle. + float a = -(c + (float)Math.Sqrt(sigma)); + + // Is the intersection point on the segment? + if (0.0f <= a && a <= input.MaxFraction * rr) + { + a /= rr; + output.Fraction = a; + Vector2 norm = (s + a * r); + norm.Normalize(); + output.Normal = norm; + return true; + } + + return false; + } + + /// + /// Given a transform, compute the associated axis aligned bounding box for a child shape. + /// + /// The aabb results. + /// The world transform of the shape. + /// The child shape index. + public override void ComputeAABB(out AABB aabb, ref Transform transform, int childIndex) + { + Vector2 p = transform.Position + MathUtils.Multiply(ref transform.R, Position); + aabb.LowerBound = new Vector2(p.X - Radius, p.Y - Radius); + aabb.UpperBound = new Vector2(p.X + Radius, p.Y + Radius); + } + + /// + /// Compute the mass properties of this shape using its dimensions and density. + /// The inertia tensor is computed about the local origin, not the centroid. + /// + public override sealed void ComputeProperties() + { + float area = Settings.Pi * Radius * Radius; + MassData.Area = area; + MassData.Mass = Density * area; + MassData.Centroid = Position; + + // inertia about the local origin + MassData.Inertia = MassData.Mass * (0.5f * Radius * Radius + Vector2.Dot(Position, Position)); + } + + public bool CompareTo(CircleShape shape) + { + return (Radius == shape.Radius && + Position == shape.Position); + } + + public override float ComputeSubmergedArea(Vector2 normal, float offset, Transform xf, out Vector2 sc) + { + sc = Vector2.Zero; + + Vector2 p = MathUtils.Multiply(ref xf, Position); + float l = -(Vector2.Dot(normal, p) - offset); + if (l < -Radius + Settings.Epsilon) + { + //Completely dry + return 0; + } + if (l > Radius) + { + //Completely wet + sc = p; + return Settings.Pi * Radius * Radius; + } + + //Magic + float r2 = Radius * Radius; + float l2 = l * l; + float area = r2 * (float)((Math.Asin(l / Radius) + Settings.Pi / 2) + l * Math.Sqrt(r2 - l2)); + float com = -2.0f / 3.0f * (float)Math.Pow(r2 - l2, 1.5f) / area; + + sc.X = p.X + normal.X * com; + sc.Y = p.Y + normal.Y * com; + + return area; + } + } +} \ No newline at end of file diff --git a/Collision/Shapes/EdgeShape.cs b/Collision/Shapes/EdgeShape.cs new file mode 100644 index 0000000..b13d86e --- /dev/null +++ b/Collision/Shapes/EdgeShape.cs @@ -0,0 +1,266 @@ +/* +* 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 FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision.Shapes +{ + /// + /// A line segment (edge) Shape. These can be connected in chains or loops + /// to other edge Shapes. The connectivity information is used to ensure + /// correct contact normals. + /// + public class EdgeShape : Shape + { + public bool HasVertex0, HasVertex3; + + /// + /// Optional adjacent vertices. These are used for smooth collision. + /// + public Vector2 Vertex0; + + /// + /// Optional adjacent vertices. These are used for smooth collision. + /// + public Vector2 Vertex3; + + /// + /// Edge start vertex + /// + private Vector2 _vertex1; + + /// + /// Edge end vertex + /// + private Vector2 _vertex2; + + internal EdgeShape() + : base(0) + { + ShapeType = ShapeType.Edge; + _radius = Settings.PolygonRadius; + } + + public EdgeShape(Vector2 start, Vector2 end) + : base(0) + { + ShapeType = ShapeType.Edge; + _radius = Settings.PolygonRadius; + Set(start, end); + } + + public override int ChildCount + { + get { return 1; } + } + + /// + /// These are the edge vertices + /// + public Vector2 Vertex1 + { + get { return _vertex1; } + set + { + _vertex1 = value; + ComputeProperties(); + } + } + + /// + /// These are the edge vertices + /// + public Vector2 Vertex2 + { + get { return _vertex2; } + set + { + _vertex2 = value; + ComputeProperties(); + } + } + + /// + /// Set this as an isolated edge. + /// + /// The start. + /// The end. + public void Set(Vector2 start, Vector2 end) + { + _vertex1 = start; + _vertex2 = end; + HasVertex0 = false; + HasVertex3 = false; + + ComputeProperties(); + } + + public override Shape Clone() + { + EdgeShape edge = new EdgeShape(); + edge._radius = _radius; + edge._density = _density; + edge.HasVertex0 = HasVertex0; + edge.HasVertex3 = HasVertex3; + edge.Vertex0 = Vertex0; + edge._vertex1 = _vertex1; + edge._vertex2 = _vertex2; + edge.Vertex3 = Vertex3; + edge.MassData = MassData; + return edge; + } + + /// + /// Test a point for containment in this shape. This only works for convex shapes. + /// + /// The shape world transform. + /// a point in world coordinates. + /// True if the point is inside the shape + public override bool TestPoint(ref Transform transform, ref Vector2 point) + { + return false; + } + + /// + /// Cast a ray against a child shape. + /// + /// The ray-cast results. + /// The ray-cast input parameters. + /// The transform to be applied to the shape. + /// The child shape index. + /// True if the ray-cast hits the shape + public override bool RayCast(out RayCastOutput output, ref RayCastInput input, + ref Transform transform, int childIndex) + { + // p = p1 + t * d + // v = v1 + s * e + // p1 + t * d = v1 + s * e + // s * e - t * d = p1 - v1 + + output = new RayCastOutput(); + + // Put the ray into the edge's frame of reference. + Vector2 p1 = MathUtils.MultiplyT(ref transform.R, input.Point1 - transform.Position); + Vector2 p2 = MathUtils.MultiplyT(ref transform.R, input.Point2 - transform.Position); + Vector2 d = p2 - p1; + + Vector2 v1 = _vertex1; + Vector2 v2 = _vertex2; + Vector2 e = v2 - v1; + Vector2 normal = new Vector2(e.Y, -e.X); + normal.Normalize(); + + // q = p1 + t * d + // dot(normal, q - v1) = 0 + // dot(normal, p1 - v1) + t * dot(normal, d) = 0 + float numerator = Vector2.Dot(normal, v1 - p1); + float denominator = Vector2.Dot(normal, d); + + if (denominator == 0.0f) + { + return false; + } + + float t = numerator / denominator; + if (t < 0.0f || 1.0f < t) + { + return false; + } + + Vector2 q = p1 + t * d; + + // q = v1 + s * r + // s = dot(q - v1, r) / dot(r, r) + Vector2 r = v2 - v1; + float rr = Vector2.Dot(r, r); + if (rr == 0.0f) + { + return false; + } + + float s = Vector2.Dot(q - v1, r) / rr; + if (s < 0.0f || 1.0f < s) + { + return false; + } + + output.Fraction = t; + if (numerator > 0.0f) + { + output.Normal = -normal; + } + else + { + output.Normal = normal; + } + return true; + } + + /// + /// Given a transform, compute the associated axis aligned bounding box for a child shape. + /// + /// The aabb results. + /// The world transform of the shape. + /// The child shape index. + public override void ComputeAABB(out AABB aabb, ref Transform transform, int childIndex) + { + Vector2 v1 = MathUtils.Multiply(ref transform, _vertex1); + Vector2 v2 = MathUtils.Multiply(ref transform, _vertex2); + + Vector2 lower = Vector2.Min(v1, v2); + Vector2 upper = Vector2.Max(v1, v2); + + Vector2 r = new Vector2(Radius, Radius); + aabb.LowerBound = lower - r; + aabb.UpperBound = upper + r; + } + + /// + /// Compute the mass properties of this shape using its dimensions and density. + /// The inertia tensor is computed about the local origin, not the centroid. + /// + public override void ComputeProperties() + { + MassData.Centroid = 0.5f * (_vertex1 + _vertex2); + } + + public override float ComputeSubmergedArea(Vector2 normal, float offset, Transform xf, out Vector2 sc) + { + sc = Vector2.Zero; + return 0; + } + + public bool CompareTo(EdgeShape shape) + { + return (HasVertex0 == shape.HasVertex0 && + HasVertex3 == shape.HasVertex3 && + Vertex0 == shape.Vertex0 && + Vertex1 == shape.Vertex1 && + Vertex2 == shape.Vertex2 && + Vertex3 == shape.Vertex3); + } + } +} \ No newline at end of file diff --git a/Collision/Shapes/LoopShape.cs b/Collision/Shapes/LoopShape.cs new file mode 100644 index 0000000..cf9df88 --- /dev/null +++ b/Collision/Shapes/LoopShape.cs @@ -0,0 +1,188 @@ +/* +* 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.Diagnostics; +using FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision.Shapes +{ + /// + /// A loop Shape is a free form sequence of line segments that form a circular list. + /// The loop may cross upon itself, but this is not recommended for smooth collision. + /// The loop has double sided collision, so you can use inside and outside collision. + /// Therefore, you may use any winding order. + /// + public class LoopShape : Shape + { + private static EdgeShape _edgeShape = new EdgeShape(); + + /// + /// The vertices. These are not owned/freed by the loop Shape. + /// + public Vertices Vertices; + + private LoopShape() + : base(0) + { + ShapeType = ShapeType.Loop; + _radius = Settings.PolygonRadius; + } + + public LoopShape(Vertices vertices) + : base(0) + { + ShapeType = ShapeType.Loop; + _radius = Settings.PolygonRadius; + +#pragma warning disable 162 + if (Settings.ConserveMemory) + Vertices = vertices; + else + // Copy vertices. + Vertices = new Vertices(vertices); +#pragma warning restore 162 + } + + public override int ChildCount + { + get { return Vertices.Count; } + } + + public override Shape Clone() + { + LoopShape loop = new LoopShape(); + loop._density = _density; + loop._radius = _radius; + loop.Vertices = Vertices; + loop.MassData = MassData; + return loop; + } + + /// + /// Get a child edge. + /// + /// The edge. + /// The index. + public void GetChildEdge(ref EdgeShape edge, int index) + { + Debug.Assert(2 <= Vertices.Count); + Debug.Assert(0 <= index && index < Vertices.Count); + edge.ShapeType = ShapeType.Edge; + edge._radius = _radius; + edge.HasVertex0 = true; + edge.HasVertex3 = true; + + int i0 = index - 1 >= 0 ? index - 1 : Vertices.Count - 1; + int i1 = index; + int i2 = index + 1 < Vertices.Count ? index + 1 : 0; + int i3 = index + 2; + while (i3 >= Vertices.Count) + { + i3 -= Vertices.Count; + } + + edge.Vertex0 = Vertices[i0]; + edge.Vertex1 = Vertices[i1]; + edge.Vertex2 = Vertices[i2]; + edge.Vertex3 = Vertices[i3]; + } + + /// + /// Test a point for containment in this shape. This only works for convex shapes. + /// + /// The shape world transform. + /// a point in world coordinates. + /// True if the point is inside the shape + public override bool TestPoint(ref Transform transform, ref Vector2 point) + { + return false; + } + + /// + /// Cast a ray against a child shape. + /// + /// The ray-cast results. + /// The ray-cast input parameters. + /// The transform to be applied to the shape. + /// The child shape index. + /// True if the ray-cast hits the shape + public override bool RayCast(out RayCastOutput output, ref RayCastInput input, + ref Transform transform, int childIndex) + { + Debug.Assert(childIndex < Vertices.Count); + + int i1 = childIndex; + int i2 = childIndex + 1; + if (i2 == Vertices.Count) + { + i2 = 0; + } + + _edgeShape.Vertex1 = Vertices[i1]; + _edgeShape.Vertex2 = Vertices[i2]; + + return _edgeShape.RayCast(out output, ref input, ref transform, 0); + } + + /// + /// Given a transform, compute the associated axis aligned bounding box for a child shape. + /// + /// The aabb results. + /// The world transform of the shape. + /// The child shape index. + public override void ComputeAABB(out AABB aabb, ref Transform transform, int childIndex) + { + Debug.Assert(childIndex < Vertices.Count); + + int i1 = childIndex; + int i2 = childIndex + 1; + if (i2 == Vertices.Count) + { + i2 = 0; + } + + Vector2 v1 = MathUtils.Multiply(ref transform, Vertices[i1]); + Vector2 v2 = MathUtils.Multiply(ref transform, Vertices[i2]); + + aabb.LowerBound = Vector2.Min(v1, v2); + aabb.UpperBound = Vector2.Max(v1, v2); + } + + /// + /// Chains have zero mass. + /// + public override void ComputeProperties() + { + //Does nothing. Loop shapes don't have properties. + } + + public override float ComputeSubmergedArea(Vector2 normal, float offset, Transform xf, out Vector2 sc) + { + sc = Vector2.Zero; + return 0; + } + } +} \ No newline at end of file diff --git a/Collision/Shapes/PolygonShape.cs b/Collision/Shapes/PolygonShape.cs new file mode 100644 index 0000000..0d81022 --- /dev/null +++ b/Collision/Shapes/PolygonShape.cs @@ -0,0 +1,556 @@ +/* +* 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.Diagnostics; +using FarseerPhysics.Common; +using FarseerPhysics.Common.Decomposition; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision.Shapes +{ + /// + /// Represents a simple non-selfintersecting convex polygon. + /// If you want to have concave polygons, you will have to use the or the + /// to decompose the concave polygon into 2 or more convex polygons. + /// + public class PolygonShape : Shape + { + public Vertices Normals; + public Vertices Vertices; + + /// + /// Initializes a new instance of the class. + /// + /// The vertices. + /// The density. + public PolygonShape(Vertices vertices, float density) + : base(density) + { + ShapeType = ShapeType.Polygon; + _radius = Settings.PolygonRadius; + + Set(vertices); + } + + public PolygonShape(float density) + : base(density) + { + ShapeType = ShapeType.Polygon; + _radius = Settings.PolygonRadius; + Normals = new Vertices(); + Vertices = new Vertices(); + } + + internal PolygonShape() + : base(0) + { + ShapeType = ShapeType.Polygon; + _radius = Settings.PolygonRadius; + Normals = new Vertices(); + Vertices = new Vertices(); + } + + public override int ChildCount + { + get { return 1; } + } + + public override Shape Clone() + { + PolygonShape clone = new PolygonShape(); + clone.ShapeType = ShapeType; + clone._radius = _radius; + clone._density = _density; + + if (Settings.ConserveMemory) + { +#pragma warning disable 162 + clone.Vertices = Vertices; + clone.Normals = Normals; +#pragma warning restore 162 + } + else + { + clone.Vertices = new Vertices(Vertices); + clone.Normals = new Vertices(Normals); + } + + clone.MassData = MassData; + return clone; + } + + /// + /// Copy vertices. This assumes the vertices define a convex polygon. + /// It is assumed that the exterior is the the right of each edge. + /// + /// The vertices. + public void Set(Vertices vertices) + { + Debug.Assert(vertices.Count >= 3 && vertices.Count <= Settings.MaxPolygonVertices); + +#pragma warning disable 162 + if (Settings.ConserveMemory) + Vertices = vertices; + else + // Copy vertices. + Vertices = new Vertices(vertices); +#pragma warning restore 162 + + Normals = new Vertices(vertices.Count); + + // Compute normals. Ensure the edges have non-zero length. + for (int i = 0; i < vertices.Count; ++i) + { + int i1 = i; + int i2 = i + 1 < vertices.Count ? i + 1 : 0; + Vector2 edge = Vertices[i2] - Vertices[i1]; + Debug.Assert(edge.LengthSquared() > Settings.Epsilon * Settings.Epsilon); + + Vector2 temp = new Vector2(edge.Y, -edge.X); + temp.Normalize(); + Normals.Add(temp); + } + +#if DEBUG + // Ensure the polygon is convex and the interior + // is to the left of each edge. + for (int i = 0; i < Vertices.Count; ++i) + { + int i1 = i; + int i2 = i + 1 < Vertices.Count ? i + 1 : 0; + Vector2 edge = Vertices[i2] - Vertices[i1]; + + for (int j = 0; j < vertices.Count; ++j) + { + // Don't check vertices on the current edge. + if (j == i1 || j == i2) + { + continue; + } + + Vector2 r = Vertices[j] - Vertices[i1]; + + // Your polygon is non-convex (it has an indentation) or + // has colinear edges. + float s = edge.X * r.Y - edge.Y * r.X; + + Debug.Assert(s > 0.0f); + } + } +#endif + + // Compute the polygon mass data + ComputeProperties(); + } + + /// + /// Compute the mass properties of this shape using its dimensions and density. + /// The inertia tensor is computed about the local origin, not the centroid. + /// + public override void ComputeProperties() + { + // Polygon mass, centroid, and inertia. + // Let rho be the polygon density in mass per unit area. + // Then: + // mass = rho * int(dA) + // centroid.X = (1/mass) * rho * int(x * dA) + // centroid.Y = (1/mass) * rho * int(y * dA) + // I = rho * int((x*x + y*y) * dA) + // + // We can compute these integrals by summing all the integrals + // for each triangle of the polygon. To evaluate the integral + // for a single triangle, we make a change of variables to + // the (u,v) coordinates of the triangle: + // x = x0 + e1x * u + e2x * v + // y = y0 + e1y * u + e2y * v + // where 0 <= u && 0 <= v && u + v <= 1. + // + // We integrate u from [0,1-v] and then v from [0,1]. + // We also need to use the Jacobian of the transformation: + // D = cross(e1, e2) + // + // Simplification: triangle centroid = (1/3) * (p1 + p2 + p3) + // + // The rest of the derivation is handled by computer algebra. + + Debug.Assert(Vertices.Count >= 3); + + if (_density <= 0) + return; + + Vector2 center = Vector2.Zero; + float area = 0.0f; + float I = 0.0f; + + // pRef is the reference point for forming triangles. + // It's location doesn't change the result (except for rounding error). + Vector2 pRef = Vector2.Zero; + +#if false + // This code would put the reference point inside the polygon. + for (int i = 0; i < count; ++i) + { + pRef += vs[i]; + } + pRef *= 1.0f / count; +#endif + + const float inv3 = 1.0f / 3.0f; + + for (int i = 0; i < Vertices.Count; ++i) + { + // Triangle vertices. + Vector2 p1 = pRef; + Vector2 p2 = Vertices[i]; + Vector2 p3 = i + 1 < Vertices.Count ? Vertices[i + 1] : Vertices[0]; + + Vector2 e1 = p2 - p1; + Vector2 e2 = p3 - p1; + + float d; + MathUtils.Cross(ref e1, ref e2, out d); + + float triangleArea = 0.5f * d; + area += triangleArea; + + // Area weighted centroid + center += triangleArea * inv3 * (p1 + p2 + p3); + + float px = p1.X, py = p1.Y; + float ex1 = e1.X, ey1 = e1.Y; + float ex2 = e2.X, ey2 = e2.Y; + + float intx2 = inv3 * (0.25f * (ex1 * ex1 + ex2 * ex1 + ex2 * ex2) + (px * ex1 + px * ex2)) + + 0.5f * px * px; + float inty2 = inv3 * (0.25f * (ey1 * ey1 + ey2 * ey1 + ey2 * ey2) + (py * ey1 + py * ey2)) + + 0.5f * py * py; + + I += d * (intx2 + inty2); + } + + //The area is too small for the engine to handle. + Debug.Assert(area > Settings.Epsilon); + + // We save the area + MassData.Area = area; + + // Total mass + MassData.Mass = _density * area; + + // Center of mass + center *= 1.0f / area; + MassData.Centroid = center; + + // Inertia tensor relative to the local origin. + MassData.Inertia = _density * I; + } + + /// + /// Build vertices to represent an axis-aligned box. + /// + /// The half-width. + /// The half-height. + public void SetAsBox(float halfWidth, float halfHeight) + { + Set(PolygonTools.CreateRectangle(halfWidth, halfHeight)); + } + + /// + /// Build vertices to represent an oriented box. + /// + /// The half-width.. + /// The half-height. + /// The center of the box in local coordinates. + /// The rotation of the box in local coordinates. + public void SetAsBox(float halfWidth, float halfHeight, Vector2 center, float angle) + { + Set(PolygonTools.CreateRectangle(halfWidth, halfHeight, center, angle)); + } + + /// + /// Test a point for containment in this shape. This only works for convex shapes. + /// + /// The shape world transform. + /// a point in world coordinates. + /// True if the point is inside the shape + public override bool TestPoint(ref Transform transform, ref Vector2 point) + { + Vector2 pLocal = MathUtils.MultiplyT(ref transform.R, point - transform.Position); + + for (int i = 0; i < Vertices.Count; ++i) + { + float dot = Vector2.Dot(Normals[i], pLocal - Vertices[i]); + if (dot > 0.0f) + { + return false; + } + } + + return true; + } + + /// + /// Cast a ray against a child shape. + /// + /// The ray-cast results. + /// The ray-cast input parameters. + /// The transform to be applied to the shape. + /// The child shape index. + /// True if the ray-cast hits the shape + public override bool RayCast(out RayCastOutput output, ref RayCastInput input, ref Transform transform, + int childIndex) + { + output = new RayCastOutput(); + + // Put the ray into the polygon's frame of reference. + Vector2 p1 = MathUtils.MultiplyT(ref transform.R, input.Point1 - transform.Position); + Vector2 p2 = MathUtils.MultiplyT(ref transform.R, input.Point2 - transform.Position); + Vector2 d = p2 - p1; + + float lower = 0.0f, upper = input.MaxFraction; + + int index = -1; + + for (int i = 0; i < Vertices.Count; ++i) + { + // p = p1 + a * d + // dot(normal, p - v) = 0 + // dot(normal, p1 - v) + a * dot(normal, d) = 0 + float numerator = Vector2.Dot(Normals[i], Vertices[i] - p1); + float denominator = Vector2.Dot(Normals[i], d); + + if (denominator == 0.0f) + { + if (numerator < 0.0f) + { + return false; + } + } + else + { + // Note: we want this predicate without division: + // lower < numerator / denominator, where denominator < 0 + // Since denominator < 0, we have to flip the inequality: + // lower < numerator / denominator <==> denominator * lower > numerator. + if (denominator < 0.0f && numerator < lower * denominator) + { + // Increase lower. + // The segment enters this half-space. + lower = numerator / denominator; + index = i; + } + else if (denominator > 0.0f && numerator < upper * denominator) + { + // Decrease upper. + // The segment exits this half-space. + upper = numerator / denominator; + } + } + + // The use of epsilon here causes the assert on lower to trip + // in some cases. Apparently the use of epsilon was to make edge + // shapes work, but now those are handled separately. + //if (upper < lower - b2_epsilon) + if (upper < lower) + { + return false; + } + } + + Debug.Assert(0.0f <= lower && lower <= input.MaxFraction); + + if (index >= 0) + { + output.Fraction = lower; + output.Normal = MathUtils.Multiply(ref transform.R, Normals[index]); + return true; + } + + return false; + } + + /// + /// Given a transform, compute the associated axis aligned bounding box for a child shape. + /// + /// The aabb results. + /// The world transform of the shape. + /// The child shape index. + public override void ComputeAABB(out AABB aabb, ref Transform transform, int childIndex) + { + Vector2 lower = MathUtils.Multiply(ref transform, Vertices[0]); + Vector2 upper = lower; + + for (int i = 1; i < Vertices.Count; ++i) + { + Vector2 v = MathUtils.Multiply(ref transform, Vertices[i]); + lower = Vector2.Min(lower, v); + upper = Vector2.Max(upper, v); + } + + Vector2 r = new Vector2(Radius, Radius); + aabb.LowerBound = lower - r; + aabb.UpperBound = upper + r; + } + + public bool CompareTo(PolygonShape shape) + { + if (Vertices.Count != shape.Vertices.Count) + return false; + + for (int i = 0; i < Vertices.Count; i++) + { + if (Vertices[i] != shape.Vertices[i]) + return false; + } + + return (Radius == shape.Radius && + MassData == shape.MassData); + } + + public override float ComputeSubmergedArea(Vector2 normal, float offset, Transform xf, out Vector2 sc) + { + sc = Vector2.Zero; + + //Transform plane into shape co-ordinates + Vector2 normalL = MathUtils.MultiplyT(ref xf.R, normal); + float offsetL = offset - Vector2.Dot(normal, xf.Position); + + float[] depths = new float[Settings.MaxPolygonVertices]; + int diveCount = 0; + int intoIndex = -1; + int outoIndex = -1; + + bool lastSubmerged = false; + int i; + for (i = 0; i < Vertices.Count; i++) + { + depths[i] = Vector2.Dot(normalL, Vertices[i]) - offsetL; + bool isSubmerged = depths[i] < -Settings.Epsilon; + if (i > 0) + { + if (isSubmerged) + { + if (!lastSubmerged) + { + intoIndex = i - 1; + diveCount++; + } + } + else + { + if (lastSubmerged) + { + outoIndex = i - 1; + diveCount++; + } + } + } + lastSubmerged = isSubmerged; + } + switch (diveCount) + { + case 0: + if (lastSubmerged) + { + //Completely submerged + sc = MathUtils.Multiply(ref xf, MassData.Centroid); + return MassData.Mass / Density; + } + else + { + //Completely dry + return 0; + } +#pragma warning disable 162 + break; +#pragma warning restore 162 + case 1: + if (intoIndex == -1) + { + intoIndex = Vertices.Count - 1; + } + else + { + outoIndex = Vertices.Count - 1; + } + break; + } + int intoIndex2 = (intoIndex + 1) % Vertices.Count; + int outoIndex2 = (outoIndex + 1) % Vertices.Count; + + float intoLambda = (0 - depths[intoIndex]) / (depths[intoIndex2] - depths[intoIndex]); + float outoLambda = (0 - depths[outoIndex]) / (depths[outoIndex2] - depths[outoIndex]); + + Vector2 intoVec = new Vector2( + Vertices[intoIndex].X * (1 - intoLambda) + Vertices[intoIndex2].X * intoLambda, + Vertices[intoIndex].Y * (1 - intoLambda) + Vertices[intoIndex2].Y * intoLambda); + Vector2 outoVec = new Vector2( + Vertices[outoIndex].X * (1 - outoLambda) + Vertices[outoIndex2].X * outoLambda, + Vertices[outoIndex].Y * (1 - outoLambda) + Vertices[outoIndex2].Y * outoLambda); + + //Initialize accumulator + float area = 0; + Vector2 center = new Vector2(0, 0); + Vector2 p2 = Vertices[intoIndex2]; + Vector2 p3; + + float k_inv3 = 1.0f / 3.0f; + + //An awkward loop from intoIndex2+1 to outIndex2 + i = intoIndex2; + while (i != outoIndex2) + { + i = (i + 1) % Vertices.Count; + if (i == outoIndex2) + p3 = outoVec; + else + p3 = Vertices[i]; + //Add the triangle formed by intoVec,p2,p3 + { + Vector2 e1 = p2 - intoVec; + Vector2 e2 = p3 - intoVec; + + float D = MathUtils.Cross(e1, e2); + + float triangleArea = 0.5f * D; + + area += triangleArea; + + // Area weighted centroid + center += triangleArea * k_inv3 * (intoVec + p2 + p3); + } + // + p2 = p3; + } + + //Normalize and transform centroid + center *= 1.0f / area; + + sc = MathUtils.Multiply(ref xf, center); + + return area; + } + } +} \ No newline at end of file diff --git a/Collision/Shapes/Shape.cs b/Collision/Shapes/Shape.cs new file mode 100644 index 0000000..49a1d4d --- /dev/null +++ b/Collision/Shapes/Shape.cs @@ -0,0 +1,222 @@ +/* +* 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 FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision.Shapes +{ + /// + /// This holds the mass data computed for a shape. + /// + public struct MassData : IEquatable + { + /// + /// The area of the shape + /// + public float Area; + + /// + /// The position of the shape's centroid relative to the shape's origin. + /// + public Vector2 Centroid; + + /// + /// The rotational inertia of the shape about the local origin. + /// + public float Inertia; + + /// + /// The mass of the shape, usually in kilograms. + /// + public float Mass; + + #region IEquatable Members + + public bool Equals(MassData other) + { + return this == other; + } + + #endregion + + public static bool operator ==(MassData left, MassData right) + { + return (left.Area == right.Area && left.Mass == right.Mass && left.Centroid == right.Centroid && + left.Inertia == right.Inertia); + } + + public static bool operator !=(MassData left, MassData right) + { + return !(left == right); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (obj.GetType() != typeof(MassData)) return false; + return Equals((MassData)obj); + } + + public override int GetHashCode() + { + unchecked + { + int result = Area.GetHashCode(); + result = (result * 397) ^ Centroid.GetHashCode(); + result = (result * 397) ^ Inertia.GetHashCode(); + result = (result * 397) ^ Mass.GetHashCode(); + return result; + } + } + } + + public enum ShapeType + { + Unknown = -1, + Circle = 0, + Edge = 1, + Polygon = 2, + Loop = 3, + TypeCount = 4, + } + + /// + /// A shape is used for collision detection. You can create a shape however you like. + /// Shapes used for simulation in World are created automatically when a Fixture + /// is created. Shapes may encapsulate a one or more child shapes. + /// + public abstract class Shape + { + private static int _shapeIdCounter; + public MassData MassData; + public int ShapeId; + + internal float _density; + internal float _radius; + + protected Shape(float density) + { + _density = density; + ShapeType = ShapeType.Unknown; + ShapeId = _shapeIdCounter++; + } + + /// + /// Get the type of this shape. + /// + /// The type of the shape. + public ShapeType ShapeType { get; internal set; } + + /// + /// Get the number of child primitives. + /// + /// + public abstract int ChildCount { get; } + + /// + /// Gets or sets the density. + /// + /// The density. + public float Density + { + get { return _density; } + set + { + _density = value; + ComputeProperties(); + } + } + + /// + /// Radius of the Shape + /// + public float Radius + { + get { return _radius; } + set + { + _radius = value; + ComputeProperties(); + } + } + + /// + /// Clone the concrete shape + /// + /// A clone of the shape + public abstract Shape Clone(); + + /// + /// Test a point for containment in this shape. This only works for convex shapes. + /// + /// The shape world transform. + /// a point in world coordinates. + /// True if the point is inside the shape + public abstract bool TestPoint(ref Transform transform, ref Vector2 point); + + /// + /// Cast a ray against a child shape. + /// + /// The ray-cast results. + /// The ray-cast input parameters. + /// The transform to be applied to the shape. + /// The child shape index. + /// True if the ray-cast hits the shape + public abstract bool RayCast(out RayCastOutput output, ref RayCastInput input, ref Transform transform, + int childIndex); + + /// + /// Given a transform, compute the associated axis aligned bounding box for a child shape. + /// + /// The aabb results. + /// The world transform of the shape. + /// The child shape index. + public abstract void ComputeAABB(out AABB aabb, ref Transform transform, int childIndex); + + /// + /// Compute the mass properties of this shape using its dimensions and density. + /// The inertia tensor is computed about the local origin, not the centroid. + /// + public abstract void ComputeProperties(); + + public bool CompareTo(Shape shape) + { + if (shape is PolygonShape && this is PolygonShape) + return ((PolygonShape)this).CompareTo((PolygonShape)shape); + + if (shape is CircleShape && this is CircleShape) + return ((CircleShape)this).CompareTo((CircleShape)shape); + + if (shape is EdgeShape && this is EdgeShape) + return ((EdgeShape)this).CompareTo((EdgeShape)shape); + + return false; + } + + public abstract float ComputeSubmergedArea(Vector2 normal, float offset, Transform xf, out Vector2 sc); + } +} \ No newline at end of file diff --git a/Collision/TimeOfImpact.cs b/Collision/TimeOfImpact.cs new file mode 100644 index 0000000..ace74de --- /dev/null +++ b/Collision/TimeOfImpact.cs @@ -0,0 +1,500 @@ +/* +* 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 FarseerPhysics.Common; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Collision +{ + /// + /// Input parameters for CalculateTimeOfImpact + /// + public class TOIInput + { + public DistanceProxy ProxyA = new DistanceProxy(); + public DistanceProxy ProxyB = new DistanceProxy(); + public Sweep SweepA; + public Sweep SweepB; + public float TMax; // defines sweep interval [0, tMax] + } + + public enum TOIOutputState + { + Unknown, + Failed, + Overlapped, + Touching, + Seperated, + } + + public struct TOIOutput + { + public TOIOutputState State; + public float T; + } + + public enum SeparationFunctionType + { + Points, + FaceA, + FaceB + } + + public static class SeparationFunction + { + private static Vector2 _axis; + private static Vector2 _localPoint; + private static DistanceProxy _proxyA = new DistanceProxy(); + private static DistanceProxy _proxyB = new DistanceProxy(); + private static Sweep _sweepA, _sweepB; + private static SeparationFunctionType _type; + + public static void Set(ref SimplexCache cache, + DistanceProxy proxyA, ref Sweep sweepA, + DistanceProxy proxyB, ref Sweep sweepB, + float t1) + { + _localPoint = Vector2.Zero; + _proxyA = proxyA; + _proxyB = proxyB; + int count = cache.Count; + Debug.Assert(0 < count && count < 3); + + _sweepA = sweepA; + _sweepB = sweepB; + + Transform xfA, xfB; + _sweepA.GetTransform(out xfA, t1); + _sweepB.GetTransform(out xfB, t1); + + if (count == 1) + { + _type = SeparationFunctionType.Points; + Vector2 localPointA = _proxyA.Vertices[cache.IndexA[0]]; + Vector2 localPointB = _proxyB.Vertices[cache.IndexB[0]]; + Vector2 pointA = MathUtils.Multiply(ref xfA, localPointA); + Vector2 pointB = MathUtils.Multiply(ref xfB, localPointB); + _axis = pointB - pointA; + _axis.Normalize(); + return; + } + else if (cache.IndexA[0] == cache.IndexA[1]) + { + // Two points on B and one on A. + _type = SeparationFunctionType.FaceB; + Vector2 localPointB1 = proxyB.Vertices[cache.IndexB[0]]; + Vector2 localPointB2 = proxyB.Vertices[cache.IndexB[1]]; + + Vector2 a = localPointB2 - localPointB1; + _axis = new Vector2(a.Y, -a.X); + _axis.Normalize(); + Vector2 normal = MathUtils.Multiply(ref xfB.R, _axis); + + _localPoint = 0.5f * (localPointB1 + localPointB2); + Vector2 pointB = MathUtils.Multiply(ref xfB, _localPoint); + + Vector2 localPointA = proxyA.Vertices[cache.IndexA[0]]; + Vector2 pointA = MathUtils.Multiply(ref xfA, localPointA); + + float s = Vector2.Dot(pointA - pointB, normal); + if (s < 0.0f) + { + _axis = -_axis; + s = -s; + } + return; + } + else + { + // Two points on A and one or two points on B. + _type = SeparationFunctionType.FaceA; + Vector2 localPointA1 = _proxyA.Vertices[cache.IndexA[0]]; + Vector2 localPointA2 = _proxyA.Vertices[cache.IndexA[1]]; + + Vector2 a = localPointA2 - localPointA1; + _axis = new Vector2(a.Y, -a.X); + _axis.Normalize(); + Vector2 normal = MathUtils.Multiply(ref xfA.R, _axis); + + _localPoint = 0.5f * (localPointA1 + localPointA2); + Vector2 pointA = MathUtils.Multiply(ref xfA, _localPoint); + + Vector2 localPointB = _proxyB.Vertices[cache.IndexB[0]]; + Vector2 pointB = MathUtils.Multiply(ref xfB, localPointB); + + float s = Vector2.Dot(pointB - pointA, normal); + if (s < 0.0f) + { + _axis = -_axis; + s = -s; + } + return; + } + } + + public static float FindMinSeparation(out int indexA, out int indexB, float t) + { + Transform xfA, xfB; + _sweepA.GetTransform(out xfA, t); + _sweepB.GetTransform(out xfB, t); + + switch (_type) + { + case SeparationFunctionType.Points: + { + Vector2 axisA = MathUtils.MultiplyT(ref xfA.R, _axis); + Vector2 axisB = MathUtils.MultiplyT(ref xfB.R, -_axis); + + indexA = _proxyA.GetSupport(axisA); + indexB = _proxyB.GetSupport(axisB); + + Vector2 localPointA = _proxyA.Vertices[indexA]; + Vector2 localPointB = _proxyB.Vertices[indexB]; + + Vector2 pointA = MathUtils.Multiply(ref xfA, localPointA); + Vector2 pointB = MathUtils.Multiply(ref xfB, localPointB); + + float separation = Vector2.Dot(pointB - pointA, _axis); + return separation; + } + + case SeparationFunctionType.FaceA: + { + Vector2 normal = MathUtils.Multiply(ref xfA.R, _axis); + Vector2 pointA = MathUtils.Multiply(ref xfA, _localPoint); + + Vector2 axisB = MathUtils.MultiplyT(ref xfB.R, -normal); + + indexA = -1; + indexB = _proxyB.GetSupport(axisB); + + Vector2 localPointB = _proxyB.Vertices[indexB]; + Vector2 pointB = MathUtils.Multiply(ref xfB, localPointB); + + float separation = Vector2.Dot(pointB - pointA, normal); + return separation; + } + + case SeparationFunctionType.FaceB: + { + Vector2 normal = MathUtils.Multiply(ref xfB.R, _axis); + Vector2 pointB = MathUtils.Multiply(ref xfB, _localPoint); + + Vector2 axisA = MathUtils.MultiplyT(ref xfA.R, -normal); + + indexB = -1; + indexA = _proxyA.GetSupport(axisA); + + Vector2 localPointA = _proxyA.Vertices[indexA]; + Vector2 pointA = MathUtils.Multiply(ref xfA, localPointA); + + float separation = Vector2.Dot(pointA - pointB, normal); + return separation; + } + + default: + Debug.Assert(false); + indexA = -1; + indexB = -1; + return 0.0f; + } + } + + public static float Evaluate(int indexA, int indexB, float t) + { + Transform xfA, xfB; + _sweepA.GetTransform(out xfA, t); + _sweepB.GetTransform(out xfB, t); + + switch (_type) + { + case SeparationFunctionType.Points: + { + Vector2 axisA = MathUtils.MultiplyT(ref xfA.R, _axis); + Vector2 axisB = MathUtils.MultiplyT(ref xfB.R, -_axis); + + Vector2 localPointA = _proxyA.Vertices[indexA]; + Vector2 localPointB = _proxyB.Vertices[indexB]; + + Vector2 pointA = MathUtils.Multiply(ref xfA, localPointA); + Vector2 pointB = MathUtils.Multiply(ref xfB, localPointB); + float separation = Vector2.Dot(pointB - pointA, _axis); + + return separation; + } + + case SeparationFunctionType.FaceA: + { + Vector2 normal = MathUtils.Multiply(ref xfA.R, _axis); + Vector2 pointA = MathUtils.Multiply(ref xfA, _localPoint); + + Vector2 axisB = MathUtils.MultiplyT(ref xfB.R, -normal); + + Vector2 localPointB = _proxyB.Vertices[indexB]; + Vector2 pointB = MathUtils.Multiply(ref xfB, localPointB); + + float separation = Vector2.Dot(pointB - pointA, normal); + return separation; + } + + case SeparationFunctionType.FaceB: + { + Vector2 normal = MathUtils.Multiply(ref xfB.R, _axis); + Vector2 pointB = MathUtils.Multiply(ref xfB, _localPoint); + + Vector2 axisA = MathUtils.MultiplyT(ref xfA.R, -normal); + + Vector2 localPointA = _proxyA.Vertices[indexA]; + Vector2 pointA = MathUtils.Multiply(ref xfA, localPointA); + + float separation = Vector2.Dot(pointA - pointB, normal); + return separation; + } + + default: + Debug.Assert(false); + return 0.0f; + } + } + } + + public static class TimeOfImpact + { + // CCD via the local separating axis method. This seeks progression + // by computing the largest time at which separation is maintained. + + public static int TOICalls, TOIIters, TOIMaxIters; + public static int TOIRootIters, TOIMaxRootIters; + private static DistanceInput _distanceInput = new DistanceInput(); + + /// + /// Compute the upper bound on time before two shapes penetrate. Time is represented as + /// a fraction between [0,tMax]. This uses a swept separating axis and may miss some intermediate, + /// non-tunneling collision. If you change the time interval, you should call this function + /// again. + /// Note: use Distance() to compute the contact point and normal at the time of impact. + /// + /// The output. + /// The input. + public static void CalculateTimeOfImpact(out TOIOutput output, TOIInput input) + { + ++TOICalls; + + output = new TOIOutput(); + output.State = TOIOutputState.Unknown; + output.T = input.TMax; + + Sweep sweepA = input.SweepA; + Sweep sweepB = input.SweepB; + + // Large rotations can make the root finder fail, so we normalize the + // sweep angles. + sweepA.Normalize(); + sweepB.Normalize(); + + float tMax = input.TMax; + + float totalRadius = input.ProxyA.Radius + input.ProxyB.Radius; + float target = Math.Max(Settings.LinearSlop, totalRadius - 3.0f * Settings.LinearSlop); + const float tolerance = 0.25f * Settings.LinearSlop; + Debug.Assert(target > tolerance); + + float t1 = 0.0f; + const int k_maxIterations = 20; + int iter = 0; + + // Prepare input for distance query. + SimplexCache cache; + _distanceInput.ProxyA = input.ProxyA; + _distanceInput.ProxyB = input.ProxyB; + _distanceInput.UseRadii = false; + + // The outer loop progressively attempts to compute new separating axes. + // This loop terminates when an axis is repeated (no progress is made). + for (; ; ) + { + Transform xfA, xfB; + sweepA.GetTransform(out xfA, t1); + sweepB.GetTransform(out xfB, t1); + + // Get the distance between shapes. We can also use the results + // to get a separating axis. + _distanceInput.TransformA = xfA; + _distanceInput.TransformB = xfB; + DistanceOutput distanceOutput; + Distance.ComputeDistance(out distanceOutput, out cache, _distanceInput); + + // If the shapes are overlapped, we give up on continuous collision. + if (distanceOutput.Distance <= 0.0f) + { + // Failure! + output.State = TOIOutputState.Overlapped; + output.T = 0.0f; + break; + } + + if (distanceOutput.Distance < target + tolerance) + { + // Victory! + output.State = TOIOutputState.Touching; + output.T = t1; + break; + } + + SeparationFunction.Set(ref cache, input.ProxyA, ref sweepA, input.ProxyB, ref sweepB, t1); + + // Compute the TOI on the separating axis. We do this by successively + // resolving the deepest point. This loop is bounded by the number of vertices. + bool done = false; + float t2 = tMax; + int pushBackIter = 0; + for (; ; ) + { + // Find the deepest point at t2. Store the witness point indices. + int indexA, indexB; + float s2 = SeparationFunction.FindMinSeparation(out indexA, out indexB, t2); + + // Is the final configuration separated? + if (s2 > target + tolerance) + { + // Victory! + output.State = TOIOutputState.Seperated; + output.T = tMax; + done = true; + break; + } + + // Has the separation reached tolerance? + if (s2 > target - tolerance) + { + // Advance the sweeps + t1 = t2; + break; + } + + // Compute the initial separation of the witness points. + float s1 = SeparationFunction.Evaluate(indexA, indexB, t1); + + // Check for initial overlap. This might happen if the root finder + // runs out of iterations. + if (s1 < target - tolerance) + { + output.State = TOIOutputState.Failed; + output.T = t1; + done = true; + break; + } + + // Check for touching + if (s1 <= target + tolerance) + { + // Victory! t1 should hold the TOI (could be 0.0). + output.State = TOIOutputState.Touching; + output.T = t1; + done = true; + break; + } + + // Compute 1D root of: f(x) - target = 0 + int rootIterCount = 0; + float a1 = t1, a2 = t2; + for (; ; ) + { + // Use a mix of the secant rule and bisection. + float t; + if ((rootIterCount & 1) != 0) + { + // Secant rule to improve convergence. + t = a1 + (target - s1) * (a2 - a1) / (s2 - s1); + } + else + { + // Bisection to guarantee progress. + t = 0.5f * (a1 + a2); + } + + float s = SeparationFunction.Evaluate(indexA, indexB, t); + + if (Math.Abs(s - target) < tolerance) + { + // t2 holds a tentative value for t1 + t2 = t; + break; + } + + // Ensure we continue to bracket the root. + if (s > target) + { + a1 = t; + s1 = s; + } + else + { + a2 = t; + s2 = s; + } + + ++rootIterCount; + ++TOIRootIters; + + if (rootIterCount == 50) + { + break; + } + } + + TOIMaxRootIters = Math.Max(TOIMaxRootIters, rootIterCount); + + ++pushBackIter; + + if (pushBackIter == Settings.MaxPolygonVertices) + { + break; + } + } + + ++iter; + ++TOIIters; + + if (done) + { + break; + } + + if (iter == k_maxIterations) + { + // Root finder got stuck. Semi-victory. + output.State = TOIOutputState.Failed; + output.T = t1; + break; + } + } + + TOIMaxIters = Math.Max(TOIMaxIters, iter); + } + } +} \ No newline at end of file diff --git a/Common/ConvexHull/ChainHull.cs b/Common/ConvexHull/ChainHull.cs new file mode 100644 index 0000000..f3bda07 --- /dev/null +++ b/Common/ConvexHull/ChainHull.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.ConvexHull +{ + public static class ChainHull + { + //Andrew's monotone chain 2D convex hull algorithm. + //Copyright 2001, softSurfer (www.softsurfer.com) + + /// + /// Gets the convex hull. + /// + /// + /// http://www.softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm + /// + /// + public static Vertices GetConvexHull(Vertices P) + { + P.Sort(new PointComparer()); + + Vector2[] H = new Vector2[P.Count]; + Vertices res = new Vertices(); + + int n = P.Count; + + int bot, top = -1; // indices for bottom and top of the stack + int i; // array scan index + + // Get the indices of points with min x-coord and min|max y-coord + int minmin = 0, minmax; + float xmin = P[0].X; + for (i = 1; i < n; i++) + if (P[i].X != xmin) break; + minmax = i - 1; + if (minmax == n - 1) + { + // degenerate case: all x-coords == xmin + H[++top] = P[minmin]; + if (P[minmax].Y != P[minmin].Y) // a nontrivial segment + H[++top] = P[minmax]; + H[++top] = P[minmin]; // add polygon endpoint + + for (int j = 0; j < top + 1; j++) + { + res.Add(H[j]); + } + + return res; + } + + top = res.Count - 1; + + // Get the indices of points with max x-coord and min|max y-coord + int maxmin, maxmax = n - 1; + float xmax = P[n - 1].X; + for (i = n - 2; i >= 0; i--) + if (P[i].X != xmax) break; + maxmin = i + 1; + + // Compute the lower hull on the stack H + H[++top] = P[minmin]; // push minmin point onto stack + i = minmax; + while (++i <= maxmin) + { + // the lower line joins P[minmin] with P[maxmin] + if (MathUtils.Area(P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin) + continue; // ignore P[i] above or on the lower line + + while (top > 0) // there are at least 2 points on the stack + { + // test if P[i] is left of the line at the stack top + if (MathUtils.Area(H[top - 1], H[top], P[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + H[++top] = P[i]; // push P[i] onto stack + } + + // Next, compute the upper hull on the stack H above the bottom hull + if (maxmax != maxmin) // if distinct xmax points + H[++top] = P[maxmax]; // push maxmax point onto stack + bot = top; // the bottom point of the upper hull stack + i = maxmin; + while (--i >= minmax) + { + // the upper line joins P[maxmax] with P[minmax] + if (MathUtils.Area(P[maxmax], P[minmax], P[i]) >= 0 && i > minmax) + continue; // ignore P[i] below or on the upper line + + while (top > bot) // at least 2 points on the upper stack + { + // test if P[i] is left of the line at the stack top + if (MathUtils.Area(H[top - 1], H[top], P[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + H[++top] = P[i]; // push P[i] onto stack + } + if (minmax != minmin) + H[++top] = P[minmin]; // push joining endpoint onto stack + + for (int j = 0; j < top + 1; j++) + { + res.Add(H[j]); + } + + return res; + } + + #region Nested type: PointComparer + + public class PointComparer : Comparer + { + public override int Compare(Vector2 a, Vector2 b) + { + int f = a.X.CompareTo(b.X); + return f != 0 ? f : a.Y.CompareTo(b.Y); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Common/ConvexHull/GiftWrap.cs b/Common/ConvexHull/GiftWrap.cs new file mode 100644 index 0000000..7aa2aed --- /dev/null +++ b/Common/ConvexHull/GiftWrap.cs @@ -0,0 +1,99 @@ +using System; + +namespace FarseerPhysics.Common.ConvexHull +{ + public static class GiftWrap + { + // From Eric Jordan's convex decomposition library (box2D rev 32) + + /// + /// Find the convex hull of a point cloud using "Gift-wrap" algorithm - start + /// with an extremal point, and walk around the outside edge by testing + /// angles. + /// + /// Runs in O(N*S) time where S is number of sides of resulting polygon. + /// Worst case: point cloud is all vertices of convex polygon: O(N^2). + /// There may be faster algorithms to do this, should you need one - + /// this is just the simplest. You can get O(N log N) expected time if you + /// try, I think, and O(N) if you restrict inputs to simple polygons. + /// Returns null if number of vertices passed is less than 3. + /// Results should be passed through convex decomposition afterwards + /// to ensure that each shape has few enough points to be used in Box2d. + /// + /// Warning: May be buggy with colinear points on hull. + /// + /// The vertices. + /// + public static Vertices GetConvexHull(Vertices vertices) + { + if (vertices.Count < 3) + return vertices; + + int[] edgeList = new int[vertices.Count]; + int numEdges = 0; + + float minY = float.MaxValue; + int minYIndex = vertices.Count; + for (int i = 0; i < vertices.Count; ++i) + { + if (vertices[i].Y < minY) + { + minY = vertices[i].Y; + minYIndex = i; + } + } + + int startIndex = minYIndex; + int winIndex = -1; + float dx = -1.0f; + float dy = 0.0f; + while (winIndex != minYIndex) + { + float maxDot = -2.0f; + float nrm; + + for (int i = 0; i < vertices.Count; ++i) + { + if (i == startIndex) + continue; + float newdx = vertices[i].X - vertices[startIndex].X; + float newdy = vertices[i].Y - vertices[startIndex].Y; + nrm = (float)Math.Sqrt(newdx * newdx + newdy * newdy); + nrm = (nrm == 0.0f) ? 1.0f : nrm; + newdx /= nrm; + newdy /= nrm; + + //Dot products act as proxy for angle + //without requiring inverse trig. + float newDot = newdx * dx + newdy * dy; + if (newDot > maxDot) + { + maxDot = newDot; + winIndex = i; + } + } + edgeList[numEdges++] = winIndex; + dx = vertices[winIndex].X - vertices[startIndex].X; + dy = vertices[winIndex].Y - vertices[startIndex].Y; + nrm = (float)Math.Sqrt(dx * dx + dy * dy); + nrm = (nrm == 0.0f) ? 1.0f : nrm; + dx /= nrm; + dy /= nrm; + startIndex = winIndex; + } + + Vertices returnVal = new Vertices(numEdges); + + for (int i = 0; i < numEdges; i++) + { + returnVal.Add(vertices[edgeList[i]]); + //Debug.WriteLine(string.Format("{0}, {1}", vertices[edgeList[i]].X, vertices[edgeList[i]].Y)); + } + + //Not sure if we need this + //returnVal.MergeParallelEdges(Settings.b2_angularSlop); + + return returnVal; + } + } +} \ No newline at end of file diff --git a/Common/ConvexHull/Melkman.cs b/Common/ConvexHull/Melkman.cs new file mode 100644 index 0000000..749e801 --- /dev/null +++ b/Common/ConvexHull/Melkman.cs @@ -0,0 +1,122 @@ +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.ConvexHull +{ + public static class Melkman + { + //Melkman based convex hull algorithm contributed by Cowdozer + + /// + /// Creates a convex hull. + /// Note: + /// 1. Vertices must be of a simple polygon, i.e. edges do not overlap. + /// 2. Melkman does not work on point clouds + /// + /// + /// Implemented using Melkman's Convex Hull Algorithm - O(n) time complexity. + /// Reference: http://www.ams.sunysb.edu/~jsbm/courses/345/melkman.pdf + /// + /// A convex hull in counterclockwise winding order. + public static Vertices GetConvexHull(Vertices vertices) + { + //With less than 3 vertices, this is about the best we can do for a convex hull + if (vertices.Count < 3) + return vertices; + + //We'll never need a queue larger than the current number of Vertices +1 + //Create double-ended queue + Vector2[] deque = new Vector2[vertices.Count + 1]; + int qf = 3, qb = 0; //Queue front index, queue back index + int qfm1, qbm1; //qfm1 = second element, qbm1 = second last element + + //Start by placing first 3 vertices in convex CCW order + int startIndex = 3; + float k = MathUtils.Area(vertices[0], vertices[1], vertices[2]); + if (k == 0) + { + //Vertices are collinear. + deque[0] = vertices[0]; + deque[1] = vertices[2]; //We can skip vertex 1 because it should be between 0 and 2 + deque[2] = vertices[0]; + qf = 2; + + //Go until the end of the collinear sequence of vertices + for (startIndex = 3; startIndex < vertices.Count; startIndex++) + { + Vector2 tmp = vertices[startIndex]; + if (MathUtils.Area(ref deque[0], ref deque[1], ref tmp) == 0) //This point is also collinear + deque[1] = vertices[startIndex]; + else break; + } + } + else + { + deque[0] = deque[3] = vertices[2]; + if (k > 0) + { + //Is Left. Set deque = {2, 0, 1, 2} + deque[1] = vertices[0]; + deque[2] = vertices[1]; + } + else + { + //Is Right. Set deque = {2, 1, 0, 2} + deque[1] = vertices[1]; + deque[2] = vertices[0]; + } + } + + qfm1 = qf == 0 ? deque.Length - 1 : qf - 1; //qfm1 = qf - 1; + qbm1 = qb == deque.Length - 1 ? 0 : qb + 1; //qbm1 = qb + 1; + + //Add vertices one at a time and adjust convex hull as needed + for (int i = startIndex; i < vertices.Count; i++) + { + Vector2 nextPt = vertices[i]; + + //Ignore if it is already within the convex hull we have constructed + if (MathUtils.Area(ref deque[qfm1], ref deque[qf], ref nextPt) > 0 && + MathUtils.Area(ref deque[qb], ref deque[qbm1], ref nextPt) > 0) + continue; + + //Pop front until convex + while (!(MathUtils.Area(ref deque[qfm1], ref deque[qf], ref nextPt) > 0)) + { + //Pop the front element from the queue + qf = qfm1; //qf--; + qfm1 = qf == 0 ? deque.Length - 1 : qf - 1; //qfm1 = qf - 1; + } + //Add vertex to the front of the queue + qf = qf == deque.Length - 1 ? 0 : qf + 1; //qf++; + qfm1 = qf == 0 ? deque.Length - 1 : qf - 1; //qfm1 = qf - 1; + deque[qf] = nextPt; + + //Pop back until convex + while (!(MathUtils.Area(ref deque[qb], ref deque[qbm1], ref nextPt) > 0)) + { + //Pop the back element from the queue + qb = qbm1; //qb++; + qbm1 = qb == deque.Length - 1 ? 0 : qb + 1; //qbm1 = qb + 1; + } + //Add vertex to the back of the queue + qb = qb == 0 ? deque.Length - 1 : qb - 1; //qb--; + qbm1 = qb == deque.Length - 1 ? 0 : qb + 1; //qbm1 = qb + 1; + deque[qb] = nextPt; + } + + //Create the convex hull from what is left in the deque + Vertices convexHull = new Vertices(vertices.Count + 1); + if (qb < qf) + for (int i = qb; i < qf; i++) + convexHull.Add(deque[i]); + else + { + for (int i = 0; i < qf; i++) + convexHull.Add(deque[i]); + for (int i = qb; i < deque.Length; i++) + convexHull.Add(deque[i]); + } + return convexHull; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/BayazitDecomposer.cs b/Common/Decomposition/BayazitDecomposer.cs new file mode 100644 index 0000000..889e678 --- /dev/null +++ b/Common/Decomposition/BayazitDecomposer.cs @@ -0,0 +1,253 @@ +using System.Collections.Generic; +using FarseerPhysics.Common.PolygonManipulation; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.Decomposition +{ + //From phed rev 36 + + /// + /// Convex decomposition algorithm created by Mark Bayazit (http://mnbayazit.com/) + /// For more information about this algorithm, see http://mnbayazit.com/406/bayazit + /// + public static class BayazitDecomposer + { + private static Vector2 At(int i, Vertices vertices) + { + int s = vertices.Count; + return vertices[i < 0 ? s - (-i % s) : i % s]; + } + + private static Vertices Copy(int i, int j, Vertices vertices) + { + Vertices p = new Vertices(); + while (j < i) j += vertices.Count; + //p.reserve(j - i + 1); + for (; i <= j; ++i) + { + p.Add(At(i, vertices)); + } + return p; + } + + /// + /// Decompose the polygon into several smaller non-concave polygon. + /// If the polygon is already convex, it will return the original polygon, unless it is over Settings.MaxPolygonVertices. + /// Precondition: Counter Clockwise polygon + /// + /// + /// + public static List ConvexPartition(Vertices vertices) + { + //We force it to CCW as it is a precondition in this algorithm. + vertices.ForceCounterClockWise(); + + List list = new List(); + float d, lowerDist, upperDist; + Vector2 p; + Vector2 lowerInt = new Vector2(); + Vector2 upperInt = new Vector2(); // intersection points + int lowerIndex = 0, upperIndex = 0; + Vertices lowerPoly, upperPoly; + + for (int i = 0; i < vertices.Count; ++i) + { + if (Reflex(i, vertices)) + { + lowerDist = upperDist = float.MaxValue; // std::numeric_limits::max(); + for (int j = 0; j < vertices.Count; ++j) + { + // if line intersects with an edge + if (Left(At(i - 1, vertices), At(i, vertices), At(j, vertices)) && + RightOn(At(i - 1, vertices), At(i, vertices), At(j - 1, vertices))) + { + // find the point of intersection + p = LineTools.LineIntersect(At(i - 1, vertices), At(i, vertices), At(j, vertices), + At(j - 1, vertices)); + if (Right(At(i + 1, vertices), At(i, vertices), p)) + { + // make sure it's inside the poly + d = SquareDist(At(i, vertices), p); + if (d < lowerDist) + { + // keep only the closest intersection + lowerDist = d; + lowerInt = p; + lowerIndex = j; + } + } + } + + if (Left(At(i + 1, vertices), At(i, vertices), At(j + 1, vertices)) && + RightOn(At(i + 1, vertices), At(i, vertices), At(j, vertices))) + { + p = LineTools.LineIntersect(At(i + 1, vertices), At(i, vertices), At(j, vertices), + At(j + 1, vertices)); + if (Left(At(i - 1, vertices), At(i, vertices), p)) + { + d = SquareDist(At(i, vertices), p); + if (d < upperDist) + { + upperDist = d; + upperIndex = j; + upperInt = p; + } + } + } + } + + // if there are no vertices to connect to, choose a point in the middle + if (lowerIndex == (upperIndex + 1) % vertices.Count) + { + Vector2 sp = ((lowerInt + upperInt) / 2); + + lowerPoly = Copy(i, upperIndex, vertices); + lowerPoly.Add(sp); + upperPoly = Copy(lowerIndex, i, vertices); + upperPoly.Add(sp); + } + else + { + double highestScore = 0, bestIndex = lowerIndex; + while (upperIndex < lowerIndex) upperIndex += vertices.Count; + for (int j = lowerIndex; j <= upperIndex; ++j) + { + if (CanSee(i, j, vertices)) + { + double score = 1 / (SquareDist(At(i, vertices), At(j, vertices)) + 1); + if (Reflex(j, vertices)) + { + if (RightOn(At(j - 1, vertices), At(j, vertices), At(i, vertices)) && + LeftOn(At(j + 1, vertices), At(j, vertices), At(i, vertices))) + { + score += 3; + } + else + { + score += 2; + } + } + else + { + score += 1; + } + if (score > highestScore) + { + bestIndex = j; + highestScore = score; + } + } + } + lowerPoly = Copy(i, (int)bestIndex, vertices); + upperPoly = Copy((int)bestIndex, i, vertices); + } + list.AddRange(ConvexPartition(lowerPoly)); + list.AddRange(ConvexPartition(upperPoly)); + return list; + } + } + + // polygon is already convex + if (vertices.Count > Settings.MaxPolygonVertices) + { + lowerPoly = Copy(0, vertices.Count / 2, vertices); + upperPoly = Copy(vertices.Count / 2, 0, vertices); + list.AddRange(ConvexPartition(lowerPoly)); + list.AddRange(ConvexPartition(upperPoly)); + } + else + list.Add(vertices); + + //The polygons are not guaranteed to be without collinear points. We remove + //them to be sure. + for (int i = 0; i < list.Count; i++) + { + list[i] = SimplifyTools.CollinearSimplify(list[i], 0); + } + + //Remove empty vertice collections + for (int i = list.Count - 1; i >= 0; i--) + { + if (list[i].Count == 0) + list.RemoveAt(i); + } + + return list; + } + + private static bool CanSee(int i, int j, Vertices vertices) + { + if (Reflex(i, vertices)) + { + if (LeftOn(At(i, vertices), At(i - 1, vertices), At(j, vertices)) && + RightOn(At(i, vertices), At(i + 1, vertices), At(j, vertices))) return false; + } + else + { + if (RightOn(At(i, vertices), At(i + 1, vertices), At(j, vertices)) || + LeftOn(At(i, vertices), At(i - 1, vertices), At(j, vertices))) return false; + } + if (Reflex(j, vertices)) + { + if (LeftOn(At(j, vertices), At(j - 1, vertices), At(i, vertices)) && + RightOn(At(j, vertices), At(j + 1, vertices), At(i, vertices))) return false; + } + else + { + if (RightOn(At(j, vertices), At(j + 1, vertices), At(i, vertices)) || + LeftOn(At(j, vertices), At(j - 1, vertices), At(i, vertices))) return false; + } + for (int k = 0; k < vertices.Count; ++k) + { + if ((k + 1) % vertices.Count == i || k == i || (k + 1) % vertices.Count == j || k == j) + { + continue; // ignore incident edges + } + Vector2 intersectionPoint; + if (LineTools.LineIntersect(At(i, vertices), At(j, vertices), At(k, vertices), At(k + 1, vertices), out intersectionPoint)) + { + return false; + } + } + return true; + } + + // precondition: ccw + private static bool Reflex(int i, Vertices vertices) + { + return Right(i, vertices); + } + + private static bool Right(int i, Vertices vertices) + { + return Right(At(i - 1, vertices), At(i, vertices), At(i + 1, vertices)); + } + + private static bool Left(Vector2 a, Vector2 b, Vector2 c) + { + return MathUtils.Area(ref a, ref b, ref c) > 0; + } + + private static bool LeftOn(Vector2 a, Vector2 b, Vector2 c) + { + return MathUtils.Area(ref a, ref b, ref c) >= 0; + } + + private static bool Right(Vector2 a, Vector2 b, Vector2 c) + { + return MathUtils.Area(ref a, ref b, ref c) < 0; + } + + private static bool RightOn(Vector2 a, Vector2 b, Vector2 c) + { + return MathUtils.Area(ref a, ref b, ref c) <= 0; + } + + private static float SquareDist(Vector2 a, Vector2 b) + { + float dx = b.X - a.X; + float dy = b.Y - a.Y; + return dx * dx + dy * dy; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs b/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs new file mode 100644 index 0000000..38fdb31 --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/DelaunayTriangle.cs @@ -0,0 +1,420 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Changes from the Java version +// attributification +// Future possibilities +// Flattening out the number of indirections +// Replacing arrays of 3 with fixed-length arrays? +// Replacing bool[3] with a bit array of some sort? +// Bundling everything into an AoS mess? +// Hardcode them all as ABC ? + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Poly2Tri.Triangulation.Delaunay.Sweep; +using Poly2Tri.Triangulation.Util; + +namespace Poly2Tri.Triangulation.Delaunay +{ + public class DelaunayTriangle + { + /** Neighbor pointers */ + + /** Flags to determine if an edge is a Delauney edge */ + public FixedBitArray3 EdgeIsConstrained; + + /** Flags to determine if an edge is a Constrained edge */ + public FixedBitArray3 EdgeIsDelaunay; + public FixedArray3 Neighbors; + + /** Has this triangle been marked as an interior triangle? */ + + public FixedArray3 Points; + + public DelaunayTriangle(TriangulationPoint p1, TriangulationPoint p2, TriangulationPoint p3) + { + Points[0] = p1; + Points[1] = p2; + Points[2] = p3; + } + + public bool IsInterior { get; set; } + + public int IndexOf(TriangulationPoint p) + { + int i = Points.IndexOf(p); + if (i == -1) throw new Exception("Calling index with a point that doesn't exist in triangle"); + return i; + } + + //TODO: Port note - different implementation + public int IndexCW(TriangulationPoint p) + { + int index = IndexOf(p); + switch (index) + { + case 0: + return 2; + case 1: + return 0; + default: + return 1; + } + } + + //TODO: Port note - different implementation + public int IndexCCW(TriangulationPoint p) + { + int index = IndexOf(p); + switch (index) + { + case 0: + return 1; + case 1: + return 2; + default: + return 0; + } + } + + public bool Contains(TriangulationPoint p) + { + return (p == Points[0] || p == Points[1] || p == Points[2]); + } + + public bool Contains(DTSweepConstraint e) + { + return (Contains(e.P) && Contains(e.Q)); + } + + public bool Contains(TriangulationPoint p, TriangulationPoint q) + { + return (Contains(p) && Contains(q)); + } + + /// + /// Update neighbor pointers + /// + /// Point 1 of the shared edge + /// Point 2 of the shared edge + /// This triangle's new neighbor + private void MarkNeighbor(TriangulationPoint p1, TriangulationPoint p2, DelaunayTriangle t) + { + if ((p1 == Points[2] && p2 == Points[1]) || (p1 == Points[1] && p2 == Points[2])) + { + Neighbors[0] = t; + } + else if ((p1 == Points[0] && p2 == Points[2]) || (p1 == Points[2] && p2 == Points[0])) + { + Neighbors[1] = t; + } + else if ((p1 == Points[0] && p2 == Points[1]) || (p1 == Points[1] && p2 == Points[0])) + { + Neighbors[2] = t; + } + else + { + Debug.WriteLine("Neighbor error, please report!"); + // throw new Exception("Neighbor error, please report!"); + } + } + + /// + /// Exhaustive search to update neighbor pointers + /// + public void MarkNeighbor(DelaunayTriangle t) + { + if (t.Contains(Points[1], Points[2])) + { + Neighbors[0] = t; + t.MarkNeighbor(Points[1], Points[2], this); + } + else if (t.Contains(Points[0], Points[2])) + { + Neighbors[1] = t; + t.MarkNeighbor(Points[0], Points[2], this); + } + else if (t.Contains(Points[0], Points[1])) + { + Neighbors[2] = t; + t.MarkNeighbor(Points[0], Points[1], this); + } + else + { + Debug.WriteLine("markNeighbor failed"); + } + } + + public void ClearNeighbors() + { + Neighbors[0] = Neighbors[1] = Neighbors[2] = null; + } + + public void ClearNeighbor(DelaunayTriangle triangle) + { + if (Neighbors[0] == triangle) + { + Neighbors[0] = null; + } + else if (Neighbors[1] == triangle) + { + Neighbors[1] = null; + } + else + { + Neighbors[2] = null; + } + } + + /** + * Clears all references to all other triangles and points + */ + + public void Clear() + { + DelaunayTriangle t; + for (int i = 0; i < 3; i++) + { + t = Neighbors[i]; + if (t != null) + { + t.ClearNeighbor(this); + } + } + ClearNeighbors(); + Points[0] = Points[1] = Points[2] = null; + } + + /// Opposite triangle + /// The point in t that isn't shared between the triangles + public TriangulationPoint OppositePoint(DelaunayTriangle t, TriangulationPoint p) + { + Debug.Assert(t != this, "self-pointer error"); + return PointCW(t.PointCW(p)); + } + + public DelaunayTriangle NeighborCW(TriangulationPoint point) + { + return Neighbors[(Points.IndexOf(point) + 1)%3]; + } + + public DelaunayTriangle NeighborCCW(TriangulationPoint point) + { + return Neighbors[(Points.IndexOf(point) + 2)%3]; + } + + public DelaunayTriangle NeighborAcross(TriangulationPoint point) + { + return Neighbors[Points.IndexOf(point)]; + } + + public TriangulationPoint PointCCW(TriangulationPoint point) + { + return Points[(IndexOf(point) + 1)%3]; + } + + public TriangulationPoint PointCW(TriangulationPoint point) + { + return Points[(IndexOf(point) + 2)%3]; + } + + private void RotateCW() + { + var t = Points[2]; + Points[2] = Points[1]; + Points[1] = Points[0]; + Points[0] = t; + } + + /// + /// Legalize triangle by rotating clockwise around oPoint + /// + /// The origin point to rotate around + /// ??? + public void Legalize(TriangulationPoint oPoint, TriangulationPoint nPoint) + { + RotateCW(); + Points[IndexCCW(oPoint)] = nPoint; + } + + public override string ToString() + { + return Points[0] + "," + Points[1] + "," + Points[2]; + } + + /// + /// Finalize edge marking + /// + public void MarkNeighborEdges() + { + for (int i = 0; i < 3; i++) + if (EdgeIsConstrained[i] && Neighbors[i] != null) + { + Neighbors[i].MarkConstrainedEdge(Points[(i + 1)%3], Points[(i + 2)%3]); + } + } + + public void MarkEdge(DelaunayTriangle triangle) + { + for (int i = 0; i < 3; i++) + if (EdgeIsConstrained[i]) + { + triangle.MarkConstrainedEdge(Points[(i + 1)%3], Points[(i + 2)%3]); + } + } + + public void MarkEdge(List tList) + { + foreach (DelaunayTriangle t in tList) + for (int i = 0; i < 3; i++) + if (t.EdgeIsConstrained[i]) + { + MarkConstrainedEdge(t.Points[(i + 1)%3], t.Points[(i + 2)%3]); + } + } + + public void MarkConstrainedEdge(int index) + { + EdgeIsConstrained[index] = true; + } + + public void MarkConstrainedEdge(DTSweepConstraint edge) + { + MarkConstrainedEdge(edge.P, edge.Q); + } + + /// + /// Mark edge as constrained + /// + public void MarkConstrainedEdge(TriangulationPoint p, TriangulationPoint q) + { + int i = EdgeIndex(p, q); + if (i != -1) EdgeIsConstrained[i] = true; + } + + public double Area() + { + double b = Points[0].X - Points[1].X; + double h = Points[2].Y - Points[1].Y; + + return Math.Abs((b*h*0.5f)); + } + + public TriangulationPoint Centroid() + { + double cx = (Points[0].X + Points[1].X + Points[2].X)/3f; + double cy = (Points[0].Y + Points[1].Y + Points[2].Y)/3f; + return new TriangulationPoint(cx, cy); + } + + /// + /// Get the index of the neighbor that shares this edge (or -1 if it isn't shared) + /// + /// index of the shared edge or -1 if edge isn't shared + public int EdgeIndex(TriangulationPoint p1, TriangulationPoint p2) + { + int i1 = Points.IndexOf(p1); + int i2 = Points.IndexOf(p2); + + // Points of this triangle in the edge p1-p2 + bool a = (i1 == 0 || i2 == 0); + bool b = (i1 == 1 || i2 == 1); + bool c = (i1 == 2 || i2 == 2); + + if (b && c) return 0; + if (a && c) return 1; + if (a && b) return 2; + return -1; + } + + public bool GetConstrainedEdgeCCW(TriangulationPoint p) + { + return EdgeIsConstrained[(IndexOf(p) + 2)%3]; + } + + public bool GetConstrainedEdgeCW(TriangulationPoint p) + { + return EdgeIsConstrained[(IndexOf(p) + 1)%3]; + } + + public bool GetConstrainedEdgeAcross(TriangulationPoint p) + { + return EdgeIsConstrained[IndexOf(p)]; + } + + public void SetConstrainedEdgeCCW(TriangulationPoint p, bool ce) + { + EdgeIsConstrained[(IndexOf(p) + 2)%3] = ce; + } + + public void SetConstrainedEdgeCW(TriangulationPoint p, bool ce) + { + EdgeIsConstrained[(IndexOf(p) + 1)%3] = ce; + } + + public void SetConstrainedEdgeAcross(TriangulationPoint p, bool ce) + { + EdgeIsConstrained[IndexOf(p)] = ce; + } + + public bool GetDelaunayEdgeCCW(TriangulationPoint p) + { + return EdgeIsDelaunay[(IndexOf(p) + 2)%3]; + } + + public bool GetDelaunayEdgeCW(TriangulationPoint p) + { + return EdgeIsDelaunay[(IndexOf(p) + 1)%3]; + } + + public bool GetDelaunayEdgeAcross(TriangulationPoint p) + { + return EdgeIsDelaunay[IndexOf(p)]; + } + + public void SetDelaunayEdgeCCW(TriangulationPoint p, bool ce) + { + EdgeIsDelaunay[(IndexOf(p) + 2)%3] = ce; + } + + public void SetDelaunayEdgeCW(TriangulationPoint p, bool ce) + { + EdgeIsDelaunay[(IndexOf(p) + 1)%3] = ce; + } + + public void SetDelaunayEdgeAcross(TriangulationPoint p, bool ce) + { + EdgeIsDelaunay[IndexOf(p)] = ce; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/AdvancingFront.cs b/Common/Decomposition/CDT/Delaunay/Sweep/AdvancingFront.cs new file mode 100644 index 0000000..12a7b64 --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/AdvancingFront.cs @@ -0,0 +1,180 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Changes from the Java version +// Removed BST code, but not all artifacts of it +// Future possibilities +// Eliminate Add/RemoveNode ? +// Comments comments and more comments! + +using System; +using System.Text; + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + /** + * @author Thomas Åhlen (thahlen@gmail.com) + */ + + public class AdvancingFront + { + public AdvancingFrontNode Head; + protected AdvancingFrontNode Search; + public AdvancingFrontNode Tail; + + public AdvancingFront(AdvancingFrontNode head, AdvancingFrontNode tail) + { + Head = head; + Tail = tail; + Search = head; + AddNode(head); + AddNode(tail); + } + + public void AddNode(AdvancingFrontNode node) + { + //_searchTree.put(node.key, node); + } + + public void RemoveNode(AdvancingFrontNode node) + { + //_searchTree.delete( node.key ); + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + AdvancingFrontNode node = Head; + while (node != Tail) + { + sb.Append(node.Point.X).Append("->"); + node = node.Next; + } + sb.Append(Tail.Point.X); + return sb.ToString(); + } + + /// + /// MM: This seems to be used by LocateNode to guess a position in the implicit linked list of AdvancingFrontNodes near x + /// Removed an overload that depended on this being exact + /// + private AdvancingFrontNode FindSearchNode(double x) + { + // TODO: implement BST index + return Search; + } + + /// + /// We use a balancing tree to locate a node smaller or equal to given key value + /// + public AdvancingFrontNode LocateNode(TriangulationPoint point) + { + return LocateNode(point.X); + } + + private AdvancingFrontNode LocateNode(double x) + { + AdvancingFrontNode node = FindSearchNode(x); + if (x < node.Value) + { + while ((node = node.Prev) != null) + if (x >= node.Value) + { + Search = node; + return node; + } + } + else + { + while ((node = node.Next) != null) + if (x < node.Value) + { + Search = node.Prev; + return node.Prev; + } + } + return null; + } + + /// + /// This implementation will use simple node traversal algorithm to find a point on the front + /// + public AdvancingFrontNode LocatePoint(TriangulationPoint point) + { + double px = point.X; + AdvancingFrontNode node = FindSearchNode(px); + double nx = node.Point.X; + + if (px == nx) + { + if (point != node.Point) + { + // We might have two nodes with same x value for a short time + if (point == node.Prev.Point) + { + node = node.Prev; + } + else if (point == node.Next.Point) + { + node = node.Next; + } + else + { + throw new Exception("Failed to find Node for given afront point"); + //node = null; + } + } + } + else if (px < nx) + { + while ((node = node.Prev) != null) + { + if (point == node.Point) + { + break; + } + } + } + else + { + while ((node = node.Next) != null) + { + if (point == node.Point) + { + break; + } + } + } + Search = node; + return node; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/AdvancingFrontNode.cs b/Common/Decomposition/CDT/Delaunay/Sweep/AdvancingFrontNode.cs new file mode 100644 index 0000000..e77042f --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/AdvancingFrontNode.cs @@ -0,0 +1,64 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Changes from the Java version +// Removed getters +// Has* turned into attributes +// Future possibilities +// Comments! + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + public class AdvancingFrontNode + { + public AdvancingFrontNode Next; + public TriangulationPoint Point; + public AdvancingFrontNode Prev; + public DelaunayTriangle Triangle; + public double Value; + + public AdvancingFrontNode(TriangulationPoint point) + { + Point = point; + Value = point.X; + } + + public bool HasNext + { + get { return Next != null; } + } + + public bool HasPrev + { + get { return Prev != null; } + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs new file mode 100644 index 0000000..bb63775 --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweep.cs @@ -0,0 +1,1132 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Sweep-line, Constrained Delauney Triangulation (CDT) See: Domiter, V. and + * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', + * International Journal of Geographical Information Science + * + * "FlipScan" Constrained Edge Algorithm invented by author of this code. + * + * Author: Thomas Åhlén, thahlen@gmail.com + */ + +// Changes from the Java version +// Turned DTSweep into a static class +// Lots of deindentation via early bailout +// Future possibilities +// Comments! + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using FarseerPhysics.Common.Decomposition.CDT; + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + public static class DTSweep + { + private const double PI_div2 = Math.PI/2; + private const double PI_3div4 = 3*Math.PI/4; + + /// + /// Triangulate simple polygon with holes + /// + public static void Triangulate(DTSweepContext tcx) + { + tcx.CreateAdvancingFront(); + + Sweep(tcx); + + // Finalize triangulation + if (tcx.TriangulationMode == TriangulationMode.Polygon) + { + FinalizationPolygon(tcx); + } + else + { + FinalizationConvexHull(tcx); + } + + tcx.Done(); + } + + /// + /// Start sweeping the Y-sorted point set from bottom to top + /// + private static void Sweep(DTSweepContext tcx) + { + List points = tcx.Points; + TriangulationPoint point; + AdvancingFrontNode node; + + for (int i = 1; i < points.Count; i++) + { + point = points[i]; + + node = PointEvent(tcx, point); + + if (point.HasEdges) + { + foreach (DTSweepConstraint e in point.Edges) + { + EdgeEvent(tcx, e, node); + } + } + tcx.Update(null); + } + } + + /// + /// If this is a Delaunay Triangulation of a pointset we need to fill so the triangle mesh gets a ConvexHull + /// + private static void FinalizationConvexHull(DTSweepContext tcx) + { + AdvancingFrontNode n1, n2; + DelaunayTriangle t1, t2; + TriangulationPoint first, p1; + + n1 = tcx.aFront.Head.Next; + n2 = n1.Next; + first = n1.Point; + + TurnAdvancingFrontConvex(tcx, n1, n2); + + // TODO: implement ConvexHull for lower right and left boundary + + // Lets remove triangles connected to the two "algorithm" points + + // XXX: When the first the nodes are points in a triangle we need to do a flip before + // removing triangles or we will lose a valid triangle. + // Same for last three nodes! + // !!! If I implement ConvexHull for lower right and left boundary this fix should not be + // needed and the removed triangles will be added again by default + n1 = tcx.aFront.Tail.Prev; + if (n1.Triangle.Contains(n1.Next.Point) && n1.Triangle.Contains(n1.Prev.Point)) + { + t1 = n1.Triangle.NeighborAcross(n1.Point); + RotateTrianglePair(n1.Triangle, n1.Point, t1, t1.OppositePoint(n1.Triangle, n1.Point)); + tcx.MapTriangleToNodes(n1.Triangle); + tcx.MapTriangleToNodes(t1); + } + n1 = tcx.aFront.Head.Next; + if (n1.Triangle.Contains(n1.Prev.Point) && n1.Triangle.Contains(n1.Next.Point)) + { + t1 = n1.Triangle.NeighborAcross(n1.Point); + RotateTrianglePair(n1.Triangle, n1.Point, t1, t1.OppositePoint(n1.Triangle, n1.Point)); + tcx.MapTriangleToNodes(n1.Triangle); + tcx.MapTriangleToNodes(t1); + } + + // Lower right boundary + first = tcx.aFront.Head.Point; + n2 = tcx.aFront.Tail.Prev; + t1 = n2.Triangle; + p1 = n2.Point; + n2.Triangle = null; + do + { + tcx.RemoveFromList(t1); + p1 = t1.PointCCW(p1); + if (p1 == first) break; + t2 = t1.NeighborCCW(p1); + t1.Clear(); + t1 = t2; + } while (true); + + // Lower left boundary + first = tcx.aFront.Head.Next.Point; + p1 = t1.PointCW(tcx.aFront.Head.Point); + t2 = t1.NeighborCW(tcx.aFront.Head.Point); + t1.Clear(); + t1 = t2; + while (p1 != first) //TODO: Port note. This was do while before. + { + tcx.RemoveFromList(t1); + p1 = t1.PointCCW(p1); + t2 = t1.NeighborCCW(p1); + t1.Clear(); + t1 = t2; + } + + // Remove current head and tail node now that we have removed all triangles attached + // to them. Then set new head and tail node points + tcx.aFront.Head = tcx.aFront.Head.Next; + tcx.aFront.Head.Prev = null; + tcx.aFront.Tail = tcx.aFront.Tail.Prev; + tcx.aFront.Tail.Next = null; + + tcx.FinalizeTriangulation(); + } + + /// + /// We will traverse the entire advancing front and fill it to form a convex hull. + /// + private static void TurnAdvancingFrontConvex(DTSweepContext tcx, AdvancingFrontNode b, AdvancingFrontNode c) + { + AdvancingFrontNode first = b; + while (c != tcx.aFront.Tail) + { + if (TriangulationUtil.Orient2d(b.Point, c.Point, c.Next.Point) == Orientation.CCW) + { + // [b,c,d] Concave - fill around c + Fill(tcx, c); + c = c.Next; + } + else + { + // [b,c,d] Convex + if (b != first && TriangulationUtil.Orient2d(b.Prev.Point, b.Point, c.Point) == Orientation.CCW) + { + // [a,b,c] Concave - fill around b + Fill(tcx, b); + b = b.Prev; + } + else + { + // [a,b,c] Convex - nothing to fill + b = c; + c = c.Next; + } + } + } + } + + private static void FinalizationPolygon(DTSweepContext tcx) + { + // Get an Internal triangle to start with + DelaunayTriangle t = tcx.aFront.Head.Next.Triangle; + TriangulationPoint p = tcx.aFront.Head.Next.Point; + while (!t.GetConstrainedEdgeCW(p)) + { + t = t.NeighborCCW(p); + } + + // Collect interior triangles constrained by edges + tcx.MeshClean(t); + } + + /// + /// Find closes node to the left of the new point and + /// create a new triangle. If needed new holes and basins + /// will be filled to. + /// + private static AdvancingFrontNode PointEvent(DTSweepContext tcx, TriangulationPoint point) + { + AdvancingFrontNode node, newNode; + + node = tcx.LocateNode(point); + newNode = NewFrontTriangle(tcx, point, node); + + // Only need to check +epsilon since point never have smaller + // x value than node due to how we fetch nodes from the front + if (point.X <= node.Point.X + TriangulationUtil.EPSILON) + { + Fill(tcx, node); + } + + tcx.AddNode(newNode); + + FillAdvancingFront(tcx, newNode); + return newNode; + } + + /// + /// Creates a new front triangle and legalize it + /// + private static AdvancingFrontNode NewFrontTriangle(DTSweepContext tcx, TriangulationPoint point, + AdvancingFrontNode node) + { + AdvancingFrontNode newNode; + DelaunayTriangle triangle; + + triangle = new DelaunayTriangle(point, node.Point, node.Next.Point); + triangle.MarkNeighbor(node.Triangle); + tcx.Triangles.Add(triangle); + + newNode = new AdvancingFrontNode(point); + newNode.Next = node.Next; + newNode.Prev = node; + node.Next.Prev = newNode; + node.Next = newNode; + + tcx.AddNode(newNode); // XXX: BST + + if (!Legalize(tcx, triangle)) + { + tcx.MapTriangleToNodes(triangle); + } + + return newNode; + } + + private static void EdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + try + { + tcx.EdgeEvent.ConstrainedEdge = edge; + tcx.EdgeEvent.Right = edge.P.X > edge.Q.X; + + if (IsEdgeSideOfTriangle(node.Triangle, edge.P, edge.Q)) + { + return; + } + + // For now we will do all needed filling + // TODO: integrate with flip process might give some better performance + // but for now this avoid the issue with cases that needs both flips and fills + FillEdgeEvent(tcx, edge, node); + + EdgeEvent(tcx, edge.P, edge.Q, node.Triangle, edge.Q); + } + catch (PointOnEdgeException e) + { + Debug.WriteLine(String.Format("Skipping Edge: {0}", e.Message)); + } + } + + private static void FillEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + if (tcx.EdgeEvent.Right) + { + FillRightAboveEdgeEvent(tcx, edge, node); + } + else + { + FillLeftAboveEdgeEvent(tcx, edge, node); + } + } + + private static void FillRightConcaveEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, + AdvancingFrontNode node) + { + Fill(tcx, node.Next); + if (node.Next.Point != edge.P) + { + // Next above or below edge? + if (TriangulationUtil.Orient2d(edge.Q, node.Next.Point, edge.P) == Orientation.CCW) + { + // Below + if (TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Orientation.CCW) + { + // Next is concave + FillRightConcaveEdgeEvent(tcx, edge, node); + } + else + { + // Next is convex + } + } + } + } + + private static void FillRightConvexEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + // Next concave or convex? + if (TriangulationUtil.Orient2d(node.Next.Point, node.Next.Next.Point, node.Next.Next.Next.Point) == + Orientation.CCW) + { + // Concave + FillRightConcaveEdgeEvent(tcx, edge, node.Next); + } + else + { + // Convex + // Next above or below edge? + if (TriangulationUtil.Orient2d(edge.Q, node.Next.Next.Point, edge.P) == Orientation.CCW) + { + // Below + FillRightConvexEdgeEvent(tcx, edge, node.Next); + } + else + { + // Above + } + } + } + + private static void FillRightBelowEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + if (node.Point.X < edge.P.X) // needed? + { + if (TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Orientation.CCW) + { + // Concave + FillRightConcaveEdgeEvent(tcx, edge, node); + } + else + { + // Convex + FillRightConvexEdgeEvent(tcx, edge, node); + // Retry this one + FillRightBelowEdgeEvent(tcx, edge, node); + } + } + } + + private static void FillRightAboveEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + while (node.Next.Point.X < edge.P.X) + { + // Check if next node is below the edge + Orientation o1 = TriangulationUtil.Orient2d(edge.Q, node.Next.Point, edge.P); + if (o1 == Orientation.CCW) + { + FillRightBelowEdgeEvent(tcx, edge, node); + } + else + { + node = node.Next; + } + } + } + + private static void FillLeftConvexEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + // Next concave or convex? + if (TriangulationUtil.Orient2d(node.Prev.Point, node.Prev.Prev.Point, node.Prev.Prev.Prev.Point) == + Orientation.CW) + { + // Concave + FillLeftConcaveEdgeEvent(tcx, edge, node.Prev); + } + else + { + // Convex + // Next above or below edge? + if (TriangulationUtil.Orient2d(edge.Q, node.Prev.Prev.Point, edge.P) == Orientation.CW) + { + // Below + FillLeftConvexEdgeEvent(tcx, edge, node.Prev); + } + else + { + // Above + } + } + } + + private static void FillLeftConcaveEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + Fill(tcx, node.Prev); + if (node.Prev.Point != edge.P) + { + // Next above or below edge? + if (TriangulationUtil.Orient2d(edge.Q, node.Prev.Point, edge.P) == Orientation.CW) + { + // Below + if (TriangulationUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point) == Orientation.CW) + { + // Next is concave + FillLeftConcaveEdgeEvent(tcx, edge, node); + } + else + { + // Next is convex + } + } + } + } + + private static void FillLeftBelowEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + if (node.Point.X > edge.P.X) + { + if (TriangulationUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point) == Orientation.CW) + { + // Concave + FillLeftConcaveEdgeEvent(tcx, edge, node); + } + else + { + // Convex + FillLeftConvexEdgeEvent(tcx, edge, node); + // Retry this one + FillLeftBelowEdgeEvent(tcx, edge, node); + } + } + } + + private static void FillLeftAboveEdgeEvent(DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node) + { + while (node.Prev.Point.X > edge.P.X) + { + // Check if next node is below the edge + Orientation o1 = TriangulationUtil.Orient2d(edge.Q, node.Prev.Point, edge.P); + if (o1 == Orientation.CW) + { + FillLeftBelowEdgeEvent(tcx, edge, node); + } + else + { + node = node.Prev; + } + } + } + + //TODO: Port note: There were some structural differences here. + private static bool IsEdgeSideOfTriangle(DelaunayTriangle triangle, TriangulationPoint ep, TriangulationPoint eq) + { + int index; + index = triangle.EdgeIndex(ep, eq); + if (index != -1) + { + triangle.MarkConstrainedEdge(index); + triangle = triangle.Neighbors[index]; + if (triangle != null) + { + triangle.MarkConstrainedEdge(ep, eq); + } + return true; + } + return false; + } + + private static void EdgeEvent(DTSweepContext tcx, TriangulationPoint ep, TriangulationPoint eq, + DelaunayTriangle triangle, TriangulationPoint point) + { + TriangulationPoint p1, p2; + + if (IsEdgeSideOfTriangle(triangle, ep, eq)) + { + return; + } + + p1 = triangle.PointCCW(point); + Orientation o1 = TriangulationUtil.Orient2d(eq, p1, ep); + if (o1 == Orientation.Collinear) + { + if (triangle.Contains(eq, p1)) + { + triangle.MarkConstrainedEdge(eq, p1); + // We are modifying the constraint maybe it would be better to + // not change the given constraint and just keep a variable for the new constraint + tcx.EdgeEvent.ConstrainedEdge.Q = p1; + triangle = triangle.NeighborAcross(point); + EdgeEvent(tcx, ep, p1, triangle, p1); + } + else + { + throw new PointOnEdgeException("EdgeEvent - Point on constrained edge not supported yet"); + } + if (tcx.IsDebugEnabled) + { + Debug.WriteLine("EdgeEvent - Point on constrained edge"); + } + return; + } + + p2 = triangle.PointCW(point); + Orientation o2 = TriangulationUtil.Orient2d(eq, p2, ep); + if (o2 == Orientation.Collinear) + { + if (triangle.Contains(eq, p2)) + { + triangle.MarkConstrainedEdge(eq, p2); + // We are modifying the constraint maybe it would be better to + // not change the given constraint and just keep a variable for the new constraint + tcx.EdgeEvent.ConstrainedEdge.Q = p2; + triangle = triangle.NeighborAcross(point); + EdgeEvent(tcx, ep, p2, triangle, p2); + } + else + { + throw new PointOnEdgeException("EdgeEvent - Point on constrained edge not supported yet"); + } + if (tcx.IsDebugEnabled) + { + Debug.WriteLine("EdgeEvent - Point on constrained edge"); + } + return; + } + + if (o1 == o2) + { + // Need to decide if we are rotating CW or CCW to get to a triangle + // that will cross edge + if (o1 == Orientation.CW) + { + triangle = triangle.NeighborCCW(point); + } + else + { + triangle = triangle.NeighborCW(point); + } + EdgeEvent(tcx, ep, eq, triangle, point); + } + else + { + // This triangle crosses constraint so lets flippin start! + FlipEdgeEvent(tcx, ep, eq, triangle, point); + } + } + + private static void FlipEdgeEvent(DTSweepContext tcx, TriangulationPoint ep, TriangulationPoint eq, + DelaunayTriangle t, TriangulationPoint p) + { + TriangulationPoint op, newP; + DelaunayTriangle ot; + bool inScanArea; + + ot = t.NeighborAcross(p); + op = ot.OppositePoint(t, p); + + if (ot == null) + { + // If we want to integrate the fillEdgeEvent do it here + // With current implementation we should never get here + throw new InvalidOperationException("[BUG:FIXME] FLIP failed due to missing triangle"); + } + + inScanArea = TriangulationUtil.InScanArea(p, t.PointCCW(p), t.PointCW(p), op); + if (inScanArea) + { + // Lets rotate shared edge one vertex CW + RotateTrianglePair(t, p, ot, op); + tcx.MapTriangleToNodes(t); + tcx.MapTriangleToNodes(ot); + + if (p == eq && op == ep) + { + if (eq == tcx.EdgeEvent.ConstrainedEdge.Q + && ep == tcx.EdgeEvent.ConstrainedEdge.P) + { + if (tcx.IsDebugEnabled) Console.WriteLine("[FLIP] - constrained edge done"); // TODO: remove + t.MarkConstrainedEdge(ep, eq); + ot.MarkConstrainedEdge(ep, eq); + Legalize(tcx, t); + Legalize(tcx, ot); + } + else + { + if (tcx.IsDebugEnabled) Console.WriteLine("[FLIP] - subedge done"); // TODO: remove + // XXX: I think one of the triangles should be legalized here? + } + } + else + { + if (tcx.IsDebugEnabled) + Console.WriteLine("[FLIP] - flipping and continuing with triangle still crossing edge"); + // TODO: remove + Orientation o = TriangulationUtil.Orient2d(eq, op, ep); + t = NextFlipTriangle(tcx, o, t, ot, p, op); + FlipEdgeEvent(tcx, ep, eq, t, p); + } + } + else + { + newP = NextFlipPoint(ep, eq, ot, op); + FlipScanEdgeEvent(tcx, ep, eq, t, ot, newP); + EdgeEvent(tcx, ep, eq, t, p); + } + } + + /// + /// When we need to traverse from one triangle to the next we need + /// the point in current triangle that is the opposite point to the next + /// triangle. + /// + private static TriangulationPoint NextFlipPoint(TriangulationPoint ep, TriangulationPoint eq, + DelaunayTriangle ot, TriangulationPoint op) + { + Orientation o2d = TriangulationUtil.Orient2d(eq, op, ep); + if (o2d == Orientation.CW) + { + // Right + return ot.PointCCW(op); + } + else if (o2d == Orientation.CCW) + { + // Left + return ot.PointCW(op); + } + else + { + // TODO: implement support for point on constraint edge + throw new PointOnEdgeException("Point on constrained edge not supported yet"); + } + } + + /// + /// After a flip we have two triangles and know that only one will still be + /// intersecting the edge. So decide which to contiune with and legalize the other + /// + /// + /// should be the result of an TriangulationUtil.orient2d( eq, op, ep ) + /// triangle 1 + /// triangle 2 + /// a point shared by both triangles + /// another point shared by both triangles + /// returns the triangle still intersecting the edge + private static DelaunayTriangle NextFlipTriangle(DTSweepContext tcx, Orientation o, DelaunayTriangle t, + DelaunayTriangle ot, TriangulationPoint p, + TriangulationPoint op) + { + int edgeIndex; + if (o == Orientation.CCW) + { + // ot is not crossing edge after flip + edgeIndex = ot.EdgeIndex(p, op); + ot.EdgeIsDelaunay[edgeIndex] = true; + Legalize(tcx, ot); + ot.EdgeIsDelaunay.Clear(); + return t; + } + // t is not crossing edge after flip + edgeIndex = t.EdgeIndex(p, op); + t.EdgeIsDelaunay[edgeIndex] = true; + Legalize(tcx, t); + t.EdgeIsDelaunay.Clear(); + return ot; + } + + /// + /// Scan part of the FlipScan algorithm
+ /// When a triangle pair isn't flippable we will scan for the next + /// point that is inside the flip triangle scan area. When found + /// we generate a new flipEdgeEvent + ///
+ /// + /// last point on the edge we are traversing + /// first point on the edge we are traversing + /// the current triangle sharing the point eq with edge + /// + /// + private static void FlipScanEdgeEvent(DTSweepContext tcx, TriangulationPoint ep, TriangulationPoint eq, + DelaunayTriangle flipTriangle, DelaunayTriangle t, TriangulationPoint p) + { + DelaunayTriangle ot; + TriangulationPoint op, newP; + bool inScanArea; + + ot = t.NeighborAcross(p); + op = ot.OppositePoint(t, p); + + if (ot == null) + { + // If we want to integrate the fillEdgeEvent do it here + // With current implementation we should never get here + throw new Exception("[BUG:FIXME] FLIP failed due to missing triangle"); + } + + inScanArea = TriangulationUtil.InScanArea(eq, flipTriangle.PointCCW(eq), flipTriangle.PointCW(eq), op); + if (inScanArea) + { + // flip with new edge op->eq + FlipEdgeEvent(tcx, eq, op, ot, op); + // TODO: Actually I just figured out that it should be possible to + // improve this by getting the next ot and op before the the above + // flip and continue the flipScanEdgeEvent here + // set new ot and op here and loop back to inScanArea test + // also need to set a new flipTriangle first + // Turns out at first glance that this is somewhat complicated + // so it will have to wait. + } + else + { + newP = NextFlipPoint(ep, eq, ot, op); + FlipScanEdgeEvent(tcx, ep, eq, flipTriangle, ot, newP); + } + } + + /// + /// Fills holes in the Advancing Front + /// + private static void FillAdvancingFront(DTSweepContext tcx, AdvancingFrontNode n) + { + AdvancingFrontNode node; + double angle; + + // Fill right holes + node = n.Next; + while (node.HasNext) + { + angle = HoleAngle(node); + if (angle > PI_div2 || angle < -PI_div2) + { + break; + } + Fill(tcx, node); + node = node.Next; + } + + // Fill left holes + node = n.Prev; + while (node.HasPrev) + { + angle = HoleAngle(node); + if (angle > PI_div2 || angle < -PI_div2) + { + break; + } + Fill(tcx, node); + node = node.Prev; + } + + // Fill right basins + if (n.HasNext && n.Next.HasNext) + { + angle = BasinAngle(n); + if (angle < PI_3div4) + { + FillBasin(tcx, n); + } + } + } + + /// + /// Fills a basin that has formed on the Advancing Front to the right + /// of given node.
+ /// First we decide a left,bottom and right node that forms the + /// boundaries of the basin. Then we do a reqursive fill. + ///
+ /// + /// starting node, this or next node will be left node + private static void FillBasin(DTSweepContext tcx, AdvancingFrontNode node) + { + if (TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Orientation.CCW) + { + // tcx.basin.leftNode = node.next.next; + tcx.Basin.leftNode = node; + } + else + { + tcx.Basin.leftNode = node.Next; + } + + // Find the bottom and right node + tcx.Basin.bottomNode = tcx.Basin.leftNode; + while (tcx.Basin.bottomNode.HasNext && tcx.Basin.bottomNode.Point.Y >= tcx.Basin.bottomNode.Next.Point.Y) + { + tcx.Basin.bottomNode = tcx.Basin.bottomNode.Next; + } + + if (tcx.Basin.bottomNode == tcx.Basin.leftNode) + { + // No valid basins + return; + } + + tcx.Basin.rightNode = tcx.Basin.bottomNode; + while (tcx.Basin.rightNode.HasNext && tcx.Basin.rightNode.Point.Y < tcx.Basin.rightNode.Next.Point.Y) + { + tcx.Basin.rightNode = tcx.Basin.rightNode.Next; + } + + if (tcx.Basin.rightNode == tcx.Basin.bottomNode) + { + // No valid basins + return; + } + + tcx.Basin.width = tcx.Basin.rightNode.Point.X - tcx.Basin.leftNode.Point.X; + tcx.Basin.leftHighest = tcx.Basin.leftNode.Point.Y > tcx.Basin.rightNode.Point.Y; + + FillBasinReq(tcx, tcx.Basin.bottomNode); + } + + /// + /// Recursive algorithm to fill a Basin with triangles + /// + private static void FillBasinReq(DTSweepContext tcx, AdvancingFrontNode node) + { + // if shallow stop filling + if (IsShallow(tcx, node)) + { + return; + } + + Fill(tcx, node); + if (node.Prev == tcx.Basin.leftNode && node.Next == tcx.Basin.rightNode) + { + return; + } + else if (node.Prev == tcx.Basin.leftNode) + { + Orientation o = TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point); + if (o == Orientation.CW) + { + return; + } + node = node.Next; + } + else if (node.Next == tcx.Basin.rightNode) + { + Orientation o = TriangulationUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point); + if (o == Orientation.CCW) + { + return; + } + node = node.Prev; + } + else + { + // Continue with the neighbor node with lowest Y value + if (node.Prev.Point.Y < node.Next.Point.Y) + { + node = node.Prev; + } + else + { + node = node.Next; + } + } + FillBasinReq(tcx, node); + } + + private static bool IsShallow(DTSweepContext tcx, AdvancingFrontNode node) + { + double height; + + if (tcx.Basin.leftHighest) + { + height = tcx.Basin.leftNode.Point.Y - node.Point.Y; + } + else + { + height = tcx.Basin.rightNode.Point.Y - node.Point.Y; + } + if (tcx.Basin.width > height) + { + return true; + } + return false; + } + + /// + /// ??? + /// + /// middle node + /// the angle between 3 front nodes + private static double HoleAngle(AdvancingFrontNode node) + { + // XXX: do we really need a signed angle for holeAngle? + // could possible save some cycles here + /* Complex plane + * ab = cosA +i*sinA + * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) + * atan2(y,x) computes the principal value of the argument function + * applied to the complex number x+iy + * Where x = ax*bx + ay*by + * y = ax*by - ay*bx + */ + double px = node.Point.X; + double py = node.Point.Y; + double ax = node.Next.Point.X - px; + double ay = node.Next.Point.Y - py; + double bx = node.Prev.Point.X - px; + double by = node.Prev.Point.Y - py; + return Math.Atan2(ax*by - ay*bx, ax*bx + ay*by); + } + + /// + /// The basin angle is decided against the horizontal line [1,0] + /// + private static double BasinAngle(AdvancingFrontNode node) + { + double ax = node.Point.X - node.Next.Next.Point.X; + double ay = node.Point.Y - node.Next.Next.Point.Y; + return Math.Atan2(ay, ax); + } + + /// + /// Adds a triangle to the advancing front to fill a hole. + /// + /// + /// middle node, that is the bottom of the hole + private static void Fill(DTSweepContext tcx, AdvancingFrontNode node) + { + DelaunayTriangle triangle = new DelaunayTriangle(node.Prev.Point, node.Point, node.Next.Point); + // TODO: should copy the cEdge value from neighbor triangles + // for now cEdge values are copied during the legalize + triangle.MarkNeighbor(node.Prev.Triangle); + triangle.MarkNeighbor(node.Triangle); + tcx.Triangles.Add(triangle); + + // Update the advancing front + node.Prev.Next = node.Next; + node.Next.Prev = node.Prev; + tcx.RemoveNode(node); + + // If it was legalized the triangle has already been mapped + if (!Legalize(tcx, triangle)) + { + tcx.MapTriangleToNodes(triangle); + } + } + + /// + /// Returns true if triangle was legalized + /// + private static bool Legalize(DTSweepContext tcx, DelaunayTriangle t) + { + int oi; + bool inside; + TriangulationPoint p, op; + DelaunayTriangle ot; + + // To legalize a triangle we start by finding if any of the three edges + // violate the Delaunay condition + for (int i = 0; i < 3; i++) + { + // TODO: fix so that cEdge is always valid when creating new triangles then we can check it here + // instead of below with ot + if (t.EdgeIsDelaunay[i]) + { + continue; + } + + ot = t.Neighbors[i]; + if (ot != null) + { + p = t.Points[i]; + op = ot.OppositePoint(t, p); + oi = ot.IndexOf(op); + // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) + // then we should not try to legalize + if (ot.EdgeIsConstrained[oi] || ot.EdgeIsDelaunay[oi]) + { + t.EdgeIsConstrained[i] = ot.EdgeIsConstrained[oi]; + // XXX: have no good way of setting this property when creating new triangles so lets set it here + continue; + } + + inside = TriangulationUtil.SmartIncircle(p, + t.PointCCW(p), + t.PointCW(p), + op); + + if (inside) + { + bool notLegalized; + + // Lets mark this shared edge as Delaunay + t.EdgeIsDelaunay[i] = true; + ot.EdgeIsDelaunay[oi] = true; + + // Lets rotate shared edge one vertex CW to legalize it + RotateTrianglePair(t, p, ot, op); + + // We now got one valid Delaunay Edge shared by two triangles + // This gives us 4 new edges to check for Delaunay + + // Make sure that triangle to node mapping is done only one time for a specific triangle + notLegalized = !Legalize(tcx, t); + + if (notLegalized) + { + tcx.MapTriangleToNodes(t); + } + notLegalized = !Legalize(tcx, ot); + if (notLegalized) + { + tcx.MapTriangleToNodes(ot); + } + + // Reset the Delaunay edges, since they only are valid Delaunay edges + // until we add a new triangle or point. + // XXX: need to think about this. Can these edges be tried after we + // return to previous recursive level? + t.EdgeIsDelaunay[i] = false; + ot.EdgeIsDelaunay[oi] = false; + + // If triangle have been legalized no need to check the other edges since + // the recursive legalization will handles those so we can end here. + return true; + } + } + } + return false; + } + + /// + /// Rotates a triangle pair one vertex CW + /// n2 n2 + /// P +-----+ P +-----+ + /// | t /| |\ t | + /// | / | | \ | + /// n1| / |n3 n1| \ |n3 + /// | / | after CW | \ | + /// |/ oT | | oT \| + /// +-----+ oP +-----+ + /// n4 n4 + /// + private static void RotateTrianglePair(DelaunayTriangle t, TriangulationPoint p, DelaunayTriangle ot, + TriangulationPoint op) + { + DelaunayTriangle n1, n2, n3, n4; + n1 = t.NeighborCCW(p); + n2 = t.NeighborCW(p); + n3 = ot.NeighborCCW(op); + n4 = ot.NeighborCW(op); + + bool ce1, ce2, ce3, ce4; + ce1 = t.GetConstrainedEdgeCCW(p); + ce2 = t.GetConstrainedEdgeCW(p); + ce3 = ot.GetConstrainedEdgeCCW(op); + ce4 = ot.GetConstrainedEdgeCW(op); + + bool de1, de2, de3, de4; + de1 = t.GetDelaunayEdgeCCW(p); + de2 = t.GetDelaunayEdgeCW(p); + de3 = ot.GetDelaunayEdgeCCW(op); + de4 = ot.GetDelaunayEdgeCW(op); + + t.Legalize(p, op); + ot.Legalize(op, p); + + // Remap dEdge + ot.SetDelaunayEdgeCCW(p, de1); + t.SetDelaunayEdgeCW(p, de2); + t.SetDelaunayEdgeCCW(op, de3); + ot.SetDelaunayEdgeCW(op, de4); + + // Remap cEdge + ot.SetConstrainedEdgeCCW(p, ce1); + t.SetConstrainedEdgeCW(p, ce2); + t.SetConstrainedEdgeCCW(op, ce3); + ot.SetConstrainedEdgeCW(op, ce4); + + // Remap neighbors + // XXX: might optimize the markNeighbor by keeping track of + // what side should be assigned to what neighbor after the + // rotation. Now mark neighbor does lots of testing to find + // the right side. + t.Neighbors.Clear(); + ot.Neighbors.Clear(); + if (n1 != null) ot.MarkNeighbor(n1); + if (n2 != null) t.MarkNeighbor(n2); + if (n3 != null) t.MarkNeighbor(n3); + if (n4 != null) ot.MarkNeighbor(n4); + t.MarkNeighbor(ot); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepConstraint.cs b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepConstraint.cs new file mode 100644 index 0000000..bb0b7ae --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepConstraint.cs @@ -0,0 +1,66 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + public class DTSweepConstraint : TriangulationConstraint + { + /// + /// Give two points in any order. Will always be ordered so + /// that q.y > p.y and q.x > p.x if same y value + /// + public DTSweepConstraint(TriangulationPoint p1, TriangulationPoint p2) + { + P = p1; + Q = p2; + if (p1.Y > p2.Y) + { + Q = p1; + P = p2; + } + else if (p1.Y == p2.Y) + { + if (p1.X > p2.X) + { + Q = p1; + P = p2; + } + else if (p1.X == p2.X) + { + // logger.info( "Failed to create constraint {}={}", p1, p2 ); + // throw new DuplicatePointException( p1 + "=" + p2 ); + // return; + } + } + Q.AddEdge(this); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepContext.cs b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepContext.cs new file mode 100644 index 0000000..2ee18d3 --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepContext.cs @@ -0,0 +1,236 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + /** + * + * @author Thomas Åhlén, thahlen@gmail.com + * + */ + + public class DTSweepContext : TriangulationContext + { + // Inital triangle factor, seed triangle will extend 30% of + // PointSet width to both left and right. + private const float ALPHA = 0.3f; + + public DTSweepBasin Basin = new DTSweepBasin(); + public DTSweepEdgeEvent EdgeEvent = new DTSweepEdgeEvent(); + + private DTSweepPointComparator _comparator = new DTSweepPointComparator(); + public AdvancingFront aFront; + + public DTSweepContext() + { + Clear(); + } + + public TriangulationPoint Head { get; set; } + public TriangulationPoint Tail { get; set; } + + public void RemoveFromList(DelaunayTriangle triangle) + { + Triangles.Remove(triangle); + // TODO: remove all neighbor pointers to this triangle + // for( int i=0; i<3; i++ ) + // { + // if( triangle.neighbors[i] != null ) + // { + // triangle.neighbors[i].clearNeighbor( triangle ); + // } + // } + // triangle.clearNeighbors(); + } + + public void MeshClean(DelaunayTriangle triangle) + { + MeshCleanReq(triangle); + } + + private void MeshCleanReq(DelaunayTriangle triangle) + { + if (triangle != null && !triangle.IsInterior) + { + triangle.IsInterior = true; + Triangulatable.AddTriangle(triangle); + for (int i = 0; i < 3; i++) + { + if (!triangle.EdgeIsConstrained[i]) + { + MeshCleanReq(triangle.Neighbors[i]); + } + } + } + } + + public override void Clear() + { + base.Clear(); + Triangles.Clear(); + } + + public void AddNode(AdvancingFrontNode node) + { + // Console.WriteLine( "add:" + node.key + ":" + System.identityHashCode(node.key)); + // m_nodeTree.put( node.getKey(), node ); + aFront.AddNode(node); + } + + public void RemoveNode(AdvancingFrontNode node) + { + // Console.WriteLine( "remove:" + node.key + ":" + System.identityHashCode(node.key)); + // m_nodeTree.delete( node.getKey() ); + aFront.RemoveNode(node); + } + + public AdvancingFrontNode LocateNode(TriangulationPoint point) + { + return aFront.LocateNode(point); + } + + public void CreateAdvancingFront() + { + AdvancingFrontNode head, tail, middle; + // Initial triangle + DelaunayTriangle iTriangle = new DelaunayTriangle(Points[0], Tail, Head); + Triangles.Add(iTriangle); + + head = new AdvancingFrontNode(iTriangle.Points[1]); + head.Triangle = iTriangle; + middle = new AdvancingFrontNode(iTriangle.Points[0]); + middle.Triangle = iTriangle; + tail = new AdvancingFrontNode(iTriangle.Points[2]); + + aFront = new AdvancingFront(head, tail); + aFront.AddNode(middle); + + // TODO: I think it would be more intuitive if head is middles next and not previous + // so swap head and tail + aFront.Head.Next = middle; + middle.Next = aFront.Tail; + middle.Prev = aFront.Head; + aFront.Tail.Prev = middle; + } + + /// + /// Try to map a node to all sides of this triangle that don't have + /// a neighbor. + /// + public void MapTriangleToNodes(DelaunayTriangle t) + { + AdvancingFrontNode n; + for (int i = 0; i < 3; i++) + { + if (t.Neighbors[i] == null) + { + n = aFront.LocatePoint(t.PointCW(t.Points[i])); + if (n != null) + { + n.Triangle = t; + } + } + } + } + + public override void PrepareTriangulation(Triangulatable t) + { + base.PrepareTriangulation(t); + + double xmax, xmin; + double ymax, ymin; + + xmax = xmin = Points[0].X; + ymax = ymin = Points[0].Y; + + // Calculate bounds. Should be combined with the sorting + foreach (TriangulationPoint p in Points) + { + if (p.X > xmax) + xmax = p.X; + if (p.X < xmin) + xmin = p.X; + if (p.Y > ymax) + ymax = p.Y; + if (p.Y < ymin) + ymin = p.Y; + } + + double deltaX = ALPHA*(xmax - xmin); + double deltaY = ALPHA*(ymax - ymin); + TriangulationPoint p1 = new TriangulationPoint(xmax + deltaX, ymin - deltaY); + TriangulationPoint p2 = new TriangulationPoint(xmin - deltaX, ymin - deltaY); + + Head = p1; + Tail = p2; + + // long time = System.nanoTime(); + // Sort the points along y-axis + Points.Sort(_comparator); + // logger.info( "Triangulation setup [{}ms]", ( System.nanoTime() - time ) / 1e6 ); + } + + + public void FinalizeTriangulation() + { + Triangulatable.AddTriangles(Triangles); + Triangles.Clear(); + } + + public override TriangulationConstraint NewConstraint(TriangulationPoint a, TriangulationPoint b) + { + return new DTSweepConstraint(a, b); + } + + #region Nested type: DTSweepBasin + + public class DTSweepBasin + { + public AdvancingFrontNode bottomNode; + public bool leftHighest; + public AdvancingFrontNode leftNode; + public AdvancingFrontNode rightNode; + public double width; + } + + #endregion + + #region Nested type: DTSweepEdgeEvent + + public class DTSweepEdgeEvent + { + public DTSweepConstraint ConstrainedEdge; + public bool Right; + } + + #endregion + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepPointComparator.cs b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepPointComparator.cs new file mode 100644 index 0000000..8bd3641 --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/DTSweepPointComparator.cs @@ -0,0 +1,69 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + public class DTSweepPointComparator : IComparer + { + #region IComparer Members + + public int Compare(TriangulationPoint p1, TriangulationPoint p2) + { + if (p1.Y < p2.Y) + { + return -1; + } + else if (p1.Y > p2.Y) + { + return 1; + } + else + { + if (p1.X < p2.X) + { + return -1; + } + else if (p1.X > p2.X) + { + return 1; + } + else + { + return 0; + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Delaunay/Sweep/PointOnEdgeException.cs b/Common/Decomposition/CDT/Delaunay/Sweep/PointOnEdgeException.cs new file mode 100644 index 0000000..2f75a3f --- /dev/null +++ b/Common/Decomposition/CDT/Delaunay/Sweep/PointOnEdgeException.cs @@ -0,0 +1,43 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace Poly2Tri.Triangulation.Delaunay.Sweep +{ + public class PointOnEdgeException : NotImplementedException + { + public PointOnEdgeException(string message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/ITriangulatable.cs b/Common/Decomposition/CDT/ITriangulatable.cs new file mode 100644 index 0000000..3bc199e --- /dev/null +++ b/Common/Decomposition/CDT/ITriangulatable.cs @@ -0,0 +1,48 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using Poly2Tri.Triangulation.Delaunay; + +namespace Poly2Tri.Triangulation +{ + public interface Triangulatable + { + IList Points { get; } // MM: Neither of these are used via interface (yet?) + IList Triangles { get; } + TriangulationMode TriangulationMode { get; } + void PrepareTriangulation(TriangulationContext tcx); + + void AddTriangle(DelaunayTriangle t); + void AddTriangles(IEnumerable list); + void ClearTriangles(); + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Orientation.cs b/Common/Decomposition/CDT/Orientation.cs new file mode 100644 index 0000000..64c2ea3 --- /dev/null +++ b/Common/Decomposition/CDT/Orientation.cs @@ -0,0 +1,40 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace FarseerPhysics.Common.Decomposition.CDT +{ + public enum Orientation + { + CW, + CCW, + Collinear + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Polygon/Polygon.cs b/Common/Decomposition/CDT/Polygon/Polygon.cs new file mode 100644 index 0000000..c2621fa --- /dev/null +++ b/Common/Decomposition/CDT/Polygon/Polygon.cs @@ -0,0 +1,272 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Changes from the Java version +// Polygon constructors sprused up, checks for 3+ polys +// Naming of everything +// getTriangulationMode() -> TriangulationMode { get; } +// Exceptions replaced +// Future possibilities +// We have a lot of Add/Clear methods -- we may prefer to just expose the container +// Some self-explanitory methods may deserve commenting anyways + +using System; +using System.Collections.Generic; +using System.Linq; +using Poly2Tri.Triangulation.Delaunay; + +namespace Poly2Tri.Triangulation.Polygon +{ + public class Polygon : Triangulatable + { + protected List _holes; + protected PolygonPoint _last; + protected List _points = new List(); + protected List _steinerPoints; + protected List _triangles; + + /// + /// Create a polygon from a list of at least 3 points with no duplicates. + /// + /// A list of unique points + public Polygon(IList points) + { + if (points.Count < 3) throw new ArgumentException("List has fewer than 3 points", "points"); + + // Lets do one sanity check that first and last point hasn't got same position + // Its something that often happen when importing polygon data from other formats + if (points[0].Equals(points[points.Count - 1])) points.RemoveAt(points.Count - 1); + + _points.AddRange(points.Cast()); + } + + /// + /// Create a polygon from a list of at least 3 points with no duplicates. + /// + /// A list of unique points. + public Polygon(IEnumerable points) : this((points as IList) ?? points.ToArray()) + { + } + + public Polygon() + { + } + + public IList Holes + { + get { return _holes; } + } + + #region Triangulatable Members + + public TriangulationMode TriangulationMode + { + get { return TriangulationMode.Polygon; } + } + + public IList Points + { + get { return _points; } + } + + public IList Triangles + { + get { return _triangles; } + } + + public void AddTriangle(DelaunayTriangle t) + { + _triangles.Add(t); + } + + public void AddTriangles(IEnumerable list) + { + _triangles.AddRange(list); + } + + public void ClearTriangles() + { + if (_triangles != null) _triangles.Clear(); + } + + /// + /// Creates constraints and populates the context with points + /// + /// The context + public void PrepareTriangulation(TriangulationContext tcx) + { + if (_triangles == null) + { + _triangles = new List(_points.Count); + } + else + { + _triangles.Clear(); + } + + // Outer constraints + for (int i = 0; i < _points.Count - 1; i++) + { + tcx.NewConstraint(_points[i], _points[i + 1]); + } + tcx.NewConstraint(_points[0], _points[_points.Count - 1]); + tcx.Points.AddRange(_points); + + // Hole constraints + if (_holes != null) + { + foreach (Polygon p in _holes) + { + for (int i = 0; i < p._points.Count - 1; i++) + { + tcx.NewConstraint(p._points[i], p._points[i + 1]); + } + tcx.NewConstraint(p._points[0], p._points[p._points.Count - 1]); + tcx.Points.AddRange(p._points); + } + } + + if (_steinerPoints != null) + { + tcx.Points.AddRange(_steinerPoints); + } + } + + #endregion + + public void AddSteinerPoint(TriangulationPoint point) + { + if (_steinerPoints == null) + { + _steinerPoints = new List(); + } + _steinerPoints.Add(point); + } + + public void AddSteinerPoints(List points) + { + if (_steinerPoints == null) + { + _steinerPoints = new List(); + } + _steinerPoints.AddRange(points); + } + + public void ClearSteinerPoints() + { + if (_steinerPoints != null) + { + _steinerPoints.Clear(); + } + } + + /// + /// Add a hole to the polygon. + /// + /// A subtraction polygon fully contained inside this polygon. + public void AddHole(Polygon poly) + { + if (_holes == null) _holes = new List(); + _holes.Add(poly); + // XXX: tests could be made here to be sure it is fully inside + // addSubtraction( poly.getPoints() ); + } + + /// + /// Inserts newPoint after point. + /// + /// The point to insert after in the polygon + /// The point to insert into the polygon + public void InsertPointAfter(PolygonPoint point, PolygonPoint newPoint) + { + // Validate that + int index = _points.IndexOf(point); + if (index == -1) + throw new ArgumentException( + "Tried to insert a point into a Polygon after a point not belonging to the Polygon", "point"); + newPoint.Next = point.Next; + newPoint.Previous = point; + point.Next.Previous = newPoint; + point.Next = newPoint; + _points.Insert(index + 1, newPoint); + } + + /// + /// Inserts list (after last point in polygon?) + /// + /// + public void AddPoints(IEnumerable list) + { + PolygonPoint first; + foreach (PolygonPoint p in list) + { + p.Previous = _last; + if (_last != null) + { + p.Next = _last.Next; + _last.Next = p; + } + _last = p; + _points.Add(p); + } + first = (PolygonPoint) _points[0]; + _last.Next = first; + first.Previous = _last; + } + + /// + /// Adds a point after the last in the polygon. + /// + /// The point to add + public void AddPoint(PolygonPoint p) + { + p.Previous = _last; + p.Next = _last.Next; + _last.Next = p; + _points.Add(p); + } + + /// + /// Removes a point from the polygon. + /// + /// + public void RemovePoint(PolygonPoint p) + { + PolygonPoint next, prev; + + next = p.Next; + prev = p.Previous; + prev.Next = next; + next.Previous = prev; + _points.Remove(p); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Polygon/PolygonPoint.cs b/Common/Decomposition/CDT/Polygon/PolygonPoint.cs new file mode 100644 index 0000000..145621a --- /dev/null +++ b/Common/Decomposition/CDT/Polygon/PolygonPoint.cs @@ -0,0 +1,48 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Changes from the Java version +// Replaced get/set Next/Previous with attributes +// Future possibilities +// Documentation! + +namespace Poly2Tri.Triangulation.Polygon +{ + public class PolygonPoint : TriangulationPoint + { + public PolygonPoint(double x, double y) : base(x, y) + { + } + + public PolygonPoint Next { get; set; } + public PolygonPoint Previous { get; set; } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Polygon/PolygonSet.cs b/Common/Decomposition/CDT/Polygon/PolygonSet.cs new file mode 100644 index 0000000..425ff80 --- /dev/null +++ b/Common/Decomposition/CDT/Polygon/PolygonSet.cs @@ -0,0 +1,65 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Changes from the Java version +// Replaced getPolygons with attribute +// Future possibilities +// Replace Add(Polygon) with exposed container? +// Replace entire class with HashSet ? + +using System.Collections.Generic; + +namespace Poly2Tri.Triangulation.Polygon +{ + public class PolygonSet + { + protected List _polygons = new List(); + + public PolygonSet() + { + } + + public PolygonSet(Polygon poly) + { + _polygons.Add(poly); + } + + public IEnumerable Polygons + { + get { return _polygons; } + } + + public void Add(Polygon p) + { + _polygons.Add(p); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Sets/ConstrainedPointSet.cs b/Common/Decomposition/CDT/Sets/ConstrainedPointSet.cs new file mode 100644 index 0000000..5bc4137 --- /dev/null +++ b/Common/Decomposition/CDT/Sets/ConstrainedPointSet.cs @@ -0,0 +1,114 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; + +namespace Poly2Tri.Triangulation.Sets +{ + /* + * Extends the PointSet by adding some Constraints on how it will be triangulated
+ * A constraint defines an edge between two points in the set, these edges can not + * be crossed. They will be enforced triangle edges after a triangulation. + *

+ * + * + * @author Thomas Åhlén, thahlen@gmail.com + */ + + public class ConstrainedPointSet : PointSet + { + private List _constrainedPointList = null; + + public ConstrainedPointSet(List points, int[] index) + : base(points) + { + EdgeIndex = index; + } + + /** + * + * @param points - A list of all points in PointSet + * @param constraints - Pairs of two points defining a constraint, all points must be part of given PointSet! + */ + + public ConstrainedPointSet(List points, IEnumerable constraints) + : base(points) + { + _constrainedPointList = new List(); + _constrainedPointList.AddRange(constraints); + } + + public int[] EdgeIndex { get; private set; } + + public override TriangulationMode TriangulationMode + { + get { return TriangulationMode.Constrained; } + } + + public override void PrepareTriangulation(TriangulationContext tcx) + { + base.PrepareTriangulation(tcx); + if (_constrainedPointList != null) + { + TriangulationPoint p1, p2; + List.Enumerator iterator = _constrainedPointList.GetEnumerator(); + while (iterator.MoveNext()) + { + p1 = iterator.Current; + iterator.MoveNext(); + p2 = iterator.Current; + tcx.NewConstraint(p1, p2); + } + } + else + { + for (int i = 0; i < EdgeIndex.Length; i += 2) + { + // XXX: must change!! + tcx.NewConstraint(Points[EdgeIndex[i]], Points[EdgeIndex[i + 1]]); + } + } + } + + /** + * TODO: TO BE IMPLEMENTED! + * Peforms a validation on given input
+ * 1. Check's if there any constraint edges are crossing or collinear
+ * 2. + * @return + */ + + public bool isValid() + { + return true; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Sets/PointSet.cs b/Common/Decomposition/CDT/Sets/PointSet.cs new file mode 100644 index 0000000..b1e2353 --- /dev/null +++ b/Common/Decomposition/CDT/Sets/PointSet.cs @@ -0,0 +1,84 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using Poly2Tri.Triangulation.Delaunay; + +namespace Poly2Tri.Triangulation.Sets +{ + public class PointSet : Triangulatable + { + public PointSet(List points) + { + Points = new List(points); + } + + #region Triangulatable Members + + public IList Points { get; private set; } + public IList Triangles { get; private set; } + + public virtual TriangulationMode TriangulationMode + { + get { return TriangulationMode.Unconstrained; } + } + + public void AddTriangle(DelaunayTriangle t) + { + Triangles.Add(t); + } + + public void AddTriangles(IEnumerable list) + { + foreach (DelaunayTriangle tri in list) Triangles.Add(tri); + } + + public void ClearTriangles() + { + Triangles.Clear(); + } + + public virtual void PrepareTriangulation(TriangulationContext tcx) + { + if (Triangles == null) + { + Triangles = new List(Points.Count); + } + else + { + Triangles.Clear(); + } + tcx.Points.AddRange(Points); + } + + #endregion + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/TriangulationConstraint.cs b/Common/Decomposition/CDT/TriangulationConstraint.cs new file mode 100644 index 0000000..c36aca1 --- /dev/null +++ b/Common/Decomposition/CDT/TriangulationConstraint.cs @@ -0,0 +1,46 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Forces a triangle edge between two points p and q + * when triangulating. For example used to enforce + * Polygon Edges during a polygon triangulation. + * + * @author Thomas Åhlén, thahlen@gmail.com + */ +namespace Poly2Tri.Triangulation +{ + public class TriangulationConstraint + { + public TriangulationPoint P; + public TriangulationPoint Q; + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/TriangulationContext.cs b/Common/Decomposition/CDT/TriangulationContext.cs new file mode 100644 index 0000000..c4ab958 --- /dev/null +++ b/Common/Decomposition/CDT/TriangulationContext.cs @@ -0,0 +1,87 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Poly2Tri.Triangulation.Delaunay; + +namespace Poly2Tri.Triangulation +{ + public abstract class TriangulationContext + { + public readonly List Points = new List(200); + public readonly List Triangles = new List(); + +#pragma warning disable 414 + private int _stepTime = -1; +#pragma warning restore 414 + + public TriangulationContext() + { + Terminated = false; + } + + public TriangulationMode TriangulationMode { get; protected set; } + public Triangulatable Triangulatable { get; private set; } + + public bool WaitUntilNotified { get; private set; } + public bool Terminated { get; set; } + + public int StepCount { get; private set; } + public virtual bool IsDebugEnabled { get; protected set; } + + public void Done() + { + StepCount++; + } + + public virtual void PrepareTriangulation(Triangulatable t) + { + Triangulatable = t; + TriangulationMode = t.TriangulationMode; + t.PrepareTriangulation(this); + } + + public abstract TriangulationConstraint NewConstraint(TriangulationPoint a, TriangulationPoint b); + + [MethodImpl(MethodImplOptions.Synchronized)] + public void Update(string message) + { + } + + public virtual void Clear() + { + Points.Clear(); + Terminated = false; + StepCount = 0; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/TriangulationMode.cs b/Common/Decomposition/CDT/TriangulationMode.cs new file mode 100644 index 0000000..ea77165 --- /dev/null +++ b/Common/Decomposition/CDT/TriangulationMode.cs @@ -0,0 +1,40 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace Poly2Tri.Triangulation +{ + public enum TriangulationMode + { + Unconstrained, + Constrained, + Polygon + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/TriangulationPoint.cs b/Common/Decomposition/CDT/TriangulationPoint.cs new file mode 100644 index 0000000..7b0249c --- /dev/null +++ b/Common/Decomposition/CDT/TriangulationPoint.cs @@ -0,0 +1,82 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using Poly2Tri.Triangulation.Delaunay.Sweep; + +namespace Poly2Tri.Triangulation +{ + public class TriangulationPoint + { + // List of edges this point constitutes an upper ending point (CDT) + + public double X, Y; + + public TriangulationPoint(double x, double y) + { + X = x; + Y = y; + } + + public List Edges { get; private set; } + + public float Xf + { + get { return (float) X; } + set { X = value; } + } + + public float Yf + { + get { return (float) Y; } + set { Y = value; } + } + + public bool HasEdges + { + get { return Edges != null; } + } + + public override string ToString() + { + return "[" + X + "," + Y + "]"; + } + + public void AddEdge(DTSweepConstraint e) + { + if (Edges == null) + { + Edges = new List(); + } + Edges.Add(e); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/TriangulationUtil.cs b/Common/Decomposition/CDT/TriangulationUtil.cs new file mode 100644 index 0000000..47bf233 --- /dev/null +++ b/Common/Decomposition/CDT/TriangulationUtil.cs @@ -0,0 +1,160 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using FarseerPhysics.Common.Decomposition.CDT; + +namespace Poly2Tri.Triangulation +{ + /** + * @author Thomas Åhlén, thahlen@gmail.com + */ + + public class TriangulationUtil + { + public static double EPSILON = 1e-12; + + ///

+ /// Requirements: + /// 1. a,b and c form a triangle. + /// 2. a and d is know to be on opposite side of bc + /// + /// a + /// + + /// / \ + /// / \ + /// b/ \c + /// +-------+ + /// / B \ + /// / \ + /// + /// Facts: + /// d has to be in area B to have a chance to be inside the circle formed by a,b and c + /// d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW + /// This preknowledge gives us a way to optimize the incircle test + /// + /// triangle point, opposite d + /// triangle point + /// triangle point + /// point opposite a + /// true if d is inside circle, false if on circle edge + public static bool SmartIncircle(TriangulationPoint pa, TriangulationPoint pb, TriangulationPoint pc, + TriangulationPoint pd) + { + double pdx = pd.X; + double pdy = pd.Y; + double adx = pa.X - pdx; + double ady = pa.Y - pdy; + double bdx = pb.X - pdx; + double bdy = pb.Y - pdy; + + double adxbdy = adx*bdy; + double bdxady = bdx*ady; + double oabd = adxbdy - bdxady; + // oabd = orient2d(pa,pb,pd); + if (oabd <= 0) return false; + + double cdx = pc.X - pdx; + double cdy = pc.Y - pdy; + + double cdxady = cdx*ady; + double adxcdy = adx*cdy; + double ocad = cdxady - adxcdy; + // ocad = orient2d(pc,pa,pd); + if (ocad <= 0) return false; + + double bdxcdy = bdx*cdy; + double cdxbdy = cdx*bdy; + + double alift = adx*adx + ady*ady; + double blift = bdx*bdx + bdy*bdy; + double clift = cdx*cdx + cdy*cdy; + + double det = alift*(bdxcdy - cdxbdy) + blift*ocad + clift*oabd; + + return det > 0; + } + + public static bool InScanArea(TriangulationPoint pa, TriangulationPoint pb, TriangulationPoint pc, + TriangulationPoint pd) + { + double pdx = pd.X; + double pdy = pd.Y; + double adx = pa.X - pdx; + double ady = pa.Y - pdy; + double bdx = pb.X - pdx; + double bdy = pb.Y - pdy; + + double adxbdy = adx*bdy; + double bdxady = bdx*ady; + double oabd = adxbdy - bdxady; + // oabd = orient2d(pa,pb,pd); + if (oabd <= 0) + { + return false; + } + + double cdx = pc.X - pdx; + double cdy = pc.Y - pdy; + + double cdxady = cdx*ady; + double adxcdy = adx*cdy; + double ocad = cdxady - adxcdy; + // ocad = orient2d(pc,pa,pd); + if (ocad <= 0) + { + return false; + } + return true; + } + + /// Forumla to calculate signed area + /// Positive if CCW + /// Negative if CW + /// 0 if collinear + /// A[P1,P2,P3] = (x1*y2 - y1*x2) + (x2*y3 - y2*x3) + (x3*y1 - y3*x1) + /// = (x1-x3)*(y2-y3) - (y1-y3)*(x2-x3) + public static Orientation Orient2d(TriangulationPoint pa, TriangulationPoint pb, TriangulationPoint pc) + { + double detleft = (pa.X - pc.X)*(pb.Y - pc.Y); + double detright = (pa.Y - pc.Y)*(pb.X - pc.X); + double val = detleft - detright; + if (val > -EPSILON && val < EPSILON) + { + return Orientation.Collinear; + } + else if (val > 0) + { + return Orientation.CCW; + } + return Orientation.CW; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Util/FixedArray3.cs b/Common/Decomposition/CDT/Util/FixedArray3.cs new file mode 100644 index 0000000..ab0b425 --- /dev/null +++ b/Common/Decomposition/CDT/Util/FixedArray3.cs @@ -0,0 +1,118 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Poly2Tri.Triangulation.Util +{ + public struct FixedArray3 : IEnumerable where T : class + { + public T _0, _1, _2; + + public T this[int index] + { + get + { + switch (index) + { + case 0: + return _0; + case 1: + return _1; + case 2: + return _2; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _0 = value; + break; + case 1: + _1 = value; + break; + case 2: + _2 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return Enumerate().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public bool Contains(T value) + { + for (int i = 0; i < 3; ++i) if (this[i] == value) return true; + return false; + } + + public int IndexOf(T value) + { + for (int i = 0; i < 3; ++i) if (this[i] == value) return i; + return -1; + } + + public void Clear() + { + _0 = _1 = _2 = null; + } + + public void Clear(T value) + { + for (int i = 0; i < 3; ++i) if (this[i] == value) this[i] = null; + } + + private IEnumerable Enumerate() + { + for (int i = 0; i < 3; ++i) yield return this[i]; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Util/FixedBitArray3.cs b/Common/Decomposition/CDT/Util/FixedBitArray3.cs new file mode 100644 index 0000000..d473c4f --- /dev/null +++ b/Common/Decomposition/CDT/Util/FixedBitArray3.cs @@ -0,0 +1,118 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Poly2Tri.Triangulation.Util +{ + public struct FixedBitArray3 : IEnumerable + { + public bool _0, _1, _2; + + public bool this[int index] + { + get + { + switch (index) + { + case 0: + return _0; + case 1: + return _1; + case 2: + return _2; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _0 = value; + break; + case 1: + _1 = value; + break; + case 2: + _2 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return Enumerate().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public bool Contains(bool value) + { + for (int i = 0; i < 3; ++i) if (this[i] == value) return true; + return false; + } + + public int IndexOf(bool value) + { + for (int i = 0; i < 3; ++i) if (this[i] == value) return i; + return -1; + } + + public void Clear() + { + _0 = _1 = _2 = false; + } + + public void Clear(bool value) + { + for (int i = 0; i < 3; ++i) if (this[i] == value) this[i] = false; + } + + private IEnumerable Enumerate() + { + for (int i = 0; i < 3; ++i) yield return this[i]; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Util/PointGenerator.cs b/Common/Decomposition/CDT/Util/PointGenerator.cs new file mode 100644 index 0000000..ed299a4 --- /dev/null +++ b/Common/Decomposition/CDT/Util/PointGenerator.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace Poly2Tri.Triangulation.Util +{ + public class PointGenerator + { + private static readonly Random RNG = new Random(); + + public static List UniformDistribution(int n, double scale) + { + List points = new List(); + for (int i = 0; i < n; i++) + { + points.Add(new TriangulationPoint(scale*(0.5 - RNG.NextDouble()), scale*(0.5 - RNG.NextDouble()))); + } + return points; + } + + public static List UniformGrid(int n, double scale) + { + double x = 0; + double size = scale/n; + double halfScale = 0.5*scale; + + List points = new List(); + for (int i = 0; i < n + 1; i++) + { + x = halfScale - i*size; + for (int j = 0; j < n + 1; j++) + { + points.Add(new TriangulationPoint(x, halfScale - j*size)); + } + } + return points; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDT/Util/PolygonGenerator.cs b/Common/Decomposition/CDT/Util/PolygonGenerator.cs new file mode 100644 index 0000000..8042e4f --- /dev/null +++ b/Common/Decomposition/CDT/Util/PolygonGenerator.cs @@ -0,0 +1,98 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using Poly2Tri.Triangulation.Polygon; + +namespace Poly2Tri.Triangulation.Util +{ + public class PolygonGenerator + { + private static readonly Random RNG = new Random(); + + private static double PI_2 = 2.0*Math.PI; + + public static Polygon.Polygon RandomCircleSweep(double scale, int vertexCount) + { + PolygonPoint point; + PolygonPoint[] points; + double radius = scale/4; + + points = new PolygonPoint[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + do + { + if (i%250 == 0) + { + radius += scale/2*(0.5 - RNG.NextDouble()); + } + else if (i%50 == 0) + { + radius += scale/5*(0.5 - RNG.NextDouble()); + } + else + { + radius += 25*scale/vertexCount*(0.5 - RNG.NextDouble()); + } + radius = radius > scale/2 ? scale/2 : radius; + radius = radius < scale/10 ? scale/10 : radius; + } while (radius < scale/10 || radius > scale/2); + point = new PolygonPoint(radius*Math.Cos((PI_2*i)/vertexCount), + radius*Math.Sin((PI_2*i)/vertexCount)); + points[i] = point; + } + return new Polygon.Polygon(points); + } + + public static Polygon.Polygon RandomCircleSweep2(double scale, int vertexCount) + { + PolygonPoint point; + PolygonPoint[] points; + double radius = scale/4; + + points = new PolygonPoint[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + do + { + radius += scale/5*(0.5 - RNG.NextDouble()); + radius = radius > scale/2 ? scale/2 : radius; + radius = radius < scale/10 ? scale/10 : radius; + } while (radius < scale/10 || radius > scale/2); + point = new PolygonPoint(radius*Math.Cos((PI_2*i)/vertexCount), + radius*Math.Sin((PI_2*i)/vertexCount)); + points[i] = point; + } + return new Polygon.Polygon(points); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/CDTDecomposer.cs b/Common/Decomposition/CDTDecomposer.cs new file mode 100644 index 0000000..ffcc80e --- /dev/null +++ b/Common/Decomposition/CDTDecomposer.cs @@ -0,0 +1,110 @@ +/* Poly2Tri + * Copyright (c) 2009-2010, Poly2Tri Contributors + * http://code.google.com/p/poly2tri/ + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Poly2Tri nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Poly2Tri.Triangulation; +using Poly2Tri.Triangulation.Delaunay; +using Poly2Tri.Triangulation.Delaunay.Sweep; +using Poly2Tri.Triangulation.Polygon; + +using System.Linq; + +namespace FarseerPhysics.Common.Decomposition +{ + public static class CDTDecomposer + { + public static List ConvexPartition(Vertices vertices) + { + Polygon poly = new Polygon(); + + foreach (Vector2 vertex in vertices) + { + poly.Points.Add(new TriangulationPoint(vertex.X, vertex.Y)); + } + + DTSweepContext tcx = new DTSweepContext(); + tcx.PrepareTriangulation(poly); + DTSweep.Triangulate(tcx); + + List results = new List(); + + foreach (DelaunayTriangle triangle in poly.Triangles) + { + Vertices v = new Vertices(); + foreach (TriangulationPoint p in triangle.Points) + { + v.Add(new Vector2((float)p.X, (float)p.Y)); + } + results.Add(v); + } + + return results; + } + + public static List ConvexPartition(DetectedVertices vertices) + { + Polygon poly = new Polygon(); + foreach (var vertex in vertices) + poly.Points.Add(new TriangulationPoint(vertex.X, vertex.Y)); + + if (vertices.Holes != null) + { + foreach (var holeVertices in vertices.Holes) + { + Polygon hole = new Polygon(); + foreach (var vertex in holeVertices) + hole.Points.Add(new TriangulationPoint(vertex.X, vertex.Y)); + + poly.AddHole(hole); + } + } + + DTSweepContext tcx = new DTSweepContext(); + tcx.PrepareTriangulation(poly); + DTSweep.Triangulate(tcx); + + List results = new List(); + + foreach (DelaunayTriangle triangle in poly.Triangles) + { + Vertices v = new Vertices(); + foreach (TriangulationPoint p in triangle.Points) + { + v.Add(new Vector2((float)p.X, (float)p.Y)); + } + results.Add(v); + } + + return results; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/EarclipDecomposer.cs b/Common/Decomposition/EarclipDecomposer.cs new file mode 100644 index 0000000..0149698 --- /dev/null +++ b/Common/Decomposition/EarclipDecomposer.cs @@ -0,0 +1,691 @@ +/* + * C# Version Ported by Matt Bettcher and Ian Qvist 2009-2010 + * + * Original C++ Version Copyright (c) 2007 Eric Jordan + * + * 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.Collections.Generic; +using FarseerPhysics.Common.PolygonManipulation; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.Decomposition +{ + /// + /// Ported from jBox2D. Original author: ewjordan + /// Triangulates a polygon using simple ear-clipping algorithm. + /// + /// Only works on simple polygons. + /// + /// Triangles may be degenerate, especially if you have identical points + /// in the input to the algorithm. Check this before you use them. + /// + public static class EarclipDecomposer + { + //box2D rev 32 - for details, see http://www.box2d.org/forum/viewtopic.php?f=4&t=83&start=50 + + private const float Tol = .001f; + + /// + /// Decomposes a non-convex polygon into a number of convex polygons, up + /// to maxPolys (remaining pieces are thrown out). + /// + /// Each resulting polygon will have no more than Settings.MaxPolygonVertices + /// vertices. + /// + /// Warning: Only works on simple polygons + /// + /// The vertices. + /// + public static List ConvexPartition(Vertices vertices) + { + return ConvexPartition(vertices, int.MaxValue, 0); + } + + /// + /// Decomposes a non-convex polygon into a number of convex polygons, up + /// to maxPolys (remaining pieces are thrown out). + /// Each resulting polygon will have no more than Settings.MaxPolygonVertices + /// vertices. + /// Warning: Only works on simple polygons + /// + /// The vertices. + /// The maximum number of polygons. + /// The tolerance. + /// + public static List ConvexPartition(Vertices vertices, int maxPolys, float tolerance) + { + if (vertices.Count < 3) + return new List { vertices }; + /* + if (vertices.IsConvex() && vertices.Count <= Settings.MaxPolygonVertices) + { + if (vertices.IsCounterClockWise()) + { + Vertices tempP = new Vertices(vertices); + tempP.Reverse(); + tempP = SimplifyTools.CollinearSimplify(tempP); + tempP.ForceCounterClockWise(); + return new List { tempP }; + } + vertices = SimplifyTools.CollinearSimplify(vertices); + vertices.ForceCounterClockWise(); + return new List { vertices }; + } + */ + List triangulated; + + if (vertices.IsCounterClockWise()) + { + Vertices tempP = new Vertices(vertices); + tempP.Reverse(); + triangulated = TriangulatePolygon(tempP); + } + else + { + triangulated = TriangulatePolygon(vertices); + } + if (triangulated.Count < 1) + { + //Still no luck? Oh well... + throw new Exception("Can't triangulate your polygon."); + } + + List polygonizedTriangles = PolygonizeTriangles(triangulated, maxPolys, tolerance); + + //The polygonized triangles are not guaranteed to be without collinear points. We remove + //them to be sure. + for (int i = 0; i < polygonizedTriangles.Count; i++) + { + polygonizedTriangles[i] = SimplifyTools.CollinearSimplify(polygonizedTriangles[i], 0); + } + + //Remove empty vertice collections + for (int i = polygonizedTriangles.Count - 1; i >= 0; i--) + { + if (polygonizedTriangles[i].Count == 0) + polygonizedTriangles.RemoveAt(i); + } + + return polygonizedTriangles; + } + + /// + /// Turns a list of triangles into a list of convex polygons. Very simple + /// method - start with a seed triangle, keep adding triangles to it until + /// you can't add any more without making the polygon non-convex. + /// + /// Returns an integer telling how many polygons were created. Will fill + /// polys array up to polysLength entries, which may be smaller or larger + /// than the return value. + /// + /// Takes O(N///P) where P is the number of resultant polygons, N is triangle + /// count. + /// + /// The final polygon list will not necessarily be minimal, though in + /// practice it works fairly well. + /// + /// The triangulated. + ///The maximun number of polygons + ///The tolerance + /// + public static List PolygonizeTriangles(List triangulated, int maxPolys, float tolerance) + { + List polys = new List(50); + + int polyIndex = 0; + + if (triangulated.Count <= 0) + { + //return empty polygon list + return polys; + } + + bool[] covered = new bool[triangulated.Count]; + for (int i = 0; i < triangulated.Count; ++i) + { + covered[i] = false; + + //Check here for degenerate triangles + if (((triangulated[i].X[0] == triangulated[i].X[1]) && (triangulated[i].Y[0] == triangulated[i].Y[1])) + || + ((triangulated[i].X[1] == triangulated[i].X[2]) && (triangulated[i].Y[1] == triangulated[i].Y[2])) + || + ((triangulated[i].X[0] == triangulated[i].X[2]) && (triangulated[i].Y[0] == triangulated[i].Y[2]))) + { + covered[i] = true; + } + } + + bool notDone = true; + while (notDone) + { + int currTri = -1; + for (int i = 0; i < triangulated.Count; ++i) + { + if (covered[i]) + continue; + currTri = i; + break; + } + if (currTri == -1) + { + notDone = false; + } + else + { + Vertices poly = new Vertices(3); + + for (int i = 0; i < 3; i++) + { + poly.Add(new Vector2(triangulated[currTri].X[i], triangulated[currTri].Y[i])); + } + + covered[currTri] = true; + int index = 0; + for (int i = 0; i < 2 * triangulated.Count; ++i, ++index) + { + while (index >= triangulated.Count) index -= triangulated.Count; + if (covered[index]) + { + continue; + } + Vertices newP = AddTriangle(triangulated[index], poly); + if (newP == null) + continue; // is this right + + if (newP.Count > Settings.MaxPolygonVertices) + continue; + + if (newP.IsConvex()) + { + //Or should it be IsUsable? Maybe re-write IsConvex to apply the angle threshold from Box2d + poly = new Vertices(newP); + covered[index] = true; + } + } + + //We have a maximum of polygons that we need to keep under. + if (polyIndex < maxPolys) + { + //SimplifyTools.MergeParallelEdges(poly, tolerance); + + //If identical points are present, a triangle gets + //borked by the MergeParallelEdges function, hence + //the vertex number check + if (poly.Count >= 3) + polys.Add(new Vertices(poly)); + //else + // printf("Skipping corrupt poly\n"); + } + if (poly.Count >= 3) + polyIndex++; //Must be outside (polyIndex < polysLength) test + } + } + + return polys; + } + + /// + /// Triangulates a polygon using simple ear-clipping algorithm. Returns + /// size of Triangle array unless the polygon can't be triangulated. + /// This should only happen if the polygon self-intersects, + /// though it will not _always_ return null for a bad polygon - it is the + /// caller's responsibility to check for self-intersection, and if it + /// doesn't, it should at least check that the return value is non-null + /// before using. You're warned! + /// + /// Triangles may be degenerate, especially if you have identical points + /// in the input to the algorithm. Check this before you use them. + /// + /// This is totally unoptimized, so for large polygons it should not be part + /// of the simulation loop. + /// + /// Warning: Only works on simple polygons. + /// + /// + public static List TriangulatePolygon(Vertices vertices) + { + List results = new List(); + if (vertices.Count < 3) + return new List(); + + //Recurse and split on pinch points + Vertices pA, pB; + Vertices pin = new Vertices(vertices); + if (ResolvePinchPoint(pin, out pA, out pB)) + { + List mergeA = TriangulatePolygon(pA); + List mergeB = TriangulatePolygon(pB); + + if (mergeA.Count == -1 || mergeB.Count == -1) + throw new Exception("Can't triangulate your polygon."); + + for (int i = 0; i < mergeA.Count; ++i) + { + results.Add(new Triangle(mergeA[i])); + } + for (int i = 0; i < mergeB.Count; ++i) + { + results.Add(new Triangle(mergeB[i])); + } + + return results; + } + + Triangle[] buffer = new Triangle[vertices.Count - 2]; + int bufferSize = 0; + float[] xrem = new float[vertices.Count]; + float[] yrem = new float[vertices.Count]; + for (int i = 0; i < vertices.Count; ++i) + { + xrem[i] = vertices[i].X; + yrem[i] = vertices[i].Y; + } + + int vNum = vertices.Count; + + while (vNum > 3) + { + // Find an ear + int earIndex = -1; + float earMaxMinCross = -10.0f; + for (int i = 0; i < vNum; ++i) + { + if (IsEar(i, xrem, yrem, vNum)) + { + int lower = Remainder(i - 1, vNum); + int upper = Remainder(i + 1, vNum); + Vector2 d1 = new Vector2(xrem[upper] - xrem[i], yrem[upper] - yrem[i]); + Vector2 d2 = new Vector2(xrem[i] - xrem[lower], yrem[i] - yrem[lower]); + Vector2 d3 = new Vector2(xrem[lower] - xrem[upper], yrem[lower] - yrem[upper]); + + d1.Normalize(); + d2.Normalize(); + d3.Normalize(); + float cross12; + MathUtils.Cross(ref d1, ref d2, out cross12); + cross12 = Math.Abs(cross12); + + float cross23; + MathUtils.Cross(ref d2, ref d3, out cross23); + cross23 = Math.Abs(cross23); + + float cross31; + MathUtils.Cross(ref d3, ref d1, out cross31); + cross31 = Math.Abs(cross31); + + //Find the maximum minimum angle + float minCross = Math.Min(cross12, Math.Min(cross23, cross31)); + if (minCross > earMaxMinCross) + { + earIndex = i; + earMaxMinCross = minCross; + } + } + } + + // If we still haven't found an ear, we're screwed. + // Note: sometimes this is happening because the + // remaining points are collinear. Really these + // should just be thrown out without halting triangulation. + if (earIndex == -1) + { + for (int i = 0; i < bufferSize; i++) + { + results.Add(new Triangle(buffer[i])); + } + + return results; + } + + // Clip off the ear: + // - remove the ear tip from the list + + --vNum; + float[] newx = new float[vNum]; + float[] newy = new float[vNum]; + int currDest = 0; + for (int i = 0; i < vNum; ++i) + { + if (currDest == earIndex) ++currDest; + newx[i] = xrem[currDest]; + newy[i] = yrem[currDest]; + ++currDest; + } + + // - add the clipped triangle to the triangle list + int under = (earIndex == 0) ? (vNum) : (earIndex - 1); + int over = (earIndex == vNum) ? 0 : (earIndex + 1); + Triangle toAdd = new Triangle(xrem[earIndex], yrem[earIndex], xrem[over], yrem[over], xrem[under], + yrem[under]); + buffer[bufferSize] = toAdd; + ++bufferSize; + + // - replace the old list with the new one + xrem = newx; + yrem = newy; + } + + Triangle tooAdd = new Triangle(xrem[1], yrem[1], xrem[2], yrem[2], xrem[0], yrem[0]); + buffer[bufferSize] = tooAdd; + ++bufferSize; + + for (int i = 0; i < bufferSize; i++) + { + results.Add(new Triangle(buffer[i])); + } + + return results; + } + + /// + /// Finds and fixes "pinch points," points where two polygon + /// vertices are at the same point. + /// + /// If a pinch point is found, pin is broken up into poutA and poutB + /// and true is returned; otherwise, returns false. + /// + /// Mostly for internal use. + /// + /// O(N^2) time, which sucks... + /// + /// The pin. + /// The pout A. + /// The pout B. + /// + private static bool ResolvePinchPoint(Vertices pin, out Vertices poutA, out Vertices poutB) + { + poutA = new Vertices(); + poutB = new Vertices(); + + if (pin.Count < 3) + return false; + + bool hasPinchPoint = false; + int pinchIndexA = -1; + int pinchIndexB = -1; + for (int i = 0; i < pin.Count; ++i) + { + for (int j = i + 1; j < pin.Count; ++j) + { + //Don't worry about pinch points where the points + //are actually just dupe neighbors + if (Math.Abs(pin[i].X - pin[j].X) < Tol && Math.Abs(pin[i].Y - pin[j].Y) < Tol && j != i + 1) + { + pinchIndexA = i; + pinchIndexB = j; + hasPinchPoint = true; + break; + } + } + if (hasPinchPoint) break; + } + if (hasPinchPoint) + { + int sizeA = pinchIndexB - pinchIndexA; + if (sizeA == pin.Count) return false; //has dupe points at wraparound, not a problem here + for (int i = 0; i < sizeA; ++i) + { + int ind = Remainder(pinchIndexA + i, pin.Count); // is this right + poutA.Add(pin[ind]); + } + + int sizeB = pin.Count - sizeA; + for (int i = 0; i < sizeB; ++i) + { + int ind = Remainder(pinchIndexB + i, pin.Count); // is this right + poutB.Add(pin[ind]); + } + } + return hasPinchPoint; + } + + /// + /// Fix for obnoxious behavior for the % operator for negative numbers... + /// + /// The x. + /// The modulus. + /// + private static int Remainder(int x, int modulus) + { + int rem = x % modulus; + while (rem < 0) + { + rem += modulus; + } + return rem; + } + + private static Vertices AddTriangle(Triangle t, Vertices vertices) + { + // First, find vertices that connect + int firstP = -1; + int firstT = -1; + int secondP = -1; + int secondT = -1; + for (int i = 0; i < vertices.Count; i++) + { + if (t.X[0] == vertices[i].X && t.Y[0] == vertices[i].Y) + { + if (firstP == -1) + { + firstP = i; + firstT = 0; + } + else + { + secondP = i; + secondT = 0; + } + } + else if (t.X[1] == vertices[i].X && t.Y[1] == vertices[i].Y) + { + if (firstP == -1) + { + firstP = i; + firstT = 1; + } + else + { + secondP = i; + secondT = 1; + } + } + else if (t.X[2] == vertices[i].X && t.Y[2] == vertices[i].Y) + { + if (firstP == -1) + { + firstP = i; + firstT = 2; + } + else + { + secondP = i; + secondT = 2; + } + } + } + // Fix ordering if first should be last vertex of poly + if (firstP == 0 && secondP == vertices.Count - 1) + { + firstP = vertices.Count - 1; + secondP = 0; + } + + // Didn't find it + if (secondP == -1) + { + return null; + } + + // Find tip index on triangle + int tipT = 0; + if (tipT == firstT || tipT == secondT) + tipT = 1; + if (tipT == firstT || tipT == secondT) + tipT = 2; + + Vertices result = new Vertices(vertices.Count + 1); + for (int i = 0; i < vertices.Count; i++) + { + result.Add(vertices[i]); + + if (i == firstP) + result.Add(new Vector2(t.X[tipT], t.Y[tipT])); + } + + return result; + } + + /// + /// Checks if vertex i is the tip of an ear in polygon defined by xv[] and + /// yv[]. + /// + /// Assumes clockwise orientation of polygon...ick + /// + /// The i. + /// The xv. + /// The yv. + /// Length of the xv. + /// + /// true if the specified i is ear; otherwise, false. + /// + private static bool IsEar(int i, float[] xv, float[] yv, int xvLength) + { + float dx0, dy0, dx1, dy1; + if (i >= xvLength || i < 0 || xvLength < 3) + { + return false; + } + int upper = i + 1; + int lower = i - 1; + if (i == 0) + { + dx0 = xv[0] - xv[xvLength - 1]; + dy0 = yv[0] - yv[xvLength - 1]; + dx1 = xv[1] - xv[0]; + dy1 = yv[1] - yv[0]; + lower = xvLength - 1; + } + else if (i == xvLength - 1) + { + dx0 = xv[i] - xv[i - 1]; + dy0 = yv[i] - yv[i - 1]; + dx1 = xv[0] - xv[i]; + dy1 = yv[0] - yv[i]; + upper = 0; + } + else + { + dx0 = xv[i] - xv[i - 1]; + dy0 = yv[i] - yv[i - 1]; + dx1 = xv[i + 1] - xv[i]; + dy1 = yv[i + 1] - yv[i]; + } + float cross = dx0 * dy1 - dx1 * dy0; + if (cross > 0) + return false; + Triangle myTri = new Triangle(xv[i], yv[i], xv[upper], yv[upper], + xv[lower], yv[lower]); + for (int j = 0; j < xvLength; ++j) + { + if (j == i || j == lower || j == upper) + continue; + if (myTri.IsInside(xv[j], yv[j])) + return false; + } + return true; + } + } + + public class Triangle + { + public float[] X; + public float[] Y; + + //Constructor automatically fixes orientation to ccw + public Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + X = new float[3]; + Y = new float[3]; + float dx1 = x2 - x1; + float dx2 = x3 - x1; + float dy1 = y2 - y1; + float dy2 = y3 - y1; + float cross = dx1 * dy2 - dx2 * dy1; + bool ccw = (cross > 0); + if (ccw) + { + X[0] = x1; + X[1] = x2; + X[2] = x3; + Y[0] = y1; + Y[1] = y2; + Y[2] = y3; + } + else + { + X[0] = x1; + X[1] = x3; + X[2] = x2; + Y[0] = y1; + Y[1] = y3; + Y[2] = y2; + } + } + + public Triangle(Triangle t) + { + X = new float[3]; + Y = new float[3]; + + X[0] = t.X[0]; + X[1] = t.X[1]; + X[2] = t.X[2]; + Y[0] = t.Y[0]; + Y[1] = t.Y[1]; + Y[2] = t.Y[2]; + } + + public bool IsInside(float x, float y) + { + if (x < X[0] && x < X[1] && x < X[2]) return false; + if (x > X[0] && x > X[1] && x > X[2]) return false; + if (y < Y[0] && y < Y[1] && y < Y[2]) return false; + if (y > Y[0] && y > Y[1] && y > Y[2]) return false; + + float vx2 = x - X[0]; + float vy2 = y - Y[0]; + float vx1 = X[1] - X[0]; + float vy1 = Y[1] - Y[0]; + float vx0 = X[2] - X[0]; + float vy0 = Y[2] - Y[0]; + + float dot00 = vx0 * vx0 + vy0 * vy0; + float dot01 = vx0 * vx1 + vy0 * vy1; + float dot02 = vx0 * vx2 + vy0 * vy2; + float dot11 = vx1 * vx1 + vy1 * vy1; + float dot12 = vx1 * vx2 + vy1 * vy2; + float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + return ((u > 0) && (v > 0) && (u + v < 1)); + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/FlipcodeDecomposer.cs b/Common/Decomposition/FlipcodeDecomposer.cs new file mode 100644 index 0000000..aa3c9c7 --- /dev/null +++ b/Common/Decomposition/FlipcodeDecomposer.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.Decomposition +{ + // Original code can be found here: http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml + + /// + /// Triangulates a polygon into triangles. + /// Doesn't handle holes. + /// + public static class FlipcodeDecomposer + { + private static Vector2 _tmpA; + private static Vector2 _tmpB; + private static Vector2 _tmpC; + + /// + /// Check if the point P is inside the triangle defined by + /// the points A, B, C + /// + /// The A point. + /// The B point. + /// The C point. + /// The point to be tested. + /// True if the point is inside the triangle + private static bool InsideTriangle(ref Vector2 a, ref Vector2 b, ref Vector2 c, ref Vector2 p) + { + //A cross bp + float abp = (c.X - b.X) * (p.Y - b.Y) - (c.Y - b.Y) * (p.X - b.X); + + //A cross ap + float aap = (b.X - a.X) * (p.Y - a.Y) - (b.Y - a.Y) * (p.X - a.X); + + //b cross cp + float bcp = (a.X - c.X) * (p.Y - c.Y) - (a.Y - c.Y) * (p.X - c.X); + + return ((abp >= 0.0f) && (bcp >= 0.0f) && (aap >= 0.0f)); + } + + /// + /// Cut a the contour and add a triangle into V to describe the + /// location of the cut + /// + /// The list of points defining the polygon + /// The index of the first point + /// The index of the second point + /// The index of the third point + /// The number of elements in the array. + /// The array to populate with indicies of triangles. + /// True if a triangle was found + private static bool Snip(Vertices contour, int u, int v, int w, int n, + int[] V) + { + if (Settings.Epsilon > MathUtils.Area(ref _tmpA, ref _tmpB, ref _tmpC)) + { + return false; + } + + for (int p = 0; p < n; p++) + { + if ((p == u) || (p == v) || (p == w)) + { + continue; + } + + Vector2 point = contour[V[p]]; + + if (InsideTriangle(ref _tmpA, ref _tmpB, ref _tmpC, ref point)) + { + return false; + } + } + + return true; + } + + /// + /// Decompose the polygon into triangles + /// + /// The list of points describing the polygon + /// + public static List ConvexPartition(Vertices contour) + { + int n = contour.Count; + if (n < 3) + return new List(); + + int[] V = new int[n]; + + // We want a counter-clockwise polygon in V + if (contour.IsCounterClockWise()) + { + for (int v = 0; v < n; v++) + V[v] = v; + } + else + { + for (int v = 0; v < n; v++) + V[v] = (n - 1) - v; + } + + int nv = n; + + // Remove nv-2 Vertices, creating 1 triangle every time + int count = 2 * nv; /* error detection */ + + List result = new List(); + + for (int v = nv - 1; nv > 2; ) + { + // If we loop, it is probably a non-simple polygon + if (0 >= (count--)) + { + // Triangulate: ERROR - probable bad polygon! + return new List(); + } + + // Three consecutive vertices in current polygon, + int u = v; + if (nv <= u) + u = 0; // Previous + v = u + 1; + if (nv <= v) + v = 0; // New v + int w = v + 1; + if (nv <= w) + w = 0; // Next + + _tmpA = contour[V[u]]; + _tmpB = contour[V[v]]; + _tmpC = contour[V[w]]; + + if (Snip(contour, u, v, w, nv, V)) + { + int s, t; + + // Output Triangle + Vertices triangle = new Vertices(3); + triangle.Add(_tmpA); + triangle.Add(_tmpB); + triangle.Add(_tmpC); + result.Add(triangle); + + // Remove v from remaining polygon + for (s = v, t = v + 1; t < nv; s++, t++) + { + V[s] = V[t]; + } + nv--; + + // Reset error detection counter + count = 2 * nv; + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Common/Decomposition/SeidelDecomposer.cs b/Common/Decomposition/SeidelDecomposer.cs new file mode 100644 index 0000000..47c4e56 --- /dev/null +++ b/Common/Decomposition/SeidelDecomposer.cs @@ -0,0 +1,1057 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.Decomposition +{ + //From the Poly2Tri project by Mason Green: http://code.google.com/p/poly2tri/source/browse?repo=archive#hg/scala/src/org/poly2tri/seidel + + /// + /// Convex decomposition algorithm based on Raimund Seidel's paper "A simple and fast incremental randomized + /// algorithm for computing trapezoidal decompositions and for triangulating polygons" + /// See also: "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2 + /// "Computational Geometry in C", 2nd edition, by Joseph O'Rourke + /// + public static class SeidelDecomposer + { + /// + /// Decompose the polygon into several smaller non-concave polygon. + /// + /// The polygon to decompose. + /// The sheer to use. If you get bad results, try using a higher value. The default value is 0.001 + /// A list of triangles + public static List ConvexPartition(Vertices vertices, float sheer) + { + List compatList = new List(vertices.Count); + + foreach (Vector2 vertex in vertices) + { + compatList.Add(new Point(vertex.X, vertex.Y)); + } + + Triangulator t = new Triangulator(compatList, sheer); + + List list = new List(); + + foreach (List triangle in t.Triangles) + { + Vertices verts = new Vertices(triangle.Count); + + foreach (Point point in triangle) + { + verts.Add(new Vector2(point.X, point.Y)); + } + + list.Add(verts); + } + + return list; + } + + /// + /// Decompose the polygon into several smaller non-concave polygon. + /// + /// The polygon to decompose. + /// The sheer to use. If you get bad results, try using a higher value. The default value is 0.001 + /// A list of trapezoids + public static List ConvexPartitionTrapezoid(Vertices vertices, float sheer) + { + List compatList = new List(vertices.Count); + + foreach (Vector2 vertex in vertices) + { + compatList.Add(new Point(vertex.X, vertex.Y)); + } + + Triangulator t = new Triangulator(compatList, sheer); + + List list = new List(); + + foreach (Trapezoid trapezoid in t.Trapezoids) + { + Vertices verts = new Vertices(); + + List points = trapezoid.Vertices(); + foreach (Point point in points) + { + verts.Add(new Vector2(point.X, point.Y)); + } + + list.Add(verts); + } + + return list; + } + } + + internal class MonotoneMountain + { + private const float PiSlop = 3.1f; + public List> Triangles; + private HashSet _convexPoints; + private Point _head; + + // Monotone mountain points + private List _monoPoly; + + // Triangles that constitute the mountain + + // Used to track which side of the line we are on + private bool _positive; + private int _size; + private Point _tail; + + // Almost Pi! + + public MonotoneMountain() + { + _size = 0; + _tail = null; + _head = null; + _positive = false; + _convexPoints = new HashSet(); + _monoPoly = new List(); + Triangles = new List>(); + } + + // Append a point to the list + public void Add(Point point) + { + if (_size == 0) + { + _head = point; + _size = 1; + } + else if (_size == 1) + { + // Keep repeat points out of the list + _tail = point; + _tail.Prev = _head; + _head.Next = _tail; + _size = 2; + } + else + { + // Keep repeat points out of the list + _tail.Next = point; + point.Prev = _tail; + _tail = point; + _size += 1; + } + } + + // Remove a point from the list + public void Remove(Point point) + { + Point next = point.Next; + Point prev = point.Prev; + point.Prev.Next = next; + point.Next.Prev = prev; + _size -= 1; + } + + // Partition a x-monotone mountain into triangles O(n) + // See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52 + public void Process() + { + // Establish the proper sign + _positive = AngleSign(); + // create monotone polygon - for dubug purposes + GenMonoPoly(); + + // Initialize internal angles at each nonbase vertex + // Link strictly convex vertices into a list, ignore reflex vertices + Point p = _head.Next; + while (p.Neq(_tail)) + { + float a = Angle(p); + // If the point is almost colinear with it's neighbor, remove it! + if (a >= PiSlop || a <= -PiSlop || a == 0.0) + Remove(p); + else if (IsConvex(p)) + _convexPoints.Add(p); + p = p.Next; + } + + Triangulate(); + } + + private void Triangulate() + { + while (_convexPoints.Count != 0) + { + IEnumerator e = _convexPoints.GetEnumerator(); + e.MoveNext(); + Point ear = e.Current; + + _convexPoints.Remove(ear); + Point a = ear.Prev; + Point b = ear; + Point c = ear.Next; + List triangle = new List(3); + triangle.Add(a); + triangle.Add(b); + triangle.Add(c); + + Triangles.Add(triangle); + + // Remove ear, update angles and convex list + Remove(ear); + if (Valid(a)) + _convexPoints.Add(a); + if (Valid(c)) + _convexPoints.Add(c); + } + + Debug.Assert(_size <= 3, "Triangulation bug, please report"); + } + + private bool Valid(Point p) + { + return p.Neq(_head) && p.Neq(_tail) && IsConvex(p); + } + + // Create the monotone polygon + private void GenMonoPoly() + { + Point p = _head; + while (p != null) + { + _monoPoly.Add(p); + p = p.Next; + } + } + + private float Angle(Point p) + { + Point a = (p.Next - p); + Point b = (p.Prev - p); + return (float)Math.Atan2(a.Cross(b), a.Dot(b)); + } + + private bool AngleSign() + { + Point a = (_head.Next - _head); + Point b = (_tail - _head); + return Math.Atan2(a.Cross(b), a.Dot(b)) >= 0; + } + + // Determines if the inslide angle is convex or reflex + private bool IsConvex(Point p) + { + if (_positive != (Angle(p) >= 0)) + return false; + return true; + } + } + + // Node for a Directed Acyclic graph (DAG) + internal abstract class Node + { + protected Node LeftChild; + public List ParentList; + protected Node RightChild; + + protected Node(Node left, Node right) + { + ParentList = new List(); + LeftChild = left; + RightChild = right; + + if (left != null) + left.ParentList.Add(this); + if (right != null) + right.ParentList.Add(this); + } + + public abstract Sink Locate(Edge s); + + // Replace a node in the graph with this node + // Make sure parent pointers are updated + public void Replace(Node node) + { + foreach (Node parent in node.ParentList) + { + // Select the correct node to replace (left or right child) + if (parent.LeftChild == node) + parent.LeftChild = this; + else + parent.RightChild = this; + } + ParentList.AddRange(node.ParentList); + } + } + + // Directed Acyclic graph (DAG) + // See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2 + internal class QueryGraph + { + private Node _head; + + public QueryGraph(Node head) + { + _head = head; + } + + private Trapezoid Locate(Edge edge) + { + return _head.Locate(edge).Trapezoid; + } + + public List FollowEdge(Edge edge) + { + List trapezoids = new List(); + trapezoids.Add(Locate(edge)); + int j = 0; + + while (edge.Q.X > trapezoids[j].RightPoint.X) + { + if (edge.IsAbove(trapezoids[j].RightPoint)) + { + trapezoids.Add(trapezoids[j].UpperRight); + } + else + { + trapezoids.Add(trapezoids[j].LowerRight); + } + j += 1; + } + return trapezoids; + } + + private void Replace(Sink sink, Node node) + { + if (sink.ParentList.Count == 0) + _head = node; + else + node.Replace(sink); + } + + public void Case1(Sink sink, Edge edge, Trapezoid[] tList) + { + YNode yNode = new YNode(edge, Sink.Isink(tList[1]), Sink.Isink(tList[2])); + XNode qNode = new XNode(edge.Q, yNode, Sink.Isink(tList[3])); + XNode pNode = new XNode(edge.P, Sink.Isink(tList[0]), qNode); + Replace(sink, pNode); + } + + public void Case2(Sink sink, Edge edge, Trapezoid[] tList) + { + YNode yNode = new YNode(edge, Sink.Isink(tList[1]), Sink.Isink(tList[2])); + XNode pNode = new XNode(edge.P, Sink.Isink(tList[0]), yNode); + Replace(sink, pNode); + } + + public void Case3(Sink sink, Edge edge, Trapezoid[] tList) + { + YNode yNode = new YNode(edge, Sink.Isink(tList[0]), Sink.Isink(tList[1])); + Replace(sink, yNode); + } + + public void Case4(Sink sink, Edge edge, Trapezoid[] tList) + { + YNode yNode = new YNode(edge, Sink.Isink(tList[0]), Sink.Isink(tList[1])); + XNode qNode = new XNode(edge.Q, yNode, Sink.Isink(tList[2])); + Replace(sink, qNode); + } + } + + internal class Sink : Node + { + public Trapezoid Trapezoid; + + private Sink(Trapezoid trapezoid) + : base(null, null) + { + Trapezoid = trapezoid; + trapezoid.Sink = this; + } + + public static Sink Isink(Trapezoid trapezoid) + { + if (trapezoid.Sink == null) + return new Sink(trapezoid); + return trapezoid.Sink; + } + + public override Sink Locate(Edge edge) + { + return this; + } + } + + // See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2 + internal class TrapezoidalMap + { + // Trapezoid container + public HashSet Map; + + // AABB margin + + // Bottom segment that spans multiple trapezoids + private Edge _bCross; + + // Top segment that spans multiple trapezoids + private Edge _cross; + private float _margin; + + public TrapezoidalMap() + { + Map = new HashSet(); + _margin = 50.0f; + _bCross = null; + _cross = null; + } + + public void Clear() + { + _bCross = null; + _cross = null; + } + + // Case 1: segment completely enclosed by trapezoid + // break trapezoid into 4 smaller trapezoids + public Trapezoid[] Case1(Trapezoid t, Edge e) + { + Trapezoid[] trapezoids = new Trapezoid[4]; + trapezoids[0] = new Trapezoid(t.LeftPoint, e.P, t.Top, t.Bottom); + trapezoids[1] = new Trapezoid(e.P, e.Q, t.Top, e); + trapezoids[2] = new Trapezoid(e.P, e.Q, e, t.Bottom); + trapezoids[3] = new Trapezoid(e.Q, t.RightPoint, t.Top, t.Bottom); + + trapezoids[0].UpdateLeft(t.UpperLeft, t.LowerLeft); + trapezoids[1].UpdateLeftRight(trapezoids[0], null, trapezoids[3], null); + trapezoids[2].UpdateLeftRight(null, trapezoids[0], null, trapezoids[3]); + trapezoids[3].UpdateRight(t.UpperRight, t.LowerRight); + + return trapezoids; + } + + // Case 2: Trapezoid contains point p, q lies outside + // break trapezoid into 3 smaller trapezoids + public Trapezoid[] Case2(Trapezoid t, Edge e) + { + Point rp; + if (e.Q.X == t.RightPoint.X) + rp = e.Q; + else + rp = t.RightPoint; + + Trapezoid[] trapezoids = new Trapezoid[3]; + trapezoids[0] = new Trapezoid(t.LeftPoint, e.P, t.Top, t.Bottom); + trapezoids[1] = new Trapezoid(e.P, rp, t.Top, e); + trapezoids[2] = new Trapezoid(e.P, rp, e, t.Bottom); + + trapezoids[0].UpdateLeft(t.UpperLeft, t.LowerLeft); + trapezoids[1].UpdateLeftRight(trapezoids[0], null, t.UpperRight, null); + trapezoids[2].UpdateLeftRight(null, trapezoids[0], null, t.LowerRight); + + _bCross = t.Bottom; + _cross = t.Top; + + e.Above = trapezoids[1]; + e.Below = trapezoids[2]; + + return trapezoids; + } + + // Case 3: Trapezoid is bisected + public Trapezoid[] Case3(Trapezoid t, Edge e) + { + Point lp; + if (e.P.X == t.LeftPoint.X) + lp = e.P; + else + lp = t.LeftPoint; + + Point rp; + if (e.Q.X == t.RightPoint.X) + rp = e.Q; + else + rp = t.RightPoint; + + Trapezoid[] trapezoids = new Trapezoid[2]; + + if (_cross == t.Top) + { + trapezoids[0] = t.UpperLeft; + trapezoids[0].UpdateRight(t.UpperRight, null); + trapezoids[0].RightPoint = rp; + } + else + { + trapezoids[0] = new Trapezoid(lp, rp, t.Top, e); + trapezoids[0].UpdateLeftRight(t.UpperLeft, e.Above, t.UpperRight, null); + } + + if (_bCross == t.Bottom) + { + trapezoids[1] = t.LowerLeft; + trapezoids[1].UpdateRight(null, t.LowerRight); + trapezoids[1].RightPoint = rp; + } + else + { + trapezoids[1] = new Trapezoid(lp, rp, e, t.Bottom); + trapezoids[1].UpdateLeftRight(e.Below, t.LowerLeft, null, t.LowerRight); + } + + _bCross = t.Bottom; + _cross = t.Top; + + e.Above = trapezoids[0]; + e.Below = trapezoids[1]; + + return trapezoids; + } + + // Case 4: Trapezoid contains point q, p lies outside + // break trapezoid into 3 smaller trapezoids + public Trapezoid[] Case4(Trapezoid t, Edge e) + { + Point lp; + if (e.P.X == t.LeftPoint.X) + lp = e.P; + else + lp = t.LeftPoint; + + Trapezoid[] trapezoids = new Trapezoid[3]; + + if (_cross == t.Top) + { + trapezoids[0] = t.UpperLeft; + trapezoids[0].RightPoint = e.Q; + } + else + { + trapezoids[0] = new Trapezoid(lp, e.Q, t.Top, e); + trapezoids[0].UpdateLeft(t.UpperLeft, e.Above); + } + + if (_bCross == t.Bottom) + { + trapezoids[1] = t.LowerLeft; + trapezoids[1].RightPoint = e.Q; + } + else + { + trapezoids[1] = new Trapezoid(lp, e.Q, e, t.Bottom); + trapezoids[1].UpdateLeft(e.Below, t.LowerLeft); + } + + trapezoids[2] = new Trapezoid(e.Q, t.RightPoint, t.Top, t.Bottom); + trapezoids[2].UpdateLeftRight(trapezoids[0], trapezoids[1], t.UpperRight, t.LowerRight); + + return trapezoids; + } + + // Create an AABB around segments + public Trapezoid BoundingBox(List edges) + { + Point max = edges[0].P + _margin; + Point min = edges[0].Q - _margin; + + foreach (Edge e in edges) + { + if (e.P.X > max.X) max = new Point(e.P.X + _margin, max.Y); + if (e.P.Y > max.Y) max = new Point(max.X, e.P.Y + _margin); + if (e.Q.X > max.X) max = new Point(e.Q.X + _margin, max.Y); + if (e.Q.Y > max.Y) max = new Point(max.X, e.Q.Y + _margin); + if (e.P.X < min.X) min = new Point(e.P.X - _margin, min.Y); + if (e.P.Y < min.Y) min = new Point(min.X, e.P.Y - _margin); + if (e.Q.X < min.X) min = new Point(e.Q.X - _margin, min.Y); + if (e.Q.Y < min.Y) min = new Point(min.X, e.Q.Y - _margin); + } + + Edge top = new Edge(new Point(min.X, max.Y), new Point(max.X, max.Y)); + Edge bottom = new Edge(new Point(min.X, min.Y), new Point(max.X, min.Y)); + Point left = bottom.P; + Point right = top.Q; + + return new Trapezoid(left, right, top, bottom); + } + } + + internal class Point + { + // Pointers to next and previous points in Monontone Mountain + public Point Next, Prev; + public float X, Y; + + public Point(float x, float y) + { + X = x; + Y = y; + Next = null; + Prev = null; + } + + public static Point operator -(Point p1, Point p2) + { + return new Point(p1.X - p2.X, p1.Y - p2.Y); + } + + public static Point operator +(Point p1, Point p2) + { + return new Point(p1.X + p2.X, p1.Y + p2.Y); + } + + public static Point operator -(Point p1, float f) + { + return new Point(p1.X - f, p1.Y - f); + } + + public static Point operator +(Point p1, float f) + { + return new Point(p1.X + f, p1.Y + f); + } + + public float Cross(Point p) + { + return X * p.Y - Y * p.X; + } + + public float Dot(Point p) + { + return X * p.X + Y * p.Y; + } + + public bool Neq(Point p) + { + return p.X != X || p.Y != Y; + } + + public float Orient2D(Point pb, Point pc) + { + float acx = X - pc.X; + float bcx = pb.X - pc.X; + float acy = Y - pc.Y; + float bcy = pb.Y - pc.Y; + return acx * bcy - acy * bcx; + } + } + + internal class Edge + { + // Pointers used for building trapezoidal map + public Trapezoid Above; + public float B; + public Trapezoid Below; + + // Equation of a line: y = m*x + b + // Slope of the line (m) + + // Montone mountain points + public HashSet MPoints; + public Point P; + public Point Q; + public float Slope; + + // Y intercept + + public Edge(Point p, Point q) + { + P = p; + Q = q; + + if (q.X - p.X != 0) + Slope = (q.Y - p.Y) / (q.X - p.X); + else + Slope = 0; + + B = p.Y - (p.X * Slope); + Above = null; + Below = null; + MPoints = new HashSet(); + MPoints.Add(p); + MPoints.Add(q); + } + + public bool IsAbove(Point point) + { + return P.Orient2D(Q, point) < 0; + } + + public bool IsBelow(Point point) + { + return P.Orient2D(Q, point) > 0; + } + + public void AddMpoint(Point point) + { + foreach (Point mp in MPoints) + if (!mp.Neq(point)) + return; + + MPoints.Add(point); + } + } + + internal class Trapezoid + { + public Edge Bottom; + public bool Inside; + public Point LeftPoint; + + // Neighbor pointers + public Trapezoid LowerLeft; + public Trapezoid LowerRight; + + public Point RightPoint; + public Sink Sink; + + public Edge Top; + public Trapezoid UpperLeft; + public Trapezoid UpperRight; + + public Trapezoid(Point leftPoint, Point rightPoint, Edge top, Edge bottom) + { + LeftPoint = leftPoint; + RightPoint = rightPoint; + Top = top; + Bottom = bottom; + UpperLeft = null; + UpperRight = null; + LowerLeft = null; + LowerRight = null; + Inside = true; + Sink = null; + } + + // Update neighbors to the left + public void UpdateLeft(Trapezoid ul, Trapezoid ll) + { + UpperLeft = ul; + if (ul != null) ul.UpperRight = this; + LowerLeft = ll; + if (ll != null) ll.LowerRight = this; + } + + // Update neighbors to the right + public void UpdateRight(Trapezoid ur, Trapezoid lr) + { + UpperRight = ur; + if (ur != null) ur.UpperLeft = this; + LowerRight = lr; + if (lr != null) lr.LowerLeft = this; + } + + // Update neighbors on both sides + public void UpdateLeftRight(Trapezoid ul, Trapezoid ll, Trapezoid ur, Trapezoid lr) + { + UpperLeft = ul; + if (ul != null) ul.UpperRight = this; + LowerLeft = ll; + if (ll != null) ll.LowerRight = this; + UpperRight = ur; + if (ur != null) ur.UpperLeft = this; + LowerRight = lr; + if (lr != null) lr.LowerLeft = this; + } + + // Recursively trim outside neighbors + public void TrimNeighbors() + { + if (Inside) + { + Inside = false; + if (UpperLeft != null) UpperLeft.TrimNeighbors(); + if (LowerLeft != null) LowerLeft.TrimNeighbors(); + if (UpperRight != null) UpperRight.TrimNeighbors(); + if (LowerRight != null) LowerRight.TrimNeighbors(); + } + } + + // Determines if this point lies inside the trapezoid + public bool Contains(Point point) + { + return (point.X > LeftPoint.X && point.X < RightPoint.X && Top.IsAbove(point) && Bottom.IsBelow(point)); + } + + public List Vertices() + { + List verts = new List(4); + verts.Add(LineIntersect(Top, LeftPoint.X)); + verts.Add(LineIntersect(Bottom, LeftPoint.X)); + verts.Add(LineIntersect(Bottom, RightPoint.X)); + verts.Add(LineIntersect(Top, RightPoint.X)); + return verts; + } + + private Point LineIntersect(Edge edge, float x) + { + float y = edge.Slope * x + edge.B; + return new Point(x, y); + } + + // Add points to monotone mountain + public void AddPoints() + { + if (LeftPoint != Bottom.P) + { + Bottom.AddMpoint(LeftPoint); + } + if (RightPoint != Bottom.Q) + { + Bottom.AddMpoint(RightPoint); + } + if (LeftPoint != Top.P) + { + Top.AddMpoint(LeftPoint); + } + if (RightPoint != Top.Q) + { + Top.AddMpoint(RightPoint); + } + } + } + + internal class XNode : Node + { + private Point _point; + + public XNode(Point point, Node lChild, Node rChild) + : base(lChild, rChild) + { + _point = point; + } + + public override Sink Locate(Edge edge) + { + if (edge.P.X >= _point.X) + // Move to the right in the graph + return RightChild.Locate(edge); + // Move to the left in the graph + return LeftChild.Locate(edge); + } + } + + internal class YNode : Node + { + private Edge _edge; + + public YNode(Edge edge, Node lChild, Node rChild) + : base(lChild, rChild) + { + _edge = edge; + } + + public override Sink Locate(Edge edge) + { + if (_edge.IsAbove(edge.P)) + // Move down the graph + return RightChild.Locate(edge); + + if (_edge.IsBelow(edge.P)) + // Move up the graph + return LeftChild.Locate(edge); + + // s and segment share the same endpoint, p + if (edge.Slope < _edge.Slope) + // Move down the graph + return RightChild.Locate(edge); + + // Move up the graph + return LeftChild.Locate(edge); + } + } + + internal class Triangulator + { + // Trapezoid decomposition list + public List Trapezoids; + public List> Triangles; + + // Initialize trapezoidal map and query structure + private Trapezoid _boundingBox; + private List _edgeList; + private QueryGraph _queryGraph; + private float _sheer = 0.001f; + private TrapezoidalMap _trapezoidalMap; + private List _xMonoPoly; + + public Triangulator(List polyLine, float sheer) + { + _sheer = sheer; + Triangles = new List>(); + Trapezoids = new List(); + _xMonoPoly = new List(); + _edgeList = InitEdges(polyLine); + _trapezoidalMap = new TrapezoidalMap(); + _boundingBox = _trapezoidalMap.BoundingBox(_edgeList); + _queryGraph = new QueryGraph(Sink.Isink(_boundingBox)); + + Process(); + } + + // Build the trapezoidal map and query graph + private void Process() + { + foreach (Edge edge in _edgeList) + { + List traps = _queryGraph.FollowEdge(edge); + + // Remove trapezoids from trapezoidal Map + foreach (Trapezoid t in traps) + { + _trapezoidalMap.Map.Remove(t); + + bool cp = t.Contains(edge.P); + bool cq = t.Contains(edge.Q); + Trapezoid[] tList; + + if (cp && cq) + { + tList = _trapezoidalMap.Case1(t, edge); + _queryGraph.Case1(t.Sink, edge, tList); + } + else if (cp && !cq) + { + tList = _trapezoidalMap.Case2(t, edge); + _queryGraph.Case2(t.Sink, edge, tList); + } + else if (!cp && !cq) + { + tList = _trapezoidalMap.Case3(t, edge); + _queryGraph.Case3(t.Sink, edge, tList); + } + else + { + tList = _trapezoidalMap.Case4(t, edge); + _queryGraph.Case4(t.Sink, edge, tList); + } + // Add new trapezoids to map + foreach (Trapezoid y in tList) + { + _trapezoidalMap.Map.Add(y); + } + } + _trapezoidalMap.Clear(); + } + + // Mark outside trapezoids + foreach (Trapezoid t in _trapezoidalMap.Map) + { + MarkOutside(t); + } + + // Collect interior trapezoids + foreach (Trapezoid t in _trapezoidalMap.Map) + { + if (t.Inside) + { + Trapezoids.Add(t); + t.AddPoints(); + } + } + + // Generate the triangles + CreateMountains(); + } + + // Build a list of x-monotone mountains + private void CreateMountains() + { + foreach (Edge edge in _edgeList) + { + if (edge.MPoints.Count > 2) + { + MonotoneMountain mountain = new MonotoneMountain(); + + // Sorting is a perfromance hit. Literature says this can be accomplised in + // linear time, although I don't see a way around using traditional methods + // when using a randomized incremental algorithm + + // Insertion sort is one of the fastest algorithms for sorting arrays containing + // fewer than ten elements, or for lists that are already mostly sorted. + + List points = new List(edge.MPoints); + points.Sort((p1, p2) => p1.X.CompareTo(p2.X)); + + foreach (Point p in points) + mountain.Add(p); + + // Triangulate monotone mountain + mountain.Process(); + + // Extract the triangles into a single list + foreach (List t in mountain.Triangles) + { + Triangles.Add(t); + } + + _xMonoPoly.Add(mountain); + } + } + } + + // Mark the outside trapezoids surrounding the polygon + private void MarkOutside(Trapezoid t) + { + if (t.Top == _boundingBox.Top || t.Bottom == _boundingBox.Bottom) + t.TrimNeighbors(); + } + + // Create segments and connect end points; update edge event pointer + private List InitEdges(List points) + { + List edges = new List(); + + for (int i = 0; i < points.Count - 1; i++) + { + edges.Add(new Edge(points[i], points[i + 1])); + } + edges.Add(new Edge(points[0], points[points.Count - 1])); + return OrderSegments(edges); + } + + private List OrderSegments(List edgeInput) + { + // Ignore vertical segments! + List edges = new List(); + + foreach (Edge e in edgeInput) + { + Point p = ShearTransform(e.P); + Point q = ShearTransform(e.Q); + + // Point p must be to the left of point q + if (p.X > q.X) + { + edges.Add(new Edge(q, p)); + } + else if (p.X < q.X) + { + edges.Add(new Edge(p, q)); + } + } + + // Randomized triangulation improves performance + // See Seidel's paper, or O'Rourke's book, p. 57 + Shuffle(edges); + return edges; + } + + private static void Shuffle(IList list) + { + Random rng = new Random(); + int n = list.Count; + while (n > 1) + { + n--; + int k = rng.Next(n + 1); + T value = list[k]; + list[k] = list[n]; + list[n] = value; + } + } + + // Prevents any two distinct endpoints from lying on a common vertical line, and avoiding + // the degenerate case. See Mark de Berg et al, Chapter 6.3 + private Point ShearTransform(Point point) + { + return new Point(point.X + _sheer * point.Y, point.Y); + } + } +} \ No newline at end of file diff --git a/Common/FixedArray.cs b/Common/FixedArray.cs new file mode 100644 index 0000000..6758c7a --- /dev/null +++ b/Common/FixedArray.cs @@ -0,0 +1,227 @@ +/* +* 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; + +namespace FarseerPhysics.Common +{ + public struct FixedArray2 + { + private T _value0; + private T _value1; + + public T this[int index] + { + get + { + switch (index) + { + case 0: + return _value0; + case 1: + return _value1; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _value0 = value; + break; + case 1: + _value1 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + } + + public struct FixedArray3 + { + private T _value0; + private T _value1; + private T _value2; + + public T this[int index] + { + get + { + switch (index) + { + case 0: + return _value0; + case 1: + return _value1; + case 2: + return _value2; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _value0 = value; + break; + case 1: + _value1 = value; + break; + case 2: + _value2 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + } + + public struct FixedArray4 + { + private T _value0; + private T _value1; + private T _value2; + private T _value3; + + public T this[int index] + { + get + { + switch (index) + { + case 0: + return _value0; + case 1: + return _value1; + case 2: + return _value2; + case 3: + return _value3; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _value0 = value; + break; + case 1: + _value1 = value; + break; + case 2: + _value2 = value; + break; + case 3: + _value3 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + } + + public struct FixedArray8 + { + private T _value0; + private T _value1; + private T _value2; + private T _value3; + private T _value4; + private T _value5; + private T _value6; + private T _value7; + + public T this[int index] + { + get + { + switch (index) + { + case 0: + return _value0; + case 1: + return _value1; + case 2: + return _value2; + case 3: + return _value3; + case 4: + return _value4; + case 5: + return _value5; + case 6: + return _value6; + case 7: + return _value7; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + _value0 = value; + break; + case 1: + _value1 = value; + break; + case 2: + _value2 = value; + break; + case 3: + _value3 = value; + break; + case 4: + _value4 = value; + break; + case 5: + _value5 = value; + break; + case 6: + _value6 = value; + break; + case 7: + _value7 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + } +} \ No newline at end of file diff --git a/Common/HashSet.cs b/Common/HashSet.cs new file mode 100644 index 0000000..f548535 --- /dev/null +++ b/Common/HashSet.cs @@ -0,0 +1,81 @@ + +#if WINDOWS_PHONE || XBOX + +//TODO: FIX + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace FarseerPhysics.Common +{ + + public class HashSet : ICollection + { + private Dictionary _dict; + + public HashSet(int capacity) + { + _dict = new Dictionary(capacity); + } + + public HashSet() + { + _dict = new Dictionary(); + } + + // Methods + +#region ICollection Members + + public void Add(T item) + { + // We don't care for the value in dictionary, Keys matter. + _dict.Add(item, 0); + } + + public void Clear() + { + _dict.Clear(); + } + + public bool Contains(T item) + { + return _dict.ContainsKey(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(T item) + { + return _dict.Remove(item); + } + + public IEnumerator GetEnumerator() + { + return _dict.Keys.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _dict.Keys.GetEnumerator(); + } + + // Properties + public int Count + { + get { return _dict.Keys.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Common/LineTools.cs b/Common/LineTools.cs new file mode 100644 index 0000000..c0873d1 --- /dev/null +++ b/Common/LineTools.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Collision; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + /// + /// Collection of helper methods for misc collisions. + /// Does float tolerance and line collisions with lines and AABBs. + /// + public static class LineTools + { + public static float DistanceBetweenPointAndPoint(ref Vector2 point1, ref Vector2 point2) + { + Vector2 v; + Vector2.Subtract(ref point1, ref point2, out v); + return v.Length(); + } + + public static float DistanceBetweenPointAndLineSegment(ref Vector2 point, ref Vector2 lineEndPoint1, + ref Vector2 lineEndPoint2) + { + Vector2 v = Vector2.Subtract(lineEndPoint2, lineEndPoint1); + Vector2 w = Vector2.Subtract(point, lineEndPoint1); + + float c1 = Vector2.Dot(w, v); + if (c1 <= 0) return DistanceBetweenPointAndPoint(ref point, ref lineEndPoint1); + + float c2 = Vector2.Dot(v, v); + if (c2 <= c1) return DistanceBetweenPointAndPoint(ref point, ref lineEndPoint2); + + float b = c1 / c2; + Vector2 pointOnLine = Vector2.Add(lineEndPoint1, Vector2.Multiply(v, b)); + return DistanceBetweenPointAndPoint(ref point, ref pointOnLine); + } + + // From Eric Jordan's convex decomposition library + /// + ///Check if the lines a0->a1 and b0->b1 cross. + ///If they do, intersectionPoint will be filled + ///with the point of crossing. + /// + ///Grazing lines should not return true. + /// + /// + /// + /// + /// + /// + /// + /// + public static bool LineIntersect2(Vector2 a0, Vector2 a1, Vector2 b0, Vector2 b1, out Vector2 intersectionPoint) + { + intersectionPoint = Vector2.Zero; + + if (a0 == b0 || a0 == b1 || a1 == b0 || a1 == b1) + return false; + + float x1 = a0.X; + float y1 = a0.Y; + float x2 = a1.X; + float y2 = a1.Y; + float x3 = b0.X; + float y3 = b0.Y; + float x4 = b1.X; + float y4 = b1.Y; + + //AABB early exit + if (Math.Max(x1, x2) < Math.Min(x3, x4) || Math.Max(x3, x4) < Math.Min(x1, x2)) + return false; + + if (Math.Max(y1, y2) < Math.Min(y3, y4) || Math.Max(y3, y4) < Math.Min(y1, y2)) + return false; + + float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)); + float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)); + float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + if (Math.Abs(denom) < Settings.Epsilon) + { + //Lines are too close to parallel to call + return false; + } + ua /= denom; + ub /= denom; + + if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1)) + { + intersectionPoint.X = (x1 + ua * (x2 - x1)); + intersectionPoint.Y = (y1 + ua * (y2 - y1)); + return true; + } + + return false; + } + + //From Mark Bayazit's convex decomposition algorithm + public static Vector2 LineIntersect(Vector2 p1, Vector2 p2, Vector2 q1, Vector2 q2) + { + Vector2 i = Vector2.Zero; + float a1 = p2.Y - p1.Y; + float b1 = p1.X - p2.X; + float c1 = a1 * p1.X + b1 * p1.Y; + float a2 = q2.Y - q1.Y; + float b2 = q1.X - q2.X; + float c2 = a2 * q1.X + b2 * q1.Y; + float det = a1 * b2 - a2 * b1; + + if (!MathUtils.FloatEquals(det, 0)) + { + // lines are not parallel + i.X = (b2 * c1 - b1 * c2) / det; + i.Y = (a1 * c2 - a2 * c1) / det; + } + return i; + } + + /// + /// This method detects if two line segments (or lines) intersect, + /// and, if so, the point of intersection. Use the and + /// parameters to set whether the intersection point + /// must be on the first and second line segments. Setting these + /// both to true means you are doing a line-segment to line-segment + /// intersection. Setting one of them to true means you are doing a + /// line to line-segment intersection test, and so on. + /// Note: If two line segments are coincident, then + /// no intersection is detected (there are actually + /// infinite intersection points). + /// Author: Jeremy Bell + /// + /// The first point of the first line segment. + /// The second point of the first line segment. + /// The first point of the second line segment. + /// The second point of the second line segment. + /// This is set to the intersection + /// point if an intersection is detected. + /// Set this to true to require that the + /// intersection point be on the first line segment. + /// Set this to true to require that the + /// intersection point be on the second line segment. + /// True if an intersection is detected, false otherwise. + public static bool LineIntersect(ref Vector2 point1, ref Vector2 point2, ref Vector2 point3, ref Vector2 point4, + bool firstIsSegment, bool secondIsSegment, + out Vector2 point) + { + point = new Vector2(); + + // these are reused later. + // each lettered sub-calculation is used twice, except + // for b and d, which are used 3 times + float a = point4.Y - point3.Y; + float b = point2.X - point1.X; + float c = point4.X - point3.X; + float d = point2.Y - point1.Y; + + // denominator to solution of linear system + float denom = (a * b) - (c * d); + + // if denominator is 0, then lines are parallel + if (!(denom >= -Settings.Epsilon && denom <= Settings.Epsilon)) + { + float e = point1.Y - point3.Y; + float f = point1.X - point3.X; + float oneOverDenom = 1.0f / denom; + + // numerator of first equation + float ua = (c * e) - (a * f); + ua *= oneOverDenom; + + // check if intersection point of the two lines is on line segment 1 + if (!firstIsSegment || ua >= 0.0f && ua <= 1.0f) + { + // numerator of second equation + float ub = (b * e) - (d * f); + ub *= oneOverDenom; + + // check if intersection point of the two lines is on line segment 2 + // means the line segments intersect, since we know it is on + // segment 1 as well. + if (!secondIsSegment || ub >= 0.0f && ub <= 1.0f) + { + // check if they are coincident (no collision in this case) + if (ua != 0f || ub != 0f) + { + //There is an intersection + point.X = point1.X + ua * b; + point.Y = point1.Y + ua * d; + return true; + } + } + } + } + + return false; + } + + /// + /// This method detects if two line segments (or lines) intersect, + /// and, if so, the point of intersection. Use the and + /// parameters to set whether the intersection point + /// must be on the first and second line segments. Setting these + /// both to true means you are doing a line-segment to line-segment + /// intersection. Setting one of them to true means you are doing a + /// line to line-segment intersection test, and so on. + /// Note: If two line segments are coincident, then + /// no intersection is detected (there are actually + /// infinite intersection points). + /// Author: Jeremy Bell + /// + /// The first point of the first line segment. + /// The second point of the first line segment. + /// The first point of the second line segment. + /// The second point of the second line segment. + /// This is set to the intersection + /// point if an intersection is detected. + /// Set this to true to require that the + /// intersection point be on the first line segment. + /// Set this to true to require that the + /// intersection point be on the second line segment. + /// True if an intersection is detected, false otherwise. + public static bool LineIntersect(Vector2 point1, Vector2 point2, Vector2 point3, Vector2 point4, + bool firstIsSegment, + bool secondIsSegment, out Vector2 intersectionPoint) + { + return LineIntersect(ref point1, ref point2, ref point3, ref point4, firstIsSegment, secondIsSegment, + out intersectionPoint); + } + + /// + /// This method detects if two line segments intersect, + /// and, if so, the point of intersection. + /// Note: If two line segments are coincident, then + /// no intersection is detected (there are actually + /// infinite intersection points). + /// + /// The first point of the first line segment. + /// The second point of the first line segment. + /// The first point of the second line segment. + /// The second point of the second line segment. + /// This is set to the intersection + /// point if an intersection is detected. + /// True if an intersection is detected, false otherwise. + public static bool LineIntersect(ref Vector2 point1, ref Vector2 point2, ref Vector2 point3, ref Vector2 point4, + out Vector2 intersectionPoint) + { + return LineIntersect(ref point1, ref point2, ref point3, ref point4, true, true, out intersectionPoint); + } + + /// + /// This method detects if two line segments intersect, + /// and, if so, the point of intersection. + /// Note: If two line segments are coincident, then + /// no intersection is detected (there are actually + /// infinite intersection points). + /// + /// The first point of the first line segment. + /// The second point of the first line segment. + /// The first point of the second line segment. + /// The second point of the second line segment. + /// This is set to the intersection + /// point if an intersection is detected. + /// True if an intersection is detected, false otherwise. + public static bool LineIntersect(Vector2 point1, Vector2 point2, Vector2 point3, Vector2 point4, + out Vector2 intersectionPoint) + { + return LineIntersect(ref point1, ref point2, ref point3, ref point4, true, true, out intersectionPoint); + } + + /// + /// Get all intersections between a line segment and a list of vertices + /// representing a polygon. The vertices reuse adjacent points, so for example + /// edges one and two are between the first and second vertices and between the + /// second and third vertices. The last edge is between vertex vertices.Count - 1 + /// and verts0. (ie, vertices from a Geometry or AABB) + /// + /// The first point of the line segment to test + /// The second point of the line segment to test. + /// The vertices, as described above + /// An list of intersection points. Any intersection points + /// found will be added to this list. + public static void LineSegmentVerticesIntersect(ref Vector2 point1, ref Vector2 point2, Vertices vertices, + ref List intersectionPoints) + { + for (int i = 0; i < vertices.Count; i++) + { + Vector2 point; + if (LineIntersect(vertices[i], vertices[vertices.NextIndex(i)], + point1, point2, true, true, out point)) + { + intersectionPoints.Add(point); + } + } + } + + /// + /// Get all intersections between a line segment and an AABB. + /// + /// The first point of the line segment to test + /// The second point of the line segment to test. + /// The AABB that is used for testing intersection. + /// An list of intersection points. Any intersection points found will be added to this list. + public static void LineSegmentAABBIntersect(ref Vector2 point1, ref Vector2 point2, AABB aabb, + ref List intersectionPoints) + { + LineSegmentVerticesIntersect(ref point1, ref point2, aabb.Vertices, ref intersectionPoints); + } + } +} \ No newline at end of file diff --git a/Common/Math.cs b/Common/Math.cs new file mode 100644 index 0000000..c03f6ae --- /dev/null +++ b/Common/Math.cs @@ -0,0 +1,638 @@ +/* +* 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 Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + public static class MathUtils + { + public static float Cross(Vector2 a, Vector2 b) + { + return a.X * b.Y - a.Y * b.X; + } + + public static Vector2 Cross(Vector2 a, float s) + { + return new Vector2(s * a.Y, -s * a.X); + } + + public static Vector2 Cross(float s, Vector2 a) + { + return new Vector2(-s * a.Y, s * a.X); + } + + public static Vector2 Abs(Vector2 v) + { + return new Vector2(Math.Abs(v.X), Math.Abs(v.Y)); + } + + public static Vector2 Multiply(ref Mat22 A, Vector2 v) + { + return Multiply(ref A, ref v); + } + + public static Vector2 Multiply(ref Mat22 A, ref Vector2 v) + { + return new Vector2(A.Col1.X * v.X + A.Col2.X * v.Y, A.Col1.Y * v.X + A.Col2.Y * v.Y); + } + + public static Vector2 MultiplyT(ref Mat22 A, Vector2 v) + { + return MultiplyT(ref A, ref v); + } + + public static Vector2 MultiplyT(ref Mat22 A, ref Vector2 v) + { + return new Vector2(v.X * A.Col1.X + v.Y * A.Col1.Y, v.X * A.Col2.X + v.Y * A.Col2.Y); + } + + public static Vector2 Multiply(ref Transform T, Vector2 v) + { + return Multiply(ref T, ref v); + } + + public static Vector2 Multiply(ref Transform T, ref Vector2 v) + { + return new Vector2(T.Position.X + T.R.Col1.X * v.X + T.R.Col2.X * v.Y, + T.Position.Y + T.R.Col1.Y * v.X + T.R.Col2.Y * v.Y); + } + + public static Vector2 MultiplyT(ref Transform T, Vector2 v) + { + return MultiplyT(ref T, ref v); + } + + public static Vector2 MultiplyT(ref Transform T, ref Vector2 v) + { + Vector2 tmp = Vector2.Zero; + tmp.X = v.X - T.Position.X; + tmp.Y = v.Y - T.Position.Y; + return MultiplyT(ref T.R, ref tmp); + } + + // A^T * B + public static void MultiplyT(ref Mat22 A, ref Mat22 B, out Mat22 C) + { + C = new Mat22(); + C.Col1.X = A.Col1.X * B.Col1.X + A.Col1.Y * B.Col1.Y; + C.Col1.Y = A.Col2.X * B.Col1.X + A.Col2.Y * B.Col1.Y; + C.Col2.X = A.Col1.X * B.Col2.X + A.Col1.Y * B.Col2.Y; + C.Col2.Y = A.Col2.X * B.Col2.X + A.Col2.Y * B.Col2.Y; + } + + // v2 = A.R' * (B.R * v1 + B.p - A.p) = (A.R' * B.R) * v1 + (B.p - A.p) + public static void MultiplyT(ref Transform A, ref Transform B, out Transform C) + { + C = new Transform(); + MultiplyT(ref A.R, ref B.R, out C.R); + C.Position.X = B.Position.X - A.Position.X; + C.Position.Y = B.Position.Y - A.Position.Y; + } + + public static void Swap(ref T a, ref T b) + { + T tmp = a; + a = b; + b = tmp; + } + + /// + /// This function is used to ensure that a floating point number is + /// not a NaN or infinity. + /// + /// The x. + /// + /// true if the specified x is valid; otherwise, false. + /// + public static bool IsValid(float x) + { + if (float.IsNaN(x)) + { + // NaN. + return false; + } + + return !float.IsInfinity(x); + } + + public static bool IsValid(this Vector2 x) + { + return IsValid(x.X) && IsValid(x.Y); + } + + /// + /// This is a approximate yet fast inverse square-root. + /// + /// The x. + /// + public static float InvSqrt(float x) + { + FloatConverter convert = new FloatConverter(); + convert.x = x; + float xhalf = 0.5f * x; + convert.i = 0x5f3759df - (convert.i >> 1); + x = convert.x; + x = x * (1.5f - xhalf * x * x); + return x; + } + + public static int Clamp(int a, int low, int high) + { + return Math.Max(low, Math.Min(a, high)); + } + + public static float Clamp(float a, float low, float high) + { + return Math.Max(low, Math.Min(a, high)); + } + + public static Vector2 Clamp(Vector2 a, Vector2 low, Vector2 high) + { + return Vector2.Max(low, Vector2.Min(a, high)); + } + + public static void Cross(ref Vector2 a, ref Vector2 b, out float c) + { + c = a.X * b.Y - a.Y * b.X; + } + + /// + /// Return the angle between two vectors on a plane + /// The angle is from vector 1 to vector 2, positive anticlockwise + /// The result is between -pi -> pi + /// + public static double VectorAngle(ref Vector2 p1, ref Vector2 p2) + { + double theta1 = Math.Atan2(p1.Y, p1.X); + double theta2 = Math.Atan2(p2.Y, p2.X); + double dtheta = theta2 - theta1; + while (dtheta > Math.PI) + dtheta -= (2 * Math.PI); + while (dtheta < -Math.PI) + dtheta += (2 * Math.PI); + + return (dtheta); + } + + public static double VectorAngle(Vector2 p1, Vector2 p2) + { + return VectorAngle(ref p1, ref p2); + } + + /// + /// Returns a positive number if c is to the left of the line going from a to b. + /// + /// Positive number if point is left, negative if point is right, + /// and 0 if points are collinear. + public static float Area(Vector2 a, Vector2 b, Vector2 c) + { + return Area(ref a, ref b, ref c); + } + + /// + /// Returns a positive number if c is to the left of the line going from a to b. + /// + /// Positive number if point is left, negative if point is right, + /// and 0 if points are collinear. + public static float Area(ref Vector2 a, ref Vector2 b, ref Vector2 c) + { + return a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y); + } + + /// + /// Determines if three vertices are collinear (ie. on a straight line) + /// + /// First vertex + /// Second vertex + /// Third vertex + /// + public static bool Collinear(ref Vector2 a, ref Vector2 b, ref Vector2 c) + { + return Collinear(ref a, ref b, ref c, 0); + } + + public static bool Collinear(ref Vector2 a, ref Vector2 b, ref Vector2 c, float tolerance) + { + return FloatInRange(Area(ref a, ref b, ref c), -tolerance, tolerance); + } + + public static void Cross(float s, ref Vector2 a, out Vector2 b) + { + b = new Vector2(-s * a.Y, s * a.X); + } + + public static bool FloatEquals(float value1, float value2) + { + return Math.Abs(value1 - value2) <= Settings.Epsilon; + } + + /// + /// Checks if a floating point Value is equal to another, + /// within a certain tolerance. + /// + /// The first floating point Value. + /// The second floating point Value. + /// The floating point tolerance. + /// True if the values are "equal", false otherwise. + public static bool FloatEquals(float value1, float value2, float delta) + { + return FloatInRange(value1, value2 - delta, value2 + delta); + } + + /// + /// Checks if a floating point Value is within a specified + /// range of values (inclusive). + /// + /// The Value to check. + /// The minimum Value. + /// The maximum Value. + /// True if the Value is within the range specified, + /// false otherwise. + public static bool FloatInRange(float value, float min, float max) + { + return (value >= min && value <= max); + } + + #region Nested type: FloatConverter + + [StructLayout(LayoutKind.Explicit)] + private struct FloatConverter + { + [FieldOffset(0)] + public float x; + [FieldOffset(0)] + public int i; + } + + #endregion + } + + /// + /// A 2-by-2 matrix. Stored in column-major order. + /// + public struct Mat22 + { + public Vector2 Col1, Col2; + + /// + /// Construct this matrix using columns. + /// + /// The c1. + /// The c2. + public Mat22(Vector2 c1, Vector2 c2) + { + Col1 = c1; + Col2 = c2; + } + + /// + /// Construct this matrix using scalars. + /// + /// The a11. + /// The a12. + /// The a21. + /// The a22. + public Mat22(float a11, float a12, float a21, float a22) + { + Col1 = new Vector2(a11, a21); + Col2 = new Vector2(a12, a22); + } + + /// + /// Construct this matrix using an angle. This matrix becomes + /// an orthonormal rotation matrix. + /// + /// The angle. + public Mat22(float angle) + { + // TODO_ERIN compute sin+cos together. + float c = (float)Math.Cos(angle), s = (float)Math.Sin(angle); + Col1 = new Vector2(c, s); + Col2 = new Vector2(-s, c); + } + + /// + /// Extract the angle from this matrix (assumed to be + /// a rotation matrix). + /// + /// + public float Angle + { + get { return (float)Math.Atan2(Col1.Y, Col1.X); } + } + + public Mat22 Inverse + { + get + { + float a = Col1.X, b = Col2.X, c = Col1.Y, d = Col2.Y; + float det = a * d - b * c; + if (det != 0.0f) + { + det = 1.0f / det; + } + + Mat22 result = new Mat22(); + result.Col1.X = det * d; + result.Col1.Y = -det * c; + + result.Col2.X = -det * b; + result.Col2.Y = det * a; + + return result; + } + } + + /// + /// Initialize this matrix using columns. + /// + /// The c1. + /// The c2. + public void Set(Vector2 c1, Vector2 c2) + { + Col1 = c1; + Col2 = c2; + } + + /// + /// Initialize this matrix using an angle. This matrix becomes + /// an orthonormal rotation matrix. + /// + /// The angle. + public void Set(float angle) + { + float c = (float)Math.Cos(angle), s = (float)Math.Sin(angle); + Col1.X = c; + Col2.X = -s; + Col1.Y = s; + Col2.Y = c; + } + + /// + /// Set this to the identity matrix. + /// + public void SetIdentity() + { + Col1.X = 1.0f; + Col2.X = 0.0f; + Col1.Y = 0.0f; + Col2.Y = 1.0f; + } + + /// + /// Set this matrix to all zeros. + /// + public void SetZero() + { + Col1.X = 0.0f; + Col2.X = 0.0f; + Col1.Y = 0.0f; + Col2.Y = 0.0f; + } + + /// + /// Solve A * x = b, where b is a column vector. This is more efficient + /// than computing the inverse in one-shot cases. + /// + /// The b. + /// + public Vector2 Solve(Vector2 b) + { + float a11 = Col1.X, a12 = Col2.X, a21 = Col1.Y, a22 = Col2.Y; + float det = a11 * a22 - a12 * a21; + if (det != 0.0f) + { + det = 1.0f / det; + } + + return new Vector2(det * (a22 * b.X - a12 * b.Y), det * (a11 * b.Y - a21 * b.X)); + } + + public static void Add(ref Mat22 A, ref Mat22 B, out Mat22 R) + { + R.Col1 = A.Col1 + B.Col1; + R.Col2 = A.Col2 + B.Col2; + } + } + + /// + /// A 3-by-3 matrix. Stored in column-major order. + /// + public struct Mat33 + { + public Vector3 Col1, Col2, Col3; + + /// + /// Construct this matrix using columns. + /// + /// The c1. + /// The c2. + /// The c3. + public Mat33(Vector3 c1, Vector3 c2, Vector3 c3) + { + Col1 = c1; + Col2 = c2; + Col3 = c3; + } + + /// + /// Set this matrix to all zeros. + /// + public void SetZero() + { + Col1 = Vector3.Zero; + Col2 = Vector3.Zero; + Col3 = Vector3.Zero; + } + + /// + /// Solve A * x = b, where b is a column vector. This is more efficient + /// than computing the inverse in one-shot cases. + /// + /// The b. + /// + public Vector3 Solve33(Vector3 b) + { + float det = Vector3.Dot(Col1, Vector3.Cross(Col2, Col3)); + if (det != 0.0f) + { + det = 1.0f / det; + } + + return new Vector3(det * Vector3.Dot(b, Vector3.Cross(Col2, Col3)), + det * Vector3.Dot(Col1, Vector3.Cross(b, Col3)), + det * Vector3.Dot(Col1, Vector3.Cross(Col2, b))); + } + + /// + /// Solve A * x = b, where b is a column vector. This is more efficient + /// than computing the inverse in one-shot cases. Solve only the upper + /// 2-by-2 matrix equation. + /// + /// The b. + /// + public Vector2 Solve22(Vector2 b) + { + float a11 = Col1.X, a12 = Col2.X, a21 = Col1.Y, a22 = Col2.Y; + float det = a11 * a22 - a12 * a21; + + if (det != 0.0f) + { + det = 1.0f / det; + } + + return new Vector2(det * (a22 * b.X - a12 * b.Y), det * (a11 * b.Y - a21 * b.X)); + } + } + + /// + /// A transform contains translation and rotation. It is used to represent + /// the position and orientation of rigid frames. + /// + public struct Transform + { + public Vector2 Position; + public Mat22 R; + + /// + /// Initialize using a position vector and a rotation matrix. + /// + /// The position. + /// The r. + public Transform(ref Vector2 position, ref Mat22 r) + { + Position = position; + R = r; + } + + /// + /// Calculate the angle that the rotation matrix represents. + /// + /// + public float Angle + { + get { return (float)Math.Atan2(R.Col1.Y, R.Col1.X); } + } + + /// + /// Set this to the identity transform. + /// + public void SetIdentity() + { + Position = Vector2.Zero; + R.SetIdentity(); + } + + /// + /// Set this based on the position and angle. + /// + /// The position. + /// The angle. + public void Set(Vector2 position, float angle) + { + Position = position; + R.Set(angle); + } + } + + /// + /// This describes the motion of a body/shape for TOI computation. + /// Shapes are defined with respect to the body origin, which may + /// no coincide with the center of mass. However, to support dynamics + /// we must interpolate the center of mass position. + /// + public struct Sweep + { + /// + /// World angles + /// + public float A; + + public float A0; + + /// + /// Fraction of the current time step in the range [0,1] + /// c0 and a0 are the positions at alpha0. + /// + public float Alpha0; + + /// + /// Center world positions + /// + public Vector2 C; + + public Vector2 C0; + + /// + /// Local center of mass position + /// + public Vector2 LocalCenter; + + /// + /// Get the interpolated transform at a specific time. + /// + /// The transform. + /// beta is a factor in [0,1], where 0 indicates alpha0. + public void GetTransform(out Transform xf, float beta) + { + xf = new Transform(); + xf.Position.X = (1.0f - beta) * C0.X + beta * C.X; + xf.Position.Y = (1.0f - beta) * C0.Y + beta * C.Y; + float angle = (1.0f - beta) * A0 + beta * A; + xf.R.Set(angle); + + // Shift to origin + xf.Position -= MathUtils.Multiply(ref xf.R, ref LocalCenter); + } + + /// + /// Advance the sweep forward, yielding a new initial state. + /// + /// new initial time.. + public void Advance(float alpha) + { + Debug.Assert(Alpha0 < 1.0f); + float beta = (alpha - Alpha0) / (1.0f - Alpha0); + C0.X = (1.0f - beta) * C0.X + beta * C.X; + C0.Y = (1.0f - beta) * C0.Y + beta * C.Y; + A0 = (1.0f - beta) * A0 + beta * A; + Alpha0 = alpha; + } + + /// + /// Normalize the angles. + /// + public void Normalize() + { + float d = MathHelper.TwoPi * (float)Math.Floor(A0 / MathHelper.TwoPi); + A0 -= d; + A -= d; + } + } +} \ No newline at end of file diff --git a/Common/Path.cs b/Common/Path.cs new file mode 100644 index 0000000..f8efb6f --- /dev/null +++ b/Common/Path.cs @@ -0,0 +1,341 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + //Contributed by Matthew Bettcher + + /// + /// Path: + /// Very similar to Vertices, but this + /// class contains vectors describing + /// control points on a Catmull-Rom + /// curve. + /// + [XmlRoot("Path")] + public class Path + { + /// + /// All the points that makes up the curve + /// + [XmlElement("ControlPoints")] + public List ControlPoints; + + private float _deltaT; + + /// + /// Initializes a new instance of the class. + /// + public Path() + { + ControlPoints = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The vertices to created the path from. + public Path(Vector2[] vertices) + { + ControlPoints = new List(vertices.Length); + + for (int i = 0; i < vertices.Length; i++) + { + Add(vertices[i]); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The vertices to created the path from. + public Path(IList vertices) + { + ControlPoints = new List(vertices.Count); + for (int i = 0; i < vertices.Count; i++) + { + Add(vertices[i]); + } + } + + /// + /// True if the curve is closed. + /// + /// true if closed; otherwise, false. + [XmlElement("Closed")] + public bool Closed { get; set; } + + /// + /// Gets the next index of a controlpoint + /// + /// The index. + /// + public int NextIndex(int index) + { + if (index == ControlPoints.Count - 1) + { + return 0; + } + return index + 1; + } + + /// + /// Gets the previous index of a controlpoint + /// + /// The index. + /// + public int PreviousIndex(int index) + { + if (index == 0) + { + return ControlPoints.Count - 1; + } + return index - 1; + } + + /// + /// Translates the control points by the specified vector. + /// + /// The vector. + public void Translate(ref Vector2 vector) + { + for (int i = 0; i < ControlPoints.Count; i++) + ControlPoints[i] = Vector2.Add(ControlPoints[i], vector); + } + + /// + /// Scales the control points by the specified vector. + /// + /// The Value. + public void Scale(ref Vector2 value) + { + for (int i = 0; i < ControlPoints.Count; i++) + ControlPoints[i] = Vector2.Multiply(ControlPoints[i], value); + } + + /// + /// Rotate the control points by the defined value in radians. + /// + /// The amount to rotate by in radians. + public void Rotate(float value) + { + Matrix rotationMatrix; + Matrix.CreateRotationZ(value, out rotationMatrix); + + for (int i = 0; i < ControlPoints.Count; i++) + ControlPoints[i] = Vector2.Transform(ControlPoints[i], rotationMatrix); + } + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < ControlPoints.Count; i++) + { + builder.Append(ControlPoints[i].ToString()); + if (i < ControlPoints.Count - 1) + { + builder.Append(" "); + } + } + return builder.ToString(); + } + + /// + /// Returns a set of points defining the + /// curve with the specifed number of divisions + /// between each control point. + /// + /// Number of divisions between each control point. + /// + public Vertices GetVertices(int divisions) + { + Vertices verts = new Vertices(); + + float timeStep = 1f / divisions; + + for (float i = 0; i < 1f; i += timeStep) + { + verts.Add(GetPosition(i)); + } + + return verts; + } + + public Vector2 GetPosition(float time) + { + Vector2 temp; + + if (ControlPoints.Count < 2) + throw new Exception("You need at least 2 control points to calculate a position."); + + if (Closed) + { + Add(ControlPoints[0]); + + _deltaT = 1f / (ControlPoints.Count - 1); + + int p = (int)(time / _deltaT); + + // use a circular indexing system + int p0 = p - 1; + if (p0 < 0) p0 = p0 + (ControlPoints.Count - 1); + else if (p0 >= ControlPoints.Count - 1) p0 = p0 - (ControlPoints.Count - 1); + int p1 = p; + if (p1 < 0) p1 = p1 + (ControlPoints.Count - 1); + else if (p1 >= ControlPoints.Count - 1) p1 = p1 - (ControlPoints.Count - 1); + int p2 = p + 1; + if (p2 < 0) p2 = p2 + (ControlPoints.Count - 1); + else if (p2 >= ControlPoints.Count - 1) p2 = p2 - (ControlPoints.Count - 1); + int p3 = p + 2; + if (p3 < 0) p3 = p3 + (ControlPoints.Count - 1); + else if (p3 >= ControlPoints.Count - 1) p3 = p3 - (ControlPoints.Count - 1); + + // relative time + float lt = (time - _deltaT * p) / _deltaT; + + temp = Vector2.CatmullRom(ControlPoints[p0], ControlPoints[p1], ControlPoints[p2], ControlPoints[p3], lt); + + RemoveAt(ControlPoints.Count - 1); + } + else + { + int p = (int)(time / _deltaT); + + // + int p0 = p - 1; + if (p0 < 0) p0 = 0; + else if (p0 >= ControlPoints.Count - 1) p0 = ControlPoints.Count - 1; + int p1 = p; + if (p1 < 0) p1 = 0; + else if (p1 >= ControlPoints.Count - 1) p1 = ControlPoints.Count - 1; + int p2 = p + 1; + if (p2 < 0) p2 = 0; + else if (p2 >= ControlPoints.Count - 1) p2 = ControlPoints.Count - 1; + int p3 = p + 2; + if (p3 < 0) p3 = 0; + else if (p3 >= ControlPoints.Count - 1) p3 = ControlPoints.Count - 1; + + // relative time + float lt = (time - _deltaT * p) / _deltaT; + + temp = Vector2.CatmullRom(ControlPoints[p0], ControlPoints[p1], ControlPoints[p2], ControlPoints[p3], lt); + } + + return temp; + } + + /// + /// Gets the normal for the given time. + /// + /// The time + /// The normal. + public Vector2 GetPositionNormal(float time) + { + float offsetTime = time + 0.0001f; + + Vector2 a = GetPosition(time); + Vector2 b = GetPosition(offsetTime); + + Vector2 output, temp; + + Vector2.Subtract(ref a, ref b, out temp); + +#if (XBOX360 || WINDOWS_PHONE) +output = new Vector2(); +#endif + output.X = -temp.Y; + output.Y = temp.X; + + Vector2.Normalize(ref output, out output); + + return output; + } + + public void Add(Vector2 point) + { + ControlPoints.Add(point); + _deltaT = 1f / (ControlPoints.Count - 1); + } + + public void Remove(Vector2 point) + { + ControlPoints.Remove(point); + _deltaT = 1f / (ControlPoints.Count - 1); + } + + public void RemoveAt(int index) + { + ControlPoints.RemoveAt(index); + _deltaT = 1f / (ControlPoints.Count - 1); + } + + public float GetLength() + { + List verts = GetVertices(ControlPoints.Count * 25); + float length = 0; + + for (int i = 1; i < verts.Count; i++) + { + length += Vector2.Distance(verts[i - 1], verts[i]); + } + + if (Closed) + length += Vector2.Distance(verts[ControlPoints.Count - 1], verts[0]); + + return length; + } + + public List SubdivideEvenly(int divisions) + { + List verts = new List(); + + float length = GetLength(); + + float deltaLength = length / divisions + 0.001f; + float t = 0.000f; + + // we always start at the first control point + Vector2 start = ControlPoints[0]; + Vector2 end = GetPosition(t); + + // increment t until we are at half the distance + while (deltaLength * 0.5f >= Vector2.Distance(start, end)) + { + end = GetPosition(t); + t += 0.0001f; + + if (t >= 1f) + break; + } + + start = end; + + // for each box + for (int i = 1; i < divisions; i++) + { + Vector2 normal = GetPositionNormal(t); + float angle = (float)Math.Atan2(normal.Y, normal.X); + + verts.Add(new Vector3(end, angle)); + + // until we reach the correct distance down the curve + while (deltaLength >= Vector2.Distance(start, end)) + { + end = GetPosition(t); + t += 0.00001f; + + if (t >= 1f) + break; + } + if (t >= 1f) + break; + + start = end; + } + return verts; + } + } +} \ No newline at end of file diff --git a/Common/PathManager.cs b/Common/PathManager.cs new file mode 100644 index 0000000..9255d59 --- /dev/null +++ b/Common/PathManager.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Common; +using FarseerPhysics.Common.Decomposition; +using FarseerPhysics.Dynamics; +using FarseerPhysics.Dynamics.Joints; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Factories +{ + /// + /// An easy to use manager for creating paths. + /// + public static class PathManager + { + #region LinkType enum + + public enum LinkType + { + Revolute, + Slider + } + + #endregion + + //Contributed by Matthew Bettcher + + /// + /// Convert a path into a set of edges and attaches them to the specified body. + /// Note: use only for static edges. + /// + /// The path. + /// The body. + /// The subdivisions. + public static void ConvertPathToEdges(Path path, Body body, int subdivisions) + { + Vertices verts = path.GetVertices(subdivisions); + + if (path.Closed) + { + LoopShape loop = new LoopShape(verts); + body.CreateFixture(loop); + } + else + { + for (int i = 1; i < verts.Count; i++) + { + body.CreateFixture(new EdgeShape(verts[i], verts[i - 1])); + } + } + } + + /// + /// Convert a closed path into a polygon. + /// Convex decomposition is automatically performed. + /// + /// The path. + /// The body. + /// The density. + /// The subdivisions. + public static void ConvertPathToPolygon(Path path, Body body, float density, int subdivisions) + { + if (!path.Closed) + throw new Exception("The path must be closed to convert to a polygon."); + + List verts = path.GetVertices(subdivisions); + + List decomposedVerts = EarclipDecomposer.ConvexPartition(new Vertices(verts)); + //List decomposedVerts = BayazitDecomposer.ConvexPartition(new Vertices(verts)); + + foreach (Vertices item in decomposedVerts) + { + body.CreateFixture(new PolygonShape(item, density)); + } + } + + /// + /// Duplicates the given Body along the given path for approximatly the given copies. + /// + /// The world. + /// The path. + /// The shapes. + /// The type. + /// The copies. + /// + /// + public static List EvenlyDistributeShapesAlongPath(World world, Path path, IEnumerable shapes, + BodyType type, int copies, object userData) + { + List centers = path.SubdivideEvenly(copies); + List bodyList = new List(); + + for (int i = 0; i < centers.Count; i++) + { + Body b = new Body(world); + + // copy the type from original body + b.BodyType = type; + b.Position = new Vector2(centers[i].X, centers[i].Y); + b.Rotation = centers[i].Z; + + foreach (Shape shape in shapes) + { + b.CreateFixture(shape, userData); + } + + bodyList.Add(b); + } + + return bodyList; + } + + public static List EvenlyDistributeShapesAlongPath(World world, Path path, IEnumerable shapes, + BodyType type, int copies) + { + return EvenlyDistributeShapesAlongPath(world, path, shapes, type, copies, null); + } + + + /// + /// Duplicates the given Body along the given path for approximatly the given copies. + /// + /// The world. + /// The path. + /// The shape. + /// The type. + /// The copies. + /// The user data. + /// + public static List EvenlyDistributeShapesAlongPath(World world, Path path, Shape shape, BodyType type, + int copies, object userData) + { + List shapes = new List(1); + shapes.Add(shape); + + return EvenlyDistributeShapesAlongPath(world, path, shapes, type, copies, userData); + } + + public static List EvenlyDistributeShapesAlongPath(World world, Path path, Shape shape, BodyType type, + int copies) + { + return EvenlyDistributeShapesAlongPath(world, path, shape, type, copies, null); + } + + //TODO: Comment better + /// + /// Moves the body on the path. + /// + /// The path. + /// The body. + /// The time. + /// The strength. + /// The time step. + public static void MoveBodyOnPath(Path path, Body body, float time, float strength, float timeStep) + { + Vector2 destination = path.GetPosition(time); + Vector2 positionDelta = body.Position - destination; + Vector2 velocity = (positionDelta / timeStep) * strength; + + body.LinearVelocity = -velocity; + } + + /// + /// Attaches the bodies with revolute joints. + /// + /// The world. + /// The bodies. + /// The local anchor A. + /// The local anchor B. + /// if set to true [connect first and last]. + /// if set to true [collide connected]. + public static List AttachBodiesWithRevoluteJoint(World world, List bodies, + Vector2 localAnchorA, + Vector2 localAnchorB, bool connectFirstAndLast, + bool collideConnected) + { + List joints = new List(bodies.Count + 1); + + for (int i = 1; i < bodies.Count; i++) + { + RevoluteJoint joint = new RevoluteJoint(bodies[i], bodies[i - 1], localAnchorA, localAnchorB); + joint.CollideConnected = collideConnected; + world.AddJoint(joint); + joints.Add(joint); + } + + if (connectFirstAndLast) + { + RevoluteJoint lastjoint = new RevoluteJoint(bodies[0], bodies[bodies.Count - 1], localAnchorA, + localAnchorB); + lastjoint.CollideConnected = collideConnected; + world.AddJoint(lastjoint); + joints.Add(lastjoint); + } + + return joints; + } + + /// + /// Attaches the bodies with revolute joints. + /// + /// The world. + /// The bodies. + /// The local anchor A. + /// The local anchor B. + /// if set to true [connect first and last]. + /// if set to true [collide connected]. + /// Minimum length of the slider joint. + /// Maximum length of the slider joint. + /// + public static List AttachBodiesWithSliderJoint(World world, List bodies, Vector2 localAnchorA, + Vector2 localAnchorB, bool connectFirstAndLast, + bool collideConnected, float minLength, + float maxLength) + { + List joints = new List(bodies.Count + 1); + + for (int i = 1; i < bodies.Count; i++) + { + SliderJoint joint = new SliderJoint(bodies[i], bodies[i - 1], localAnchorA, localAnchorB, minLength, + maxLength); + joint.CollideConnected = collideConnected; + world.AddJoint(joint); + joints.Add(joint); + } + + if (connectFirstAndLast) + { + SliderJoint lastjoint = new SliderJoint(bodies[0], bodies[bodies.Count - 1], localAnchorA, localAnchorB, + minLength, maxLength); + lastjoint.CollideConnected = collideConnected; + world.AddJoint(lastjoint); + joints.Add(lastjoint); + } + + return joints; + } + } +} \ No newline at end of file diff --git a/Common/PhysicsLogic/Explosion.cs b/Common/PhysicsLogic/Explosion.cs new file mode 100644 index 0000000..3b28ca6 --- /dev/null +++ b/Common/PhysicsLogic/Explosion.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FarseerPhysics.Collision; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.PhysicsLogic +{ + internal struct ShapeData + { + public Body Body; + public float Max; + public float Min; // absolute angles + } + + /// + /// This is a comprarer used for + /// detecting angle difference between rays + /// + internal class RayDataComparer : IComparer + { + #region IComparer Members + + int IComparer.Compare(float a, float b) + { + float diff = (a - b); + if (diff > 0) + return 1; + if (diff < 0) + return -1; + return 0; + } + + #endregion + } + + /* Methodology: + * Force applied at a ray is inversely proportional to the square of distance from source + * AABB is used to query for shapes that may be affected + * For each RIGID BODY (not shape -- this is an optimization) that is matched, loop through its vertices to determine + * the extreme points -- if there is structure that contains outlining polygon, use that as an additional optimization + * Evenly cast a number of rays against the shape - number roughly proportional to the arc coverage + * -Something like every 3 degrees should do the trick although this can be altered depending on the distance (if really close don't need such a high density of rays) + * -There should be a minimum number of rays (3-5?) applied to each body so that small bodies far away are still accurately modeled + * -Be sure to have the forces of each ray be proportional to the average arc length covered by each. + * For each ray that actually intersects with the shape (non intersections indicate something blocking the path of explosion): + * > apply the appropriate force dotted with the negative of the collision normal at the collision point + * > optionally apply linear interpolation between aforementioned Normal force and the original explosion force in the direction of ray to simulate "surface friction" of sorts + */ + + /// + /// This is an explosive... it explodes. + /// + /// + /// Original Code by Steven Lu - see http://www.box2d.org/forum/viewtopic.php?f=3&t=1688 + /// Ported to Farseer 3.0 by Nicolás Hormazábal + /// + public sealed class Explosion : PhysicsLogic + { + /// + /// Two degrees: maximum angle from edges to first ray tested + /// + private const float MaxEdgeOffset = MathHelper.Pi / 90; + + /// + /// Ratio of arc length to angle from edges to first ray tested. + /// Defaults to 1/40. + /// + public float EdgeRatio = 1.0f / 40.0f; + + /// + /// Ignore Explosion if it happens inside a shape. + /// Default value is false. + /// + public bool IgnoreWhenInsideShape = false; + + /// + /// Max angle between rays (used when segment is large). + /// Defaults to 15 degrees + /// + public float MaxAngle = MathHelper.Pi / 15; + + /// + /// Maximum number of shapes involved in the explosion. + /// Defaults to 100 + /// + public int MaxShapes = 100; + + /// + /// How many rays per shape/body/segment. + /// Defaults to 5 + /// + public int MinRays = 5; + + private List _data = new List(); + private Dictionary> _exploded; + private RayDataComparer _rdc; + + public Explosion(World world) + : base(world, PhysicsLogicType.Explosion) + { + _exploded = new Dictionary>(); + _rdc = new RayDataComparer(); + _data = new List(); + } + + /// + /// This makes the explosive explode + /// + /// + /// The position where the explosion happens + /// + /// + /// The explosion radius + /// + /// + /// The explosion force at the explosion point + /// (then is inversely proportional to the square of the distance) + /// + /// + /// A dictionnary containing all the "exploded" fixtures + /// with a list of the applied impulses + /// + public Dictionary> Activate(Vector2 pos, float radius, float maxForce) + { + _exploded.Clear(); + + AABB aabb; + aabb.LowerBound = pos + new Vector2(-radius, -radius); + aabb.UpperBound = pos + new Vector2(radius, radius); + Fixture[] shapes = new Fixture[MaxShapes]; + + // More than 5 shapes in an explosion could be possible, but still strange. + Fixture[] containedShapes = new Fixture[5]; + bool exit = false; + + int shapeCount = 0; + int containedShapeCount = 0; + + // Query the world for overlapping shapes. + World.QueryAABB( + fixture => + { + if (fixture.TestPoint(ref pos)) + { + if (IgnoreWhenInsideShape) + exit = true; + else + containedShapes[containedShapeCount++] = fixture; + } + else + { + shapes[shapeCount++] = fixture; + } + + // Continue the query. + return true; + }, ref aabb); + + if (exit) + { + return _exploded; + } + + // Per shape max/min angles for now. + float[] vals = new float[shapeCount * 2]; + int valIndex = 0; + for (int i = 0; i < shapeCount; ++i) + { + PolygonShape ps; + CircleShape cs = shapes[i].Shape as CircleShape; + if (cs != null) + { + // We create a "diamond" approximation of the circle + Vertices v = new Vertices(); + Vector2 vec = Vector2.Zero + new Vector2(cs.Radius, 0); + v.Add(vec); + vec = Vector2.Zero + new Vector2(0, cs.Radius); + v.Add(vec); + vec = Vector2.Zero + new Vector2(-cs.Radius, cs.Radius); + v.Add(vec); + vec = Vector2.Zero + new Vector2(0, -cs.Radius); + v.Add(vec); + ps = new PolygonShape(v, 0); + } + else + ps = shapes[i].Shape as PolygonShape; + + if ((shapes[i].Body.BodyType == BodyType.Dynamic) && ps != null) + { + Vector2 toCentroid = shapes[i].Body.GetWorldPoint(ps.MassData.Centroid) - pos; + float angleToCentroid = (float)Math.Atan2(toCentroid.Y, toCentroid.X); + float min = float.MaxValue; + float max = float.MinValue; + float minAbsolute = 0.0f; + float maxAbsolute = 0.0f; + + for (int j = 0; j < (ps.Vertices.Count()); ++j) + { + Vector2 toVertex = (shapes[i].Body.GetWorldPoint(ps.Vertices[j]) - pos); + float newAngle = (float)Math.Atan2(toVertex.Y, toVertex.X); + float diff = (newAngle - angleToCentroid); + + diff = (diff - MathHelper.Pi) % (2 * MathHelper.Pi); + // the minus pi is important. It means cutoff for going other direction is at 180 deg where it needs to be + + if (diff < 0.0f) + diff += 2 * MathHelper.Pi; // correction for not handling negs + + diff -= MathHelper.Pi; + + if (Math.Abs(diff) > MathHelper.Pi) + throw new ArgumentException("OMG!"); + // Something's wrong, point not in shape but exists angle diff > 180 + + if (diff > max) + { + max = diff; + maxAbsolute = newAngle; + } + if (diff < min) + { + min = diff; + minAbsolute = newAngle; + } + } + + vals[valIndex] = minAbsolute; + ++valIndex; + vals[valIndex] = maxAbsolute; + ++valIndex; + } + } + + Array.Sort(vals, 0, valIndex, _rdc); + _data.Clear(); + bool rayMissed = true; + + for (int i = 0; i < valIndex; ++i) + { + Fixture shape = null; + float midpt; + + int iplus = (i == valIndex - 1 ? 0 : i + 1); + if (vals[i] == vals[iplus]) + continue; + + if (i == valIndex - 1) + { + // the single edgecase + midpt = (vals[0] + MathHelper.Pi * 2 + vals[i]); + } + else + { + midpt = (vals[i + 1] + vals[i]); + } + + midpt = midpt / 2; + + Vector2 p1 = pos; + Vector2 p2 = radius * new Vector2((float)Math.Cos(midpt), + (float)Math.Sin(midpt)) + pos; + + // RaycastOne + bool hitClosest = false; + World.RayCast((f, p, n, fr) => + { + Body body = f.Body; + + if (!IsActiveOn(body)) + return 0; + + if (body.UserData != null) + { + int index = (int)body.UserData; + if (index == 0) + { + // filter + return -1.0f; + } + } + + hitClosest = true; + shape = f; + return fr; + }, p1, p2); + + //draws radius points + if ((hitClosest) && (shape.Body.BodyType == BodyType.Dynamic)) + { + if ((_data.Count() > 0) && (_data.Last().Body == shape.Body) && (!rayMissed)) + { + int laPos = _data.Count - 1; + ShapeData la = _data[laPos]; + la.Max = vals[iplus]; + _data[laPos] = la; + } + else + { + // make new + ShapeData d; + d.Body = shape.Body; + d.Min = vals[i]; + d.Max = vals[iplus]; + _data.Add(d); + } + + if ((_data.Count() > 1) + && (i == valIndex - 1) + && (_data.Last().Body == _data.First().Body) + && (_data.Last().Max == _data.First().Min)) + { + ShapeData fi = _data[0]; + fi.Min = _data.Last().Min; + _data.RemoveAt(_data.Count() - 1); + _data[0] = fi; + while (_data.First().Min >= _data.First().Max) + { + fi.Min -= MathHelper.Pi * 2; + _data[0] = fi; + } + } + + int lastPos = _data.Count - 1; + ShapeData last = _data[lastPos]; + while ((_data.Count() > 0) + && (_data.Last().Min >= _data.Last().Max)) // just making sure min fl = _data[i].Body.FixtureList; + for (int x = 0; x < fl.Count; x++) + { + Fixture f = fl[x]; + RayCastInput ri; + ri.Point1 = p1; + ri.Point2 = p2; + ri.MaxFraction = 50f; + + RayCastOutput ro; + if (f.RayCast(out ro, ref ri, 0)) + { + if (minlambda > ro.Fraction) + { + minlambda = ro.Fraction; + hitpoint = ro.Fraction * p2 + (1 - ro.Fraction) * p1; + } + } + + // the force that is to be applied for this particular ray. + // offset is angular coverage. lambda*length of segment is distance. + float impulse = (arclen / (MinRays + insertedRays)) * maxForce * 180.0f / MathHelper.Pi * + (1.0f - Math.Min(1.0f, minlambda)); + + // We Apply the impulse!!! + Vector2 vectImp = Vector2.Dot(impulse * new Vector2((float)Math.Cos(j), + (float)Math.Sin(j)), -ro.Normal) * + new Vector2((float)Math.Cos(j), + (float)Math.Sin(j)); + + _data[i].Body.ApplyLinearImpulse(ref vectImp, ref hitpoint); + + // We gather the fixtures for returning them + Vector2 val = Vector2.Zero; + List vectorList; + if (_exploded.TryGetValue(f, out vectorList)) + { + val.X += Math.Abs(vectImp.X); + val.Y += Math.Abs(vectImp.Y); + + vectorList.Add(val); + } + else + { + vectorList = new List(); + val.X = Math.Abs(vectImp.X); + val.Y = Math.Abs(vectImp.Y); + + vectorList.Add(val); + _exploded.Add(f, vectorList); + } + + if (minlambda > 1.0f) + { + hitpoint = p2; + } + } + } + } + + // We check contained shapes + for (int i = 0; i < containedShapeCount; ++i) + { + Fixture fix = containedShapes[i]; + + if (!IsActiveOn(fix.Body)) + continue; + + float impulse = MinRays * maxForce * 180.0f / MathHelper.Pi; + Vector2 hitPoint; + + CircleShape circShape = fix.Shape as CircleShape; + if (circShape != null) + { + hitPoint = fix.Body.GetWorldPoint(circShape.Position); + } + else + { + PolygonShape shape = fix.Shape as PolygonShape; + hitPoint = fix.Body.GetWorldPoint(shape.MassData.Centroid); + } + + Vector2 vectImp = impulse * (hitPoint - pos); + + List vectorList = new List(); + vectorList.Add(vectImp); + + fix.Body.ApplyLinearImpulse(ref vectImp, ref hitPoint); + + if (!_exploded.ContainsKey(fix)) + _exploded.Add(fix, vectorList); + } + + return _exploded; + } + } +} \ No newline at end of file diff --git a/Common/PhysicsLogic/PhysicsLogic.cs b/Common/PhysicsLogic/PhysicsLogic.cs new file mode 100644 index 0000000..a821e88 --- /dev/null +++ b/Common/PhysicsLogic/PhysicsLogic.cs @@ -0,0 +1,66 @@ +using System; +using FarseerPhysics.Dynamics; + +namespace FarseerPhysics.Common.PhysicsLogic +{ + [Flags] + public enum PhysicsLogicType + { + Explosion = (1 << 0) + } + + public struct PhysicsLogicFilter + { + public PhysicsLogicType ControllerIgnores; + + /// + /// Ignores the controller. The controller has no effect on this body. + /// + /// The logic type. + public void IgnorePhysicsLogic(PhysicsLogicType type) + { + ControllerIgnores |= type; + } + + /// + /// Restore the controller. The controller affects this body. + /// + /// The logic type. + public void RestorePhysicsLogic(PhysicsLogicType type) + { + ControllerIgnores &= ~type; + } + + /// + /// Determines whether this body ignores the the specified controller. + /// + /// The logic type. + /// + /// true if the body has the specified flag; otherwise, false. + /// + public bool IsPhysicsLogicIgnored(PhysicsLogicType type) + { + return (ControllerIgnores & type) == type; + } + } + + public abstract class PhysicsLogic : FilterData + { + private PhysicsLogicType _type; + public World World; + + public override bool IsActiveOn(Body body) + { + if (body.PhysicsLogicFilter.IsPhysicsLogicIgnored(_type)) + return false; + + return base.IsActiveOn(body); + } + + public PhysicsLogic(World world, PhysicsLogicType type) + { + _type = type; + World = world; + } + } +} \ No newline at end of file diff --git a/Common/PolygonManipulation/CuttingTools.cs b/Common/PolygonManipulation/CuttingTools.cs new file mode 100644 index 0000000..0406bb9 --- /dev/null +++ b/Common/PolygonManipulation/CuttingTools.cs @@ -0,0 +1,246 @@ +using System.Collections.Generic; +using System.Diagnostics; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Dynamics; +using FarseerPhysics.Factories; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.PolygonManipulation +{ + public static class CuttingTools + { + //Cutting a shape into two is based on the work of Daid and his prototype BoxCutter: http://www.box2d.org/forum/viewtopic.php?f=3&t=1473 + + /// + /// Split a fixture into 2 vertice collections using the given entry and exit-point. + /// + /// The Fixture to split + /// The entry point - The start point + /// The exit point - The end point + /// The size of the split. Think of this as the laser-width + /// The first collection of vertexes + /// The second collection of vertexes + public static void SplitShape(Fixture fixture, Vector2 entryPoint, Vector2 exitPoint, float splitSize, + out Vertices first, out Vertices second) + { + Vector2 localEntryPoint = fixture.Body.GetLocalPoint(ref entryPoint); + Vector2 localExitPoint = fixture.Body.GetLocalPoint(ref exitPoint); + + PolygonShape shape = fixture.Shape as PolygonShape; + + if (shape == null) + { + first = new Vertices(); + second = new Vertices(); + return; + } + + Vertices vertices = new Vertices(shape.Vertices); + Vertices[] newPolygon = new Vertices[2]; + + for (int i = 0; i < newPolygon.Length; i++) + { + newPolygon[i] = new Vertices(vertices.Count); + } + + int[] cutAdded = { -1, -1 }; + int last = -1; + for (int i = 0; i < vertices.Count; i++) + { + int n; + //Find out if this vertex is on the old or new shape. + if (Vector2.Dot(MathUtils.Cross(localExitPoint - localEntryPoint, 1), vertices[i] - localEntryPoint) > Settings.Epsilon) + n = 0; + else + n = 1; + + if (last != n) + { + //If we switch from one shape to the other add the cut vertices. + if (last == 0) + { + Debug.Assert(cutAdded[0] == -1); + cutAdded[0] = newPolygon[last].Count; + newPolygon[last].Add(localExitPoint); + newPolygon[last].Add(localEntryPoint); + } + if (last == 1) + { + Debug.Assert(cutAdded[last] == -1); + cutAdded[last] = newPolygon[last].Count; + newPolygon[last].Add(localEntryPoint); + newPolygon[last].Add(localExitPoint); + } + } + + newPolygon[n].Add(vertices[i]); + last = n; + } + + //Add the cut in case it has not been added yet. + if (cutAdded[0] == -1) + { + cutAdded[0] = newPolygon[0].Count; + newPolygon[0].Add(localExitPoint); + newPolygon[0].Add(localEntryPoint); + } + if (cutAdded[1] == -1) + { + cutAdded[1] = newPolygon[1].Count; + newPolygon[1].Add(localEntryPoint); + newPolygon[1].Add(localExitPoint); + } + + for (int n = 0; n < 2; n++) + { + Vector2 offset; + if (cutAdded[n] > 0) + { + offset = (newPolygon[n][cutAdded[n] - 1] - newPolygon[n][cutAdded[n]]); + } + else + { + offset = (newPolygon[n][newPolygon[n].Count - 1] - newPolygon[n][0]); + } + offset.Normalize(); + + newPolygon[n][cutAdded[n]] += splitSize * offset; + + if (cutAdded[n] < newPolygon[n].Count - 2) + { + offset = (newPolygon[n][cutAdded[n] + 2] - newPolygon[n][cutAdded[n] + 1]); + } + else + { + offset = (newPolygon[n][0] - newPolygon[n][newPolygon[n].Count - 1]); + } + offset.Normalize(); + + newPolygon[n][cutAdded[n] + 1] += splitSize * offset; + } + + first = newPolygon[0]; + second = newPolygon[1]; + } + + /// + /// This is a high-level function to cuts fixtures inside the given world, using the start and end points. + /// Note: We don't support cutting when the start or end is inside a shape. + /// + /// The world. + /// The startpoint. + /// The endpoint. + /// The thickness of the cut + public static void Cut(World world, Vector2 start, Vector2 end, float thickness) + { + List fixtures = new List(); + List entryPoints = new List(); + List exitPoints = new List(); + + //We don't support cutting when the start or end is inside a shape. + if (world.TestPoint(start) != null || world.TestPoint(end) != null) + return; + + //Get the entry points + world.RayCast((f, p, n, fr) => + { + fixtures.Add(f); + entryPoints.Add(p); + return 1; + }, start, end); + + //Reverse the ray to get the exitpoints + world.RayCast((f, p, n, fr) => + { + exitPoints.Add(p); + return 1; + }, end, start); + + //We only have a single point. We need at least 2 + if (entryPoints.Count + exitPoints.Count < 2) + return; + + for (int i = 0; i < fixtures.Count; i++) + { + // can't cut circles yet ! + if (fixtures[i].Shape.ShapeType != ShapeType.Polygon) + continue; + + if (fixtures[i].Body.BodyType != BodyType.Static) + { + //Split the shape up into two shapes + Vertices first; + Vertices second; + SplitShape(fixtures[i], entryPoints[i], exitPoints[i], thickness, out first, out second); + + //Delete the original shape and create two new. Retain the properties of the body. + if (SanityCheck(first)) + { + Body firstFixture = BodyFactory.CreatePolygon(world, first, fixtures[i].Shape.Density, + fixtures[i].Body.Position); + firstFixture.Rotation = fixtures[i].Body.Rotation; + firstFixture.LinearVelocity = fixtures[i].Body.LinearVelocity; + firstFixture.AngularVelocity = fixtures[i].Body.AngularVelocity; + firstFixture.BodyType = BodyType.Dynamic; + } + + if (SanityCheck(second)) + { + Body secondFixture = BodyFactory.CreatePolygon(world, second, fixtures[i].Shape.Density, + fixtures[i].Body.Position); + secondFixture.Rotation = fixtures[i].Body.Rotation; + secondFixture.LinearVelocity = fixtures[i].Body.LinearVelocity; + secondFixture.AngularVelocity = fixtures[i].Body.AngularVelocity; + secondFixture.BodyType = BodyType.Dynamic; + } + world.RemoveBody(fixtures[i].Body); + } + } + } + + private static bool SanityCheck(Vertices vertices) + { + if (vertices.Count < 3) + return false; + + if (vertices.GetArea() < 0.00001f) + return false; + + for (int i = 0; i < vertices.Count; ++i) + { + int i1 = i; + int i2 = i + 1 < vertices.Count ? i + 1 : 0; + Vector2 edge = vertices[i2] - vertices[i1]; + if (edge.LengthSquared() < Settings.Epsilon * Settings.Epsilon) + return false; + } + + for (int i = 0; i < vertices.Count; ++i) + { + int i1 = i; + int i2 = i + 1 < vertices.Count ? i + 1 : 0; + Vector2 edge = vertices[i2] - vertices[i1]; + + for (int j = 0; j < vertices.Count; ++j) + { + // Don't check vertices on the current edge. + if (j == i1 || j == i2) + { + continue; + } + + Vector2 r = vertices[j] - vertices[i1]; + + // Your polygon is non-convex (it has an indentation) or + // has colinear edges. + float s = edge.X * r.Y - edge.Y * r.X; + + if (s < 0.0f) + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Common/PolygonManipulation/SimplifyTools.cs b/Common/PolygonManipulation/SimplifyTools.cs new file mode 100644 index 0000000..5d79c2d --- /dev/null +++ b/Common/PolygonManipulation/SimplifyTools.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.PolygonManipulation +{ + public static class SimplifyTools + { + private static bool[] _usePt; + private static double _distanceTolerance; + + /// + /// Removes all collinear points on the polygon. + /// + /// The polygon that needs simplification. + /// The collinearity tolerance. + /// A simplified polygon. + public static Vertices CollinearSimplify(Vertices vertices, float collinearityTolerance) + { + //We can't simplify polygons under 3 vertices + if (vertices.Count < 3) + return vertices; + + Vertices simplified = new Vertices(); + + for (int i = 0; i < vertices.Count; i++) + { + int prevId = vertices.PreviousIndex(i); + int nextId = vertices.NextIndex(i); + + Vector2 prev = vertices[prevId]; + Vector2 current = vertices[i]; + Vector2 next = vertices[nextId]; + + //If they collinear, continue + if (MathUtils.Collinear(ref prev, ref current, ref next, collinearityTolerance)) + continue; + + simplified.Add(current); + } + + return simplified; + } + + /// + /// Removes all collinear points on the polygon. + /// Has a default bias of 0 + /// + /// The polygon that needs simplification. + /// A simplified polygon. + public static Vertices CollinearSimplify(Vertices vertices) + { + return CollinearSimplify(vertices, 0); + } + + /// + /// Ramer-Douglas-Peucker polygon simplification algorithm. This is the general recursive version that does not use the + /// speed-up technique by using the Melkman convex hull. + /// + /// If you pass in 0, it will remove all collinear points + /// + /// The simplified polygon + public static Vertices DouglasPeuckerSimplify(Vertices vertices, float distanceTolerance) + { + _distanceTolerance = distanceTolerance; + + _usePt = new bool[vertices.Count]; + for (int i = 0; i < vertices.Count; i++) + _usePt[i] = true; + + SimplifySection(vertices, 0, vertices.Count - 1); + Vertices result = new Vertices(); + + for (int i = 0; i < vertices.Count; i++) + if (_usePt[i]) + result.Add(vertices[i]); + + return result; + } + + private static void SimplifySection(Vertices vertices, int i, int j) + { + if ((i + 1) == j) + return; + + Vector2 A = vertices[i]; + Vector2 B = vertices[j]; + double maxDistance = -1.0; + int maxIndex = i; + for (int k = i + 1; k < j; k++) + { + double distance = DistancePointLine(vertices[k], A, B); + + if (distance > maxDistance) + { + maxDistance = distance; + maxIndex = k; + } + } + if (maxDistance <= _distanceTolerance) + for (int k = i + 1; k < j; k++) + _usePt[k] = false; + else + { + SimplifySection(vertices, i, maxIndex); + SimplifySection(vertices, maxIndex, j); + } + } + + private static double DistancePointPoint(Vector2 p, Vector2 p2) + { + double dx = p.X - p2.X; + double dy = p.Y - p2.X; + return Math.Sqrt(dx * dx + dy * dy); + } + + private static double DistancePointLine(Vector2 p, Vector2 A, Vector2 B) + { + // if start == end, then use point-to-point distance + if (A.X == B.X && A.Y == B.Y) + return DistancePointPoint(p, A); + + // otherwise use comp.graphics.algorithms Frequently Asked Questions method + /*(1) AC dot AB + r = --------- + ||AB||^2 + + r has the following meaning: + r=0 Point = A + r=1 Point = B + r<0 Point is on the backward extension of AB + r>1 Point is on the forward extension of AB + 0= 1.0) return DistancePointPoint(p, B); + + + /*(2) + (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) + s = ----------------------------- + Curve^2 + + Then the distance from C to Point = |s|*Curve. + */ + + double s = ((A.Y - p.Y) * (B.X - A.X) - (A.X - p.X) * (B.Y - A.Y)) + / + ((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y)); + + return Math.Abs(s) * Math.Sqrt(((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y))); + } + + //From physics2d.net + public static Vertices ReduceByArea(Vertices vertices, float areaTolerance) + { + if (vertices.Count <= 3) + return vertices; + + if (areaTolerance < 0) + { + throw new ArgumentOutOfRangeException("areaTolerance", "must be equal to or greater then zero."); + } + + Vertices result = new Vertices(); + Vector2 v1, v2, v3; + float old1, old2, new1; + v1 = vertices[vertices.Count - 2]; + v2 = vertices[vertices.Count - 1]; + areaTolerance *= 2; + for (int index = 0; index < vertices.Count; ++index, v2 = v3) + { + if (index == vertices.Count - 1) + { + if (result.Count == 0) + { + throw new ArgumentOutOfRangeException("areaTolerance", "The tolerance is too high!"); + } + v3 = result[0]; + } + else + { + v3 = vertices[index]; + } + MathUtils.Cross(ref v1, ref v2, out old1); + MathUtils.Cross(ref v2, ref v3, out old2); + MathUtils.Cross(ref v1, ref v3, out new1); + if (Math.Abs(new1 - (old1 + old2)) > areaTolerance) + { + result.Add(v2); + v1 = v2; + } + } + return result; + } + + //From Eric Jordan's convex decomposition library + + /// + /// Merges all parallel edges in the list of vertices + /// + /// The vertices. + /// The tolerance. + public static void MergeParallelEdges(Vertices vertices, float tolerance) + { + if (vertices.Count <= 3) + return; //Can't do anything useful here to a triangle + + bool[] mergeMe = new bool[vertices.Count]; + int newNVertices = vertices.Count; + + //Gather points to process + for (int i = 0; i < vertices.Count; ++i) + { + int lower = (i == 0) ? (vertices.Count - 1) : (i - 1); + int middle = i; + int upper = (i == vertices.Count - 1) ? (0) : (i + 1); + + float dx0 = vertices[middle].X - vertices[lower].X; + float dy0 = vertices[middle].Y - vertices[lower].Y; + float dx1 = vertices[upper].Y - vertices[middle].X; + float dy1 = vertices[upper].Y - vertices[middle].Y; + float norm0 = (float)Math.Sqrt(dx0 * dx0 + dy0 * dy0); + float norm1 = (float)Math.Sqrt(dx1 * dx1 + dy1 * dy1); + + if (!(norm0 > 0.0f && norm1 > 0.0f) && newNVertices > 3) + { + //Merge identical points + mergeMe[i] = true; + --newNVertices; + } + + dx0 /= norm0; + dy0 /= norm0; + dx1 /= norm1; + dy1 /= norm1; + float cross = dx0 * dy1 - dx1 * dy0; + float dot = dx0 * dx1 + dy0 * dy1; + + if (Math.Abs(cross) < tolerance && dot > 0 && newNVertices > 3) + { + mergeMe[i] = true; + --newNVertices; + } + else + mergeMe[i] = false; + } + + if (newNVertices == vertices.Count || newNVertices == 0) + return; + + int currIndex = 0; + + //Copy the vertices to a new list and clear the old + Vertices oldVertices = new Vertices(vertices); + vertices.Clear(); + + for (int i = 0; i < oldVertices.Count; ++i) + { + if (mergeMe[i] || newNVertices == 0 || currIndex == newNVertices) + continue; + + Debug.Assert(currIndex < newNVertices); + + vertices.Add(oldVertices[i]); + ++currIndex; + } + } + + //Misc + + /// + /// Merges the identical points in the polygon. + /// + /// The vertices. + /// + public static Vertices MergeIdenticalPoints(Vertices vertices) + { + //We use a dictonary here because HashSet is not avaliable on all platforms. + HashSet results = new HashSet(); + + for (int i = 0; i < vertices.Count; i++) + { + results.Add(vertices[i]); + } + + Vertices returnResults = new Vertices(); + foreach (Vector2 v in results) + { + returnResults.Add(v); + } + + return returnResults; + } + + /// + /// Reduces the polygon by distance. + /// + /// The vertices. + /// The distance between points. Points closer than this will be 'joined'. + /// + public static Vertices ReduceByDistance(Vertices vertices, float distance) + { + //We can't simplify polygons under 3 vertices + if (vertices.Count < 3) + return vertices; + + Vertices simplified = new Vertices(); + + for (int i = 0; i < vertices.Count; i++) + { + Vector2 current = vertices[i]; + Vector2 next = vertices.NextVertex(i); + + //If they are closer than the distance, continue + if ((next - current).LengthSquared() <= distance) + continue; + + simplified.Add(current); + } + + return simplified; + } + + /// + /// Reduces the polygon by removing the Nth vertex in the vertices list. + /// + /// The vertices. + /// The Nth point to remove. Example: 5. + /// + public static Vertices ReduceByNth(Vertices vertices, int nth) + { + //We can't simplify polygons under 3 vertices + if (vertices.Count < 3) + return vertices; + + if (nth == 0) + return vertices; + + Vertices result = new Vertices(vertices.Count); + + for (int i = 0; i < vertices.Count; i++) + { + if (i % nth == 0) + continue; + + result.Add(vertices[i]); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Common/PolygonManipulation/YuPengClipper.cs b/Common/PolygonManipulation/YuPengClipper.cs new file mode 100644 index 0000000..088693a --- /dev/null +++ b/Common/PolygonManipulation/YuPengClipper.cs @@ -0,0 +1,513 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using FarseerPhysics.Collision.Shapes; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common.PolygonManipulation +{ + internal enum PolyClipType + { + Intersect, + Union, + Difference + } + + public enum PolyClipError + { + None, + DegeneratedOutput, + NonSimpleInput, + BrokenResult + } + + //Clipper contributed by Helge Backhaus + + public static class YuPengClipper + { + private const float ClipperEpsilonSquared = 1.192092896e-07f; + + public static List Union(Vertices polygon1, Vertices polygon2, out PolyClipError error) + { + return Execute(polygon1, polygon2, PolyClipType.Union, out error); + } + + public static List Difference(Vertices polygon1, Vertices polygon2, out PolyClipError error) + { + return Execute(polygon1, polygon2, PolyClipType.Difference, out error); + } + + public static List Intersect(Vertices polygon1, Vertices polygon2, out PolyClipError error) + { + return Execute(polygon1, polygon2, PolyClipType.Intersect, out error); + } + + /// + /// Implements "A new algorithm for Boolean operations on general polygons" + /// available here: http://liama.ia.ac.cn/wiki/_media/user:dong:dong_cg_05.pdf + /// Merges two polygons, a subject and a clip with the specified operation. Polygons may not be + /// self-intersecting. + /// + /// Warning: May yield incorrect results or even crash if polygons contain collinear points. + /// + /// The subject polygon. + /// The clip polygon, which is added, + /// substracted or intersected with the subject + /// The operation to be performed. Either + /// Union, Difference or Intersection. + /// The error generated (if any) + /// A list of closed polygons, which make up the result of the clipping operation. + /// Outer contours are ordered counter clockwise, holes are ordered clockwise. + private static List Execute(Vertices subject, Vertices clip, + PolyClipType clipType, out PolyClipError error) + { + Debug.Assert(subject.IsSimple() && clip.IsSimple(), "Non simple input!", "Input polygons must be simple (cannot intersect themselves)."); + + // Copy polygons + Vertices slicedSubject; + Vertices slicedClip; + // Calculate the intersection and touch points between + // subject and clip and add them to both + CalculateIntersections(subject, clip, out slicedSubject, out slicedClip); + + // Translate polygons into upper right quadrant + // as the algorithm depends on it + Vector2 lbSubject = subject.GetCollisionBox().LowerBound; + Vector2 lbClip = clip.GetCollisionBox().LowerBound; + Vector2 translate; + Vector2.Min(ref lbSubject, ref lbClip, out translate); + translate = Vector2.One - translate; + if (translate != Vector2.Zero) + { + slicedSubject.Translate(ref translate); + slicedClip.Translate(ref translate); + } + + // Enforce counterclockwise contours + slicedSubject.ForceCounterClockWise(); + slicedClip.ForceCounterClockWise(); + + List subjectSimplices; + List subjectCoeff; + List clipSimplices; + List clipCoeff; + // Build simplical chains from the polygons and calculate the + // the corresponding coefficients + CalculateSimplicalChain(slicedSubject, out subjectCoeff, out subjectSimplices); + CalculateSimplicalChain(slicedClip, out clipCoeff, out clipSimplices); + + List resultSimplices; + + // Determine the characteristics function for all non-original edges + // in subject and clip simplical chain and combine the edges contributing + // to the result, depending on the clipType + CalculateResultChain(subjectCoeff, subjectSimplices, clipCoeff, clipSimplices, clipType, + out resultSimplices); + + List result; + // Convert result chain back to polygon(s) + error = BuildPolygonsFromChain(resultSimplices, out result); + + // Reverse the polygon translation from the beginning + // and remove collinear points from output + translate *= -1f; + for (int i = 0; i < result.Count; ++i) + { + result[i].Translate(ref translate); + SimplifyTools.CollinearSimplify(result[i]); + } + return result; + } + + /// + /// Calculates all intersections between two polygons. + /// + /// The first polygon. + /// The second polygon. + /// Returns the first polygon with added intersection points. + /// Returns the second polygon with added intersection points. + private static void CalculateIntersections(Vertices polygon1, Vertices polygon2, + out Vertices slicedPoly1, out Vertices slicedPoly2) + { + slicedPoly1 = new Vertices(polygon1); + slicedPoly2 = new Vertices(polygon2); + + // Iterate through polygon1's edges + for (int i = 0; i < polygon1.Count; i++) + { + // Get edge vertices + Vector2 a = polygon1[i]; + Vector2 b = polygon1[polygon1.NextIndex(i)]; + + // Get intersections between this edge and polygon2 + for (int j = 0; j < polygon2.Count; j++) + { + Vector2 c = polygon2[j]; + Vector2 d = polygon2[polygon2.NextIndex(j)]; + + Vector2 intersectionPoint; + // Check if the edges intersect + if (LineTools.LineIntersect(a, b, c, d, out intersectionPoint)) + { + // calculate alpha values for sorting multiple intersections points on a edge + float alpha; + // Insert intersection point into first polygon + alpha = GetAlpha(a, b, intersectionPoint); + if (alpha > 0f && alpha < 1f) + { + int index = slicedPoly1.IndexOf(a) + 1; + while (index < slicedPoly1.Count && + GetAlpha(a, b, slicedPoly1[index]) <= alpha) + { + ++index; + } + slicedPoly1.Insert(index, intersectionPoint); + } + // Insert intersection point into second polygon + alpha = GetAlpha(c, d, intersectionPoint); + if (alpha > 0f && alpha < 1f) + { + int index = slicedPoly2.IndexOf(c) + 1; + while (index < slicedPoly2.Count && + GetAlpha(c, d, slicedPoly2[index]) <= alpha) + { + ++index; + } + slicedPoly2.Insert(index, intersectionPoint); + } + } + } + } + // Check for very small edges + for (int i = 0; i < slicedPoly1.Count; ++i) + { + int iNext = slicedPoly1.NextIndex(i); + //If they are closer than the distance remove vertex + if ((slicedPoly1[iNext] - slicedPoly1[i]).LengthSquared() <= ClipperEpsilonSquared) + { + slicedPoly1.RemoveAt(i); + --i; + } + } + for (int i = 0; i < slicedPoly2.Count; ++i) + { + int iNext = slicedPoly2.NextIndex(i); + //If they are closer than the distance remove vertex + if ((slicedPoly2[iNext] - slicedPoly2[i]).LengthSquared() <= ClipperEpsilonSquared) + { + slicedPoly2.RemoveAt(i); + --i; + } + } + } + + /// + /// Calculates the simplical chain corresponding to the input polygon. + /// + /// Used by method Execute(). + private static void CalculateSimplicalChain(Vertices poly, out List coeff, + out List simplicies) + { + simplicies = new List(); + coeff = new List(); + for (int i = 0; i < poly.Count; ++i) + { + simplicies.Add(new Edge(poly[i], poly[poly.NextIndex(i)])); + coeff.Add(CalculateSimplexCoefficient(Vector2.Zero, poly[i], poly[poly.NextIndex(i)])); + } + } + + /// + /// Calculates the characteristics function for all edges of + /// the given simplical chains and builds the result chain. + /// + /// Used by method Execute(). + private static void CalculateResultChain(List poly1Coeff, List poly1Simplicies, + List poly2Coeff, List poly2Simplicies, + PolyClipType clipType, out List resultSimplices) + { + resultSimplices = new List(); + + for (int i = 0; i < poly1Simplicies.Count; ++i) + { + float edgeCharacter = 0f; + if (poly2Simplicies.Contains(poly1Simplicies[i]) || + (poly2Simplicies.Contains(-poly1Simplicies[i]) && clipType == PolyClipType.Union)) + { + edgeCharacter = 1f; + } + else + { + for (int j = 0; j < poly2Simplicies.Count; ++j) + { + if (!poly2Simplicies.Contains(-poly1Simplicies[i])) + { + edgeCharacter += CalculateBeta(poly1Simplicies[i].GetCenter(), + poly2Simplicies[j], poly2Coeff[j]); + } + } + } + if (clipType == PolyClipType.Intersect) + { + if (edgeCharacter == 1f) + { + resultSimplices.Add(poly1Simplicies[i]); + } + } + else + { + if (edgeCharacter == 0f) + { + resultSimplices.Add(poly1Simplicies[i]); + } + } + } + for (int i = 0; i < poly2Simplicies.Count; ++i) + { + if (!resultSimplices.Contains(poly2Simplicies[i]) && + !resultSimplices.Contains(-poly2Simplicies[i])) + { + float edgeCharacter = 0f; + if (poly1Simplicies.Contains(poly2Simplicies[i]) || + (poly1Simplicies.Contains(-poly2Simplicies[i]) && clipType == PolyClipType.Union)) + { + edgeCharacter = 1f; + } + else + { + for (int j = 0; j < poly1Simplicies.Count; ++j) + { + if (!poly1Simplicies.Contains(-poly2Simplicies[i])) + { + edgeCharacter += CalculateBeta(poly2Simplicies[i].GetCenter(), + poly1Simplicies[j], poly1Coeff[j]); + } + } + } + if (clipType == PolyClipType.Intersect || clipType == PolyClipType.Difference) + { + if (edgeCharacter == 1f) + { + resultSimplices.Add(-poly2Simplicies[i]); + } + } + else + { + if (edgeCharacter == 0f) + { + resultSimplices.Add(poly2Simplicies[i]); + } + } + } + } + } + + /// + /// Calculates the polygon(s) from the result simplical chain. + /// + /// Used by method Execute(). + private static PolyClipError BuildPolygonsFromChain(List simplicies, out List result) + { + result = new List(); + PolyClipError errVal = PolyClipError.None; + + while (simplicies.Count > 0) + { + Vertices output = new Vertices(); + output.Add(simplicies[0].EdgeStart); + output.Add(simplicies[0].EdgeEnd); + simplicies.RemoveAt(0); + bool closed = false; + int index = 0; + int count = simplicies.Count; // Needed to catch infinite loops + while (!closed && simplicies.Count > 0) + { + if (VectorEqual(output[output.Count - 1], simplicies[index].EdgeStart)) + { + if (VectorEqual(simplicies[index].EdgeEnd, output[0])) + { + closed = true; + } + else + { + output.Add(simplicies[index].EdgeEnd); + } + simplicies.RemoveAt(index); + --index; + } + else if (VectorEqual(output[output.Count - 1], simplicies[index].EdgeEnd)) + { + if (VectorEqual(simplicies[index].EdgeStart, output[0])) + { + closed = true; + } + else + { + output.Add(simplicies[index].EdgeStart); + } + simplicies.RemoveAt(index); + --index; + } + if (!closed) + { + if (++index == simplicies.Count) + { + if (count == simplicies.Count) + { + result = new List(); + Debug.WriteLine("Undefined error while building result polygon(s)."); + return PolyClipError.BrokenResult; + } + index = 0; + count = simplicies.Count; + } + } + } + if (output.Count < 3) + { + errVal = PolyClipError.DegeneratedOutput; + Debug.WriteLine("Degenerated output polygon produced (vertices < 3)."); + } + result.Add(output); + } + return errVal; + } + + /// + /// Needed to calculate the characteristics function of a simplex. + /// + /// Used by method CalculateEdgeCharacter(). + private static float CalculateBeta(Vector2 point, Edge e, float coefficient) + { + float result = 0f; + if (PointInSimplex(point, e)) + { + result = coefficient; + } + if (PointOnLineSegment(Vector2.Zero, e.EdgeStart, point) || + PointOnLineSegment(Vector2.Zero, e.EdgeEnd, point)) + { + result = .5f * coefficient; + } + return result; + } + + /// + /// Needed for sorting multiple intersections points on the same edge. + /// + /// Used by method CalculateIntersections(). + private static float GetAlpha(Vector2 start, Vector2 end, Vector2 point) + { + return (point - start).LengthSquared() / (end - start).LengthSquared(); + } + + /// + /// Returns the coefficient of a simplex. + /// + /// Used by method CalculateSimplicalChain(). + private static float CalculateSimplexCoefficient(Vector2 a, Vector2 b, Vector2 c) + { + float isLeft = MathUtils.Area(ref a, ref b, ref c); + if (isLeft < 0f) + { + return -1f; + } + + if (isLeft > 0f) + { + return 1f; + } + + return 0f; + } + + /// + /// Winding number test for a point in a simplex. + /// + /// The point to be tested. + /// The edge that the point is tested against. + /// False if the winding number is even and the point is outside + /// the simplex and True otherwise. + private static bool PointInSimplex(Vector2 point, Edge edge) + { + Vertices polygon = new Vertices(); + polygon.Add(Vector2.Zero); + polygon.Add(edge.EdgeStart); + polygon.Add(edge.EdgeEnd); + return (polygon.PointInPolygon(ref point) == 1); + } + + /// + /// Tests if a point lies on a line segment. + /// + /// Used by method CalculateBeta(). + private static bool PointOnLineSegment(Vector2 start, Vector2 end, Vector2 point) + { + Vector2 segment = end - start; + return MathUtils.Area(ref start, ref end, ref point) == 0f && + Vector2.Dot(point - start, segment) >= 0f && + Vector2.Dot(point - end, segment) <= 0f; + } + + private static bool VectorEqual(Vector2 vec1, Vector2 vec2) + { + return (vec2 - vec1).LengthSquared() <= ClipperEpsilonSquared; + } + + #region Nested type: Edge + + /// Specifies an Edge. Edges are used to represent simplicies in simplical chains + private sealed class Edge + { + public Edge(Vector2 edgeStart, Vector2 edgeEnd) + { + EdgeStart = edgeStart; + EdgeEnd = edgeEnd; + } + + public Vector2 EdgeStart { get; private set; } + public Vector2 EdgeEnd { get; private set; } + + public Vector2 GetCenter() + { + return (EdgeStart + EdgeEnd) / 2f; + } + + public static Edge operator -(Edge e) + { + return new Edge(e.EdgeEnd, e.EdgeStart); + } + + public override bool Equals(Object obj) + { + // If parameter is null return false. + if (obj == null) + { + return false; + } + + // If parameter cannot be cast to Point return false. + return Equals(obj as Edge); + } + + public bool Equals(Edge e) + { + // If parameter is null return false: + if (e == null) + { + return false; + } + + // Return true if the fields match + return VectorEqual(EdgeStart, e.EdgeStart) && VectorEqual(EdgeEnd, e.EdgeEnd); + } + + public override int GetHashCode() + { + return EdgeStart.GetHashCode() ^ EdgeEnd.GetHashCode(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Common/PolygonTools.cs b/Common/PolygonTools.cs new file mode 100644 index 0000000..7f6819f --- /dev/null +++ b/Common/PolygonTools.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + public static class PolygonTools + { + /// + /// Build vertices to represent an axis-aligned box. + /// + /// the half-width. + /// the half-height. + public static Vertices CreateRectangle(float hx, float hy) + { + Vertices vertices = new Vertices(4); + vertices.Add(new Vector2(-hx, -hy)); + vertices.Add(new Vector2(hx, -hy)); + vertices.Add(new Vector2(hx, hy)); + vertices.Add(new Vector2(-hx, hy)); + + return vertices; + } + + public static Vertices CreateTriangle(float hx, float hy) + { + Vertices vertices = new Vertices(3); + vertices.Add(new Vector2(hx/2, hy)); + vertices.Add(new Vector2(hx, 0)); + vertices.Add(new Vector2(0, 0)); + return vertices; + } + + /// + /// Build vertices to represent an oriented box. + /// + /// the half-width. + /// the half-height. + /// the center of the box in local coordinates. + /// the rotation of the box in local coordinates. + public static Vertices CreateRectangle(float hx, float hy, Vector2 center, float angle) + { + Vertices vertices = CreateRectangle(hx, hy); + + Transform xf = new Transform(); + xf.Position = center; + xf.R.Set(angle); + + // Transform vertices + for (int i = 0; i < 4; ++i) + { + vertices[i] = MathUtils.Multiply(ref xf, vertices[i]); + } + + return vertices; + } + + //Rounded rectangle contributed by Jonathan Smars - jsmars@gmail.com + + /// + /// Creates a rounded rectangle with the specified width and height. + /// + /// The width. + /// The height. + /// The rounding X radius. + /// The rounding Y radius. + /// The number of segments to subdivide the edges. + /// + public static Vertices CreateRoundedRectangle(float width, float height, float xRadius, float yRadius, + int segments) + { + if (yRadius > height / 2 || xRadius > width / 2) + throw new Exception("Rounding amount can't be more than half the height and width respectively."); + if (segments < 0) + throw new Exception("Segments must be zero or more."); + + //We need at least 8 vertices to create a rounded rectangle + Debug.Assert(Settings.MaxPolygonVertices >= 8); + + Vertices vertices = new Vertices(); + if (segments == 0) + { + vertices.Add(new Vector2(width * .5f - xRadius, -height * .5f)); + vertices.Add(new Vector2(width * .5f, -height * .5f + yRadius)); + + vertices.Add(new Vector2(width * .5f, height * .5f - yRadius)); + vertices.Add(new Vector2(width * .5f - xRadius, height * .5f)); + + vertices.Add(new Vector2(-width * .5f + xRadius, height * .5f)); + vertices.Add(new Vector2(-width * .5f, height * .5f - yRadius)); + + vertices.Add(new Vector2(-width * .5f, -height * .5f + yRadius)); + vertices.Add(new Vector2(-width * .5f + xRadius, -height * .5f)); + } + else + { + int numberOfEdges = (segments * 4 + 8); + + float stepSize = MathHelper.TwoPi / (numberOfEdges - 4); + int perPhase = numberOfEdges / 4; + + Vector2 posOffset = new Vector2(width / 2 - xRadius, height / 2 - yRadius); + vertices.Add(posOffset + new Vector2(xRadius, -yRadius + yRadius)); + short phase = 0; + for (int i = 1; i < numberOfEdges; i++) + { + if (i - perPhase == 0 || i - perPhase * 3 == 0) + { + posOffset.X *= -1; + phase--; + } + else if (i - perPhase * 2 == 0) + { + posOffset.Y *= -1; + phase--; + } + + vertices.Add(posOffset + new Vector2(xRadius * (float)Math.Cos(stepSize * -(i + phase)), + -yRadius * (float)Math.Sin(stepSize * -(i + phase)))); + } + } + + return vertices; + } + + /// + /// Set this as a single edge. + /// + /// The first point. + /// The second point. + public static Vertices CreateLine(Vector2 start, Vector2 end) + { + Vertices vertices = new Vertices(2); + vertices.Add(start); + vertices.Add(end); + + return vertices; + } + + /// + /// Creates a circle with the specified radius and number of edges. + /// + /// The radius. + /// The number of edges. The more edges, the more it resembles a circle + /// + public static Vertices CreateCircle(float radius, int numberOfEdges) + { + return CreateEllipse(radius, radius, numberOfEdges); + } + + /// + /// Creates a ellipse with the specified width, height and number of edges. + /// + /// Width of the ellipse. + /// Height of the ellipse. + /// The number of edges. The more edges, the more it resembles an ellipse + /// + public static Vertices CreateEllipse(float xRadius, float yRadius, int numberOfEdges) + { + Vertices vertices = new Vertices(); + + float stepSize = MathHelper.TwoPi / numberOfEdges; + + vertices.Add(new Vector2(xRadius, 0)); + for (int i = numberOfEdges - 1; i > 0; --i) + vertices.Add(new Vector2(xRadius * (float)Math.Cos(stepSize * i), + -yRadius * (float)Math.Sin(stepSize * i))); + + return vertices; + } + + public static Vertices CreateArc(float radians, int sides, float radius) + { + Debug.Assert(radians > 0, "The arc needs to be larger than 0"); + Debug.Assert(sides > 1, "The arc needs to have more than 1 sides"); + Debug.Assert(radius > 0, "The arc needs to have a radius larger than 0"); + + Vertices vertices = new Vertices(); + + float stepSize = radians / sides; + for (int i = sides - 1; i > 0; i--) + { + vertices.Add(new Vector2(radius * (float)Math.Cos(stepSize * i), + radius * (float)Math.Sin(stepSize * i))); + } + + return vertices; + } + + //Capsule contributed by Yobiv + + /// + /// Creates an capsule with the specified height, radius and number of edges. + /// A capsule has the same form as a pill capsule. + /// + /// Height (inner height + 2 * radius) of the capsule. + /// Radius of the capsule ends. + /// The number of edges of the capsule ends. The more edges, the more it resembles an capsule + /// + public static Vertices CreateCapsule(float height, float endRadius, int edges) + { + if (endRadius >= height / 2) + throw new ArgumentException( + "The radius must be lower than height / 2. Higher values of radius would create a circle, and not a half circle.", + "endRadius"); + + return CreateCapsule(height, endRadius, edges, endRadius, edges); + } + + /// + /// Creates an capsule with the specified height, radius and number of edges. + /// A capsule has the same form as a pill capsule. + /// + /// Height (inner height + radii) of the capsule. + /// Radius of the top. + /// The number of edges of the top. The more edges, the more it resembles an capsule + /// Radius of bottom. + /// The number of edges of the bottom. The more edges, the more it resembles an capsule + /// + public static Vertices CreateCapsule(float height, float topRadius, int topEdges, float bottomRadius, + int bottomEdges) + { + if (height <= 0) + throw new ArgumentException("Height must be longer than 0", "height"); + + if (topRadius <= 0) + throw new ArgumentException("The top radius must be more than 0", "topRadius"); + + if (topEdges <= 0) + throw new ArgumentException("Top edges must be more than 0", "topEdges"); + + if (bottomRadius <= 0) + throw new ArgumentException("The bottom radius must be more than 0", "bottomRadius"); + + if (bottomEdges <= 0) + throw new ArgumentException("Bottom edges must be more than 0", "bottomEdges"); + + if (topRadius >= height / 2) + throw new ArgumentException( + "The top radius must be lower than height / 2. Higher values of top radius would create a circle, and not a half circle.", + "topRadius"); + + if (bottomRadius >= height / 2) + throw new ArgumentException( + "The bottom radius must be lower than height / 2. Higher values of bottom radius would create a circle, and not a half circle.", + "bottomRadius"); + + Vertices vertices = new Vertices(); + + float newHeight = (height - topRadius - bottomRadius) * 0.5f; + + // top + vertices.Add(new Vector2(topRadius, newHeight)); + + float stepSize = MathHelper.Pi / topEdges; + for (int i = 1; i < topEdges; i++) + { + vertices.Add(new Vector2(topRadius * (float)Math.Cos(stepSize * i), + topRadius * (float)Math.Sin(stepSize * i) + newHeight)); + } + + vertices.Add(new Vector2(-topRadius, newHeight)); + + // bottom + vertices.Add(new Vector2(-bottomRadius, -newHeight)); + + stepSize = MathHelper.Pi / bottomEdges; + for (int i = 1; i < bottomEdges; i++) + { + vertices.Add(new Vector2(-bottomRadius * (float)Math.Cos(stepSize * i), + -bottomRadius * (float)Math.Sin(stepSize * i) - newHeight)); + } + + vertices.Add(new Vector2(bottomRadius, -newHeight)); + + return vertices; + } + + /// + /// Creates a gear shape with the specified radius and number of teeth. + /// + /// The radius. + /// The number of teeth. + /// The tip percentage. + /// Height of the tooth. + /// + public static Vertices CreateGear(float radius, int numberOfTeeth, float tipPercentage, float toothHeight) + { + Vertices vertices = new Vertices(); + + float stepSize = MathHelper.TwoPi / numberOfTeeth; + tipPercentage /= 100f; + MathHelper.Clamp(tipPercentage, 0f, 1f); + float toothTipStepSize = (stepSize / 2f) * tipPercentage; + + float toothAngleStepSize = (stepSize - (toothTipStepSize * 2f)) / 2f; + + for (int i = numberOfTeeth - 1; i >= 0; --i) + { + if (toothTipStepSize > 0f) + { + vertices.Add( + new Vector2(radius * + (float)Math.Cos(stepSize * i + toothAngleStepSize * 2f + toothTipStepSize), + -radius * + (float)Math.Sin(stepSize * i + toothAngleStepSize * 2f + toothTipStepSize))); + + vertices.Add( + new Vector2((radius + toothHeight) * + (float)Math.Cos(stepSize * i + toothAngleStepSize + toothTipStepSize), + -(radius + toothHeight) * + (float)Math.Sin(stepSize * i + toothAngleStepSize + toothTipStepSize))); + } + + vertices.Add(new Vector2((radius + toothHeight) * + (float)Math.Cos(stepSize * i + toothAngleStepSize), + -(radius + toothHeight) * + (float)Math.Sin(stepSize * i + toothAngleStepSize))); + + vertices.Add(new Vector2(radius * (float)Math.Cos(stepSize * i), + -radius * (float)Math.Sin(stepSize * i))); + } + + return vertices; + } + + /// + /// Detects the vertices by analyzing the texture data. + /// + /// The texture data. + /// The texture width. + /// + public static Vertices CreatePolygon(uint[] data, int width) + { + return TextureConverter.DetectVertices(data, width); + } + + /// + /// Detects the vertices by analyzing the texture data. + /// + /// The texture data. + /// The texture width. + /// if set to true it will perform hole detection. + /// + public static Vertices CreatePolygon(uint[] data, int width, bool holeDetection) + { + return TextureConverter.DetectVertices(data, width, holeDetection); + } + + /// + /// Detects the vertices by analyzing the texture data. + /// + /// The texture data. + /// The texture width. + /// The hull tolerance. + /// The alpha tolerance. + /// if set to true it will perform multi part detection. + /// if set to true it will perform hole detection. + /// + public static List CreatePolygon(uint[] data, int width, float hullTolerance, + byte alphaTolerance, bool multiPartDetection, bool holeDetection) + { + return TextureConverter.DetectVertices(data, width, hullTolerance, alphaTolerance, + multiPartDetection, holeDetection); + } + } +} \ No newline at end of file diff --git a/Common/Serialization.cs b/Common/Serialization.cs new file mode 100644 index 0000000..aa428d5 --- /dev/null +++ b/Common/Serialization.cs @@ -0,0 +1,1453 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Dynamics; +using FarseerPhysics.Dynamics.Joints; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + public static class WorldSerializer + { + public static void Serialize(World world, string filename) + { + using (FileStream fs = new FileStream(filename, FileMode.Create)) + { + new WorldXmlSerializer().Serialize(world, fs); + } + } + + public static void Deserialize(World world, string filename) + { + using (FileStream fs = new FileStream(filename, FileMode.Open)) + { + new WorldXmlDeserializer().Deserialize(world, fs); + } + } + + public static World Deserialize(string filename) + { + using (FileStream fs = new FileStream(filename, FileMode.Open)) + { + return new WorldXmlDeserializer().Deserialize(fs); + } + } + } + + /// + /// + public class WorldXmlSerializer + { + private List _bodies = new List(); + private List _serializedFixtures = new List(); + private List _serializedShapes = new List(); + private XmlWriter _writer; + + private void SerializeShape(Shape shape) + { + _writer.WriteStartElement("Shape"); + _writer.WriteAttributeString("Type", shape.ShapeType.ToString()); + + switch (shape.ShapeType) + { + case ShapeType.Circle: + { + CircleShape circle = (CircleShape)shape; + + _writer.WriteElementString("Radius", circle.Radius.ToString()); + + WriteElement("Position", circle.Position); + } + break; + case ShapeType.Polygon: + { + PolygonShape poly = (PolygonShape)shape; + + _writer.WriteStartElement("Vertices"); + foreach (Vector2 v in poly.Vertices) + WriteElement("Vertex", v); + _writer.WriteEndElement(); + + WriteElement("Centroid", poly.MassData.Centroid); + } + break; + case ShapeType.Edge: + { + EdgeShape poly = (EdgeShape)shape; + WriteElement("Vertex1", poly.Vertex1); + WriteElement("Vertex2", poly.Vertex2); + } + break; + default: + throw new Exception(); + } + + _writer.WriteEndElement(); + } + + private void SerializeFixture(Fixture fixture) + { + _writer.WriteStartElement("Fixture"); + _writer.WriteElementString("Shape", FindShapeIndex(fixture.Shape).ToString()); + _writer.WriteElementString("Density", fixture.Shape.Density.ToString()); + + _writer.WriteStartElement("FilterData"); + _writer.WriteElementString("CategoryBits", ((int)fixture.CollisionCategories).ToString()); + _writer.WriteElementString("MaskBits", ((int)fixture.CollidesWith).ToString()); + _writer.WriteElementString("GroupIndex", fixture.CollisionGroup.ToString()); + _writer.WriteEndElement(); + + _writer.WriteElementString("Friction", fixture.Friction.ToString()); + _writer.WriteElementString("IsSensor", fixture.IsSensor.ToString()); + _writer.WriteElementString("Restitution", fixture.Restitution.ToString()); + + if (fixture.UserData != null) + { + _writer.WriteStartElement("UserData"); + WriteDynamicType(fixture.UserData.GetType(), fixture.UserData); + _writer.WriteEndElement(); + } + + _writer.WriteEndElement(); + } + + private void SerializeBody(Body body) + { + _writer.WriteStartElement("Body"); + _writer.WriteAttributeString("Type", body.BodyType.ToString()); + + _writer.WriteElementString("Active", body.Enabled.ToString()); + _writer.WriteElementString("AllowSleep", body.SleepingAllowed.ToString()); + _writer.WriteElementString("Angle", body.Rotation.ToString()); + _writer.WriteElementString("AngularDamping", body.AngularDamping.ToString()); + _writer.WriteElementString("AngularVelocity", body.AngularVelocity.ToString()); + _writer.WriteElementString("Awake", body.Awake.ToString()); + _writer.WriteElementString("Bullet", body.IsBullet.ToString()); + _writer.WriteElementString("FixedRotation", body.FixedRotation.ToString()); + _writer.WriteElementString("LinearDamping", body.LinearDamping.ToString()); + WriteElement("LinearVelocity", body.LinearVelocity); + WriteElement("Position", body.Position); + + if (body.UserData != null) + { + _writer.WriteStartElement("UserData"); + WriteDynamicType(body.UserData.GetType(), body.UserData); + _writer.WriteEndElement(); + } + + _writer.WriteStartElement("Fixtures"); + for (int i = 0; i < body.FixtureList.Count; i++) + { + _writer.WriteElementString("ID", FindFixtureIndex(body.FixtureList[i]).ToString()); + } + + _writer.WriteEndElement(); + _writer.WriteEndElement(); + } + + private void SerializeJoint(Joint joint) + { + if (joint.IsFixedType()) + return; + + _writer.WriteStartElement("Joint"); + + _writer.WriteAttributeString("Type", joint.JointType.ToString()); + + WriteElement("BodyA", FindBodyIndex(joint.BodyA)); + WriteElement("BodyB", FindBodyIndex(joint.BodyB)); + + WriteElement("CollideConnected", joint.CollideConnected); + + WriteElement("Breakpoint", joint.Breakpoint); + + if (joint.UserData != null) + { + _writer.WriteStartElement("UserData"); + WriteDynamicType(joint.UserData.GetType(), joint.UserData); + _writer.WriteEndElement(); + } + + switch (joint.JointType) + { + case JointType.Distance: + { + DistanceJoint djd = (DistanceJoint)joint; + + WriteElement("DampingRatio", djd.DampingRatio); + WriteElement("FrequencyHz", djd.Frequency); + WriteElement("Length", djd.Length); + WriteElement("LocalAnchorA", djd.LocalAnchorA); + WriteElement("LocalAnchorB", djd.LocalAnchorB); + } + break; + case JointType.Friction: + { + FrictionJoint fjd = (FrictionJoint)joint; + WriteElement("LocalAnchorA", fjd.LocalAnchorA); + WriteElement("LocalAnchorB", fjd.LocalAnchorB); + WriteElement("MaxForce", fjd.MaxForce); + WriteElement("MaxTorque", fjd.MaxTorque); + } + break; + case JointType.Gear: + throw new Exception("Gear joint not supported by serialization"); + case JointType.Line: + { + LineJoint ljd = (LineJoint)joint; + + WriteElement("EnableMotor", ljd.MotorEnabled); + WriteElement("LocalAnchorA", ljd.LocalAnchorA); + WriteElement("LocalAnchorB", ljd.LocalAnchorB); + WriteElement("MotorSpeed", ljd.MotorSpeed); + WriteElement("DampingRatio", ljd.DampingRatio); + WriteElement("MaxMotorTorque", ljd.MaxMotorTorque); + WriteElement("FrequencyHz", ljd.Frequency); + WriteElement("LocalXAxis", ljd.LocalXAxis); + } + break; + case JointType.Prismatic: + { + PrismaticJoint pjd = (PrismaticJoint)joint; + + //NOTE: Does not conform with Box2DScene + + WriteElement("EnableLimit", pjd.LimitEnabled); + WriteElement("EnableMotor", pjd.MotorEnabled); + WriteElement("LocalAnchorA", pjd.LocalAnchorA); + WriteElement("LocalAnchorB", pjd.LocalAnchorB); + WriteElement("LocalXAxis1", pjd.LocalXAxis1); + WriteElement("LowerTranslation", pjd.LowerLimit); + WriteElement("UpperTranslation", pjd.UpperLimit); + WriteElement("MaxMotorForce", pjd.MaxMotorForce); + WriteElement("MotorSpeed", pjd.MotorSpeed); + } + break; + case JointType.Pulley: + { + PulleyJoint pjd = (PulleyJoint)joint; + + WriteElement("GroundAnchorA", pjd.GroundAnchorA); + WriteElement("GroundAnchorB", pjd.GroundAnchorB); + WriteElement("LengthA", pjd.LengthA); + WriteElement("LengthB", pjd.LengthB); + WriteElement("LocalAnchorA", pjd.LocalAnchorA); + WriteElement("LocalAnchorB", pjd.LocalAnchorB); + WriteElement("MaxLengthA", pjd.MaxLengthA); + WriteElement("MaxLengthB", pjd.MaxLengthB); + WriteElement("Ratio", pjd.Ratio); + } + break; + case JointType.Revolute: + { + RevoluteJoint rjd = (RevoluteJoint)joint; + + WriteElement("EnableLimit", rjd.LimitEnabled); + WriteElement("EnableMotor", rjd.MotorEnabled); + WriteElement("LocalAnchorA", rjd.LocalAnchorA); + WriteElement("LocalAnchorB", rjd.LocalAnchorB); + WriteElement("LowerAngle", rjd.LowerLimit); + WriteElement("MaxMotorTorque", rjd.MaxMotorTorque); + WriteElement("MotorSpeed", rjd.MotorSpeed); + WriteElement("ReferenceAngle", rjd.ReferenceAngle); + WriteElement("UpperAngle", rjd.UpperLimit); + } + break; + case JointType.Weld: + { + WeldJoint wjd = (WeldJoint)joint; + + WriteElement("LocalAnchorA", wjd.LocalAnchorA); + WriteElement("LocalAnchorB", wjd.LocalAnchorB); + } + break; + // + // Not part of Box2DScene + // + case JointType.Rope: + { + RopeJoint rjd = (RopeJoint)joint; + + WriteElement("LocalAnchorA", rjd.LocalAnchorA); + WriteElement("LocalAnchorB", rjd.LocalAnchorB); + WriteElement("MaxLength", rjd.MaxLength); + } + break; + case JointType.Angle: + { + AngleJoint aj = (AngleJoint)joint; + WriteElement("BiasFactor", aj.BiasFactor); + WriteElement("MaxImpulse", aj.MaxImpulse); + WriteElement("Softness", aj.Softness); + WriteElement("TargetAngle", aj.TargetAngle); + } + break; + case JointType.Slider: + { + SliderJoint sliderJoint = (SliderJoint)joint; + WriteElement("DampingRatio", sliderJoint.DampingRatio); + WriteElement("FrequencyHz", sliderJoint.Frequency); + WriteElement("MaxLength", sliderJoint.MaxLength); + WriteElement("MinLength", sliderJoint.MinLength); + WriteElement("LocalAnchorA", sliderJoint.LocalAnchorA); + WriteElement("LocalAnchorB", sliderJoint.LocalAnchorB); + } + break; + default: + throw new Exception("Joint not supported"); + } + + _writer.WriteEndElement(); + } + + private void WriteDynamicType(Type type, object val) + { + _writer.WriteElementString("Type", type.FullName); + + _writer.WriteStartElement("Value"); + XmlSerializer serializer = new XmlSerializer(type); + XmlSerializerNamespaces xmlnsEmpty = new XmlSerializerNamespaces(); + xmlnsEmpty.Add("", ""); + serializer.Serialize(_writer, val, xmlnsEmpty); + _writer.WriteEndElement(); + } + + private void WriteElement(string name, Vector2 vec) + { + _writer.WriteElementString(name, vec.X + " " + vec.Y); + } + + private void WriteElement(string name, int val) + { + _writer.WriteElementString(name, val.ToString()); + } + + private void WriteElement(string name, bool val) + { + _writer.WriteElementString(name, val.ToString()); + } + + private void WriteElement(string name, float val) + { + _writer.WriteElementString(name, val.ToString()); + } + + public void Serialize(World world, Stream stream) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.NewLineOnAttributes = false; + settings.OmitXmlDeclaration = true; + + _writer = XmlWriter.Create(stream, settings); + + _writer.WriteStartElement("World"); + _writer.WriteAttributeString("Version", "2"); + WriteElement("Gravity", world.Gravity); + + _writer.WriteStartElement("Shapes"); + + for (int i = 0; i < world.BodyList.Count; i++) + { + Body body = world.BodyList[i]; + for (int j = 0; j < body.FixtureList.Count; j++) + { + Fixture fixture = body.FixtureList[j]; + + bool alreadyThere = false; + for (int k = 0; k < _serializedShapes.Count; k++) + { + Shape s2 = _serializedShapes[k]; + if (fixture.Shape.CompareTo(s2)) + { + alreadyThere = true; + break; + } + } + + if (!alreadyThere) + { + SerializeShape(fixture.Shape); + _serializedShapes.Add(fixture.Shape); + } + } + } + + _writer.WriteEndElement(); + _writer.WriteStartElement("Fixtures"); + + + for (int i = 0; i < world.BodyList.Count; i++) + { + Body body = world.BodyList[i]; + for (int j = 0; j < body.FixtureList.Count; j++) + { + Fixture fixture = body.FixtureList[j]; + bool alreadyThere = false; + for (int k = 0; k < _serializedFixtures.Count; k++) + { + Fixture f2 = _serializedFixtures[k]; + if (fixture.CompareTo(f2)) + { + alreadyThere = true; + break; + } + } + + if (!alreadyThere) + { + SerializeFixture(fixture); + _serializedFixtures.Add(fixture); + } + } + } + + _writer.WriteEndElement(); + _writer.WriteStartElement("Bodies"); + + for (int i = 0; i < world.BodyList.Count; i++) + { + Body body = world.BodyList[i]; + _bodies.Add(body); + SerializeBody(body); + } + + _writer.WriteEndElement(); + _writer.WriteStartElement("Joints"); + + for (int i = 0; i < world.JointList.Count; i++) + { + Joint joint = world.JointList[i]; + SerializeJoint(joint); + } + + _writer.WriteEndElement(); + _writer.WriteEndElement(); + + _writer.Flush(); + _writer.Close(); + } + + private int FindBodyIndex(Body body) + { + for (int i = 0; i < _bodies.Count; ++i) + if (_bodies[i] == body) + return i; + + return -1; + } + + private int FindFixtureIndex(Fixture fixture) + { + for (int i = 0; i < _serializedFixtures.Count; ++i) + { + if (_serializedFixtures[i].CompareTo(fixture)) + return i; + } + + return -1; + } + + private int FindShapeIndex(Shape shape) + { + for (int i = 0; i < _serializedShapes.Count; ++i) + { + if (_serializedShapes[i].CompareTo(shape)) + return i; + } + + return -1; + } + } + + public class WorldXmlDeserializer + { + private List _bodies = new List(); + private List _fixtures = new List(); + private List _joints = new List(); + private List _shapes = new List(); + + public World Deserialize(Stream stream) + { + World world = new World(Vector2.Zero); + Deserialize(world, stream); + return world; + } + + public void Deserialize(World world, Stream stream) + { + world.Clear(); + + XMLFragmentElement root = XMLFragmentParser.LoadFromStream(stream); + + if (root.Name.ToLower() != "world") + throw new Exception(); + + foreach (XMLFragmentElement main in root.Elements) + { + if (main.Name.ToLower() == "gravity") + { + world.Gravity = ReadVector(main); + } + } + + foreach (XMLFragmentElement shapeElement in root.Elements) + { + if (shapeElement.Name.ToLower() == "shapes") + { + foreach (XMLFragmentElement n in shapeElement.Elements) + { + if (n.Name.ToLower() != "shape") + throw new Exception(); + + ShapeType type = (ShapeType)Enum.Parse(typeof(ShapeType), n.Attributes[0].Value, true); + + switch (type) + { + case ShapeType.Circle: + { + CircleShape shape = new CircleShape(); + + foreach (XMLFragmentElement sn in n.Elements) + { + switch (sn.Name.ToLower()) + { + case "radius": + shape.Radius = float.Parse(sn.Value); + break; + case "position": + shape.Position = ReadVector(sn); + break; + default: + throw new Exception(); + } + } + + _shapes.Add(shape); + } + break; + case ShapeType.Polygon: + { + PolygonShape shape = new PolygonShape(); + + foreach (XMLFragmentElement sn in n.Elements) + { + switch (sn.Name.ToLower()) + { + case "vertices": + { + List verts = new List(); + + foreach (XMLFragmentElement vert in sn.Elements) + verts.Add(ReadVector(vert)); + + shape.Set(new Vertices(verts.ToArray())); + } + break; + case "centroid": + shape.MassData.Centroid = ReadVector(sn); + break; + } + } + + _shapes.Add(shape); + } + break; + case ShapeType.Edge: + { + EdgeShape shape = new EdgeShape(); + foreach (XMLFragmentElement sn in n.Elements) + { + switch (sn.Name.ToLower()) + { + case "hasvertex0": + shape.HasVertex0 = bool.Parse(sn.Value); + break; + case "hasvertex3": + shape.HasVertex0 = bool.Parse(sn.Value); + break; + case "vertex0": + shape.Vertex0 = ReadVector(sn); + break; + case "vertex1": + shape.Vertex1 = ReadVector(sn); + break; + case "vertex2": + shape.Vertex2 = ReadVector(sn); + break; + case "vertex3": + shape.Vertex3 = ReadVector(sn); + break; + default: + throw new Exception(); + } + } + _shapes.Add(shape); + } + break; + } + } + } + } + + foreach (XMLFragmentElement fixtureElement in root.Elements) + { + if (fixtureElement.Name.ToLower() == "fixtures") + { + foreach (XMLFragmentElement n in fixtureElement.Elements) + { + Fixture fixture = new Fixture(); + + if (n.Name.ToLower() != "fixture") + throw new Exception(); + + foreach (XMLFragmentElement sn in n.Elements) + { + switch (sn.Name.ToLower()) + { + case "shape": + fixture.Shape = _shapes[int.Parse(sn.Value)]; + break; + case "density": + fixture.Shape.Density = float.Parse(sn.Value); + break; + case "filterdata": + foreach (XMLFragmentElement ssn in sn.Elements) + { + switch (ssn.Name.ToLower()) + { + case "categorybits": + fixture._collisionCategories = (Category)int.Parse(ssn.Value); + break; + case "maskbits": + fixture._collidesWith = (Category)int.Parse(ssn.Value); + break; + case "groupindex": + fixture._collisionGroup = short.Parse(ssn.Value); + break; + } + } + + break; + case "friction": + fixture.Friction = float.Parse(sn.Value); + break; + case "issensor": + fixture.IsSensor = bool.Parse(sn.Value); + break; + case "restitution": + fixture.Restitution = float.Parse(sn.Value); + break; + case "userdata": + fixture.UserData = ReadSimpleType(sn, null, false); + break; + } + } + + _fixtures.Add(fixture); + } + } + } + + foreach (XMLFragmentElement bodyElement in root.Elements) + { + if (bodyElement.Name.ToLower() == "bodies") + { + foreach (XMLFragmentElement n in bodyElement.Elements) + { + Body body = new Body(world); + + if (n.Name.ToLower() != "body") + throw new Exception(); + + body.BodyType = (BodyType)Enum.Parse(typeof(BodyType), n.Attributes[0].Value, true); + + foreach (XMLFragmentElement sn in n.Elements) + { + switch (sn.Name.ToLower()) + { + case "active": + if (bool.Parse(sn.Value)) + body.Flags |= BodyFlags.Enabled; + else + body.Flags &= ~BodyFlags.Enabled; + break; + case "allowsleep": + body.SleepingAllowed = bool.Parse(sn.Value); + break; + case "angle": + { + Vector2 position = body.Position; + body.SetTransformIgnoreContacts(ref position, float.Parse(sn.Value)); + } + break; + case "angulardamping": + body.AngularDamping = float.Parse(sn.Value); + break; + case "angularvelocity": + body.AngularVelocity = float.Parse(sn.Value); + break; + case "awake": + body.Awake = bool.Parse(sn.Value); + break; + case "bullet": + body.IsBullet = bool.Parse(sn.Value); + break; + case "fixedrotation": + body.FixedRotation = bool.Parse(sn.Value); + break; + case "lineardamping": + body.LinearDamping = float.Parse(sn.Value); + break; + case "linearvelocity": + body.LinearVelocity = ReadVector(sn); + break; + case "position": + { + float rotation = body.Rotation; + Vector2 position = ReadVector(sn); + body.SetTransformIgnoreContacts(ref position, rotation); + } + break; + case "userdata": + body.UserData = ReadSimpleType(sn, null, false); + break; + case "fixtures": + { + foreach (XMLFragmentElement v in sn.Elements) + { + Fixture blueprint = _fixtures[int.Parse(v.Value)]; + Fixture f = new Fixture(body, blueprint.Shape); + f.Restitution = blueprint.Restitution; + f.UserData = blueprint.UserData; + f.Friction = blueprint.Friction; + f.CollidesWith = blueprint.CollidesWith; + f.CollisionCategories = blueprint.CollisionCategories; + f.CollisionGroup = blueprint.CollisionGroup; + } + break; + } + } + } + + _bodies.Add(body); + } + } + } + + foreach (XMLFragmentElement jointElement in root.Elements) + { + if (jointElement.Name.ToLower() == "joints") + { + foreach (XMLFragmentElement n in jointElement.Elements) + { + Joint joint; + + if (n.Name.ToLower() != "joint") + throw new Exception(); + + JointType type = (JointType)Enum.Parse(typeof(JointType), n.Attributes[0].Value, true); + + int bodyAIndex = -1, bodyBIndex = -1; + bool collideConnected = false; + object userData = null; + + foreach (XMLFragmentElement sn in n.Elements) + { + switch (sn.Name.ToLower()) + { + case "bodya": + bodyAIndex = int.Parse(sn.Value); + break; + case "bodyb": + bodyBIndex = int.Parse(sn.Value); + break; + case "collideconnected": + collideConnected = bool.Parse(sn.Value); + break; + case "userdata": + userData = ReadSimpleType(sn, null, false); + break; + } + } + + Body bodyA = _bodies[bodyAIndex]; + Body bodyB = _bodies[bodyBIndex]; + + switch (type) + { + case JointType.Distance: + joint = new DistanceJoint(); + break; + case JointType.Friction: + joint = new FrictionJoint(); + break; + case JointType.Line: + joint = new LineJoint(); + break; + case JointType.Prismatic: + joint = new PrismaticJoint(); + break; + case JointType.Pulley: + joint = new PulleyJoint(); + break; + case JointType.Revolute: + joint = new RevoluteJoint(); + break; + case JointType.Weld: + joint = new WeldJoint(); + break; + case JointType.Rope: + joint = new RopeJoint(); + break; + case JointType.Angle: + joint = new AngleJoint(); + break; + case JointType.Slider: + joint = new SliderJoint(); + break; + case JointType.Gear: + throw new Exception("GearJoint is not supported."); + default: + throw new Exception("Invalid or unsupported joint."); + } + + joint.CollideConnected = collideConnected; + joint.UserData = userData; + joint.BodyA = bodyA; + joint.BodyB = bodyB; + _joints.Add(joint); + world.AddJoint(joint); + + foreach (XMLFragmentElement sn in n.Elements) + { + // check for specific nodes + switch (type) + { + case JointType.Distance: + { + switch (sn.Name.ToLower()) + { + case "dampingratio": + ((DistanceJoint)joint).DampingRatio = float.Parse(sn.Value); + break; + case "frequencyhz": + ((DistanceJoint)joint).Frequency = float.Parse(sn.Value); + break; + case "length": + ((DistanceJoint)joint).Length = float.Parse(sn.Value); + break; + case "localanchora": + ((DistanceJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((DistanceJoint)joint).LocalAnchorB = ReadVector(sn); + break; + } + } + break; + case JointType.Friction: + { + switch (sn.Name.ToLower()) + { + case "localanchora": + ((FrictionJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((FrictionJoint)joint).LocalAnchorB = ReadVector(sn); + break; + case "maxforce": + ((FrictionJoint)joint).MaxForce = float.Parse(sn.Value); + break; + case "maxtorque": + ((FrictionJoint)joint).MaxTorque = float.Parse(sn.Value); + break; + } + } + break; + case JointType.Line: + { + switch (sn.Name.ToLower()) + { + case "enablemotor": + ((LineJoint)joint).MotorEnabled = bool.Parse(sn.Value); + break; + case "localanchora": + ((LineJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((LineJoint)joint).LocalAnchorB = ReadVector(sn); + break; + case "motorspeed": + ((LineJoint)joint).MotorSpeed = float.Parse(sn.Value); + break; + case "dampingratio": + ((LineJoint)joint).DampingRatio = float.Parse(sn.Value); + break; + case "maxmotortorque": + ((LineJoint)joint).MaxMotorTorque = float.Parse(sn.Value); + break; + case "frequencyhz": + ((LineJoint)joint).Frequency = float.Parse(sn.Value); + break; + case "localxaxis": + ((LineJoint)joint).LocalXAxis = ReadVector(sn); + break; + } + } + break; + case JointType.Prismatic: + { + switch (sn.Name.ToLower()) + { + case "enablelimit": + ((PrismaticJoint)joint).LimitEnabled = bool.Parse(sn.Value); + break; + case "enablemotor": + ((PrismaticJoint)joint).MotorEnabled = bool.Parse(sn.Value); + break; + case "localanchora": + ((PrismaticJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((PrismaticJoint)joint).LocalAnchorB = ReadVector(sn); + break; + case "local1axis1": + ((PrismaticJoint)joint).LocalXAxis1 = ReadVector(sn); + break; + case "maxmotorforce": + ((PrismaticJoint)joint).MaxMotorForce = float.Parse(sn.Value); + break; + case "motorspeed": + ((PrismaticJoint)joint).MotorSpeed = float.Parse(sn.Value); + break; + case "lowertranslation": + ((PrismaticJoint)joint).LowerLimit = float.Parse(sn.Value); + break; + case "uppertranslation": + ((PrismaticJoint)joint).UpperLimit = float.Parse(sn.Value); + break; + case "referenceangle": + ((PrismaticJoint)joint).ReferenceAngle = float.Parse(sn.Value); + break; + } + } + break; + case JointType.Pulley: + { + switch (sn.Name.ToLower()) + { + case "groundanchora": + ((PulleyJoint)joint).GroundAnchorA = ReadVector(sn); + break; + case "groundanchorb": + ((PulleyJoint)joint).GroundAnchorB = ReadVector(sn); + break; + case "lengtha": + ((PulleyJoint)joint).LengthA = float.Parse(sn.Value); + break; + case "lengthb": + ((PulleyJoint)joint).LengthB = float.Parse(sn.Value); + break; + case "localanchora": + ((PulleyJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((PulleyJoint)joint).LocalAnchorB = ReadVector(sn); + break; + case "maxlengtha": + ((PulleyJoint)joint).MaxLengthA = float.Parse(sn.Value); + break; + case "maxlengthb": + ((PulleyJoint)joint).MaxLengthB = float.Parse(sn.Value); + break; + case "ratio": + ((PulleyJoint)joint).Ratio = float.Parse(sn.Value); + break; + } + } + break; + case JointType.Revolute: + { + switch (sn.Name.ToLower()) + { + case "enablelimit": + ((RevoluteJoint)joint).LimitEnabled = bool.Parse(sn.Value); + break; + case "enablemotor": + ((RevoluteJoint)joint).MotorEnabled = bool.Parse(sn.Value); + break; + case "localanchora": + ((RevoluteJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((RevoluteJoint)joint).LocalAnchorB = ReadVector(sn); + break; + case "maxmotortorque": + ((RevoluteJoint)joint).MaxMotorTorque = float.Parse(sn.Value); + break; + case "motorspeed": + ((RevoluteJoint)joint).MotorSpeed = float.Parse(sn.Value); + break; + case "lowerangle": + ((RevoluteJoint)joint).LowerLimit = float.Parse(sn.Value); + break; + case "upperangle": + ((RevoluteJoint)joint).UpperLimit = float.Parse(sn.Value); + break; + case "referenceangle": + ((RevoluteJoint)joint).ReferenceAngle = float.Parse(sn.Value); + break; + } + } + break; + case JointType.Weld: + { + switch (sn.Name.ToLower()) + { + case "localanchora": + ((WeldJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((WeldJoint)joint).LocalAnchorB = ReadVector(sn); + break; + } + } + break; + case JointType.Rope: + { + switch (sn.Name.ToLower()) + { + case "localanchora": + ((RopeJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((RopeJoint)joint).LocalAnchorB = ReadVector(sn); + break; + case "maxlength": + ((RopeJoint)joint).MaxLength = float.Parse(sn.Value); + break; + } + } + break; + case JointType.Gear: + throw new Exception("Gear joint is unsupported"); + case JointType.Angle: + { + switch (sn.Name.ToLower()) + { + case "biasfactor": + ((AngleJoint)joint).BiasFactor = float.Parse(sn.Value); + break; + case "maximpulse": + ((AngleJoint)joint).MaxImpulse = float.Parse(sn.Value); + break; + case "softness": + ((AngleJoint)joint).Softness = float.Parse(sn.Value); + break; + case "targetangle": + ((AngleJoint)joint).TargetAngle = float.Parse(sn.Value); + break; + } + } + break; + case JointType.Slider: + { + switch (sn.Name.ToLower()) + { + case "dampingratio": + ((SliderJoint)joint).DampingRatio = float.Parse(sn.Value); + break; + case "frequencyhz": + ((SliderJoint)joint).Frequency = float.Parse(sn.Value); + break; + case "maxlength": + ((SliderJoint)joint).MaxLength = float.Parse(sn.Value); + break; + case "minlength": + ((SliderJoint)joint).MinLength = float.Parse(sn.Value); + break; + case "localanchora": + ((SliderJoint)joint).LocalAnchorA = ReadVector(sn); + break; + case "localanchorb": + ((SliderJoint)joint).LocalAnchorB = ReadVector(sn); + break; + } + } + break; + } + } + } + } + } + } + + private Vector2 ReadVector(XMLFragmentElement node) + { + string[] values = node.Value.Split(' '); + return new Vector2(float.Parse(values[0]), float.Parse(values[1])); + } + + private object ReadSimpleType(XMLFragmentElement node, Type type, bool outer) + { + if (type == null) + return ReadSimpleType(node.Elements[1], Type.GetType(node.Elements[0].Value), outer); + + XmlSerializer serializer = new XmlSerializer(type); + XmlSerializerNamespaces xmlnsEmpty = new XmlSerializerNamespaces(); + xmlnsEmpty.Add("", ""); + + using (MemoryStream stream = new MemoryStream()) + { + StreamWriter writer = new StreamWriter(stream); + { + writer.Write((outer) ? node.OuterXml : node.InnerXml); + writer.Flush(); + stream.Position = 0; + } + XmlReaderSettings settings = new XmlReaderSettings(); + settings.ConformanceLevel = ConformanceLevel.Fragment; + + return serializer.Deserialize(XmlReader.Create(stream, settings)); + } + } + } + + #region XMLFragment + + public class XMLFragmentAttribute + { + public string Name { get; set; } + + public string Value { get; set; } + } + + public class XMLFragmentElement + { + private List _attributes = new List(); + private List _elements = new List(); + + public IList Elements + { + get { return _elements; } + } + + public IList Attributes + { + get { return _attributes; } + } + + public string Name { get; set; } + + public string Value { get; set; } + + public string OuterXml { get; set; } + + public string InnerXml { get; set; } + } + + public class XMLFragmentException : Exception + { + public XMLFragmentException() + { + } + + public XMLFragmentException(string message) + : base(message) + { + } + + public XMLFragmentException(string message, Exception inner) + : base(message, inner) + { + } + } + + public class FileBuffer + { + public FileBuffer(Stream stream) + { + using (StreamReader sr = new StreamReader(stream)) + Buffer = sr.ReadToEnd(); + + Position = 0; + } + + public string Buffer { get; set; } + + public int Position { get; set; } + + public int Length + { + get { return Buffer.Length; } + } + + public char Next + { + get + { + char c = Buffer[Position]; + Position++; + return c; + } + } + + public char Peek + { + get { return Buffer[Position]; } + } + + public bool EndOfBuffer + { + get { return Position == Length; } + } + } + + public class XMLFragmentParser + { + private static List _punctuation = new List { '/', '<', '>', '=' }; + private FileBuffer _buffer; + private XMLFragmentElement _rootNode; + + public XMLFragmentParser(Stream stream) + { + Load(stream); + } + + public XMLFragmentParser(string fileName) + { + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + Load(fs); + } + + public XMLFragmentElement RootNode + { + get { return _rootNode; } + } + + public void Load(Stream stream) + { + _buffer = new FileBuffer(stream); + } + + public static XMLFragmentElement LoadFromFile(string fileName) + { + XMLFragmentParser x = new XMLFragmentParser(fileName); + x.Parse(); + return x.RootNode; + } + + public static XMLFragmentElement LoadFromStream(Stream stream) + { + XMLFragmentParser x = new XMLFragmentParser(stream); + x.Parse(); + return x.RootNode; + } + + private string NextToken() + { + string str = ""; + bool _done = false; + + while (true) + { + char c = _buffer.Next; + + if (_punctuation.Contains(c)) + { + if (str != "") + { + _buffer.Position--; + break; + } + + _done = true; + } + else if (char.IsWhiteSpace(c)) + { + if (str != "") + break; + else + continue; + } + + str += c; + + if (_done) + break; + } + + str = TrimControl(str); + + // Trim quotes from start and end + if (str[0] == '\"') + str = str.Remove(0, 1); + + if (str[str.Length - 1] == '\"') + str = str.Remove(str.Length - 1, 1); + + return str; + } + + private string PeekToken() + { + int oldPos = _buffer.Position; + string str = NextToken(); + _buffer.Position = oldPos; + return str; + } + + private string ReadUntil(char c) + { + string str = ""; + + while (true) + { + char ch = _buffer.Next; + + if (ch == c) + { + _buffer.Position--; + break; + } + + str += ch; + } + + // Trim quotes from start and end + if (str[0] == '\"') + str = str.Remove(0, 1); + + if (str[str.Length - 1] == '\"') + str = str.Remove(str.Length - 1, 1); + + return str; + } + + private string TrimControl(string str) + { + string newStr = str; + + // Trim control characters + int i = 0; + while (true) + { + if (i == newStr.Length) + break; + + if (char.IsControl(newStr[i])) + newStr = newStr.Remove(i, 1); + else + i++; + } + + return newStr; + } + + private string TrimTags(string outer) + { + int start = outer.IndexOf('>') + 1; + int end = outer.LastIndexOf('<'); + + return TrimControl(outer.Substring(start, end - start)); + } + + public XMLFragmentElement TryParseNode() + { + if (_buffer.EndOfBuffer) + return null; + + int startOuterXml = _buffer.Position; + string token = NextToken(); + + if (token != "<") + throw new XMLFragmentException("Expected \"<\", got " + token); + + XMLFragmentElement element = new XMLFragmentElement(); + element.Name = NextToken(); + + while (true) + { + token = NextToken(); + + if (token == ">") + break; + else if (token == "/") // quick-exit case + { + NextToken(); + + element.OuterXml = + TrimControl(_buffer.Buffer.Substring(startOuterXml, _buffer.Position - startOuterXml)).Trim(); + element.InnerXml = ""; + + return element; + } + else + { + XMLFragmentAttribute attribute = new XMLFragmentAttribute(); + attribute.Name = token; + if ((token = NextToken()) != "=") + throw new XMLFragmentException("Expected \"=\", got " + token); + attribute.Value = NextToken(); + + element.Attributes.Add(attribute); + } + } + + while (true) + { + int oldPos = _buffer.Position; // for restoration below + token = NextToken(); + + if (token == "<") + { + token = PeekToken(); + + if (token == "/") // finish element + { + NextToken(); // skip the / again + token = NextToken(); + NextToken(); // skip > + + element.OuterXml = + TrimControl(_buffer.Buffer.Substring(startOuterXml, _buffer.Position - startOuterXml)).Trim(); + element.InnerXml = TrimTags(element.OuterXml); + + if (token != element.Name) + throw new XMLFragmentException("Mismatched element pairs: \"" + element.Name + "\" vs \"" + + token + "\""); + + break; + } + else + { + _buffer.Position = oldPos; + element.Elements.Add(TryParseNode()); + } + } + else + { + // value, probably + _buffer.Position = oldPos; + element.Value = ReadUntil('<'); + } + } + + return element; + } + + public void Parse() + { + _rootNode = TryParseNode(); + + if (_rootNode == null) + throw new XMLFragmentException("Unable to load root node"); + } + } + + #endregion +} \ No newline at end of file diff --git a/Common/TextureTools/MSTerrain.cs b/Common/TextureTools/MSTerrain.cs new file mode 100644 index 0000000..10034e3 --- /dev/null +++ b/Common/TextureTools/MSTerrain.cs @@ -0,0 +1,367 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using FarseerPhysics.Dynamics; +using FarseerPhysics.Collision; +using FarseerPhysics.Factories; + +namespace FarseerPhysics.Common +{ + public enum Decomposer + { + Bayazit, + CDT, + Earclip, + Flipcode, + Seidel, + } + + /// + /// Return true if the specified color is inside the terrain. + /// + public delegate bool TerrainTester(Color color); + + /// + /// Simple class to maintain a terrain. + /// + public class MSTerrain + { + /// + /// World to manage terrain in. + /// + public World World; + + /// + /// Center of terrain in world units. + /// + public Vector2 Center; + + /// + /// Width of terrain in world units. + /// + public float Width; + + /// + /// Height of terrain in world units. + /// + public float Height; + + /// + /// Points per each world unit used to define the terrain in the point cloud. + /// + public int PointsPerUnit; + + /// + /// Points per cell. + /// + public int CellSize; + + /// + /// Points per sub cell. + /// + public int SubCellSize; + + /// + /// Number of iterations to perform in the Marching Squares algorithm. + /// Note: More then 3 has almost no effect on quality. + /// + public int Iterations = 2; + + /// + /// Decomposer to use when regenerating terrain. Can be changed on the fly without consequence. + /// Note: Some decomposerers are unstable. + /// + public Decomposer Decomposer; + + /// + /// Point cloud defining the terrain. + /// + private sbyte[,] _terrainMap; + + /// + /// Generated bodies. + /// + private List[,] _bodyMap; + + private float _localWidth; + private float _localHeight; + private int _xnum; + private int _ynum; + private AABB _dirtyArea; + private Vector2 _topLeft; + + public MSTerrain(World world, AABB area) + { + World = world; + Width = area.Extents.X * 2; + Height = area.Extents.Y * 2; + Center = area.Center; + } + + /// + /// Initialize the terrain for use. + /// + public void Initialize() + { + // find top left of terrain in world space + _topLeft = new Vector2(Center.X - (Width * 0.5f), Center.Y - (-Height * 0.5f)); + + // convert the terrains size to a point cloud size + _localWidth = Width * PointsPerUnit; + _localHeight = Height * PointsPerUnit; + + _terrainMap = new sbyte[(int)_localWidth + 1, (int)_localHeight + 1]; + + for (int x = 0; x < _localWidth; x++) + { + for (int y = 0; y < _localHeight; y++) + { + _terrainMap[x, y] = 1; + } + } + + _xnum = (int)(_localWidth / CellSize); + _ynum = (int)(_localHeight / CellSize); + _bodyMap = new List[_xnum, _ynum]; + + // make sure to mark the dirty area to an infinitely small box + _dirtyArea = new AABB(new Vector2(float.MaxValue, float.MaxValue), new Vector2(float.MinValue, float.MinValue)); + } + + /// + /// Apply a texture to the terrain using the specified TerrainTester. + /// + /// Texture to apply. + /// Top left position of the texture relative to the terrain. + /// Delegate method used to determine what colors should be included in the terrain. + public void ApplyTexture(Texture2D texture, Vector2 position, TerrainTester tester) + { + Color[] colorData = new Color[texture.Width * texture.Height]; + + texture.GetData(colorData); + + for (int y = (int)position.Y; y < texture.Height + (int)position.Y; y++) + { + for (int x = (int)position.X; x < texture.Width + (int)position.X; x++) + { + if (x >= 0 && x < _localWidth && y >= 0 && y < _localHeight) + { + bool inside = tester(colorData[((y - (int)position.Y) * texture.Width) + (x - (int)position.X)]); + + if (!inside) + _terrainMap[x, y] = 1; + else + _terrainMap[x, y] = -1; + } + } + } + + // generate terrain + for (int gy = 0; gy < _ynum; gy++) + { + for (int gx = 0; gx < _xnum; gx++) + { + //remove old terrain object at grid cell + if (_bodyMap[gx, gy] != null) + { + for (int i = 0; i < _bodyMap[gx, gy].Count; i++) + { + World.RemoveBody(_bodyMap[gx, gy][i]); + } + } + + _bodyMap[gx, gy] = null; + + //generate new one + GenerateTerrain(gx, gy); + } + } + } + + /// + /// Apply a texture to the terrain using the specified TerrainTester. + /// + /// Top left position of the texture relative to the terrain. + public void ApplyData(sbyte[,] data, Vector2 position) + { + for (int y = (int)position.Y; y < data.GetUpperBound(1) + (int)position.Y; y++) + { + for (int x = (int)position.X; x < data.GetUpperBound(0) + (int)position.X; x++) + { + if (x >= 0 && x < _localWidth && y >= 0 && y < _localHeight) + { + _terrainMap[x, y] = data[x, y]; + } + } + } + + // generate terrain + for (int gy = 0; gy < _ynum; gy++) + { + for (int gx = 0; gx < _xnum; gx++) + { + //remove old terrain object at grid cell + if (_bodyMap[gx, gy] != null) + { + for (int i = 0; i < _bodyMap[gx, gy].Count; i++) + { + World.RemoveBody(_bodyMap[gx, gy][i]); + } + } + + _bodyMap[gx, gy] = null; + + //generate new one + GenerateTerrain(gx, gy); + } + } + } + + /// + /// Convert a texture to an sbtye array compatible with ApplyData(). + /// + /// Texture to convert. + /// + /// + public static sbyte[,] ConvertTextureToData(Texture2D texture, TerrainTester tester) + { + sbyte[,] data = new sbyte[texture.Width, texture.Height]; + Color[] colorData = new Color[texture.Width * texture.Height]; + + texture.GetData(colorData); + + for (int y = 0; y < texture.Height; y++) + { + for (int x = 0; x < texture.Width; x++) + { + bool inside = tester(colorData[(y * texture.Width) + x]); + + if (!inside) + data[x, y] = 1; + else + data[x, y] = -1; + } + } + + return data; + } + + /// + /// Modify a single point in the terrain. + /// + /// World location to modify. Automatically clipped. + /// -1 = inside terrain, 1 = outside terrain + public void ModifyTerrain(Vector2 location, sbyte value) + { + // find local position + // make position local to map space + Vector2 p = location - _topLeft; + + // find map position for each axis + p.X = p.X * _localWidth / Width; + p.Y = p.Y * -_localHeight / Height; + + if (p.X >= 0 && p.X < _localWidth && p.Y >= 0 && p.Y < _localHeight) + { + _terrainMap[(int)p.X, (int)p.Y] = value; + + // expand dirty area + if (p.X < _dirtyArea.LowerBound.X) _dirtyArea.LowerBound.X = p.X; + if (p.X > _dirtyArea.UpperBound.X) _dirtyArea.UpperBound.X = p.X; + + if (p.Y < _dirtyArea.LowerBound.Y) _dirtyArea.LowerBound.Y = p.Y; + if (p.Y > _dirtyArea.UpperBound.Y) _dirtyArea.UpperBound.Y = p.Y; + } + } + + /// + /// Regenerate the terrain. + /// + public void RegenerateTerrain() + { + //iterate effected cells + var gx0 = (int)(_dirtyArea.LowerBound.X / CellSize); + var gx1 = (int)(_dirtyArea.UpperBound.X / CellSize) + 1; + if (gx0 < 0) gx0 = 0; + if (gx1 > _xnum) gx1 = _xnum; + var gy0 = (int)(_dirtyArea.LowerBound.Y / CellSize); + var gy1 = (int)(_dirtyArea.UpperBound.Y / CellSize) + 1; + if (gy0 < 0) gy0 = 0; + if (gy1 > _ynum) gy1 = _ynum; + + for (int gx = gx0; gx < gx1; gx++) + { + for (int gy = gy0; gy < gy1; gy++) + { + //remove old terrain object at grid cell + if (_bodyMap[gx, gy] != null) + { + for (int i = 0; i < _bodyMap[gx, gy].Count; i++) + { + World.RemoveBody(_bodyMap[gx, gy][i]); + } + } + + _bodyMap[gx, gy] = null; + + //generate new one + GenerateTerrain(gx, gy); + } + } + + _dirtyArea = new AABB(new Vector2(float.MaxValue, float.MaxValue), new Vector2(float.MinValue, float.MinValue)); + } + + private void GenerateTerrain(int gx, int gy) + { + float ax = gx * CellSize; + float ay = gy * CellSize; + + List polys = MarchingSquares.DetectSquares(new AABB(new Vector2(ax, ay), new Vector2(ax + CellSize, ay + CellSize)), SubCellSize, SubCellSize, _terrainMap, Iterations, true); + if (polys.Count == 0) return; + + _bodyMap[gx, gy] = new List(); + + // create the scale vector + Vector2 scale = new Vector2(1f / PointsPerUnit, 1f / -PointsPerUnit); + + // create physics object for this grid cell + foreach (var item in polys) + { + // does this need to be negative? + item.Scale(ref scale); + item.Translate(ref _topLeft); + item.ForceCounterClockWise(); + Vertices p = FarseerPhysics.Common.PolygonManipulation.SimplifyTools.CollinearSimplify(item); + List decompPolys = new List(); + + switch (Decomposer) + { + case Decomposer.Bayazit: + decompPolys = Decomposition.BayazitDecomposer.ConvexPartition(p); + break; + case Decomposer.CDT: + decompPolys = Decomposition.CDTDecomposer.ConvexPartition(p); + break; + case Decomposer.Earclip: + decompPolys = Decomposition.EarclipDecomposer.ConvexPartition(p); + break; + case Decomposer.Flipcode: + decompPolys = Decomposition.FlipcodeDecomposer.ConvexPartition(p); + break; + case Decomposer.Seidel: + decompPolys = Decomposition.SeidelDecomposer.ConvexPartition(p, 0.001f); + break; + default: + break; + } + + foreach (Vertices poly in decompPolys) + { + if (poly.Count > 2) + _bodyMap[gx, gy].Add(BodyFactory.CreatePolygon(World, poly, 1)); + } + } + } + } +} diff --git a/Common/TextureTools/MarchingSquares.cs b/Common/TextureTools/MarchingSquares.cs new file mode 100644 index 0000000..a410252 --- /dev/null +++ b/Common/TextureTools/MarchingSquares.cs @@ -0,0 +1,800 @@ +using System.Collections.Generic; +using FarseerPhysics.Collision; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + // Ported by Matthew Bettcher - Feb 2011 + + /* + Copyright (c) 2010, Luca Deltodesco + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the nape project nor the names of its contributors may be used to endorse + or promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + public static class MarchingSquares + { + /// + /// Marching squares over the given domain using the mesh defined via the dimensions + /// (wid,hei) to build a set of polygons such that f(x,y) less than 0, using the given number + /// 'bin' for recursive linear inteprolation along cell boundaries. + /// + /// if 'comb' is true, then the polygons will also be composited into larger possible concave + /// polygons. + /// + /// + /// + /// + /// + /// + /// + /// + public static List DetectSquares(AABB domain, float cellWidth, float cellHeight, sbyte[,] f, + int lerpCount, bool combine) + { + CxFastList ret = new CxFastList(); + + List verticesList = new List(); + + //NOTE: removed assignments as they were not used. + List polyList; + GeomPoly gp; + + int xn = (int)(domain.Extents.X * 2 / cellWidth); + bool xp = xn == (domain.Extents.X * 2 / cellWidth); + int yn = (int)(domain.Extents.Y * 2 / cellHeight); + bool yp = yn == (domain.Extents.Y * 2 / cellHeight); + if (!xp) xn++; + if (!yp) yn++; + + sbyte[,] fs = new sbyte[xn + 1, yn + 1]; + GeomPolyVal[,] ps = new GeomPolyVal[xn + 1, yn + 1]; + + //populate shared function lookups. + for (int x = 0; x < xn + 1; x++) + { + int x0; + if (x == xn) x0 = (int)domain.UpperBound.X; + else x0 = (int)(x * cellWidth + domain.LowerBound.X); + for (int y = 0; y < yn + 1; y++) + { + int y0; + if (y == yn) y0 = (int)domain.UpperBound.Y; + else y0 = (int)(y * cellHeight + domain.LowerBound.Y); + fs[x, y] = f[x0, y0]; + } + } + + //generate sub-polys and combine to scan lines + for (int y = 0; y < yn; y++) + { + float y0 = y * cellHeight + domain.LowerBound.Y; + float y1; + if (y == yn - 1) y1 = domain.UpperBound.Y; + else y1 = y0 + cellHeight; + GeomPoly pre = null; + for (int x = 0; x < xn; x++) + { + float x0 = x * cellWidth + domain.LowerBound.X; + float x1; + if (x == xn - 1) x1 = domain.UpperBound.X; + else x1 = x0 + cellWidth; + + gp = new GeomPoly(); + + int key = MarchSquare(f, fs, ref gp, x, y, x0, y0, x1, y1, lerpCount); + if (gp.Length != 0) + { + if (combine && pre != null && (key & 9) != 0) + { + combLeft(ref pre, ref gp); + gp = pre; + } + else + ret.Add(gp); + ps[x, y] = new GeomPolyVal(gp, key); + } + else + gp = null; + pre = gp; + } + } + if (!combine) + { + polyList = ret.GetListOfElements(); + + foreach (GeomPoly poly in polyList) + { + verticesList.Add(new Vertices(poly.Points.GetListOfElements())); + } + + return verticesList; + } + + //combine scan lines together + for (int y = 1; y < yn; y++) + { + int x = 0; + while (x < xn) + { + GeomPolyVal p = ps[x, y]; + + //skip along scan line if no polygon exists at this point + if (p == null) + { + x++; + continue; + } + + //skip along if current polygon cannot be combined above. + if ((p.Key & 12) == 0) + { + x++; + continue; + } + + //skip along if no polygon exists above. + GeomPolyVal u = ps[x, y - 1]; + if (u == null) + { + x++; + continue; + } + + //skip along if polygon above cannot be combined with. + if ((u.Key & 3) == 0) + { + x++; + continue; + } + + float ax = x * cellWidth + domain.LowerBound.X; + float ay = y * cellHeight + domain.LowerBound.Y; + + CxFastList bp = p.GeomP.Points; + CxFastList ap = u.GeomP.Points; + + //skip if it's already been combined with above polygon + if (u.GeomP == p.GeomP) + { + x++; + continue; + } + + //combine above (but disallow the hole thingies + CxFastListNode bi = bp.Begin(); + while (Square(bi.Elem().Y - ay) > Settings.Epsilon || bi.Elem().X < ax) bi = bi.Next(); + + //NOTE: Unused + //Vector2 b0 = bi.elem(); + Vector2 b1 = bi.Next().Elem(); + if (Square(b1.Y - ay) > Settings.Epsilon) + { + x++; + continue; + } + + bool brk = true; + CxFastListNode ai = ap.Begin(); + while (ai != ap.End()) + { + if (VecDsq(ai.Elem(), b1) < Settings.Epsilon) + { + brk = false; + break; + } + ai = ai.Next(); + } + if (brk) + { + x++; + continue; + } + + CxFastListNode bj = bi.Next().Next(); + if (bj == bp.End()) bj = bp.Begin(); + while (bj != bi) + { + ai = ap.Insert(ai, bj.Elem()); // .clone() + bj = bj.Next(); + if (bj == bp.End()) bj = bp.Begin(); + u.GeomP.Length++; + } + //u.p.simplify(float.Epsilon,float.Epsilon); + // + ax = x + 1; + while (ax < xn) + { + GeomPolyVal p2 = ps[(int)ax, y]; + if (p2 == null || p2.GeomP != p.GeomP) + { + ax++; + continue; + } + p2.GeomP = u.GeomP; + ax++; + } + ax = x - 1; + while (ax >= 0) + { + GeomPolyVal p2 = ps[(int)ax, y]; + if (p2 == null || p2.GeomP != p.GeomP) + { + ax--; + continue; + } + p2.GeomP = u.GeomP; + ax--; + } + ret.Remove(p.GeomP); + p.GeomP = u.GeomP; + + x = (int)((bi.Next().Elem().X - domain.LowerBound.X) / cellWidth) + 1; + //x++; this was already commented out! + } + } + + polyList = ret.GetListOfElements(); + + foreach (GeomPoly poly in polyList) + { + verticesList.Add(new Vertices(poly.Points.GetListOfElements())); + } + + return verticesList; + } + + #region Private Methods + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /** Linearly interpolate between (x0 to x1) given a value at these coordinates (v0 and v1) + such as to approximate value(return) = 0 + **/ + + private static int[] _lookMarch = { + 0x00, 0xE0, 0x38, 0xD8, 0x0E, 0xEE, 0x36, 0xD6, 0x83, 0x63, 0xBB, 0x5B, 0x8D, + 0x6D, 0xB5, 0x55 + }; + + private static float Lerp(float x0, float x1, float v0, float v1) + { + float dv = v0 - v1; + float t; + if (dv * dv < Settings.Epsilon) + t = 0.5f; + else t = v0 / dv; + return x0 + t * (x1 - x0); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + /** Recursive linear interpolation for use in marching squares **/ + + private static float Xlerp(float x0, float x1, float y, float v0, float v1, sbyte[,] f, int c) + { + float xm = Lerp(x0, x1, v0, v1); + if (c == 0) + return xm; + + sbyte vm = f[(int)xm, (int)y]; + + if (v0 * vm < 0) + return Xlerp(x0, xm, y, v0, vm, f, c - 1); + + return Xlerp(xm, x1, y, vm, v1, f, c - 1); + } + + /** Recursive linear interpolation for use in marching squares **/ + + private static float Ylerp(float y0, float y1, float x, float v0, float v1, sbyte[,] f, int c) + { + float ym = Lerp(y0, y1, v0, v1); + if (c == 0) + return ym; + + sbyte vm = f[(int)x, (int)ym]; + + if (v0 * vm < 0) + return Ylerp(y0, ym, x, v0, vm, f, c - 1); + + return Ylerp(ym, y1, x, vm, v1, f, c - 1); + } + + /** Square value for use in marching squares **/ + + private static float Square(float x) + { + return x * x; + } + + private static float VecDsq(Vector2 a, Vector2 b) + { + Vector2 d = a - b; + return d.X * d.X + d.Y * d.Y; + } + + private static float VecCross(Vector2 a, Vector2 b) + { + return a.X * b.Y - a.Y * b.X; + } + + /** Look-up table to relate polygon key with the vertices that should be used for + the sub polygon in marching squares + **/ + + /** Perform a single celled marching square for for the given cell defined by (x0,y0) (x1,y1) + using the function f for recursive interpolation, given the look-up table 'fs' of + the values of 'f' at cell vertices with the result to be stored in 'poly' given the actual + coordinates of 'ax' 'ay' in the marching squares mesh. + **/ + + private static int MarchSquare(sbyte[,] f, sbyte[,] fs, ref GeomPoly poly, int ax, int ay, float x0, float y0, + float x1, float y1, int bin) + { + //key lookup + int key = 0; + sbyte v0 = fs[ax, ay]; + if (v0 < 0) key |= 8; + sbyte v1 = fs[ax + 1, ay]; + if (v1 < 0) key |= 4; + sbyte v2 = fs[ax + 1, ay + 1]; + if (v2 < 0) key |= 2; + sbyte v3 = fs[ax, ay + 1]; + if (v3 < 0) key |= 1; + + int val = _lookMarch[key]; + if (val != 0) + { + CxFastListNode pi = null; + for (int i = 0; i < 8; i++) + { + Vector2 p; + if ((val & (1 << i)) != 0) + { + if (i == 7 && (val & 1) == 0) + poly.Points.Add(p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin))); + else + { + if (i == 0) p = new Vector2(x0, y0); + else if (i == 2) p = new Vector2(x1, y0); + else if (i == 4) p = new Vector2(x1, y1); + else if (i == 6) p = new Vector2(x0, y1); + + else if (i == 1) p = new Vector2(Xlerp(x0, x1, y0, v0, v1, f, bin), y0); + else if (i == 5) p = new Vector2(Xlerp(x0, x1, y1, v3, v2, f, bin), y1); + + else if (i == 3) p = new Vector2(x1, Ylerp(y0, y1, x1, v1, v2, f, bin)); + else p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin)); + + pi = poly.Points.Insert(pi, p); + } + poly.Length++; + } + } + //poly.simplify(float.Epsilon,float.Epsilon); + } + return key; + } + + /** Used in polygon composition to composit polygons into scan lines + Combining polya and polyb into one super-polygon stored in polya. + **/ + + private static void combLeft(ref GeomPoly polya, ref GeomPoly polyb) + { + CxFastList ap = polya.Points; + CxFastList bp = polyb.Points; + CxFastListNode ai = ap.Begin(); + CxFastListNode bi = bp.Begin(); + + Vector2 b = bi.Elem(); + CxFastListNode prea = null; + while (ai != ap.End()) + { + Vector2 a = ai.Elem(); + if (VecDsq(a, b) < Settings.Epsilon) + { + //ignore shared vertex if parallel + if (prea != null) + { + Vector2 a0 = prea.Elem(); + b = bi.Next().Elem(); + + Vector2 u = a - a0; + //vec_new(u); vec_sub(a.p.p, a0.p.p, u); + Vector2 v = b - a; + //vec_new(v); vec_sub(b.p.p, a.p.p, v); + float dot = VecCross(u, v); + if (dot * dot < Settings.Epsilon) + { + ap.Erase(prea, ai); + polya.Length--; + ai = prea; + } + } + + //insert polyb into polya + bool fst = true; + CxFastListNode preb = null; + while (!bp.Empty()) + { + Vector2 bb = bp.Front(); + bp.Pop(); + if (!fst && !bp.Empty()) + { + ai = ap.Insert(ai, bb); + polya.Length++; + preb = ai; + } + fst = false; + } + + //ignore shared vertex if parallel + ai = ai.Next(); + Vector2 a1 = ai.Elem(); + ai = ai.Next(); + if (ai == ap.End()) ai = ap.Begin(); + Vector2 a2 = ai.Elem(); + Vector2 a00 = preb.Elem(); + Vector2 uu = a1 - a00; + //vec_new(u); vec_sub(a1.p, a0.p, u); + Vector2 vv = a2 - a1; + //vec_new(v); vec_sub(a2.p, a1.p, v); + float dot1 = VecCross(uu, vv); + if (dot1 * dot1 < Settings.Epsilon) + { + ap.Erase(preb, preb.Next()); + polya.Length--; + } + + return; + } + prea = ai; + ai = ai.Next(); + } + } + + #endregion + + #region CxFastList from nape physics + + #region Nested type: CxFastList + + /// + /// Designed as a complete port of CxFastList from CxStd. + /// + internal class CxFastList + { + // first node in the list + private CxFastListNode _head; + private int _count; + + /// + /// Iterator to start of list (O(1)) + /// + public CxFastListNode Begin() + { + return _head; + } + + /// + /// Iterator to end of list (O(1)) + /// + public CxFastListNode End() + { + return null; + } + + /// + /// Returns first element of list (O(1)) + /// + public T Front() + { + return _head.Elem(); + } + + /// + /// add object to list (O(1)) + /// + public CxFastListNode Add(T value) + { + CxFastListNode newNode = new CxFastListNode(value); + if (_head == null) + { + newNode._next = null; + _head = newNode; + _count++; + return newNode; + } + newNode._next = _head; + _head = newNode; + + _count++; + + return newNode; + } + + /// + /// remove object from list, returns true if an element was removed (O(n)) + /// + public bool Remove(T value) + { + CxFastListNode head = _head; + CxFastListNode prev = _head; + + EqualityComparer comparer = EqualityComparer.Default; + + if (head != null) + { + if (value != null) + { + do + { + // if we are on the value to be removed + if (comparer.Equals(head._elt, value)) + { + // then we need to patch the list + // check to see if we are removing the _head + if (head == _head) + { + _head = head._next; + _count--; + return true; + } + else + { + // were not at the head + prev._next = head._next; + _count--; + return true; + } + } + // cache the current as the previous for the next go around + prev = head; + head = head._next; + } while (head != null); + } + } + return false; + } + + /// + /// pop element from head of list (O(1)) Note: this does not return the object popped! + /// There is good reason to this, and it regards the Alloc list variants which guarantee + /// objects are released to the object pool. You do not want to retrieve an element + /// through pop or else that object may suddenly be used by another piece of code which + /// retrieves it from the object pool. + /// + public CxFastListNode Pop() + { + return Erase(null, _head); + } + + /// + /// insert object after 'node' returning an iterator to the inserted object. + /// + public CxFastListNode Insert(CxFastListNode node, T value) + { + if (node == null) + { + return Add(value); + } + CxFastListNode newNode = new CxFastListNode(value); + CxFastListNode nextNode = node._next; + newNode._next = nextNode; + node._next = newNode; + + _count++; + + return newNode; + } + + /// + /// removes the element pointed to by 'node' with 'prev' being the previous iterator, + /// returning an iterator to the element following that of 'node' (O(1)) + /// + public CxFastListNode Erase(CxFastListNode prev, CxFastListNode node) + { + // cache the node after the node to be removed + CxFastListNode nextNode = node._next; + if (prev != null) + prev._next = nextNode; + else if (_head != null) + _head = _head._next; + else + return null; + + _count--; + return nextNode; + } + + /// + /// whether the list is empty (O(1)) + /// + public bool Empty() + { + if (_head == null) + return true; + return false; + } + + /// + /// computes size of list (O(n)) + /// + public int Size() + { + CxFastListNode i = Begin(); + int count = 0; + + do + { + count++; + } while (i.Next() != null); + + return count; + } + + /// + /// empty the list (O(1) if CxMixList, O(n) otherwise) + /// + public void Clear() + { + CxFastListNode head = _head; + while (head != null) + { + CxFastListNode node2 = head; + head = head._next; + node2._next = null; + } + _head = null; + _count = 0; + } + + /// + /// returns true if 'value' is an element of the list (O(n)) + /// + public bool Has(T value) + { + return (Find(value) != null); + } + + // Non CxFastList Methods + public CxFastListNode Find(T value) + { + // start at head + CxFastListNode head = _head; + EqualityComparer comparer = EqualityComparer.Default; + if (head != null) + { + if (value != null) + { + do + { + if (comparer.Equals(head._elt, value)) + { + return head; + } + head = head._next; + } while (head != _head); + } + else + { + do + { + if (head._elt == null) + { + return head; + } + head = head._next; + } while (head != _head); + } + } + return null; + } + + public List GetListOfElements() + { + List list = new List(); + + CxFastListNode iter = Begin(); + + if (iter != null) + { + do + { + list.Add(iter._elt); + iter = iter._next; + } while (iter != null); + } + return list; + } + } + + #endregion + + #region Nested type: CxFastListNode + + internal class CxFastListNode + { + internal T _elt; + internal CxFastListNode _next; + + public CxFastListNode(T obj) + { + _elt = obj; + } + + public T Elem() + { + return _elt; + } + + public CxFastListNode Next() + { + return _next; + } + } + + #endregion + + #endregion + + #region Internal Stuff + + #region Nested type: GeomPoly + + internal class GeomPoly + { + public int Length; + public CxFastList Points; + + public GeomPoly() + { + Points = new CxFastList(); + Length = 0; + } + } + + #endregion + + #region Nested type: GeomPolyVal + + private class GeomPolyVal + { + /** Associated polygon at coordinate **/ + /** Key of original sub-polygon **/ + public int Key; + public GeomPoly GeomP; + + public GeomPolyVal(GeomPoly geomP, int K) + { + GeomP = geomP; + Key = K; + } + } + + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/Common/TextureTools/TextureConverter.cs b/Common/TextureTools/TextureConverter.cs new file mode 100644 index 0000000..f76b711 --- /dev/null +++ b/Common/TextureTools/TextureConverter.cs @@ -0,0 +1,1338 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ + // User contribution from Sickbattery aka David Reschke :). + + #region ToDo: Create a new file for each ... + /// + /// The detection type affects the resulting polygon data. + /// + public enum VerticesDetectionType + { + /// + /// Holes are integrated into the main polygon. + /// + Integrated = 0, + + /// + /// The data of the main polygon and hole polygons is returned separately. + /// + Separated = 1 + } + + /// + /// Detected vertices of a single polygon. + /// + public class DetectedVertices : Vertices + { + private List _holes; + + public List Holes + { + get { return _holes; } + set { _holes = value; } + } + + public DetectedVertices() + : base() + { + } + + public DetectedVertices(Vertices vertices) + : base(vertices) + { + } + + public void Transform(Matrix transform) + { + // Transform main polygon + for (int i = 0; i < this.Count; i++) + this[i] = Vector2.Transform(this[i], transform); + + // Transform holes + Vector2[] temp = null; + if (_holes != null && _holes.Count > 0) + { + for (int i = 0; i < _holes.Count; i++) + { + temp = _holes[i].ToArray(); + Vector2.Transform(temp, ref transform, temp); + + _holes[i] = new Vertices(temp); + } + } + } + } + #endregion + + /// + /// + /// + public sealed class TextureConverter + { + private const int _CLOSEPIXELS_LENGTH = 8; + + /// + /// This array is ment to be readonly. + /// It's not because it is accessed very frequently. + /// + private static /*readonly*/ int[,] ClosePixels = + new int[_CLOSEPIXELS_LENGTH, 2] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } }; + + private uint[] _data; + private int _dataLength; + private int _width; + private int _height; + + private VerticesDetectionType _polygonDetectionType; + + private uint _alphaTolerance; + private float _hullTolerance; + + private bool _holeDetection; + private bool _multipartDetection; + private bool _pixelOffsetOptimization; + + private Matrix _transform = Matrix.Identity; + + #region Properties + /// + /// Get or set the polygon detection type. + /// + public VerticesDetectionType PolygonDetectionType + { + get { return _polygonDetectionType; } + set { _polygonDetectionType = value; } + } + + /// + /// Will detect texture 'holes' if set to true. Slows down the detection. Default is false. + /// + public bool HoleDetection + { + get { return _holeDetection; } + set { _holeDetection = value; } + } + + /// + /// Will detect texture multiple 'solid' isles if set to true. Slows down the detection. Default is false. + /// + public bool MultipartDetection + { + get { return _multipartDetection; } + set { _multipartDetection = value; } + } + + /// + /// Will optimize the vertex positions along the interpolated normal between two edges about a half pixel (post processing). Default is false. + /// + public bool PixelOffsetOptimization + { + get { return _pixelOffsetOptimization; } + set { _pixelOffsetOptimization = value; } + } + + /// + /// Can be used for scaling. + /// + public Matrix Transform + { + get { return _transform; } + set { _transform = value; } + } + + /// + /// Alpha (coverage) tolerance. Default is 20: Every pixel with a coverage value equal or greater to 20 will be counts as solid. + /// + public byte AlphaTolerance + { + get { return (byte)(_alphaTolerance >> 24); } + set { _alphaTolerance = (uint)value << 24; } + } + + /// + /// Default is 1.5f. + /// + public float HullTolerance + { + get { return _hullTolerance; } + set + { + if (value > 4f) + { + _hullTolerance = 4f; + } + else if (value < 0.9f) + { + _hullTolerance = 0.9f; + } + else + { + _hullTolerance = value; + } + } + } + #endregion + + #region Constructors + public TextureConverter() + { + Initialize(null, null, null, null, null, null, null, null); + } + + public TextureConverter(byte? alphaTolerance, float? hullTolerance, + bool? holeDetection, bool? multipartDetection, bool? pixelOffsetOptimization, Matrix? transform) + { + Initialize(null, null, alphaTolerance, hullTolerance, holeDetection, + multipartDetection, pixelOffsetOptimization, transform); + } + + public TextureConverter(uint[] data, int width) + { + Initialize(data, width, null, null, null, null, null, null); + } + + public TextureConverter(uint[] data, int width, byte? alphaTolerance, + float? hullTolerance, bool? holeDetection, bool? multipartDetection, + bool? pixelOffsetOptimization, Matrix? transform) + { + Initialize(data, width, alphaTolerance, hullTolerance, holeDetection, + multipartDetection, pixelOffsetOptimization, transform); + } + #endregion + + #region Initialization + private void Initialize(uint[] data, int? width, byte? alphaTolerance, + float? hullTolerance, bool? holeDetection, bool? multipartDetection, + bool? pixelOffsetOptimization, Matrix? transform) + { + if (data != null && !width.HasValue) + throw new ArgumentNullException("width", "'width' can't be null if 'data' is set."); + + if (data == null && width.HasValue) + throw new ArgumentNullException("data", "'data' can't be null if 'width' is set."); + + if (data != null && width.HasValue) + SetTextureData(data, width.Value); + + if (alphaTolerance.HasValue) + AlphaTolerance = alphaTolerance.Value; + else + AlphaTolerance = 20; + + if (hullTolerance.HasValue) + HullTolerance = hullTolerance.Value; + else + HullTolerance = 1.5f; + + if (holeDetection.HasValue) + HoleDetection = holeDetection.Value; + else + HoleDetection = false; + + if (multipartDetection.HasValue) + MultipartDetection = multipartDetection.Value; + else + MultipartDetection = false; + + if (pixelOffsetOptimization.HasValue) + PixelOffsetOptimization = pixelOffsetOptimization.Value; + else + PixelOffsetOptimization = false; + + if (transform.HasValue) + Transform = transform.Value; + else + Transform = Matrix.Identity; + } + #endregion + + /// + /// + /// + /// + /// + private void SetTextureData(uint[] data, int width) + { + if (data == null) + throw new ArgumentNullException("data", "'data' can't be null."); + + if (data.Length < 4) + throw new ArgumentOutOfRangeException("data", "'data' length can't be less then 4. Your texture must be at least 2 x 2 pixels in size."); + + if (width < 2) + throw new ArgumentOutOfRangeException("width", "'width' can't be less then 2. Your texture must be at least 2 x 2 pixels in size."); + + if (data.Length % width != 0) + throw new ArgumentException("'width' has an invalid value."); + + _data = data; + _dataLength = _data.Length; + _width = width; + _height = _dataLength / width; + } + + /// + /// Detects the vertices of the supplied texture data. (PolygonDetectionType.Integrated) + /// + /// The texture data. + /// The texture width. + /// + public static Vertices DetectVertices(uint[] data, int width) + { + TextureConverter tc = new TextureConverter(data, width); + + List detectedVerticesList = tc.DetectVertices(); + + return detectedVerticesList[0]; + } + + /// + /// Detects the vertices of the supplied texture data. + /// + /// The texture data. + /// The texture width. + /// if set to true it will perform hole detection. + /// + public static Vertices DetectVertices(uint[] data, int width, bool holeDetection) + { + TextureConverter tc = + new TextureConverter(data, width) + { + HoleDetection = holeDetection + }; + + List detectedVerticesList = tc.DetectVertices(); + + return detectedVerticesList[0]; + } + + /// + /// Detects the vertices of the supplied texture data. + /// + /// The texture data. + /// The texture width. + /// if set to true it will perform hole detection. + /// The hull tolerance. + /// The alpha tolerance. + /// if set to true it will perform multi part detection. + /// + public static List DetectVertices(uint[] data, int width, float hullTolerance, + byte alphaTolerance, bool multiPartDetection, bool holeDetection) + { + TextureConverter tc = + new TextureConverter(data, width) + { + HullTolerance = hullTolerance, + AlphaTolerance = alphaTolerance, + MultipartDetection = multiPartDetection, + HoleDetection = holeDetection + }; + + List detectedVerticesList = tc.DetectVertices(); + List result = new List(); + + for (int i = 0; i < detectedVerticesList.Count; i++) + { + result.Add(detectedVerticesList[i]); + } + + return result; + } + + public List DetectVertices() + { + #region Check TextureConverter setup. + + if (_data == null) + throw new Exception( + "'_data' can't be null. You have to use SetTextureData(uint[] data, int width) before calling this method."); + + if (_data.Length < 4) + throw new Exception( + "'_data' length can't be less then 4. Your texture must be at least 2 x 2 pixels in size. " + + "You have to use SetTextureData(uint[] data, int width) before calling this method."); + + if (_width < 2) + throw new Exception( + "'_width' can't be less then 2. Your texture must be at least 2 x 2 pixels in size. " + + "You have to use SetTextureData(uint[] data, int width) before calling this method."); + + if (_data.Length % _width != 0) + throw new Exception( + "'_width' has an invalid value. You have to use SetTextureData(uint[] data, int width) before calling this method."); + + #endregion + + + List detectedPolygons = new List(); + + DetectedVertices polygon; + Vertices holePolygon; + + Vector2? holeEntrance = null; + Vector2? polygonEntrance = null; + + List blackList = new List(); + + bool searchOn; + do + { + if (detectedPolygons.Count == 0) + { + // First pass / single polygon + polygon = new DetectedVertices(CreateSimplePolygon(Vector2.Zero, Vector2.Zero)); + + if (polygon.Count > 2) + polygonEntrance = GetTopMostVertex(polygon); + } + else if (polygonEntrance.HasValue) + { + // Multi pass / multiple polygons + polygon = new DetectedVertices(CreateSimplePolygon( + polygonEntrance.Value, new Vector2(polygonEntrance.Value.X - 1f, polygonEntrance.Value.Y))); + } + else + break; + + searchOn = false; + + + if (polygon.Count > 2) + { + if (_holeDetection) + { + do + { + holeEntrance = SearchHoleEntrance(polygon, holeEntrance); + + if (holeEntrance.HasValue) + { + if (!blackList.Contains(holeEntrance.Value)) + { + blackList.Add(holeEntrance.Value); + holePolygon = CreateSimplePolygon(holeEntrance.Value, + new Vector2(holeEntrance.Value.X + 1, holeEntrance.Value.Y)); + + if (holePolygon != null && holePolygon.Count > 2) + { + switch (_polygonDetectionType) + { + case VerticesDetectionType.Integrated: + + // Add first hole polygon vertex to close the hole polygon. + holePolygon.Add(holePolygon[0]); + + int vertex1Index, vertex2Index; + if (SplitPolygonEdge(polygon, holeEntrance.Value, out vertex1Index, out vertex2Index)) + polygon.InsertRange(vertex2Index, holePolygon); + + break; + + case VerticesDetectionType.Separated: + if (polygon.Holes == null) + polygon.Holes = new List(); + + polygon.Holes.Add(holePolygon); + break; + } + } + } + else + break; + } + else + break; + } + while (true); + } + + detectedPolygons.Add(polygon); + } + + if (_multipartDetection || polygon.Count <= 2) + { + if (SearchNextHullEntrance(detectedPolygons, polygonEntrance.Value, out polygonEntrance)) + searchOn = true; + } + } + while (searchOn); + + if (detectedPolygons == null || (detectedPolygons != null && detectedPolygons.Count == 0)) + throw new Exception("Couldn't detect any vertices."); + + + // Post processing. + if (PolygonDetectionType == VerticesDetectionType.Separated) // Only when VerticesDetectionType.Separated? -> Recheck. + ApplyTriangulationCompatibleWinding(ref detectedPolygons); + + if (_pixelOffsetOptimization) + ApplyPixelOffsetOptimization(ref detectedPolygons); + + if (_transform != Matrix.Identity) + ApplyTransform(ref detectedPolygons); + + + return detectedPolygons; + } + + private void ApplyTriangulationCompatibleWinding(ref List detectedPolygons) + { + for (int i = 0; i < detectedPolygons.Count; i++) + { + detectedPolygons[i].Reverse(); + + if (detectedPolygons[i].Holes != null && detectedPolygons[i].Holes.Count > 0) + { + for (int j = 0; j < detectedPolygons[i].Holes.Count; j++) + detectedPolygons[i].Holes[j].Reverse(); + } + } + } + + private void ApplyPixelOffsetOptimization(ref List detectedPolygons) + { + + } + + private void ApplyTransform(ref List detectedPolygons) + { + for (int i = 0; i < detectedPolygons.Count; i++) + detectedPolygons[i].Transform(_transform); + } + + #region Data[] functions + private int _tempIsSolidX; + private int _tempIsSolidY; + public bool IsSolid(ref Vector2 v) + { + _tempIsSolidX = (int)v.X; + _tempIsSolidY = (int)v.Y; + + if (_tempIsSolidX >= 0 && _tempIsSolidX < _width && _tempIsSolidY >= 0 && _tempIsSolidY < _height) + return (_data[_tempIsSolidX + _tempIsSolidY * _width] >= _alphaTolerance); + //return ((_data[_tempIsSolidX + _tempIsSolidY * _width] & 0xFF000000) >= _alphaTolerance); + + return false; + } + + public bool IsSolid(ref int x, ref int y) + { + if (x >= 0 && x < _width && y >= 0 && y < _height) + return (_data[x + y * _width] >= _alphaTolerance); + //return ((_data[x + y * _width] & 0xFF000000) >= _alphaTolerance); + + return false; + } + + public bool IsSolid(ref int index) + { + if (index >= 0 && index < _dataLength) + return (_data[index] >= _alphaTolerance); + //return ((_data[index] & 0xFF000000) >= _alphaTolerance); + + return false; + } + + public bool InBounds(ref Vector2 coord) + { + return (coord.X >= 0f && coord.X < _width && coord.Y >= 0f && coord.Y < _height); + } + #endregion + + /// + /// Function to search for an entrance point of a hole in a polygon. It searches the polygon from top to bottom between the polygon edges. + /// + /// The polygon to search in. + /// The last entrance point. + /// The next holes entrance point. Null if ther are no holes. + private Vector2? SearchHoleEntrance(Vertices polygon, Vector2? lastHoleEntrance) + { + if (polygon == null) + throw new ArgumentNullException("'polygon' can't be null."); + + if (polygon.Count < 3) + throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3."); + + + List xCoords; + Vector2? entrance; + + int startY; + int endY; + + int lastSolid = 0; + bool foundSolid; + bool foundTransparent; + + // Set start y coordinate. + if (lastHoleEntrance.HasValue) + { + // We need the y coordinate only. + startY = (int)lastHoleEntrance.Value.Y; + } + else + { + // Start from the top of the polygon if last entrance == null. + startY = (int)GetTopMostCoord(polygon); + } + + // Set the end y coordinate. + endY = (int)GetBottomMostCoord(polygon); + + if (startY > 0 && startY < _height && endY > 0 && endY < _height) + { + // go from top to bottom of the polygon + for (int y = startY; y <= endY; y++) + { + // get x-coord of every polygon edge which crosses y + xCoords = SearchCrossingEdges(polygon, y); + + // We need an even number of crossing edges. + // It's always a pair of start and end edge: nothing | polygon | hole | polygon | nothing ... + // If it's not then don't bother, it's probably a peak ... + // ...which should be filtered out by SearchCrossingEdges() anyway. + if (xCoords.Count > 1 && xCoords.Count % 2 == 0) + { + // Ok, this is short, but probably a little bit confusing. + // This part searches from left to right between the edges inside the polygon. + // The problem: We are using the polygon data to search in the texture data. + // That's simply not accurate, but necessary because of performance. + for (int i = 0; i < xCoords.Count; i += 2) + { + foundSolid = false; + foundTransparent = false; + + // We search between the edges inside the polygon. + for (int x = (int)xCoords[i]; x <= (int)xCoords[i + 1]; x++) + { + // First pass: IsSolid might return false. + // In that case the polygon edge doesn't lie on the texture's solid pixel, because of the hull tolearance. + // If the edge lies before the first solid pixel then we need to skip our transparent pixel finds. + + // The algorithm starts to search for a relevant transparent pixel (which indicates a possible hole) + // after it has found a solid pixel. + + // After we've found a solid and a transparent pixel (a hole's left edge) + // we search for a solid pixel again (a hole's right edge). + // When found the distance of that coodrinate has to be greater then the hull tolerance. + + if (IsSolid(ref x, ref y)) + { + if (!foundTransparent) + { + foundSolid = true; + lastSolid = x; + } + + if (foundSolid && foundTransparent) + { + entrance = new Vector2(lastSolid, y); + + if (DistanceToHullAcceptable(polygon, entrance.Value, true)) + return entrance; + + entrance = null; + break; + } + } + else + { + if (foundSolid) + foundTransparent = true; + } + } + } + } + else + { + if (xCoords.Count % 2 == 0) + Debug.WriteLine("SearchCrossingEdges() % 2 != 0"); + } + } + } + + return null; + } + + private bool DistanceToHullAcceptable(DetectedVertices polygon, Vector2 point, bool higherDetail) + { + if (polygon == null) + throw new ArgumentNullException("polygon", "'polygon' can't be null."); + + if (polygon.Count < 3) + throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3."); + + // Check the distance to main polygon. + if (DistanceToHullAcceptable((Vertices)polygon, point, higherDetail)) + { + if (polygon.Holes != null) + { + for (int i = 0; i < polygon.Holes.Count; i++) + { + // If there is one distance not acceptable then return false. + if (!DistanceToHullAcceptable(polygon.Holes[i], point, higherDetail)) + return false; + } + } + + // All distances are larger then _hullTolerance. + return true; + } + + // Default to false. + return false; + } + + private bool DistanceToHullAcceptable(Vertices polygon, Vector2 point, bool higherDetail) + { + if (polygon == null) + throw new ArgumentNullException("polygon", "'polygon' can't be null."); + + if (polygon.Count < 3) + throw new ArgumentException("'polygon.Count' can't be less then 3."); + + + Vector2 edgeVertex2 = polygon[polygon.Count - 1]; + Vector2 edgeVertex1; + + if (higherDetail) + { + for (int i = 0; i < polygon.Count; i++) + { + edgeVertex1 = polygon[i]; + + if (LineTools.DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= _hullTolerance || + LineTools.DistanceBetweenPointAndPoint(ref point, ref edgeVertex1) <= _hullTolerance) + { + return false; + } + + edgeVertex2 = polygon[i]; + } + + return true; + } + else + { + for (int i = 0; i < polygon.Count; i++) + { + edgeVertex1 = polygon[i]; + + if (LineTools.DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= _hullTolerance) + { + return false; + } + + edgeVertex2 = polygon[i]; + } + + return true; + } + } + + private bool InPolygon(DetectedVertices polygon, Vector2 point) + { + bool inPolygon = !DistanceToHullAcceptable(polygon, point, true); + + if (!inPolygon) + { + List xCoords = SearchCrossingEdges(polygon, (int)point.Y); + + if (xCoords.Count > 0 && xCoords.Count % 2 == 0) + { + for (int i = 0; i < xCoords.Count; i += 2) + { + if (xCoords[i] <= point.X && xCoords[i + 1] >= point.X) + return true; + } + } + + return false; + } + + return true; + } + + private Vector2? GetTopMostVertex(Vertices vertices) + { + float topMostValue = float.MaxValue; + Vector2? topMost = null; + + for (int i = 0; i < vertices.Count; i++) + { + if (topMostValue > vertices[i].Y) + { + topMostValue = vertices[i].Y; + topMost = vertices[i]; + } + } + + return topMost; + } + + private float GetTopMostCoord(Vertices vertices) + { + float returnValue = float.MaxValue; + + for (int i = 0; i < vertices.Count; i++) + { + if (returnValue > vertices[i].Y) + { + returnValue = vertices[i].Y; + } + } + + return returnValue; + } + + private float GetBottomMostCoord(Vertices vertices) + { + float returnValue = float.MinValue; + + for (int i = 0; i < vertices.Count; i++) + { + if (returnValue < vertices[i].Y) + { + returnValue = vertices[i].Y; + } + } + + return returnValue; + } + + private List SearchCrossingEdges(DetectedVertices polygon, int y) + { + if (polygon == null) + throw new ArgumentNullException("polygon", "'polygon' can't be null."); + + if (polygon.Count < 3) + throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3."); + + List result = SearchCrossingEdges((Vertices)polygon, y); + + if (polygon.Holes != null) + { + for (int i = 0; i < polygon.Holes.Count; i++) + { + result.AddRange(SearchCrossingEdges(polygon.Holes[i], y)); + } + } + + result.Sort(); + return result; + } + + /// + /// Searches the polygon for the x coordinates of the edges that cross the specified y coordinate. + /// + /// Polygon to search in. + /// Y coordinate to check for edges. + /// Descending sorted list of x coordinates of edges that cross the specified y coordinate. + private List SearchCrossingEdges(Vertices polygon, int y) + { + // sick-o-note: + // Used to search the x coordinates of edges in the polygon for a specific y coordinate. + // (Usualy comming from the texture data, that's why it's an int and not a float.) + + List edges = new List(); + + // current edge + Vector2 slope; + Vector2 vertex1; // i + Vector2 vertex2; // i - 1 + + // next edge + Vector2 nextSlope; + Vector2 nextVertex; // i + 1 + + bool addFind; + + if (polygon.Count > 2) + { + // There is a gap between the last and the first vertex in the vertex list. + // We will bridge that by setting the last vertex (vertex2) to the last + // vertex in the list. + vertex2 = polygon[polygon.Count - 1]; + + // We are moving along the polygon edges. + for (int i = 0; i < polygon.Count; i++) + { + vertex1 = polygon[i]; + + // Approx. check if the edge crosses our y coord. + if ((vertex1.Y >= y && vertex2.Y <= y) || + (vertex1.Y <= y && vertex2.Y >= y)) + { + // Ignore edges that are parallel to y. + if (vertex1.Y != vertex2.Y) + { + addFind = true; + slope = vertex2 - vertex1; + + // Special threatment for edges that end at the y coord. + if (vertex1.Y == y) + { + // Create preview of the next edge. + nextVertex = polygon[(i + 1) % polygon.Count]; + nextSlope = vertex1 - nextVertex; + + // Ignore peaks. + // If thwo edges are aligned like this: /\ and the y coordinate lies on the top, + // then we get the same x coord twice and we don't need that. + if (slope.Y > 0) + addFind = (nextSlope.Y <= 0); + else + addFind = (nextSlope.Y >= 0); + } + + if (addFind) + edges.Add((y - vertex1.Y) / slope.Y * slope.X + vertex1.X); // Calculate and add the x coord. + } + } + + // vertex1 becomes vertex2 :). + vertex2 = vertex1; + } + } + + edges.Sort(); + return edges; + } + + private bool SplitPolygonEdge(Vertices polygon, Vector2 coordInsideThePolygon, + out int vertex1Index, out int vertex2Index) + { + Vector2 slope; + int nearestEdgeVertex1Index = 0; + int nearestEdgeVertex2Index = 0; + bool edgeFound = false; + + float shortestDistance = float.MaxValue; + + bool edgeCoordFound = false; + Vector2 foundEdgeCoord = Vector2.Zero; + + List xCoords = SearchCrossingEdges(polygon, (int)coordInsideThePolygon.Y); + + vertex1Index = 0; + vertex2Index = 0; + + foundEdgeCoord.Y = coordInsideThePolygon.Y; + + if (xCoords != null && xCoords.Count > 1 && xCoords.Count % 2 == 0) + { + float distance; + for (int i = 0; i < xCoords.Count; i++) + { + if (xCoords[i] < coordInsideThePolygon.X) + { + distance = coordInsideThePolygon.X - xCoords[i]; + + if (distance < shortestDistance) + { + shortestDistance = distance; + foundEdgeCoord.X = xCoords[i]; + + edgeCoordFound = true; + } + } + } + + if (edgeCoordFound) + { + shortestDistance = float.MaxValue; + + int edgeVertex2Index = polygon.Count - 1; + + int edgeVertex1Index; + for (edgeVertex1Index = 0; edgeVertex1Index < polygon.Count; edgeVertex1Index++) + { + Vector2 tempVector1 = polygon[edgeVertex1Index]; + Vector2 tempVector2 = polygon[edgeVertex2Index]; + distance = LineTools.DistanceBetweenPointAndLineSegment(ref foundEdgeCoord, + ref tempVector1, ref tempVector2); + if (distance < shortestDistance) + { + shortestDistance = distance; + + nearestEdgeVertex1Index = edgeVertex1Index; + nearestEdgeVertex2Index = edgeVertex2Index; + + edgeFound = true; + } + + edgeVertex2Index = edgeVertex1Index; + } + + if (edgeFound) + { + slope = polygon[nearestEdgeVertex2Index] - polygon[nearestEdgeVertex1Index]; + slope.Normalize(); + + Vector2 tempVector = polygon[nearestEdgeVertex1Index]; + distance = LineTools.DistanceBetweenPointAndPoint(ref tempVector, ref foundEdgeCoord); + + vertex1Index = nearestEdgeVertex1Index; + vertex2Index = nearestEdgeVertex1Index + 1; + + polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex1Index]); + polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex2Index]); + + return true; + } + } + } + + return false; + } + + /// + /// + /// + /// + /// + /// + private Vertices CreateSimplePolygon(Vector2 entrance, Vector2 last) + { + bool entranceFound = false; + bool endOfHull = false; + + Vertices polygon = new Vertices(32); + Vertices hullArea = new Vertices(32); + Vertices endOfHullArea = new Vertices(32); + + Vector2 current = Vector2.Zero; + + #region Entrance check + + // Get the entrance point. //todo: alle möglichkeiten testen + if (entrance == Vector2.Zero || !InBounds(ref entrance)) + { + entranceFound = SearchHullEntrance(out entrance); + + if (entranceFound) + { + current = new Vector2(entrance.X - 1f, entrance.Y); + } + } + else + { + if (IsSolid(ref entrance)) + { + if (IsNearPixel(ref entrance, ref last)) + { + current = last; + entranceFound = true; + } + else + { + Vector2 temp; + if (SearchNearPixels(false, ref entrance, out temp)) + { + current = temp; + entranceFound = true; + } + else + { + entranceFound = false; + } + } + } + } + + #endregion + + if (entranceFound) + { + polygon.Add(entrance); + hullArea.Add(entrance); + + Vector2 next = entrance; + + do + { + // Search in the pre vision list for an outstanding point. + Vector2 outstanding; + if (SearchForOutstandingVertex(hullArea, out outstanding)) + { + if (endOfHull) + { + // We have found the next pixel, but is it on the last bit of the hull? + if (endOfHullArea.Contains(outstanding)) + { + // Indeed. + polygon.Add(outstanding); + } + + // That's enough, quit. + break; + } + + // Add it and remove all vertices that don't matter anymore + // (all the vertices before the outstanding). + polygon.Add(outstanding); + hullArea.RemoveRange(0, hullArea.IndexOf(outstanding)); + } + + // Last point gets current and current gets next. Our little spider is moving forward on the hull ;). + last = current; + current = next; + + // Get the next point on hull. + if (GetNextHullPoint(ref last, ref current, out next)) + { + // Add the vertex to a hull pre vision list. + hullArea.Add(next); + } + else + { + // Quit + break; + } + + if (next == entrance && !endOfHull) + { + // It's the last bit of the hull, search on and exit at next found vertex. + endOfHull = true; + endOfHullArea.AddRange(hullArea); + + // We don't want the last vertex to be the same as the first one, because it causes the triangulation code to crash. + if (endOfHullArea.Contains(entrance)) + endOfHullArea.Remove(entrance); + } + + } while (true); + } + + return polygon; + } + + private bool SearchNearPixels(bool searchingForSolidPixel, ref Vector2 current, out Vector2 foundPixel) + { + for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++) + { + int x = (int)current.X + ClosePixels[i, 0]; + int y = (int)current.Y + ClosePixels[i, 1]; + + if (!searchingForSolidPixel ^ IsSolid(ref x, ref y)) + { + foundPixel = new Vector2(x, y); + return true; + } + } + + // Nothing found. + foundPixel = Vector2.Zero; + return false; + } + + private bool IsNearPixel(ref Vector2 current, ref Vector2 near) + { + for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++) + { + int x = (int)current.X + ClosePixels[i, 0]; + int y = (int)current.Y + ClosePixels[i, 1]; + + if (x >= 0 && x <= _width && y >= 0 && y <= _height) + { + if (x == (int)near.X && y == (int)near.Y) + { + return true; + } + } + } + + return false; + } + + private bool SearchHullEntrance(out Vector2 entrance) + { + // Search for first solid pixel. + for (int y = 0; y <= _height; y++) + { + for (int x = 0; x <= _width; x++) + { + if (IsSolid(ref x, ref y)) + { + entrance = new Vector2(x, y); + return true; + } + } + } + + // If there are no solid pixels. + entrance = Vector2.Zero; + return false; + } + + /// + /// Searches for the next shape. + /// + /// Already detected polygons. + /// Search start coordinate. + /// Returns the found entrance coordinate. Null if no other shapes found. + /// True if a new shape was found. + private bool SearchNextHullEntrance(List detectedPolygons, Vector2 start, out Vector2? entrance) + { + int x; + + bool foundTransparent = false; + bool inPolygon = false; + + for (int i = (int)start.X + (int)start.Y * _width; i <= _dataLength; i++) + { + if (IsSolid(ref i)) + { + if (foundTransparent) + { + x = i % _width; + entrance = new Vector2(x, (i - x) / (float)_width); + + inPolygon = false; + for (int polygonIdx = 0; polygonIdx < detectedPolygons.Count; polygonIdx++) + { + if (InPolygon(detectedPolygons[polygonIdx], entrance.Value)) + { + inPolygon = true; + break; + } + } + + if (inPolygon) + foundTransparent = false; + else + return true; + } + } + else + foundTransparent = true; + } + + entrance = null; + return false; + } + + private bool GetNextHullPoint(ref Vector2 last, ref Vector2 current, out Vector2 next) + { + int x; + int y; + + int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(ref last, ref current); + int indexOfPixelToCheck; + + for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++) + { + indexOfPixelToCheck = (indexOfFirstPixelToCheck + i) % _CLOSEPIXELS_LENGTH; + + x = (int)current.X + ClosePixels[indexOfPixelToCheck, 0]; + y = (int)current.Y + ClosePixels[indexOfPixelToCheck, 1]; + + if (x >= 0 && x < _width && y >= 0 && y <= _height) + { + if (IsSolid(ref x, ref y)) + { + next = new Vector2(x, y); + return true; + } + } + } + + next = Vector2.Zero; + return false; + } + + private bool SearchForOutstandingVertex(Vertices hullArea, out Vector2 outstanding) + { + Vector2 outstandingResult = Vector2.Zero; + bool found = false; + + if (hullArea.Count > 2) + { + int hullAreaLastPoint = hullArea.Count - 1; + + Vector2 tempVector1; + Vector2 tempVector2 = hullArea[0]; + Vector2 tempVector3 = hullArea[hullAreaLastPoint]; + + // Search between the first and last hull point. + for (int i = 1; i < hullAreaLastPoint; i++) + { + tempVector1 = hullArea[i]; + + // Check if the distance is over the one that's tolerable. + if (LineTools.DistanceBetweenPointAndLineSegment(ref tempVector1, ref tempVector2, ref tempVector3) >= _hullTolerance) + { + outstandingResult = hullArea[i]; + found = true; + break; + } + } + } + + outstanding = outstandingResult; + return found; + } + + private int GetIndexOfFirstPixelToCheck(ref Vector2 last, ref Vector2 current) + { + // .: pixel + // l: last position + // c: current position + // f: first pixel for next search + + // f . . + // l c . + // . . . + + //Calculate in which direction the last move went and decide over the next pixel to check. + switch ((int)(current.X - last.X)) + { + case 1: + switch ((int)(current.Y - last.Y)) + { + case 1: + return 1; + + case 0: + return 0; + + case -1: + return 7; + } + break; + + case 0: + switch ((int)(current.Y - last.Y)) + { + case 1: + return 2; + + case -1: + return 6; + } + break; + + case -1: + switch ((int)(current.Y - last.Y)) + { + case 1: + return 3; + + case 0: + return 4; + + case -1: + return 5; + } + break; + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Common/Vertices.cs b/Common/Vertices.cs new file mode 100644 index 0000000..d08e2d7 --- /dev/null +++ b/Common/Vertices.cs @@ -0,0 +1,955 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using FarseerPhysics.Collision; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Common +{ +#if !(XBOX360) + [DebuggerDisplay("Count = {Count} Vertices = {ToString()}")] +#endif + public class Vertices : List + { + public Vertices() + { + } + + public Vertices(int capacity) + { + Capacity = capacity; + } + + public Vertices(Vector2[] vector2) + { + for (int i = 0; i < vector2.Length; i++) + { + Add(vector2[i]); + } + } + + public Vertices(IList vertices) + { + for (int i = 0; i < vertices.Count; i++) + { + Add(vertices[i]); + } + } + + /// + /// Nexts the index. + /// + /// The index. + /// + public int NextIndex(int index) + { + if (index == Count - 1) + { + return 0; + } + return index + 1; + } + + public Vector2 NextVertex(int index) + { + return this[NextIndex(index)]; + } + + /// + /// Gets the previous index. + /// + /// The index. + /// + public int PreviousIndex(int index) + { + if (index == 0) + { + return Count - 1; + } + return index - 1; + } + + public Vector2 PreviousVertex(int index) + { + return this[PreviousIndex(index)]; + } + + /// + /// Gets the signed area. + /// + /// + public float GetSignedArea() + { + int i; + float area = 0; + + for (i = 0; i < Count; i++) + { + int j = (i + 1) % Count; + area += this[i].X * this[j].Y; + area -= this[i].Y * this[j].X; + } + area /= 2.0f; + return area; + } + + /// + /// Gets the area. + /// + /// + public float GetArea() + { + int i; + float area = 0; + + for (i = 0; i < Count; i++) + { + int j = (i + 1) % Count; + area += this[i].X * this[j].Y; + area -= this[i].Y * this[j].X; + } + area /= 2.0f; + return (area < 0 ? -area : area); + } + + /// + /// Gets the centroid. + /// + /// + public Vector2 GetCentroid() + { + // Same algorithm is used by Box2D + + Vector2 c = Vector2.Zero; + float area = 0.0f; + + const float inv3 = 1.0f / 3.0f; + Vector2 pRef = Vector2.Zero; + for (int i = 0; i < Count; ++i) + { + // Triangle vertices. + Vector2 p1 = pRef; + Vector2 p2 = this[i]; + Vector2 p3 = i + 1 < Count ? this[i + 1] : this[0]; + + Vector2 e1 = p2 - p1; + Vector2 e2 = p3 - p1; + + float D = MathUtils.Cross(e1, e2); + + float triangleArea = 0.5f * D; + area += triangleArea; + + // Area weighted centroid + c += triangleArea * inv3 * (p1 + p2 + p3); + } + + // Centroid + c *= 1.0f / area; + return c; + } + + /// + /// Gets the radius based on area. + /// + /// + public float GetRadius() + { + float area = GetSignedArea(); + + double radiusSqrd = (double)area / MathHelper.Pi; + if (radiusSqrd < 0) + { + radiusSqrd *= -1; + } + + return (float)Math.Sqrt(radiusSqrd); + } + + /// + /// Returns an AABB for vertex. + /// + /// + public AABB GetCollisionBox() + { + AABB aabb; + Vector2 lowerBound = new Vector2(float.MaxValue, float.MaxValue); + Vector2 upperBound = new Vector2(float.MinValue, float.MinValue); + + for (int i = 0; i < Count; ++i) + { + if (this[i].X < lowerBound.X) + { + lowerBound.X = this[i].X; + } + if (this[i].X > upperBound.X) + { + upperBound.X = this[i].X; + } + + if (this[i].Y < lowerBound.Y) + { + lowerBound.Y = this[i].Y; + } + if (this[i].Y > upperBound.Y) + { + upperBound.Y = this[i].Y; + } + } + + aabb.LowerBound = lowerBound; + aabb.UpperBound = upperBound; + + return aabb; + } + + public void Translate(Vector2 vector) + { + Translate(ref vector); + } + + /// + /// Translates the vertices with the specified vector. + /// + /// The vector. + public void Translate(ref Vector2 vector) + { + for (int i = 0; i < Count; i++) + this[i] = Vector2.Add(this[i], vector); + } + + /// + /// Scales the vertices with the specified vector. + /// + /// The Value. + public void Scale(ref Vector2 value) + { + for (int i = 0; i < Count; i++) + this[i] = Vector2.Multiply(this[i], value); + } + + /// + /// Rotate the vertices with the defined value in radians. + /// + /// The amount to rotate by in radians. + public void Rotate(float value) + { + Matrix rotationMatrix; + Matrix.CreateRotationZ(value, out rotationMatrix); + + for (int i = 0; i < Count; i++) + this[i] = Vector2.Transform(this[i], rotationMatrix); + } + + /// + /// Assuming the polygon is simple; determines whether the polygon is convex. + /// NOTE: It will also return false if the input contains colinear edges. + /// + /// + /// true if it is convex; otherwise, false. + /// + public bool IsConvex() + { + // Ensure the polygon is convex and the interior + // is to the left of each edge. + for (int i = 0; i < Count; ++i) + { + int i1 = i; + int i2 = i + 1 < Count ? i + 1 : 0; + Vector2 edge = this[i2] - this[i1]; + + for (int j = 0; j < Count; ++j) + { + // Don't check vertices on the current edge. + if (j == i1 || j == i2) + { + continue; + } + + Vector2 r = this[j] - this[i1]; + + float s = edge.X * r.Y - edge.Y * r.X; + + if (s <= 0.0f) + return false; + } + } + return true; + } + + public bool IsCounterClockWise() + { + //We just return true for lines + if (Count < 3) + return true; + + return (GetSignedArea() > 0.0f); + } + + /// + /// Forces counter clock wise order. + /// + public void ForceCounterClockWise() + { + if (!IsCounterClockWise()) + { + Reverse(); + } + } + + /// + /// Check for edge crossings + /// + /// + public bool IsSimple() + { + for (int i = 0; i < Count; ++i) + { + int iplus = (i + 1 > Count - 1) ? 0 : i + 1; + Vector2 a1 = new Vector2(this[i].X, this[i].Y); + Vector2 a2 = new Vector2(this[iplus].X, this[iplus].Y); + for (int j = i + 1; j < Count; ++j) + { + int jplus = (j + 1 > Count - 1) ? 0 : j + 1; + Vector2 b1 = new Vector2(this[j].X, this[j].Y); + Vector2 b2 = new Vector2(this[jplus].X, this[jplus].Y); + + Vector2 temp; + + if (LineTools.LineIntersect2(a1, a2, b1, b2, out temp)) + { + return false; + } + } + } + return true; + } + + //TODO: Test + //Implementation found here: http://www.gamedev.net/community/forums/topic.asp?topic_id=548477 + public bool IsSimple2() + { + for (int i = 0; i < Count; ++i) + { + if (i < Count - 1) + { + for (int h = i + 1; h < Count; ++h) + { + // Do two vertices lie on top of one another? + if (this[i] == this[h]) + { + return true; + } + } + } + + int j = (i + 1) % Count; + Vector2 iToj = this[j] - this[i]; + Vector2 iTojNormal = new Vector2(iToj.Y, -iToj.X); + + // i is the first vertex and j is the second + int startK = (j + 1) % Count; + int endK = (i - 1 + Count) % Count; + endK += startK < endK ? 0 : startK + 1; + int k = startK; + Vector2 iTok = this[k] - this[i]; + bool onLeftSide = Vector2.Dot(iTok, iTojNormal) >= 0; + Vector2 prevK = this[k]; + ++k; + for (; k <= endK; ++k) + { + int modK = k % Count; + iTok = this[modK] - this[i]; + if (onLeftSide != Vector2.Dot(iTok, iTojNormal) >= 0) + { + Vector2 prevKtoK = this[modK] - prevK; + Vector2 prevKtoKNormal = new Vector2(prevKtoK.Y, -prevKtoK.X); + if ((Vector2.Dot(this[i] - prevK, prevKtoKNormal) >= 0) != + (Vector2.Dot(this[j] - prevK, prevKtoKNormal) >= 0)) + { + return true; + } + } + onLeftSide = Vector2.Dot(iTok, iTojNormal) > 0; + prevK = this[modK]; + } + } + return false; + } + + // From Eric Jordan's convex decomposition library + + /// + /// Checks if polygon is valid for use in Box2d engine. + /// Last ditch effort to ensure no invalid polygons are + /// added to world geometry. + /// + /// Performs a full check, for simplicity, convexity, + /// orientation, minimum angle, and volume. This won't + /// be very efficient, and a lot of it is redundant when + /// other tools in this section are used. + /// + /// + public bool CheckPolygon() + { + int error = -1; + if (Count < 3 || Count > Settings.MaxPolygonVertices) + { + error = 0; + } + if (!IsConvex()) + { + error = 1; + } + if (!IsSimple()) + { + error = 2; + } + if (GetArea() < Settings.Epsilon) + { + error = 3; + } + + //Compute normals + Vector2[] normals = new Vector2[Count]; + Vertices vertices = new Vertices(Count); + for (int i = 0; i < Count; ++i) + { + vertices.Add(new Vector2(this[i].X, this[i].Y)); + int i1 = i; + int i2 = i + 1 < Count ? i + 1 : 0; + Vector2 edge = new Vector2(this[i2].X - this[i1].X, this[i2].Y - this[i1].Y); + normals[i] = MathUtils.Cross(edge, 1.0f); + normals[i].Normalize(); + } + + //Required side checks + for (int i = 0; i < Count; ++i) + { + int iminus = (i == 0) ? Count - 1 : i - 1; + + //Parallel sides check + float cross = MathUtils.Cross(normals[iminus], normals[i]); + cross = MathUtils.Clamp(cross, -1.0f, 1.0f); + float angle = (float)Math.Asin(cross); + if (angle <= Settings.AngularSlop) + { + error = 4; + break; + } + + //Too skinny check + for (int j = 0; j < Count; ++j) + { + if (j == i || j == (i + 1) % Count) + { + continue; + } + float s = Vector2.Dot(normals[i], vertices[j] - vertices[i]); + if (s >= -Settings.LinearSlop) + { + error = 5; + } + } + + + Vector2 centroid = vertices.GetCentroid(); + Vector2 n1 = normals[iminus]; + Vector2 n2 = normals[i]; + Vector2 v = vertices[i] - centroid; + + Vector2 d = new Vector2(); + d.X = Vector2.Dot(n1, v); // - toiSlop; + d.Y = Vector2.Dot(n2, v); // - toiSlop; + + // Shifting the edge inward by toiSlop should + // not cause the plane to pass the centroid. + if ((d.X < 0.0f) || (d.Y < 0.0f)) + { + error = 6; + } + } + + if (error != -1) + { + Debug.WriteLine("Found invalid polygon, "); + switch (error) + { + case 0: + Debug.WriteLine(string.Format("must have between 3 and {0} vertices.\n", + Settings.MaxPolygonVertices)); + break; + case 1: + Debug.WriteLine("must be convex.\n"); + break; + case 2: + Debug.WriteLine("must be simple (cannot intersect itself).\n"); + break; + case 3: + Debug.WriteLine("area is too small.\n"); + break; + case 4: + Debug.WriteLine("sides are too close to parallel.\n"); + break; + case 5: + Debug.WriteLine("polygon is too thin.\n"); + break; + case 6: + Debug.WriteLine("core shape generation would move edge past centroid (too thin).\n"); + break; + default: + Debug.WriteLine("don't know why.\n"); + break; + } + } + return error != -1; + } + + // From Eric Jordan's convex decomposition library + + /// + /// Trace the edge of a non-simple polygon and return a simple polygon. + /// + /// Method: + /// Start at vertex with minimum y (pick maximum x one if there are two). + /// We aim our "lastDir" vector at (1.0, 0) + /// We look at the two rays going off from our start vertex, and follow whichever + /// has the smallest angle (in -Pi . Pi) wrt lastDir ("rightest" turn) + /// Loop until we hit starting vertex: + /// We add our current vertex to the list. + /// We check the seg from current vertex to next vertex for intersections + /// - if no intersections, follow to next vertex and continue + /// - if intersections, pick one with minimum distance + /// - if more than one, pick one with "rightest" next point (two possibilities for each) + /// + /// The vertices. + /// + public Vertices TraceEdge(Vertices verts) + { + PolyNode[] nodes = new PolyNode[verts.Count * verts.Count]; + //overkill, but sufficient (order of mag. is right) + int nNodes = 0; + + //Add base nodes (raw outline) + for (int i = 0; i < verts.Count; ++i) + { + Vector2 pos = new Vector2(verts[i].X, verts[i].Y); + nodes[i].Position = pos; + ++nNodes; + int iplus = (i == verts.Count - 1) ? 0 : i + 1; + int iminus = (i == 0) ? verts.Count - 1 : i - 1; + nodes[i].AddConnection(nodes[iplus]); + nodes[i].AddConnection(nodes[iminus]); + } + + //Process intersection nodes - horribly inefficient + bool dirty = true; + int counter = 0; + while (dirty) + { + dirty = false; + for (int i = 0; i < nNodes; ++i) + { + for (int j = 0; j < nodes[i].NConnected; ++j) + { + for (int k = 0; k < nNodes; ++k) + { + if (k == i || nodes[k] == nodes[i].Connected[j]) continue; + for (int l = 0; l < nodes[k].NConnected; ++l) + { + if (nodes[k].Connected[l] == nodes[i].Connected[j] || + nodes[k].Connected[l] == nodes[i]) continue; + + //Check intersection + Vector2 intersectPt; + + bool crosses = LineTools.LineIntersect(nodes[i].Position, nodes[i].Connected[j].Position, + nodes[k].Position, nodes[k].Connected[l].Position, + out intersectPt); + if (crosses) + { + dirty = true; + //Destroy and re-hook connections at crossing point + PolyNode connj = nodes[i].Connected[j]; + PolyNode connl = nodes[k].Connected[l]; + nodes[i].Connected[j].RemoveConnection(nodes[i]); + nodes[i].RemoveConnection(connj); + nodes[k].Connected[l].RemoveConnection(nodes[k]); + nodes[k].RemoveConnection(connl); + nodes[nNodes] = new PolyNode(intersectPt); + nodes[nNodes].AddConnection(nodes[i]); + nodes[i].AddConnection(nodes[nNodes]); + nodes[nNodes].AddConnection(nodes[k]); + nodes[k].AddConnection(nodes[nNodes]); + nodes[nNodes].AddConnection(connj); + connj.AddConnection(nodes[nNodes]); + nodes[nNodes].AddConnection(connl); + connl.AddConnection(nodes[nNodes]); + ++nNodes; + goto SkipOut; + } + } + } + } + } + SkipOut: + ++counter; + } + + //Collapse duplicate points + bool foundDupe = true; + int nActive = nNodes; + while (foundDupe) + { + foundDupe = false; + for (int i = 0; i < nNodes; ++i) + { + if (nodes[i].NConnected == 0) continue; + for (int j = i + 1; j < nNodes; ++j) + { + if (nodes[j].NConnected == 0) continue; + Vector2 diff = nodes[i].Position - nodes[j].Position; + if (diff.LengthSquared() <= Settings.Epsilon * Settings.Epsilon) + { + if (nActive <= 3) + return new Vertices(); + + //printf("Found dupe, %d left\n",nActive); + --nActive; + foundDupe = true; + PolyNode inode = nodes[i]; + PolyNode jnode = nodes[j]; + //Move all of j's connections to i, and orphan j + int njConn = jnode.NConnected; + for (int k = 0; k < njConn; ++k) + { + PolyNode knode = jnode.Connected[k]; + Debug.Assert(knode != jnode); + if (knode != inode) + { + inode.AddConnection(knode); + knode.AddConnection(inode); + } + knode.RemoveConnection(jnode); + } + jnode.NConnected = 0; + } + } + } + } + + //Now walk the edge of the list + + //Find node with minimum y value (max x if equal) + float minY = float.MaxValue; + float maxX = -float.MaxValue; + int minYIndex = -1; + for (int i = 0; i < nNodes; ++i) + { + if (nodes[i].Position.Y < minY && nodes[i].NConnected > 1) + { + minY = nodes[i].Position.Y; + minYIndex = i; + maxX = nodes[i].Position.X; + } + else if (nodes[i].Position.Y == minY && nodes[i].Position.X > maxX && nodes[i].NConnected > 1) + { + minYIndex = i; + maxX = nodes[i].Position.X; + } + } + + Vector2 origDir = new Vector2(1.0f, 0.0f); + Vector2[] resultVecs = new Vector2[4 * nNodes]; + // nodes may be visited more than once, unfortunately - change to growable array! + int nResultVecs = 0; + PolyNode currentNode = nodes[minYIndex]; + PolyNode startNode = currentNode; + Debug.Assert(currentNode.NConnected > 0); + PolyNode nextNode = currentNode.GetRightestConnection(origDir); + if (nextNode == null) + { + Vertices vertices = new Vertices(nResultVecs); + + for (int i = 0; i < nResultVecs; ++i) + { + vertices.Add(resultVecs[i]); + } + + return vertices; + } + + // Borked, clean up our mess and return + resultVecs[0] = startNode.Position; + ++nResultVecs; + while (nextNode != startNode) + { + if (nResultVecs > 4 * nNodes) + { + Debug.Assert(false); + } + resultVecs[nResultVecs++] = nextNode.Position; + PolyNode oldNode = currentNode; + currentNode = nextNode; + nextNode = currentNode.GetRightestConnection(oldNode); + if (nextNode == null) + { + Vertices vertices = new Vertices(nResultVecs); + for (int i = 0; i < nResultVecs; ++i) + { + vertices.Add(resultVecs[i]); + } + return vertices; + } + // There was a problem, so jump out of the loop and use whatever garbage we've generated so far + } + + return new Vertices(); + } + + private class PolyNode + { + private const int MaxConnected = 32; + + /* + * Given sines and cosines, tells if A's angle is less than B's on -Pi, Pi + * (in other words, is A "righter" than B) + */ + public PolyNode[] Connected = new PolyNode[MaxConnected]; + public int NConnected; + public Vector2 Position; + + public PolyNode(Vector2 pos) + { + Position = pos; + NConnected = 0; + } + + private bool IsRighter(float sinA, float cosA, float sinB, float cosB) + { + if (sinA < 0) + { + if (sinB > 0 || cosA <= cosB) return true; + else return false; + } + else + { + if (sinB < 0 || cosA <= cosB) return false; + else return true; + } + } + + public void AddConnection(PolyNode toMe) + { + Debug.Assert(NConnected < MaxConnected); + + // Ignore duplicate additions + for (int i = 0; i < NConnected; ++i) + { + if (Connected[i] == toMe) return; + } + Connected[NConnected] = toMe; + ++NConnected; + } + + public void RemoveConnection(PolyNode fromMe) + { + bool isFound = false; + int foundIndex = -1; + for (int i = 0; i < NConnected; ++i) + { + if (fromMe == Connected[i]) + { + //.position == connected[i].position){ + isFound = true; + foundIndex = i; + break; + } + } + Debug.Assert(isFound); + --NConnected; + for (int i = foundIndex; i < NConnected; ++i) + { + Connected[i] = Connected[i + 1]; + } + } + + public PolyNode GetRightestConnection(PolyNode incoming) + { + if (NConnected == 0) Debug.Assert(false); // This means the connection graph is inconsistent + if (NConnected == 1) + { + //b2Assert(false); + // Because of the possibility of collapsing nearby points, + // we may end up with "spider legs" dangling off of a region. + // The correct behavior here is to turn around. + return incoming; + } + Vector2 inDir = Position - incoming.Position; + + float inLength = inDir.Length(); + inDir.Normalize(); + + Debug.Assert(inLength > Settings.Epsilon); + + PolyNode result = null; + for (int i = 0; i < NConnected; ++i) + { + if (Connected[i] == incoming) continue; + Vector2 testDir = Connected[i].Position - Position; + float testLengthSqr = testDir.LengthSquared(); + testDir.Normalize(); + Debug.Assert(testLengthSqr >= Settings.Epsilon * Settings.Epsilon); + float myCos = Vector2.Dot(inDir, testDir); + float mySin = MathUtils.Cross(inDir, testDir); + if (result != null) + { + Vector2 resultDir = result.Position - Position; + resultDir.Normalize(); + float resCos = Vector2.Dot(inDir, resultDir); + float resSin = MathUtils.Cross(inDir, resultDir); + if (IsRighter(mySin, myCos, resSin, resCos)) + { + result = Connected[i]; + } + } + else + { + result = Connected[i]; + } + } + + Debug.Assert(result != null); + + return result; + } + + public PolyNode GetRightestConnection(Vector2 incomingDir) + { + Vector2 diff = Position - incomingDir; + PolyNode temp = new PolyNode(diff); + PolyNode res = GetRightestConnection(temp); + Debug.Assert(res != null); + return res; + } + } + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < Count; i++) + { + builder.Append(this[i].ToString()); + if (i < Count - 1) + { + builder.Append(" "); + } + } + return builder.ToString(); + } + + /// + /// Projects to axis. + /// + /// The axis. + /// The min. + /// The max. + public void ProjectToAxis(ref Vector2 axis, out float min, out float max) + { + // To project a point on an axis use the dot product + float dotProduct = Vector2.Dot(axis, this[0]); + min = dotProduct; + max = dotProduct; + + for (int i = 0; i < Count; i++) + { + dotProduct = Vector2.Dot(this[i], axis); + if (dotProduct < min) + { + min = dotProduct; + } + else + { + if (dotProduct > max) + { + max = dotProduct; + } + } + } + } + + /// + /// Winding number test for a point in a polygon. + /// + /// See more info about the algorithm here: http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm + /// The point to be tested. + /// -1 if the winding number is zero and the point is outside + /// the polygon, 1 if the point is inside the polygon, and 0 if the point + /// is on the polygons edge. + public int PointInPolygon(ref Vector2 point) + { + // Winding number + int wn = 0; + + // Iterate through polygon's edges + for (int i = 0; i < Count; i++) + { + // Get points + Vector2 p1 = this[i]; + Vector2 p2 = this[NextIndex(i)]; + + // Test if a point is directly on the edge + Vector2 edge = p2 - p1; + float area = MathUtils.Area(ref p1, ref p2, ref point); + if (area == 0f && Vector2.Dot(point - p1, edge) >= 0f && Vector2.Dot(point - p2, edge) <= 0f) + { + return 0; + } + // Test edge for intersection with ray from point + if (p1.Y <= point.Y) + { + if (p2.Y > point.Y && area > 0f) + { + ++wn; + } + } + else + { + if (p2.Y <= point.Y && area < 0f) + { + --wn; + } + } + } + return (wn == 0 ? -1 : 1); + } + + /// + /// Compute the sum of the angles made between the test point and each pair of points making up the polygon. + /// If this sum is 2pi then the point is an interior point, if 0 then the point is an exterior point. + /// ref: http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ - Solution 2 + /// + public bool PointInPolygonAngle(ref Vector2 point) + { + double angle = 0; + + // Iterate through polygon's edges + for (int i = 0; i < Count; i++) + { + // Get points + Vector2 p1 = this[i] - point; + Vector2 p2 = this[NextIndex(i)] - point; + + angle += MathUtils.VectorAngle(ref p1, ref p2); + } + + if (Math.Abs(angle) < Math.PI) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Controllers/AbstractForceController.cs b/Controllers/AbstractForceController.cs new file mode 100644 index 0000000..4782624 --- /dev/null +++ b/Controllers/AbstractForceController.cs @@ -0,0 +1,323 @@ +using System; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Controllers +{ + public abstract class AbstractForceController : Controller + { + #region DecayModes enum + + /// + /// Modes for Decay. Actual Decay must be implemented in inheriting + /// classes + /// + public enum DecayModes + { + None, + Step, + Linear, + InverseSquare, + Curve + } + + #endregion + + #region ForceTypes enum + + /// + /// Forcetypes are used in the decay math to properly get the distance. + /// They are also used to draw a representation in DebugView + /// + public enum ForceTypes + { + Point, + Line, + Area + } + + #endregion + + #region TimingModes enum + + /// + /// Timing Modes + /// Switched: Standard on/off mode using the baseclass enabled property + /// Triggered: When the Trigger() method is called the force is active + /// for a specified Impulse Length + /// Curve: Still to be defined. The basic idea is having a Trigger + /// combined with a curve for the strength + /// + public enum TimingModes + { + Switched, + Triggered, + Curve + } + + #endregion + + /// + /// Curve to be used for Decay in Curve mode + /// + public Curve DecayCurve; + + /// + /// The Forcetype of the instance + /// + public ForceTypes ForceType; + + /// + /// Provided for reuse to provide Variation functionality in + /// inheriting classes + /// + protected Random Randomize; + + /// + /// Curve used by Curve Mode as an animated multiplier for the force + /// strength. + /// Only positions between 0 and 1 are considered as that range is + /// stretched to have ImpulseLength. + /// + public Curve StrengthCurve; + + /// + /// Constructor + /// + public AbstractForceController() + : base(ControllerType.AbstractForceController) + { + Enabled = true; + + Strength = 1.0f; + Position = new Vector2(0, 0); + MaximumSpeed = 100.0f; + TimingMode = TimingModes.Switched; + ImpulseTime = 0.0f; + ImpulseLength = 1.0f; + Triggered = false; + StrengthCurve = new Curve(); + Variation = 0.0f; + Randomize = new Random(1234); + DecayMode = DecayModes.None; + DecayCurve = new Curve(); + DecayStart = 0.0f; + DecayEnd = 0.0f; + + StrengthCurve.Keys.Add(new CurveKey(0, 5)); + StrengthCurve.Keys.Add(new CurveKey(0.1f, 5)); + StrengthCurve.Keys.Add(new CurveKey(0.2f, -4)); + StrengthCurve.Keys.Add(new CurveKey(1f, 0)); + } + + /// + /// Overloaded Contstructor with supplying Timing Mode + /// + /// + public AbstractForceController(TimingModes mode) + : base(ControllerType.AbstractForceController) + { + TimingMode = mode; + switch (mode) + { + case TimingModes.Switched: + Enabled = true; + break; + case TimingModes.Triggered: + Enabled = false; + break; + case TimingModes.Curve: + Enabled = false; + break; + } + } + + /// + /// Global Strength of the force to be applied + /// + public float Strength { get; set; } + + /// + /// Position of the Force. Can be ignored (left at (0,0) for forces + /// that are not position-dependent + /// + public Vector2 Position { get; set; } + + /// + /// Maximum speed of the bodies. Bodies that are travelling faster are + /// supposed to be ignored + /// + public float MaximumSpeed { get; set; } + + /// + /// Maximum Force to be applied. As opposed to Maximum Speed this is + /// independent of the velocity of + /// the affected body + /// + public float MaximumForce { get; set; } + + /// + /// Timing Mode of the force instance + /// + public TimingModes TimingMode { get; set; } + + /// + /// Time of the current impulse. Incremented in update till + /// ImpulseLength is reached + /// + public float ImpulseTime { get; private set; } + + /// + /// Length of a triggered impulse. Used in both Triggered and Curve Mode + /// + public float ImpulseLength { get; set; } + + /// + /// Indicating if we are currently during an Impulse + /// (Triggered and Curve Mode) + /// + public bool Triggered { get; private set; } + + /// + /// Variation of the force applied to each body affected + /// !! Must be used in inheriting classes properly !! + /// + public float Variation { get; set; } + + /// + /// See DecayModes + /// + public DecayModes DecayMode { get; set; } + + /// + /// Start of the distance based Decay. To set a non decaying area + /// + public float DecayStart { get; set; } + + /// + /// Maximum distance a force should be applied + /// + public float DecayEnd { get; set; } + + /// + /// Calculate the Decay for a given body. Meant to ease force + /// development and stick to the DRY principle and provide unified and + /// predictable decay math. + /// + /// The body to calculate decay for + /// A multiplier to multiply the force with to add decay + /// support in inheriting classes + protected float GetDecayMultiplier(Body body) + { + //TODO: Consider ForceType in distance calculation! + float distance = (body.Position - Position).Length(); + switch (DecayMode) + { + case DecayModes.None: + { + return 1.0f; + } + case DecayModes.Step: + { + if (distance < DecayEnd) + return 1.0f; + else + return 0.0f; + } + case DecayModes.Linear: + { + if (distance < DecayStart) + return 1.0f; + if (distance > DecayEnd) + return 0.0f; + return (DecayEnd - DecayStart / distance - DecayStart); + } + case DecayModes.InverseSquare: + { + if (distance < DecayStart) + return 1.0f; + else + return 1.0f / ((distance - DecayStart) * (distance - DecayStart)); + } + case DecayModes.Curve: + { + if (distance < DecayStart) + return 1.0f; + else + return DecayCurve.Evaluate(distance - DecayStart); + } + default: + return 1.0f; + } + } + + /// + /// Triggers the trigger modes (Trigger and Curve) + /// + public void Trigger() + { + Triggered = true; + ImpulseTime = 0; + } + + /// + /// Inherited from Controller + /// Depending on the TimingMode perform timing logic and call ApplyForce() + /// + /// + public override void Update(float dt) + { + switch (TimingMode) + { + case TimingModes.Switched: + { + if (Enabled) + { + ApplyForce(dt, Strength); + } + break; + } + case TimingModes.Triggered: + { + if (Enabled && Triggered) + { + if (ImpulseTime < ImpulseLength) + { + ApplyForce(dt, Strength); + ImpulseTime += dt; + } + else + { + Triggered = false; + } + } + break; + } + case TimingModes.Curve: + { + if (Enabled && Triggered) + { + if (ImpulseTime < ImpulseLength) + { + ApplyForce(dt, Strength * StrengthCurve.Evaluate(ImpulseTime)); + ImpulseTime += dt; + } + else + { + Triggered = false; + } + } + break; + } + } + } + + /// + /// Apply the force supplying strength (wich is modified in Update() + /// according to the TimingMode + /// + /// + /// The strength + public abstract void ApplyForce(float dt, float strength); + } +} \ No newline at end of file diff --git a/Controllers/BuoyancyController.cs b/Controllers/BuoyancyController.cs new file mode 100644 index 0000000..42e653b --- /dev/null +++ b/Controllers/BuoyancyController.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using FarseerPhysics.Collision; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Controllers +{ + public sealed class BuoyancyController : Controller + { + /// + /// Controls the rotational drag that the fluid exerts on the bodies within it. Use higher values will simulate thick fluid, like honey, lower values to + /// simulate water-like fluids. + /// + public float AngularDragCoefficient; + + /// + /// Density of the fluid. Higher values will make things more buoyant, lower values will cause things to sink. + /// + public float Density; + + /// + /// Controls the linear drag that the fluid exerts on the bodies within it. Use higher values will simulate thick fluid, like honey, lower values to + /// simulate water-like fluids. + /// + public float LinearDragCoefficient; + + /// + /// Acts like waterflow. Defaults to 0,0. + /// + public Vector2 Velocity; + + private AABB _container; + + private Vector2 _gravity; + private Vector2 _normal; + private float _offset; + private Dictionary _uniqueBodies = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Only bodies inside this AABB will be influenced by the controller + /// Density of the fluid + /// Linear drag coefficient of the fluid + /// Rotational drag coefficient of the fluid + /// The direction gravity acts. Buoyancy force will act in opposite direction of gravity. + public BuoyancyController(AABB container, float density, float linearDragCoefficient, + float rotationalDragCoefficient, Vector2 gravity) + : base(ControllerType.BuoyancyController) + { + Container = container; + _normal = new Vector2(0, 1); + Density = density; + LinearDragCoefficient = linearDragCoefficient; + AngularDragCoefficient = rotationalDragCoefficient; + _gravity = gravity; + } + + public AABB Container + { + get { return _container; } + set + { + _container = value; + _offset = _container.UpperBound.Y; + } + } + + public override void Update(float dt) + { + _uniqueBodies.Clear(); + World.QueryAABB(fixture => + { + if (fixture.Body.IsStatic || !fixture.Body.Awake) + return true; + + if (!_uniqueBodies.ContainsKey(fixture.Body.BodyId)) + _uniqueBodies.Add(fixture.Body.BodyId, fixture.Body); + + return true; + }, ref _container); + + foreach (KeyValuePair kv in _uniqueBodies) + { + Body body = kv.Value; + + Vector2 areac = Vector2.Zero; + Vector2 massc = Vector2.Zero; + float area = 0; + float mass = 0; + + for (int j = 0; j < body.FixtureList.Count; j++) + { + Fixture fixture = body.FixtureList[j]; + + if (fixture.Shape.ShapeType != ShapeType.Polygon && fixture.Shape.ShapeType != ShapeType.Circle) + continue; + + Shape shape = fixture.Shape; + + Vector2 sc; + float sarea = shape.ComputeSubmergedArea(_normal, _offset, body.Xf, out sc); + area += sarea; + areac.X += sarea * sc.X; + areac.Y += sarea * sc.Y; + + mass += sarea * shape.Density; + massc.X += sarea * sc.X * shape.Density; + massc.Y += sarea * sc.Y * shape.Density; + } + + areac.X /= area; + areac.Y /= area; + massc.X /= mass; + massc.Y /= mass; + + if (area < Settings.Epsilon) + continue; + + //Buoyancy + Vector2 buoyancyForce = -Density * area * _gravity; + body.ApplyForce(buoyancyForce, massc); + + //Linear drag + Vector2 dragForce = body.GetLinearVelocityFromWorldPoint(areac) - Velocity; + dragForce *= -LinearDragCoefficient * area; + body.ApplyForce(dragForce, areac); + + //Angular drag + body.ApplyTorque(-body.Inertia / body.Mass * area * body.AngularVelocity * AngularDragCoefficient); + } + } + } +} \ No newline at end of file diff --git a/Controllers/Controller.cs b/Controllers/Controller.cs new file mode 100644 index 0000000..3e556c0 --- /dev/null +++ b/Controllers/Controller.cs @@ -0,0 +1,71 @@ +using System; +using FarseerPhysics.Dynamics; + +namespace FarseerPhysics.Controllers +{ + [Flags] + public enum ControllerType + { + GravityController = (1 << 0), + VelocityLimitController = (1 << 1), + AbstractForceController = (1 << 2), + BuoyancyController = (1 << 3), + } + + public struct ControllerFilter + { + public ControllerType ControllerFlags; + + /// + /// Ignores the controller. The controller has no effect on this body. + /// + /// The controller type. + public void IgnoreController(ControllerType controller) + { + ControllerFlags |= controller; + } + + /// + /// Restore the controller. The controller affects this body. + /// + /// The controller type. + public void RestoreController(ControllerType controller) + { + ControllerFlags &= ~controller; + } + + /// + /// Determines whether this body ignores the the specified controller. + /// + /// The controller type. + /// + /// true if the body has the specified flag; otherwise, false. + /// + public bool IsControllerIgnored(ControllerType controller) + { + return (ControllerFlags & controller) == controller; + } + } + + public abstract class Controller : FilterData + { + public bool Enabled; + public World World; + private ControllerType _type; + + public Controller(ControllerType controllerType) + { + _type = controllerType; + } + + public override bool IsActiveOn(Body body) + { + if (body.ControllerFilter.IsControllerIgnored(_type)) + return false; + + return base.IsActiveOn(body); + } + + public abstract void Update(float dt); + } +} \ No newline at end of file diff --git a/Controllers/GravityController.cs b/Controllers/GravityController.cs new file mode 100644 index 0000000..6381c0b --- /dev/null +++ b/Controllers/GravityController.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Controllers +{ + public enum GravityType + { + Linear, + DistanceSquared + } + + public class GravityController : Controller + { + public List Bodies = new List(); + public List Points = new List(); + + public GravityController(float strength) + : base(ControllerType.GravityController) + { + Strength = strength; + MaxRadius = float.MaxValue; + } + + public GravityController(float strength, float maxRadius, float minRadius) + : base(ControllerType.GravityController) + { + MinRadius = minRadius; + MaxRadius = maxRadius; + Strength = strength; + } + + public float MinRadius { get; set; } + public float MaxRadius { get; set; } + public float Strength { get; set; } + public GravityType GravityType { get; set; } + + public override void Update(float dt) + { + Vector2 f = Vector2.Zero; + + foreach (Body body1 in World.BodyList) + { + if (!IsActiveOn(body1)) + continue; + + foreach (Body body2 in Bodies) + { + if (body1 == body2 || (body1.IsStatic && body2.IsStatic) || !body2.Enabled) + continue; + + Vector2 d = body2.WorldCenter - body1.WorldCenter; + float r2 = d.LengthSquared(); + + if (r2 < Settings.Epsilon) + continue; + + float r = d.Length(); + + if (r >= MaxRadius || r <= MinRadius) + continue; + + switch (GravityType) + { + case GravityType.DistanceSquared: + f = Strength / r2 / (float)Math.Sqrt(r2) * body1.Mass * body2.Mass * d; + break; + case GravityType.Linear: + f = Strength / r2 * body1.Mass * body2.Mass * d; + break; + } + + body1.ApplyForce(ref f); + Vector2.Negate(ref f, out f); + body2.ApplyForce(ref f); + } + + foreach (Vector2 point in Points) + { + Vector2 d = point - body1.Position; + float r2 = d.LengthSquared(); + + if (r2 < Settings.Epsilon) + continue; + + float r = d.Length(); + + if (r >= MaxRadius || r <= MinRadius) + continue; + + switch (GravityType) + { + case GravityType.DistanceSquared: + f = Strength / r2 / (float)Math.Sqrt(r2) * body1.Mass * d; + break; + case GravityType.Linear: + f = Strength / r2 * body1.Mass * d; + break; + } + + body1.ApplyForce(ref f); + } + } + } + + public void AddBody(Body body) + { + Bodies.Add(body); + } + + public void AddPoint(Vector2 point) + { + Points.Add(point); + } + } +} \ No newline at end of file diff --git a/Controllers/SimpleWindForce.cs b/Controllers/SimpleWindForce.cs new file mode 100644 index 0000000..541d7ff --- /dev/null +++ b/Controllers/SimpleWindForce.cs @@ -0,0 +1,75 @@ +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Controllers +{ + /// + /// Reference implementation for forces based on AbstractForceController + /// It supports all features provided by the base class and illustrates proper + /// usage as an easy to understand example. + /// As a side-effect it is a nice and easy to use wind force for your projects + /// + public class SimpleWindForce : AbstractForceController + { + /// + /// Direction of the windforce + /// + public Vector2 Direction { get; set; } + + /// + /// The amount of Direction randomization. Allowed range is 0-1. + /// + public float Divergence { get; set; } + + /// + /// Ignore the position and apply the force. If off only in the "front" (relative to position and direction) + /// will be affected + /// + public bool IgnorePosition { get; set; } + + + public override void ApplyForce(float dt, float strength) + { + foreach (Body body in World.BodyList) + { + //TODO: Consider Force Type + float decayMultiplier = GetDecayMultiplier(body); + + if (decayMultiplier != 0) + { + Vector2 forceVector; + + if (ForceType == ForceTypes.Point) + { + forceVector = body.Position - Position; + } + else + { + Direction.Normalize(); + + forceVector = Direction; + + if (forceVector.Length() == 0) + forceVector = new Vector2(0, 1); + } + + //TODO: Consider Divergence: + //forceVector = Vector2.Transform(forceVector, Matrix.CreateRotationZ((MathHelper.Pi - MathHelper.Pi/2) * (float)Randomize.NextDouble())); + + // Calculate random Variation + if (Variation != 0) + { + float strengthVariation = (float)Randomize.NextDouble() * MathHelper.Clamp(Variation, 0, 1); + forceVector.Normalize(); + body.ApplyForce(forceVector * strength * decayMultiplier * strengthVariation); + } + else + { + forceVector.Normalize(); + body.ApplyForce(forceVector * strength * decayMultiplier); + } + } + } + } + } +} \ No newline at end of file diff --git a/Controllers/VelocityLimitController.cs b/Controllers/VelocityLimitController.cs new file mode 100644 index 0000000..1eb0ede --- /dev/null +++ b/Controllers/VelocityLimitController.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Dynamics; + +namespace FarseerPhysics.Controllers +{ + /// + /// Put a limit on the linear (translation - the movespeed) and angular (rotation) velocity + /// of bodies added to this controller. + /// + public class VelocityLimitController : Controller + { + public bool LimitAngularVelocity = true; + public bool LimitLinearVelocity = true; + private List _bodies = new List(); + private float _maxAngularSqared; + private float _maxAngularVelocity; + private float _maxLinearSqared; + private float _maxLinearVelocity; + + /// + /// Initializes a new instance of the class. + /// Sets the max linear velocity to Settings.MaxTranslation + /// Sets the max angular velocity to Settings.MaxRotation + /// + public VelocityLimitController() + : base(ControllerType.VelocityLimitController) + { + MaxLinearVelocity = Settings.MaxTranslation; + MaxAngularVelocity = Settings.MaxRotation; + } + + /// + /// Initializes a new instance of the class. + /// Pass in 0 or float.MaxValue to disable the limit. + /// maxAngularVelocity = 0 will disable the angular velocity limit. + /// + /// The max linear velocity. + /// The max angular velocity. + public VelocityLimitController(float maxLinearVelocity, float maxAngularVelocity) + : base(ControllerType.VelocityLimitController) + { + if (maxLinearVelocity == 0 || maxLinearVelocity == float.MaxValue) + LimitLinearVelocity = false; + + if (maxAngularVelocity == 0 || maxAngularVelocity == float.MaxValue) + LimitAngularVelocity = false; + + MaxLinearVelocity = maxLinearVelocity; + MaxAngularVelocity = maxAngularVelocity; + } + + /// + /// Gets or sets the max angular velocity. + /// + /// The max angular velocity. + public float MaxAngularVelocity + { + get { return _maxAngularVelocity; } + set + { + _maxAngularVelocity = value; + _maxAngularSqared = _maxAngularVelocity * _maxAngularVelocity; + } + } + + /// + /// Gets or sets the max linear velocity. + /// + /// The max linear velocity. + public float MaxLinearVelocity + { + get { return _maxLinearVelocity; } + set + { + _maxLinearVelocity = value; + _maxLinearSqared = _maxLinearVelocity * _maxLinearVelocity; + } + } + + public override void Update(float dt) + { + foreach (Body body in _bodies) + { + if (!IsActiveOn(body)) + continue; + + if (LimitLinearVelocity) + { + //Translation + // Check for large velocities. + float translationX = dt * body.LinearVelocityInternal.X; + float translationY = dt * body.LinearVelocityInternal.Y; + float result = translationX * translationX + translationY * translationY; + + if (result > dt * _maxLinearSqared) + { + float sq = (float)Math.Sqrt(result); + + float ratio = _maxLinearVelocity / sq; + body.LinearVelocityInternal.X *= ratio; + body.LinearVelocityInternal.Y *= ratio; + } + } + + if (LimitAngularVelocity) + { + //Rotation + float rotation = dt * body.AngularVelocityInternal; + if (rotation * rotation > _maxAngularSqared) + { + float ratio = _maxAngularVelocity / Math.Abs(rotation); + body.AngularVelocityInternal *= ratio; + } + } + } + } + + public void AddBody(Body body) + { + _bodies.Add(body); + } + + public void RemoveBody(Body body) + { + _bodies.Remove(body); + } + } +} \ No newline at end of file diff --git a/DrawingSystem/AssetCreator.cs b/DrawingSystem/AssetCreator.cs new file mode 100644 index 0000000..ec92967 --- /dev/null +++ b/DrawingSystem/AssetCreator.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Collision; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Common; +using FarseerPhysics.Common.Decomposition; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace FarseerPhysics.SamplesFramework +{ + public enum MaterialType + { + Blank, + Dots, + Squares, + Waves, + Pavement + } + + public class AssetCreator + { + private const int CircleSegments = 32; + + private GraphicsDevice _device; + private BasicEffect _effect; + private Dictionary _materials = new Dictionary(); + + public AssetCreator(GraphicsDevice device) + { + _device = device; + _effect = new BasicEffect(_device); + } + + public static Vector2 CalculateOrigin(Body b) + { + Vector2 lBound = new Vector2(float.MaxValue); + AABB bounds; + Transform trans; + b.GetTransform(out trans); + + for (int i = 0; i < b.FixtureList.Count; ++i) + { + for (int j = 0; j < b.FixtureList[i].Shape.ChildCount; ++j) + { + b.FixtureList[i].Shape.ComputeAABB(out bounds, ref trans, j); + Vector2.Min(ref lBound, ref bounds.LowerBound, out lBound); + } + } + // calculate body offset from its center and add a 1 pixel border + // because we generate the textures a little bigger than the actual body's fixtures + return ConvertUnits.ToDisplayUnits(b.Position - lBound) + new Vector2(1f); + } + + public void LoadContent(ContentManager contentManager) + { + _materials[MaterialType.Blank] = contentManager.Load("Materials/blank"); + _materials[MaterialType.Dots] = contentManager.Load("Materials/dots"); + _materials[MaterialType.Squares] = contentManager.Load("Materials/squares"); + _materials[MaterialType.Waves] = contentManager.Load("Materials/waves"); + _materials[MaterialType.Pavement] = contentManager.Load("Materials/pavement"); + } + + public Texture2D TextureFromShape(Shape shape, MaterialType type, Color color, float materialScale) + { + switch (shape.ShapeType) + { + case ShapeType.Circle: + return CircleTexture(shape.Radius, type, color, materialScale); + case ShapeType.Polygon: + return TextureFromVertices(((PolygonShape) shape).Vertices, type, color, materialScale); + default: + throw new NotSupportedException("The specified shape type is not supported."); + } + } + + public Texture2D TextureFromVertices(Vertices vertices, MaterialType type, Color color, float materialScale) + { + // copy vertices + Vertices verts = new Vertices(vertices); + + // scale to display units (i.e. pixels) for rendering to texture + Vector2 scale = ConvertUnits.ToDisplayUnits(Vector2.One); + verts.Scale(ref scale); + + // translate the boundingbox center to the texture center + // because we use an orthographic projection for rendering later + AABB vertsBounds = verts.GetCollisionBox(); + verts.Translate(-vertsBounds.Center); + + List decomposedVerts; + if (!verts.IsConvex()) + { + decomposedVerts = EarclipDecomposer.ConvexPartition(verts); + } + else + { + decomposedVerts = new List(); + decomposedVerts.Add(verts); + } + List verticesFill = + new List(decomposedVerts.Count); + + materialScale /= _materials[type].Width; + + for (int i = 0; i < decomposedVerts.Count; ++i) + { + verticesFill.Add(new VertexPositionColorTexture[3 * (decomposedVerts[i].Count - 2)]); + for (int j = 0; j < decomposedVerts[i].Count - 2; ++j) + { + // fill vertices + verticesFill[i][3 * j].Position = new Vector3(decomposedVerts[i][0], 0f); + verticesFill[i][3 * j + 1].Position = new Vector3(decomposedVerts[i].NextVertex(j), 0f); + verticesFill[i][3 * j + 2].Position = new Vector3(decomposedVerts[i].NextVertex(j + 1), 0f); + verticesFill[i][3 * j].TextureCoordinate = decomposedVerts[i][0] * materialScale; + verticesFill[i][3 * j + 1].TextureCoordinate = decomposedVerts[i].NextVertex(j) * materialScale; + verticesFill[i][3 * j + 2].TextureCoordinate = decomposedVerts[i].NextVertex(j + 1) * materialScale; + verticesFill[i][3 * j].Color = + verticesFill[i][3 * j + 1].Color = verticesFill[i][3 * j + 2].Color = color; + } + } + + // calculate outline + VertexPositionColor[] verticesOutline = new VertexPositionColor[2 * verts.Count]; + for (int i = 0; i < verts.Count; ++i) + { + verticesOutline[2 * i].Position = new Vector3(verts[i], 0f); + verticesOutline[2 * i + 1].Position = new Vector3(verts.NextVertex(i), 0f); + verticesOutline[2 * i].Color = verticesOutline[2 * i + 1].Color = Color.Black; + } + + Vector2 vertsSize = new Vector2(vertsBounds.UpperBound.X - vertsBounds.LowerBound.X, + vertsBounds.UpperBound.Y - vertsBounds.LowerBound.Y); + return RenderTexture((int)vertsSize.X, (int)vertsSize.Y, + _materials[type], verticesFill, verticesOutline); + } + + public Texture2D CircleTexture(float radius, MaterialType type, Color color, float materialScale) + { + return EllipseTexture(radius, radius, type, color, materialScale); + } + + public Texture2D EllipseTexture(float radiusX, float radiusY, MaterialType type, Color color, + float materialScale) + { + VertexPositionColorTexture[] verticesFill = new VertexPositionColorTexture[3 * (CircleSegments - 2)]; + VertexPositionColor[] verticesOutline = new VertexPositionColor[2 * CircleSegments]; + const float segmentSize = MathHelper.TwoPi / CircleSegments; + float theta = segmentSize; + + radiusX = ConvertUnits.ToDisplayUnits(radiusX); + radiusY = ConvertUnits.ToDisplayUnits(radiusY); + materialScale /= _materials[type].Width; + + Vector2 start = new Vector2(radiusX, 0f); + + for (int i = 0; i < CircleSegments - 2; ++i) + { + Vector2 p1 = new Vector2(radiusX * (float)Math.Cos(theta), radiusY * (float)Math.Sin(theta)); + Vector2 p2 = new Vector2(radiusX * (float)Math.Cos(theta + segmentSize), + radiusY * (float)Math.Sin(theta + segmentSize)); + // fill vertices + verticesFill[3 * i].Position = new Vector3(start, 0f); + verticesFill[3 * i + 1].Position = new Vector3(p1, 0f); + verticesFill[3 * i + 2].Position = new Vector3(p2, 0f); + verticesFill[3 * i].TextureCoordinate = start * materialScale; + verticesFill[3 * i + 1].TextureCoordinate = p1 * materialScale; + verticesFill[3 * i + 2].TextureCoordinate = p2 * materialScale; + verticesFill[3 * i].Color = verticesFill[3 * i + 1].Color = verticesFill[3 * i + 2].Color = color; + + // outline vertices + if (i == 0) + { + verticesOutline[0].Position = new Vector3(start, 0f); + verticesOutline[1].Position = new Vector3(p1, 0f); + verticesOutline[0].Color = verticesOutline[1].Color = Color.Black; + } + if (i == CircleSegments - 3) + { + verticesOutline[2 * CircleSegments - 2].Position = new Vector3(p2, 0f); + verticesOutline[2 * CircleSegments - 1].Position = new Vector3(start, 0f); + verticesOutline[2 * CircleSegments - 2].Color = + verticesOutline[2 * CircleSegments - 1].Color = Color.Black; + } + verticesOutline[2 * i + 2].Position = new Vector3(p1, 0f); + verticesOutline[2 * i + 3].Position = new Vector3(p2, 0f); + verticesOutline[2 * i + 2].Color = verticesOutline[2 * i + 3].Color = Color.Black; + + theta += segmentSize; + } + + return RenderTexture((int)(radiusX * 2f), (int)(radiusY * 2f), + _materials[type], verticesFill, verticesOutline); + } + + private Texture2D RenderTexture(int width, int height, Texture2D material, + VertexPositionColorTexture[] verticesFill, + VertexPositionColor[] verticesOutline) + { + List fill = new List(1); + fill.Add(verticesFill); + return RenderTexture(width, height, material, fill, verticesOutline); + } + + private Texture2D RenderTexture(int width, int height, Texture2D material, + List verticesFill, + VertexPositionColor[] verticesOutline) + { + Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0f); + PresentationParameters pp = _device.PresentationParameters; + RenderTarget2D texture = new RenderTarget2D(_device, width + 2, height + 2, false, SurfaceFormat.Color, + DepthFormat.None, pp.MultiSampleCount, + RenderTargetUsage.DiscardContents); + _device.RasterizerState = RasterizerState.CullNone; + _device.SamplerStates[0] = SamplerState.LinearWrap; + + _device.SetRenderTarget(texture); + _device.Clear(Color.Transparent); + _effect.Projection = Matrix.CreateOrthographic(width + 2f, -height - 2f, 0f, 1f); + _effect.View = halfPixelOffset; + // render shape; + _effect.TextureEnabled = true; + _effect.Texture = material; + _effect.VertexColorEnabled = true; + _effect.Techniques[0].Passes[0].Apply(); + for (int i = 0; i < verticesFill.Count; ++i) + { + _device.DrawUserPrimitives(PrimitiveType.TriangleList, verticesFill[i], 0, verticesFill[i].Length / 3); + } + // render outline; + _effect.TextureEnabled = false; + _effect.Techniques[0].Passes[0].Apply(); + _device.DrawUserPrimitives(PrimitiveType.LineList, verticesOutline, 0, verticesOutline.Length / 2); + _device.SetRenderTarget(null); + return texture; + } + } +} \ No newline at end of file diff --git a/DrawingSystem/LineBatch.cs b/DrawingSystem/LineBatch.cs new file mode 100644 index 0000000..b8bf4e5 --- /dev/null +++ b/DrawingSystem/LineBatch.cs @@ -0,0 +1,183 @@ +using System; +using FarseerPhysics.Collision.Shapes; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FarseerPhysics.SamplesFramework +{ + public class LineBatch : IDisposable + { + private const int DefaultBufferSize = 500; + + // a basic effect, which contains the shaders that we will use to draw our + // primitives. + private BasicEffect _basicEffect; + + // the device that we will issue draw calls to. + private GraphicsDevice _device; + + // hasBegun is flipped to true once Begin is called, and is used to make + // sure users don't call End before Begin is called. + private bool _hasBegun; + + private bool _isDisposed; + private VertexPositionColor[] _lineVertices; + private int _lineVertsCount; + + public LineBatch(GraphicsDevice graphicsDevice) + : this(graphicsDevice, DefaultBufferSize) + { + } + + public LineBatch(GraphicsDevice graphicsDevice, int bufferSize) + { + if (graphicsDevice == null) + { + throw new ArgumentNullException("graphicsDevice"); + } + _device = graphicsDevice; + + _lineVertices = new VertexPositionColor[bufferSize - bufferSize % 2]; + + // set up a new basic effect, and enable vertex colors. + _basicEffect = new BasicEffect(graphicsDevice); + _basicEffect.VertexColorEnabled = true; + } + + #region IDisposable Members + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_isDisposed) + { + if (_basicEffect != null) + _basicEffect.Dispose(); + + _isDisposed = true; + } + } + + public void Begin(Matrix projection, Matrix view) + { + if (_hasBegun) + { + throw new InvalidOperationException("End must be called before Begin can be called again."); + } + + _device.SamplerStates[0] = SamplerState.AnisotropicClamp; + //tell our basic effect to begin. + _basicEffect.Projection = projection; + _basicEffect.View = view; + _basicEffect.CurrentTechnique.Passes[0].Apply(); + + // flip the error checking boolean. It's now ok to call DrawLineShape, Flush, + // and End. + _hasBegun = true; + } + + public void DrawLineShape(Shape shape) + { + DrawLineShape(shape, Color.Black); + } + + public void DrawLineShape(Shape shape, Color color) + { + if (!_hasBegun) + { + throw new InvalidOperationException("Begin must be called before DrawLineShape can be called."); + } + if (shape.ShapeType != ShapeType.Edge && + shape.ShapeType != ShapeType.Loop) + { + throw new NotSupportedException("The specified shapeType is not supported by LineBatch."); + } + if (shape.ShapeType == ShapeType.Edge) + { + if (_lineVertsCount >= _lineVertices.Length) + { + Flush(); + } + EdgeShape edge = (EdgeShape)shape; + _lineVertices[_lineVertsCount].Position = new Vector3(edge.Vertex1, 0f); + _lineVertices[_lineVertsCount + 1].Position = new Vector3(edge.Vertex2, 0f); + _lineVertices[_lineVertsCount].Color = _lineVertices[_lineVertsCount + 1].Color = color; + _lineVertsCount += 2; + } + else if (shape.ShapeType == ShapeType.Loop) + { + LoopShape loop = (LoopShape)shape; + for (int i = 0; i < loop.Vertices.Count; ++i) + { + if (_lineVertsCount >= _lineVertices.Length) + { + Flush(); + } + _lineVertices[_lineVertsCount].Position = new Vector3(loop.Vertices[i], 0f); + _lineVertices[_lineVertsCount + 1].Position = new Vector3(loop.Vertices.NextVertex(i), 0f); + _lineVertices[_lineVertsCount].Color = _lineVertices[_lineVertsCount + 1].Color = color; + _lineVertsCount += 2; + } + } + } + + public void DrawLine(Vector2 v1, Vector2 v2) + { + DrawLine(v1, v2, Color.Black); + } + + public void DrawLine(Vector2 v1, Vector2 v2, Color color) + { + if (!_hasBegun) + { + throw new InvalidOperationException("Begin must be called before DrawLineShape can be called."); + } + if (_lineVertsCount >= _lineVertices.Length) + { + Flush(); + } + _lineVertices[_lineVertsCount].Position = new Vector3(v1, 0f); + _lineVertices[_lineVertsCount + 1].Position = new Vector3(v2, 0f); + _lineVertices[_lineVertsCount].Color = _lineVertices[_lineVertsCount + 1].Color = color; + _lineVertsCount += 2; + } + + // End is called once all the primitives have been drawn using AddVertex. + // it will call Flush to actually submit the draw call to the graphics card, and + // then tell the basic effect to end. + public void End() + { + if (!_hasBegun) + { + throw new InvalidOperationException("Begin must be called before End can be called."); + } + + // Draw whatever the user wanted us to draw + Flush(); + + _hasBegun = false; + } + + private void Flush() + { + if (!_hasBegun) + { + throw new InvalidOperationException("Begin must be called before Flush can be called."); + } + if (_lineVertsCount >= 2) + { + int primitiveCount = _lineVertsCount / 2; + // submit the draw call to the graphics card + _device.DrawUserPrimitives(PrimitiveType.LineList, _lineVertices, 0, primitiveCount); + _lineVertsCount -= primitiveCount * 2; + } + } + } +} \ No newline at end of file diff --git a/DrawingSystem/Sprite.cs b/DrawingSystem/Sprite.cs new file mode 100644 index 0000000..6341588 --- /dev/null +++ b/DrawingSystem/Sprite.cs @@ -0,0 +1,23 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FarseerPhysics.SamplesFramework +{ + public struct Sprite + { + public Vector2 Origin; + public Texture2D Texture; + + public Sprite(Texture2D texture, Vector2 origin) + { + this.Texture = texture; + this.Origin = origin; + } + + public Sprite(Texture2D sprite) + { + Texture = sprite; + Origin = new Vector2(sprite.Width / 2f, sprite.Height / 2f); + } + } +} \ No newline at end of file diff --git a/Factories/BodyFactory.cs b/Factories/BodyFactory.cs new file mode 100644 index 0000000..d9e2ca1 --- /dev/null +++ b/Factories/BodyFactory.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Common; +using FarseerPhysics.Common.Decomposition; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Factories +{ + public static class BodyFactory + { + public static Body CreateBody(World world) + { + return CreateBody(world, null); + } + + public static Body CreateBody(World world, object userData) + { + Body body = new Body(world, userData); + return body; + } + + public static Body CreateBody(World world, Vector2 position) + { + return CreateBody(world, position, null); + } + + public static Body CreateBody(World world, Vector2 position, object userData) + { + Body body = CreateBody(world, userData); + body.Position = position; + return body; + } + + public static Body CreateEdge(World world, Vector2 start, Vector2 end) + { + return CreateEdge(world, start, end, null); + } + + public static Body CreateEdge(World world, Vector2 start, Vector2 end, object userData) + { + Body body = CreateBody(world); + FixtureFactory.AttachEdge(start, end, body, userData); + return body; + } + + public static Body CreateLoopShape(World world, Vertices vertices) + { + return CreateLoopShape(world, vertices, null); + } + + public static Body CreateLoopShape(World world, Vertices vertices, object userData) + { + return CreateLoopShape(world, vertices, Vector2.Zero, userData); + } + + public static Body CreateLoopShape(World world, Vertices vertices, Vector2 position) + { + return CreateLoopShape(world, vertices, position, null); + } + + public static Body CreateLoopShape(World world, Vertices vertices, Vector2 position, + object userData) + { + Body body = CreateBody(world, position); + FixtureFactory.AttachLoopShape(vertices, body, userData); + return body; + } + + public static Body CreateRectangle(World world, float width, float height, float density) + { + return CreateRectangle(world, width, height, density, null); + } + + public static Body CreateRectangle(World world, float width, float height, float density, object userData) + { + return CreateRectangle(world, width, height, density, Vector2.Zero, userData); + } + + public static Body CreateRectangle(World world, float width, float height, float density, Vector2 position) + { + return CreateRectangle(world, width, height, density, position, null); + } + + public static Body CreateRectangle(World world, float width, float height, float density, Vector2 position, + object userData) + { + if (width <= 0) + throw new ArgumentOutOfRangeException("width", "Width must be more than 0 meters"); + + if (height <= 0) + throw new ArgumentOutOfRangeException("height", "Height must be more than 0 meters"); + + Body newBody = CreateBody(world, position, userData); + Vertices rectangleVertices = PolygonTools.CreateRectangle(width / 2, height / 2); + PolygonShape rectangleShape = new PolygonShape(rectangleVertices, density); + newBody.CreateFixture(rectangleShape, userData); + + return newBody; + } + + public static Body CreateCircle(World world, float radius, float density) + { + return CreateCircle(world, radius, density, null); + } + + public static Body CreateCircle(World world, float radius, float density, object userData) + { + return CreateCircle(world, radius, density, Vector2.Zero, userData); + } + + public static Body CreateCircle(World world, float radius, float density, Vector2 position) + { + return CreateCircle(world, radius, density, position, null); + } + + public static Body CreateCircle(World world, float radius, float density, Vector2 position, object userData) + { + Body body = CreateBody(world, position); + FixtureFactory.AttachCircle(radius, density, body, userData); + return body; + } + + public static Body CreateEllipse(World world, float xRadius, float yRadius, int edges, float density) + { + return CreateEllipse(world, xRadius, yRadius, edges, density, null); + } + + public static Body CreateEllipse(World world, float xRadius, float yRadius, int edges, float density, + object userData) + { + return CreateEllipse(world, xRadius, yRadius, edges, density, Vector2.Zero, userData); + } + + public static Body CreateEllipse(World world, float xRadius, float yRadius, int edges, float density, + Vector2 position) + { + return CreateEllipse(world, xRadius, yRadius, edges, density, position, null); + } + + public static Body CreateEllipse(World world, float xRadius, float yRadius, int edges, float density, + Vector2 position, object userData) + { + Body body = CreateBody(world, position); + FixtureFactory.AttachEllipse(xRadius, yRadius, edges, density, body, userData); + return body; + } + + public static Body CreatePolygon(World world, Vertices vertices, float density) + { + return CreatePolygon(world, vertices, density, null); + } + + public static Body CreatePolygon(World world, Vertices vertices, float density, object userData) + { + return CreatePolygon(world, vertices, density, Vector2.Zero, userData); + } + + public static Body CreatePolygon(World world, Vertices vertices, float density, Vector2 position) + { + return CreatePolygon(world, vertices, density, position, null); + } + + public static Body CreatePolygon(World world, Vertices vertices, float density, Vector2 position, + object userData) + { + Body body = CreateBody(world, position); + FixtureFactory.AttachPolygon(vertices, density, body, userData); + return body; + } + + public static Body CreateCompoundPolygon(World world, List list, float density) + { + return CreateCompoundPolygon(world, list, density, BodyType.Static); + } + + public static Body CreateCompoundPolygon(World world, List list, float density, + object userData) + { + return CreateCompoundPolygon(world, list, density, Vector2.Zero, userData); + } + + public static Body CreateCompoundPolygon(World world, List list, float density, + Vector2 position) + { + return CreateCompoundPolygon(world, list, density, position, null); + } + + public static Body CreateCompoundPolygon(World world, List list, float density, + Vector2 position, object userData) + { + //We create a single body + Body polygonBody = CreateBody(world, position); + FixtureFactory.AttachCompoundPolygon(list, density, polygonBody, userData); + return polygonBody; + } + + + public static Body CreateGear(World world, float radius, int numberOfTeeth, float tipPercentage, + float toothHeight, float density) + { + return CreateGear(world, radius, numberOfTeeth, tipPercentage, toothHeight, density, null); + } + + public static Body CreateGear(World world, float radius, int numberOfTeeth, float tipPercentage, + float toothHeight, float density, object userData) + { + Vertices gearPolygon = PolygonTools.CreateGear(radius, numberOfTeeth, tipPercentage, toothHeight); + + //Gears can in some cases be convex + if (!gearPolygon.IsConvex()) + { + //Decompose the gear: + List list = EarclipDecomposer.ConvexPartition(gearPolygon); + + return CreateCompoundPolygon(world, list, density, userData); + } + + return CreatePolygon(world, gearPolygon, density, userData); + } + + /// + /// Creates a capsule. + /// Note: Automatically decomposes the capsule if it contains too many vertices (controlled by Settings.MaxPolygonVertices) + /// + /// The world. + /// The height. + /// The top radius. + /// The top edges. + /// The bottom radius. + /// The bottom edges. + /// The density. + /// The position. + /// + public static Body CreateCapsule(World world, float height, float topRadius, int topEdges, + float bottomRadius, + int bottomEdges, float density, Vector2 position, object userData) + { + Vertices verts = PolygonTools.CreateCapsule(height, topRadius, topEdges, bottomRadius, bottomEdges); + + Body body; + + //There are too many vertices in the capsule. We decompose it. + if (verts.Count >= Settings.MaxPolygonVertices) + { + List vertList = EarclipDecomposer.ConvexPartition(verts); + body = CreateCompoundPolygon(world, vertList, density, userData); + body.Position = position; + + return body; + } + + body = CreatePolygon(world, verts, density, userData); + body.Position = position; + + return body; + } + + public static Body CreateCapsule(World world, float height, float topRadius, int topEdges, + float bottomRadius, + int bottomEdges, float density, Vector2 position) + { + return CreateCapsule(world, height, topRadius, topEdges, bottomRadius, bottomEdges, density, position, null); + } + + public static Body CreateCapsule(World world, float height, float endRadius, float density) + { + return CreateCapsule(world, height, endRadius, density, null); + } + + public static Body CreateCapsule(World world, float height, float endRadius, float density, + object userData) + { + //Create the middle rectangle + Vertices rectangle = PolygonTools.CreateRectangle(endRadius, height / 2); + + List list = new List(); + list.Add(rectangle); + + Body body = CreateCompoundPolygon(world, list, density, userData); + + //Create the two circles + CircleShape topCircle = new CircleShape(endRadius, density); + topCircle.Position = new Vector2(0, height / 2); + body.CreateFixture(topCircle, userData); + + CircleShape bottomCircle = new CircleShape(endRadius, density); + bottomCircle.Position = new Vector2(0, -(height / 2)); + body.CreateFixture(bottomCircle, userData); + return body; + } + + /// + /// Creates a rounded rectangle. + /// Note: Automatically decomposes the capsule if it contains too many vertices (controlled by Settings.MaxPolygonVertices) + /// + /// The world. + /// The width. + /// The height. + /// The x radius. + /// The y radius. + /// The segments. + /// The density. + /// The position. + /// + public static Body CreateRoundedRectangle(World world, float width, float height, float xRadius, + float yRadius, + int segments, float density, Vector2 position, + object userData) + { + Vertices verts = PolygonTools.CreateRoundedRectangle(width, height, xRadius, yRadius, segments); + + //There are too many vertices in the capsule. We decompose it. + if (verts.Count >= Settings.MaxPolygonVertices) + { + List vertList = EarclipDecomposer.ConvexPartition(verts); + Body body = CreateCompoundPolygon(world, vertList, density, userData); + body.Position = position; + return body; + } + + return CreatePolygon(world, verts, density); + } + + public static Body CreateRoundedRectangle(World world, float width, float height, float xRadius, + float yRadius, + int segments, float density, Vector2 position) + { + return CreateRoundedRectangle(world, width, height, xRadius, yRadius, segments, density, position, null); + } + + public static Body CreateRoundedRectangle(World world, float width, float height, float xRadius, + float yRadius, + int segments, float density) + { + return CreateRoundedRectangle(world, width, height, xRadius, yRadius, segments, density, null); + } + + public static Body CreateRoundedRectangle(World world, float width, float height, float xRadius, + float yRadius, + int segments, float density, object userData) + { + return CreateRoundedRectangle(world, width, height, xRadius, yRadius, segments, density, Vector2.Zero, + userData); + } + + public static BreakableBody CreateBreakableBody(World world, Vertices vertices, float density) + { + return CreateBreakableBody(world, vertices, density, null); + } + + public static BreakableBody CreateBreakableBody(World world, Vertices vertices, float density, object userData) + { + return CreateBreakableBody(world, vertices, density, Vector2.Zero, userData); + } + + /// + /// Creates a breakable body. You would want to remove collinear points before using this. + /// + /// The world. + /// The vertices. + /// The density. + /// The position. + /// + public static BreakableBody CreateBreakableBody(World world, Vertices vertices, float density, Vector2 position, + object userData) + { + List triangles = EarclipDecomposer.ConvexPartition(vertices); + + BreakableBody breakableBody = new BreakableBody(triangles, world, density, userData); + breakableBody.MainBody.Position = position; + world.AddBreakableBody(breakableBody); + + return breakableBody; + } + + public static BreakableBody CreateBreakableBody(World world, Vertices vertices, float density, Vector2 position) + { + return CreateBreakableBody(world, vertices, density, position, null); + } + + public static Body CreateLineArc(World world, float radians, int sides, float radius, Vector2 position, + float angle, bool closed) + { + Body body = CreateBody(world); + FixtureFactory.AttachLineArc(radians, sides, radius, position, angle, closed, body); + return body; + } + + public static Body CreateSolidArc(World world, float density, float radians, int sides, float radius, + Vector2 position, float angle) + { + Body body = CreateBody(world); + FixtureFactory.AttachSolidArc(density, radians, sides, radius, position, angle, body); + return body; + } + } +} \ No newline at end of file diff --git a/Factories/FixtureFactory.cs b/Factories/FixtureFactory.cs new file mode 100644 index 0000000..4043d3f --- /dev/null +++ b/Factories/FixtureFactory.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Common; +using FarseerPhysics.Common.Decomposition; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Factories +{ + /// + /// An easy to use factory for creating bodies + /// + public static class FixtureFactory + { + public static Fixture AttachEdge(Vector2 start, Vector2 end, Body body) + { + return AttachEdge(start, end, body, null); + } + + public static Fixture AttachEdge(Vector2 start, Vector2 end, Body body, object userData) + { + EdgeShape edgeShape = new EdgeShape(start, end); + return body.CreateFixture(edgeShape, userData); + } + + public static Fixture AttachLoopShape(Vertices vertices, Body body) + { + return AttachLoopShape(vertices, body, null); + } + + public static Fixture AttachLoopShape(Vertices vertices, Body body, object userData) + { + LoopShape shape = new LoopShape(vertices); + return body.CreateFixture(shape, userData); + } + + public static Fixture AttachRectangle(float width, float height, float density, Vector2 offset, Body body, + object userData) + { + Vertices rectangleVertices = PolygonTools.CreateRectangle(width / 2, height / 2); + rectangleVertices.Translate(ref offset); + PolygonShape rectangleShape = new PolygonShape(rectangleVertices, density); + return body.CreateFixture(rectangleShape, userData); + } + + public static Fixture AttachRectangle(float width, float height, float density, Vector2 offset, Body body) + { + return AttachRectangle(width, height, density, offset, body, null); + } + + public static Fixture AttachCircle(float radius, float density, Body body) + { + return AttachCircle(radius, density, body, null); + } + + public static Fixture AttachCircle(float radius, float density, Body body, object userData) + { + if (radius <= 0) + throw new ArgumentOutOfRangeException("radius", "Radius must be more than 0 meters"); + + CircleShape circleShape = new CircleShape(radius, density); + return body.CreateFixture(circleShape, userData); + } + + public static Fixture AttachCircle(float radius, float density, Body body, Vector2 offset) + { + return AttachCircle(radius, density, body, offset, null); + } + + public static Fixture AttachCircle(float radius, float density, Body body, Vector2 offset, object userData) + { + if (radius <= 0) + throw new ArgumentOutOfRangeException("radius", "Radius must be more than 0 meters"); + + CircleShape circleShape = new CircleShape(radius, density); + circleShape.Position = offset; + return body.CreateFixture(circleShape, userData); + } + + public static Fixture AttachPolygon(Vertices vertices, float density, Body body) + { + return AttachPolygon(vertices, density, body, null); + } + + public static Fixture AttachPolygon(Vertices vertices, float density, Body body, object userData) + { + if (vertices.Count <= 1) + throw new ArgumentOutOfRangeException("vertices", "Too few points to be a polygon"); + + PolygonShape polygon = new PolygonShape(vertices, density); + return body.CreateFixture(polygon, userData); + } + + public static Fixture AttachEllipse(float xRadius, float yRadius, int edges, float density, Body body) + { + return AttachEllipse(xRadius, yRadius, edges, density, body, null); + } + + public static Fixture AttachEllipse(float xRadius, float yRadius, int edges, float density, Body body, + object userData) + { + if (xRadius <= 0) + throw new ArgumentOutOfRangeException("xRadius", "X-radius must be more than 0"); + + if (yRadius <= 0) + throw new ArgumentOutOfRangeException("yRadius", "Y-radius must be more than 0"); + + Vertices ellipseVertices = PolygonTools.CreateEllipse(xRadius, yRadius, edges); + PolygonShape polygonShape = new PolygonShape(ellipseVertices, density); + return body.CreateFixture(polygonShape, userData); + } + + public static List AttachCompoundPolygon(List list, float density, Body body) + { + return AttachCompoundPolygon(list, density, body, null); + } + + public static List AttachCompoundPolygon(List list, float density, Body body, object userData) + { + List res = new List(list.Count); + + //Then we create several fixtures using the body + foreach (Vertices vertices in list) + { + if (vertices.Count == 2) + { + EdgeShape shape = new EdgeShape(vertices[0], vertices[1]); + res.Add(body.CreateFixture(shape, userData)); + } + else + { + PolygonShape shape = new PolygonShape(vertices, density); + res.Add(body.CreateFixture(shape, userData)); + } + } + + return res; + } + + public static List AttachLineArc(float radians, int sides, float radius, Vector2 position, float angle, + bool closed, Body body) + { + Vertices arc = PolygonTools.CreateArc(radians, sides, radius); + arc.Rotate((MathHelper.Pi - radians) / 2 + angle); + arc.Translate(ref position); + + List fixtures = new List(arc.Count); + + if (closed) + { + fixtures.Add(AttachLoopShape(arc, body)); + } + + for (int i = 1; i < arc.Count; i++) + { + fixtures.Add(AttachEdge(arc[i], arc[i - 1], body)); + } + + return fixtures; + } + + public static List AttachSolidArc(float density, float radians, int sides, float radius, + Vector2 position, float angle, Body body) + { + Vertices arc = PolygonTools.CreateArc(radians, sides, radius); + arc.Rotate((MathHelper.Pi - radians) / 2 + angle); + + arc.Translate(ref position); + + //Close the arc + arc.Add(arc[0]); + + List triangles = EarclipDecomposer.ConvexPartition(arc); + + return AttachCompoundPolygon(triangles, density, body); + } + } +} \ No newline at end of file diff --git a/Factories/JointFactory.cs b/Factories/JointFactory.cs new file mode 100644 index 0000000..566b375 --- /dev/null +++ b/Factories/JointFactory.cs @@ -0,0 +1,288 @@ +using FarseerPhysics.Dynamics; +using FarseerPhysics.Dynamics.Joints; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Factories +{ + /// + /// An easy to use factory for using joints. + /// + public static class JointFactory + { + #region Revolute Joint + + /// + /// Creates a revolute joint. + /// + /// + /// + /// The anchor of bodyB in local coordinates + /// + public static RevoluteJoint CreateRevoluteJoint(Body bodyA, Body bodyB, Vector2 localAnchorB) + { + Vector2 localanchorA = bodyA.GetLocalPoint(bodyB.GetWorldPoint(localAnchorB)); + RevoluteJoint joint = new RevoluteJoint(bodyA, bodyB, localanchorA, localAnchorB); + return joint; + } + + /// + /// Creates a revolute joint and adds it to the world + /// + /// + /// + /// + /// + /// + public static RevoluteJoint CreateRevoluteJoint(World world, Body bodyA, Body bodyB, Vector2 anchor) + { + RevoluteJoint joint = CreateRevoluteJoint(bodyA, bodyB, anchor); + world.AddJoint(joint); + return joint; + } + + /// + /// Creates the fixed revolute joint. + /// + /// The world. + /// The body. + /// The body anchor. + /// The world anchor. + /// + public static FixedRevoluteJoint CreateFixedRevoluteJoint(World world, Body body, Vector2 bodyAnchor, + Vector2 worldAnchor) + { + FixedRevoluteJoint fixedRevoluteJoint = new FixedRevoluteJoint(body, bodyAnchor, worldAnchor); + world.AddJoint(fixedRevoluteJoint); + return fixedRevoluteJoint; + } + + #endregion + + #region Weld Joint + + /// + /// Creates a weld joint + /// + /// + /// + /// + /// + public static WeldJoint CreateWeldJoint(Body bodyA, Body bodyB, Vector2 localAnchor) + { + WeldJoint joint = new WeldJoint(bodyA, bodyB, bodyA.GetLocalPoint(localAnchor), + bodyB.GetLocalPoint(localAnchor)); + return joint; + } + + /// + /// Creates a weld joint and adds it to the world + /// + /// + /// + /// + /// + /// + public static WeldJoint CreateWeldJoint(World world, Body bodyA, Body bodyB, Vector2 localanchorB) + { + WeldJoint joint = CreateWeldJoint(bodyA, bodyB, localanchorB); + world.AddJoint(joint); + return joint; + } + + public static WeldJoint CreateWeldJoint(World world, Body bodyA, Body bodyB, Vector2 localAnchorA, + Vector2 localAnchorB) + { + WeldJoint weldJoint = new WeldJoint(bodyA, bodyB, localAnchorA, localAnchorB); + world.AddJoint(weldJoint); + return weldJoint; + } + + #endregion + + #region Prismatic Joint + + /// + /// Creates a prsimatic joint + /// + /// + /// + /// + /// + /// + public static PrismaticJoint CreatePrismaticJoint(Body bodyA, Body bodyB, Vector2 localanchorB, Vector2 axis) + { + Vector2 localanchorA = bodyA.GetLocalPoint(bodyB.GetWorldPoint(localanchorB)); + PrismaticJoint joint = new PrismaticJoint(bodyA, bodyB, localanchorA, localanchorB, axis); + return joint; + } + + /// + /// Creates a prismatic joint and adds it to the world + /// + /// + /// + /// + /// + /// + /// + public static PrismaticJoint CreatePrismaticJoint(World world, Body bodyA, Body bodyB, Vector2 localanchorB, + Vector2 axis) + { + PrismaticJoint joint = CreatePrismaticJoint(bodyA, bodyB, localanchorB, axis); + world.AddJoint(joint); + return joint; + } + + public static FixedPrismaticJoint CreateFixedPrismaticJoint(World world, Body body, Vector2 worldAnchor, + Vector2 axis) + { + FixedPrismaticJoint joint = new FixedPrismaticJoint(body, worldAnchor, axis); + world.AddJoint(joint); + return joint; + } + + #endregion + + #region Line Joint + + /// + /// Creates a line joint + /// + /// + /// + /// + /// + /// + public static LineJoint CreateLineJoint(Body bodyA, Body bodyB, Vector2 anchor, Vector2 axis) + { + LineJoint joint = new LineJoint(bodyA, bodyB, anchor, axis); + return joint; + } + + /// + /// Creates a line joint and adds it to the world + /// + /// + /// + /// + /// + /// + /// + public static LineJoint CreateLineJoint(World world, Body bodyA, Body bodyB, Vector2 localanchorB, Vector2 axis) + { + LineJoint joint = CreateLineJoint(bodyA, bodyB, localanchorB, axis); + world.AddJoint(joint); + return joint; + } + + #endregion + + #region Angle Joint + + /// + /// Creates an angle joint. + /// + /// The world. + /// The first body. + /// The second body. + /// + public static AngleJoint CreateAngleJoint(World world, Body bodyA, Body bodyB) + { + AngleJoint angleJoint = new AngleJoint(bodyA, bodyB); + world.AddJoint(angleJoint); + + return angleJoint; + } + + /// + /// Creates a fixed angle joint. + /// + /// The world. + /// The body. + /// + public static FixedAngleJoint CreateFixedAngleJoint(World world, Body body) + { + FixedAngleJoint angleJoint = new FixedAngleJoint(body); + world.AddJoint(angleJoint); + + return angleJoint; + } + + #endregion + + #region Distance Joint + + public static DistanceJoint CreateDistanceJoint(World world, Body bodyA, Body bodyB, Vector2 anchorA, + Vector2 anchorB) + { + DistanceJoint distanceJoint = new DistanceJoint(bodyA, bodyB, anchorA, anchorB); + world.AddJoint(distanceJoint); + return distanceJoint; + } + + public static FixedDistanceJoint CreateFixedDistanceJoint(World world, Body body, Vector2 localAnchor, + Vector2 worldAnchor) + { + FixedDistanceJoint distanceJoint = new FixedDistanceJoint(body, localAnchor, worldAnchor); + world.AddJoint(distanceJoint); + return distanceJoint; + } + + #endregion + + #region Friction Joint + + public static FrictionJoint CreateFrictionJoint(World world, Body bodyA, Body bodyB, Vector2 anchorA, + Vector2 anchorB) + { + FrictionJoint frictionJoint = new FrictionJoint(bodyA, bodyB, anchorA, anchorB); + world.AddJoint(frictionJoint); + return frictionJoint; + } + + public static FixedFrictionJoint CreateFixedFrictionJoint(World world, Body body, Vector2 bodyAnchor) + { + FixedFrictionJoint frictionJoint = new FixedFrictionJoint(body, bodyAnchor); + world.AddJoint(frictionJoint); + return frictionJoint; + } + + #endregion + + #region Gear Joint + + public static GearJoint CreateGearJoint(World world, Joint jointA, Joint jointB, float ratio) + { + GearJoint gearJoint = new GearJoint(jointA, jointB, ratio); + world.AddJoint(gearJoint); + return gearJoint; + } + + #endregion + + #region Pulley Joint + + public static PulleyJoint CreatePulleyJoint(World world, Body bodyA, Body bodyB, Vector2 groundAnchorA, + Vector2 groundAnchorB, Vector2 anchorA, Vector2 anchorB, float ratio) + { + PulleyJoint pulleyJoint = new PulleyJoint(bodyA, bodyB, groundAnchorA, groundAnchorB, anchorA, anchorB, + ratio); + world.AddJoint(pulleyJoint); + return pulleyJoint; + } + + #endregion + + #region Slider Joint + + public static SliderJoint CreateSliderJoint(World world, Body bodyA, Body bodyB, Vector2 anchorA, + Vector2 anchorB, float minLength, float maxLength) + { + SliderJoint sliderJoint = new SliderJoint(bodyA, bodyB, anchorA, anchorB, minLength, maxLength); + world.AddJoint(sliderJoint); + return sliderJoint; + } + + #endregion + } +} \ No newline at end of file diff --git a/Factories/LinkFactory.cs b/Factories/LinkFactory.cs new file mode 100644 index 0000000..9411d74 --- /dev/null +++ b/Factories/LinkFactory.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using FarseerPhysics.Collision.Shapes; +using FarseerPhysics.Common; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.Factories +{ + public static class LinkFactory + { + /// + /// Creates a chain. + /// + /// The world. + /// The start. + /// The end. + /// The width. + /// The height. + /// if set to true [fix start]. + /// if set to true [fix end]. + /// The number of links. + /// The link density. + /// + public static Path CreateChain(World world, Vector2 start, Vector2 end, float linkWidth, float linkHeight, + bool fixStart, bool fixEnd, int numberOfLinks, float linkDensity) + { + //Chain start / end + Path path = new Path(); + path.Add(start); + path.Add(end); + + //A single chainlink + PolygonShape shape = new PolygonShape(PolygonTools.CreateRectangle(linkWidth, linkHeight), linkDensity); + + //Use PathManager to create all the chainlinks based on the chainlink created before. + List chainLinks = PathManager.EvenlyDistributeShapesAlongPath(world, path, shape, BodyType.Dynamic, + numberOfLinks); + + if (fixStart) + { + //Fix the first chainlink to the world + JointFactory.CreateFixedRevoluteJoint(world, chainLinks[0], new Vector2(0, -(linkHeight / 2)), + chainLinks[0].Position); + } + + if (fixEnd) + { + //Fix the last chainlink to the world + JointFactory.CreateFixedRevoluteJoint(world, chainLinks[chainLinks.Count - 1], + new Vector2(0, (linkHeight / 2)), + chainLinks[chainLinks.Count - 1].Position); + } + + //Attach all the chainlinks together with a revolute joint + PathManager.AttachBodiesWithRevoluteJoint(world, chainLinks, new Vector2(0, -linkHeight), + new Vector2(0, linkHeight), + false, false); + + return (path); + } + } +} \ No newline at end of file diff --git a/Factories/Prompt.cs b/Factories/Prompt.cs new file mode 100644 index 0000000..2b85228 --- /dev/null +++ b/Factories/Prompt.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Axios.Factories +{ + public static class Prompt + { + // Copied from http://stackoverflow.com/a/17260476/195722 + // Written by Blaze Phoenix + /// + /// Creates a rounded rectangle + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Texture2D CreateRoundedRectangleTexture(this GraphicsDevice graphics, int width, int height, int borderThickness, int borderRadius, int borderShadow, List backgroundColors, List borderColors, float initialShadowIntensity, float finalShadowIntensity) + { + if (backgroundColors == null || backgroundColors.Count == 0) throw new ArgumentException("Must define at least one background color (up to four)."); + if (borderColors == null || borderColors.Count == 0) throw new ArgumentException("Must define at least one border color (up to three)."); + if (borderRadius < 1) throw new ArgumentException("Must define a border radius (rounds off edges)."); + if (borderThickness < 1) throw new ArgumentException("Must define border thikness."); + if (borderThickness + borderRadius > height / 2 || borderThickness + borderRadius > width / 2) throw new ArgumentException("Border will be too thick and/or rounded to fit on the texture."); + if (borderShadow > borderRadius) throw new ArgumentException("Border shadow must be lesser in magnitude than the border radius (suggeted: shadow <= 0.25 * radius)."); + + Texture2D texture = new Texture2D(graphics, width, height, false, SurfaceFormat.Color); + Color[] color = new Color[width * height]; + + for (int x = 0; x < texture.Width; x++) + { + for (int y = 0; y < texture.Height; y++) + { + switch (backgroundColors.Count) + { + case 4: + Color leftColor0 = Color.Lerp(backgroundColors[0], backgroundColors[1], ((float)y / (width - 1))); + Color rightColor0 = Color.Lerp(backgroundColors[2], backgroundColors[3], ((float)y / (height - 1))); + color[x + width * y] = Color.Lerp(leftColor0, rightColor0, ((float)x / (width - 1))); + break; + case 3: + Color leftColor1 = Color.Lerp(backgroundColors[0], backgroundColors[1], ((float)y / (width - 1))); + Color rightColor1 = Color.Lerp(backgroundColors[1], backgroundColors[2], ((float)y / (height - 1))); + color[x + width * y] = Color.Lerp(leftColor1, rightColor1, ((float)x / (width - 1))); + break; + case 2: + color[x + width * y] = Color.Lerp(backgroundColors[0], backgroundColors[1], ((float)x / (width - 1))); + break; + default: + color[x + width * y] = backgroundColors[0]; + break; + } + + color[x + width * y] = ColorBorder(x, y, width, height, borderThickness, borderRadius, borderShadow, color[x + width * y], borderColors, initialShadowIntensity, finalShadowIntensity); + } + } + + texture.SetData(color); + return texture; + } + + private static Color ColorBorder(int x, int y, int width, int height, int borderThickness, int borderRadius, int borderShadow, Color initialColor, List borderColors, float initialShadowIntensity, float finalShadowIntensity) + { + Rectangle internalRectangle = new Rectangle((borderThickness + borderRadius), (borderThickness + borderRadius), width - 2 * (borderThickness + borderRadius), height - 2 * (borderThickness + borderRadius)); + + if (internalRectangle.Contains(x, y)) return initialColor; + + Vector2 origin = Vector2.Zero; + Vector2 point = new Vector2(x, y); + + if (x < borderThickness + borderRadius) + { + if (y < borderRadius + borderThickness) + origin = new Vector2(borderRadius + borderThickness, borderRadius + borderThickness); + else if (y > height - (borderRadius + borderThickness)) + origin = new Vector2(borderRadius + borderThickness, height - (borderRadius + borderThickness)); + else + origin = new Vector2(borderRadius + borderThickness, y); + } + else if (x > width - (borderRadius + borderThickness)) + { + if (y < borderRadius + borderThickness) + origin = new Vector2(width - (borderRadius + borderThickness), borderRadius + borderThickness); + else if (y > height - (borderRadius + borderThickness)) + origin = new Vector2(width - (borderRadius + borderThickness), height - (borderRadius + borderThickness)); + else + origin = new Vector2(width - (borderRadius + borderThickness), y); + } + else + { + if (y < borderRadius + borderThickness) + origin = new Vector2(x, borderRadius + borderThickness); + else if (y > height - (borderRadius + borderThickness)) + origin = new Vector2(x, height - (borderRadius + borderThickness)); + } + + if (!origin.Equals(Vector2.Zero)) + { + float distance = Vector2.Distance(point, origin); + + if (distance > borderRadius + borderThickness + 1) + { + return Color.Transparent; + } + else if (distance > borderRadius + 1) + { + if (borderColors.Count > 2) + { + float modNum = distance - borderRadius; + + if (modNum < borderThickness / 2) + { + return Color.Lerp(borderColors[2], borderColors[1], (float)((modNum) / (borderThickness / 2.0))); + } + else + { + return Color.Lerp(borderColors[1], borderColors[0], (float)((modNum - (borderThickness / 2.0)) / (borderThickness / 2.0))); + } + } + + + if (borderColors.Count > 0) + return borderColors[0]; + } + else if (distance > borderRadius - borderShadow + 1) + { + float mod = (distance - (borderRadius - borderShadow)) / borderShadow; + float shadowDiff = initialShadowIntensity - finalShadowIntensity; + return DarkenColor(initialColor, ((shadowDiff * mod) + finalShadowIntensity)); + } + } + + return initialColor; + } + + private static Color DarkenColor(Color color, float shadowIntensity) + { + return Color.Lerp(color, Color.Black, shadowIntensity); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7b7f1e9 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ae-physics")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ae-physics")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("68607752-5f66-44d4-a8a2-b3e6af81f411")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScreenSystem/Camera2D.cs b/ScreenSystem/Camera2D.cs new file mode 100644 index 0000000..badbe32 --- /dev/null +++ b/ScreenSystem/Camera2D.cs @@ -0,0 +1,387 @@ +using System; +using FarseerPhysics.Dynamics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FarseerPhysics.SamplesFramework +{ + public class Camera2D + { + private const float _minZoom = 0.02f; + private const float _maxZoom = 20f; + private static GraphicsDevice _graphics; + + private Matrix _batchView; + + private Vector2 _currentPosition; + + private float _currentRotation; + + private float _currentZoom; + private Vector2 _maxPosition; + private float _maxRotation; + private Vector2 _minPosition; + private float _minRotation; + private bool _positionTracking; + private Matrix _projection; + private bool _rotationTracking; + private Vector2 _targetPosition; + private float _targetRotation; + private Body _trackingBody; + private Vector2 _translateCenter; + private Matrix _view; + + /*private Vector2 _viewCenter; + + public Vector2 ViewCenter + { + get { return ConvertUnits.ToSimUnits(_viewCenter); } + set + { + _translateCenter = ConvertUnits.ToDisplayUnits(value); + Resize(); + } + } + + + private void Resize() + { + _batchView = Matrix.CreateTranslation(new Vector3(-ViewCenter.X, -ViewCenter.Y, 0)) * Matrix.CreateScale(Zoom); + }*/ + + /// + /// The constructor for the Camera2D class. + /// + /// + public Camera2D(GraphicsDevice graphics) + { + _graphics = graphics; + _projection = Matrix.CreateOrthographicOffCenter(0f, ConvertUnits.ToSimUnits(_graphics.Viewport.Width), + ConvertUnits.ToSimUnits(_graphics.Viewport.Height), 0f, 0f, + 1f); + _view = Matrix.Identity; + _batchView = Matrix.Identity; + + _translateCenter = new Vector2(ConvertUnits.ToSimUnits(_graphics.Viewport.Width / 2f), + ConvertUnits.ToSimUnits(_graphics.Viewport.Height / 2f)); + + ResetCamera(); + } + + public Matrix View + { + get { return _batchView; } + } + + public Matrix SimView + { + get { return _view; } + } + + public Matrix SimProjection + { + get { return _projection; } + } + + /// + /// The current position of the camera. + /// + public Vector2 Position + { + get { return ConvertUnits.ToDisplayUnits(_currentPosition); } + set + { + _targetPosition = ConvertUnits.ToSimUnits(value); + if (_minPosition != _maxPosition) + { + Vector2.Clamp(ref _targetPosition, ref _minPosition, ref _maxPosition, out _targetPosition); + } + } + } + + /// + /// The furthest up, and the furthest left the camera can go. + /// if this value equals maxPosition, then no clamping will be + /// applied (unless you override that function). + /// + public Vector2 MinPosition + { + get { return ConvertUnits.ToDisplayUnits(_minPosition); } + set { _minPosition = ConvertUnits.ToSimUnits(value); } + } + + /// + /// the furthest down, and the furthest right the camera will go. + /// if this value equals minPosition, then no clamping will be + /// applied (unless you override that function). + /// + public Vector2 MaxPosition + { + get { return ConvertUnits.ToDisplayUnits(_maxPosition); } + set { _maxPosition = ConvertUnits.ToSimUnits(value); } + } + + /// + /// The current rotation of the camera in radians. + /// + public float Rotation + { + get { return _currentRotation; } + set + { + _targetRotation = value % MathHelper.TwoPi; + if (_minRotation != _maxRotation) + { + _targetRotation = MathHelper.Clamp(_targetRotation, _minRotation, _maxRotation); + } + } + } + + /// + /// Gets or sets the minimum rotation in radians. + /// + /// The min rotation. + public float MinRotation + { + get { return _minRotation; } + set { _minRotation = MathHelper.Clamp(value, -MathHelper.Pi, 0f); } + } + + /// + /// Gets or sets the maximum rotation in radians. + /// + /// The max rotation. + public float MaxRotation + { + get { return _maxRotation; } + set { _maxRotation = MathHelper.Clamp(value, 0f, MathHelper.Pi); } + } + + /// + /// The current rotation of the camera in radians. + /// + public float Zoom + { + get { return _currentZoom; } + set + { + _currentZoom = value; + _currentZoom = MathHelper.Clamp(_currentZoom, _minZoom, _maxZoom); + } + } + + /// + /// the body that this camera is currently tracking. + /// Null if not tracking any. + /// + public Body TrackingBody + { + get { return _trackingBody; } + set + { + _trackingBody = value; + if (_trackingBody != null) + { + _positionTracking = true; + } + } + } + + public bool EnablePositionTracking + { + get { return _positionTracking; } + set + { + if (value && _trackingBody != null) + { + _positionTracking = true; + } + else + { + _positionTracking = false; + } + } + } + + public bool EnableRotationTracking + { + get { return _rotationTracking; } + set + { + if (value && _trackingBody != null) + { + _rotationTracking = true; + } + else + { + _rotationTracking = false; + } + } + } + + public bool EnableTracking + { + set + { + EnablePositionTracking = value; + EnableRotationTracking = value; + } + } + + public void MoveCamera(Vector2 amount) + { + _currentPosition += amount; + if (_minPosition != _maxPosition) + { + Vector2.Clamp(ref _currentPosition, ref _minPosition, ref _maxPosition, out _currentPosition); + } + _targetPosition = _currentPosition; + _positionTracking = false; + _rotationTracking = false; + } + + public void RotateCamera(float amount) + { + _currentRotation += amount; + if (_minRotation != _maxRotation) + { + _currentRotation = MathHelper.Clamp(_currentRotation, _minRotation, _maxRotation); + } + _targetRotation = _currentRotation; + _positionTracking = false; + _rotationTracking = false; + } + + /// + /// Resets the camera to default values. + /// + public void ResetCamera() + { + _currentPosition = Vector2.Zero; + _targetPosition = Vector2.Zero; + _minPosition = Vector2.Zero; + _maxPosition = Vector2.Zero; + + _currentRotation = 0f; + _targetRotation = 0f; + _minRotation = -MathHelper.Pi; + _maxRotation = MathHelper.Pi; + + _positionTracking = false; + _rotationTracking = false; + + _currentZoom = 1f; + + SetView(); + } + + public void Jump2Target() + { + _currentPosition = _targetPosition; + _currentRotation = _targetRotation; + + SetView(); + } + + private void SetView() + { + Matrix matRotation = Matrix.CreateRotationZ(_currentRotation); + Matrix matZoom = Matrix.CreateScale(_currentZoom); + Vector3 translateCenter = new Vector3(_translateCenter, 0f); + Vector3 translateBody = new Vector3(-_currentPosition, 0f); + + _view = Matrix.CreateTranslation(translateBody) * + matRotation * + matZoom * + Matrix.CreateTranslation(translateCenter); + + translateCenter = ConvertUnits.ToDisplayUnits(translateCenter); + translateBody = ConvertUnits.ToDisplayUnits(translateBody); + + _batchView = Matrix.CreateTranslation(translateBody) * + matRotation * + matZoom * + Matrix.CreateTranslation(translateCenter); + } + + /// + /// Moves the camera forward one timestep. + /// + public void Update(GameTime gameTime) + { + if (_trackingBody != null) + { + if (_positionTracking) + { + _targetPosition = _trackingBody.Position; + if (_minPosition != _maxPosition) + { + Vector2.Clamp(ref _targetPosition, ref _minPosition, ref _maxPosition, out _targetPosition); + } + } + if (_rotationTracking) + { + _targetRotation = -_trackingBody.Rotation % MathHelper.TwoPi; + if (_minRotation != _maxRotation) + { + _targetRotation = MathHelper.Clamp(_targetRotation, _minRotation, _maxRotation); + } + } + } + Vector2 delta = _targetPosition - _currentPosition; + float distance = delta.Length(); + if (distance > 0f) + { + delta /= distance; + } + float inertia; + if (distance < 10f) + { + inertia = (float) Math.Pow(distance / 10.0, 2.0); + } + else + { + inertia = 1f; + } + + float rotDelta = _targetRotation - _currentRotation; + + float rotInertia; + if (Math.Abs(rotDelta) < 5f) + { + rotInertia = (float) Math.Pow(rotDelta / 5.0, 2.0); + } + else + { + rotInertia = 1f; + } + if (Math.Abs(rotDelta) > 0f) + { + rotDelta /= Math.Abs(rotDelta); + } + + _currentPosition += 100f * delta * inertia * (float) gameTime.ElapsedGameTime.TotalSeconds; + _currentRotation += 80f * rotDelta * rotInertia * (float) gameTime.ElapsedGameTime.TotalSeconds; + + SetView(); + } + + public Vector2 ConvertScreenToWorld(Vector2 location) + { + Vector3 t = new Vector3(location, 0); + + t = _graphics.Viewport.Unproject(t, _projection, _view, Matrix.Identity); + + return new Vector2(t.X, t.Y); + } + + public Vector2 ConvertWorldToScreen(Vector2 location) + { + Vector3 t = new Vector3(location, 0); + + t = _graphics.Viewport.Project(t, _projection, _view, Matrix.Identity); + + return new Vector2(t.X, t.Y); + } + } +} \ No newline at end of file diff --git a/ScreenSystem/ConvertUnits.cs b/ScreenSystem/ConvertUnits.cs new file mode 100644 index 0000000..ca34eae --- /dev/null +++ b/ScreenSystem/ConvertUnits.cs @@ -0,0 +1,103 @@ +using Microsoft.Xna.Framework; + +namespace FarseerPhysics.SamplesFramework +{ + /// + /// Convert units between display and simulation units. + /// + public static class ConvertUnits + { + private static float _displayUnitsToSimUnitsRatio = 100f; + private static float _simUnitsToDisplayUnitsRatio = 1 / _displayUnitsToSimUnitsRatio; + + public static void SetDisplayUnitToSimUnitRatio(float displayUnitsPerSimUnit) + { + _displayUnitsToSimUnitsRatio = displayUnitsPerSimUnit; + _simUnitsToDisplayUnitsRatio = 1 / displayUnitsPerSimUnit; + } + + public static float ToDisplayUnits(float simUnits) + { + return simUnits * _displayUnitsToSimUnitsRatio; + } + + public static float ToDisplayUnits(int simUnits) + { + return simUnits * _displayUnitsToSimUnitsRatio; + } + + public static Vector2 ToDisplayUnits(Vector2 simUnits) + { + return simUnits * _displayUnitsToSimUnitsRatio; + } + + public static void ToDisplayUnits(ref Vector2 simUnits, out Vector2 displayUnits) + { + Vector2.Multiply(ref simUnits, _displayUnitsToSimUnitsRatio, out displayUnits); + } + + public static Vector3 ToDisplayUnits(Vector3 simUnits) + { + return simUnits * _displayUnitsToSimUnitsRatio; + } + + public static Vector2 ToDisplayUnits(float x, float y) + { + return new Vector2(x, y) * _displayUnitsToSimUnitsRatio; + } + + public static void ToDisplayUnits(float x, float y, out Vector2 displayUnits) + { + displayUnits = Vector2.Zero; + displayUnits.X = x * _displayUnitsToSimUnitsRatio; + displayUnits.Y = y * _displayUnitsToSimUnitsRatio; + } + + public static float ToSimUnits(float displayUnits) + { + return displayUnits * _simUnitsToDisplayUnitsRatio; + } + + public static float ToSimUnits(double displayUnits) + { + return (float)displayUnits * _simUnitsToDisplayUnitsRatio; + } + + public static float ToSimUnits(int displayUnits) + { + return displayUnits * _simUnitsToDisplayUnitsRatio; + } + + public static Vector2 ToSimUnits(Vector2 displayUnits) + { + return displayUnits * _simUnitsToDisplayUnitsRatio; + } + + public static Vector3 ToSimUnits(Vector3 displayUnits) + { + return displayUnits * _simUnitsToDisplayUnitsRatio; + } + + public static void ToSimUnits(ref Vector2 displayUnits, out Vector2 simUnits) + { + Vector2.Multiply(ref displayUnits, _simUnitsToDisplayUnitsRatio, out simUnits); + } + + public static Vector2 ToSimUnits(float x, float y) + { + return new Vector2(x, y) * _simUnitsToDisplayUnitsRatio; + } + + public static Vector2 ToSimUnits(double x, double y) + { + return new Vector2((float)x, (float)y) * _simUnitsToDisplayUnitsRatio; + } + + public static void ToSimUnits(float x, float y, out Vector2 simUnits) + { + simUnits = Vector2.Zero; + simUnits.X = x * _simUnitsToDisplayUnitsRatio; + simUnits.Y = y * _simUnitsToDisplayUnitsRatio; + } + } +} \ No newline at end of file diff --git a/ScreenSystem/FramerateCounterComponent.cs b/ScreenSystem/FramerateCounterComponent.cs new file mode 100644 index 0000000..a936daa --- /dev/null +++ b/ScreenSystem/FramerateCounterComponent.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using Microsoft.Xna.Framework; +using GameStateManagement; + +namespace FarseerPhysics.SamplesFramework +{ + /// + /// Displays the FPS + /// + public class FrameRateCounter : DrawableGameComponent + { + private TimeSpan _elapsedTime = TimeSpan.Zero; + private NumberFormatInfo _format; + private int _frameCounter; + private int _frameRate; + private Vector2 _position; + private ScreenManager _screenManager; + + public FrameRateCounter(ScreenManager screenManager) + : base(screenManager.Game) + { + _screenManager = screenManager; + _format = new NumberFormatInfo(); + _format.NumberDecimalSeparator = "."; +#if XBOX + _position = new Vector2(55, 35); +#else + _position = new Vector2(30, 25); +#endif + } + + public override void Update(GameTime gameTime) + { + _elapsedTime += gameTime.ElapsedGameTime; + + if (_elapsedTime <= TimeSpan.FromSeconds(1)) return; + + _elapsedTime -= TimeSpan.FromSeconds(1); + _frameRate = _frameCounter; + _frameCounter = 0; + } + + public override void Draw(GameTime gameTime) + { + _frameCounter++; + + string fps = string.Format(_format, "{0} fps", _frameRate); + + _screenManager.SpriteBatch.Begin(); + _screenManager.SpriteBatch.DrawString(_screenManager.Fonts.FrameRateCounterFont, fps, + _position + Vector2.One, Color.Black); + _screenManager.SpriteBatch.DrawString(_screenManager.Fonts.FrameRateCounterFont, fps, + _position, Color.White); + _screenManager.SpriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/ScreenSystem/IDemoScreen.cs b/ScreenSystem/IDemoScreen.cs new file mode 100644 index 0000000..3a0eac3 --- /dev/null +++ b/ScreenSystem/IDemoScreen.cs @@ -0,0 +1,8 @@ +namespace FarseerPhysics.SamplesFramework +{ + public interface IDemoScreen + { + string GetTitle(); + string GetDetails(); + } +} \ No newline at end of file diff --git a/ScreenSystem/InputHelper.cs b/ScreenSystem/InputHelper.cs new file mode 100644 index 0000000..eb62565 --- /dev/null +++ b/ScreenSystem/InputHelper.cs @@ -0,0 +1,473 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Input.Touch; +using FarseerPhysics.SamplesFramework; +using GameStateManagement; + +namespace FarseerPhysics.SamplesFramework2 +{ + /// + /// an enum of all available mouse buttons. + /// + public enum MouseButtons + { + LeftButton, + MiddleButton, + RightButton, + ExtraButton1, + ExtraButton2 + } + + public class InputHelper + { + private readonly List _gestures = new List(); + private GamePadState _currentGamePadState; + private KeyboardState _currentKeyboardState; + private MouseState _currentMouseState; + private GamePadState _currentVirtualState; + + private GamePadState _lastGamePadState; + private KeyboardState _lastKeyboardState; + private MouseState _lastMouseState; + private GamePadState _lastVirtualState; + private bool _handleVirtualStick; + + private Vector2 _cursor; + private bool _cursorIsValid; + private bool _cursorIsVisible; + private bool _cursorMoved; + private Sprite _cursorSprite; + +#if WINDOWS_PHONE + private VirtualStick _phoneStick; + private VirtualButton _phoneA; + private VirtualButton _phoneB; +#endif + + private ScreenManager _manager; + private Viewport _viewport; + + /// + /// Constructs a new input state. + /// + public InputHelper(ScreenManager manager) + { + _currentKeyboardState = new KeyboardState(); + _currentGamePadState = new GamePadState(); + _currentMouseState = new MouseState(); + _currentVirtualState = new GamePadState(); + + _lastKeyboardState = new KeyboardState(); + _lastGamePadState = new GamePadState(); + _lastMouseState = new MouseState(); + _lastVirtualState = new GamePadState(); + + _manager = manager; + + _cursorIsVisible = false; + _cursorMoved = false; +#if WINDOWS_PHONE + _cursorIsValid = false; +#else + _cursorIsValid = true; +#endif + _cursor = Vector2.Zero; + + _handleVirtualStick = false; + } + + public GamePadState GamePadState + { + get { return _currentGamePadState; } + } + + public KeyboardState KeyboardState + { + get { return _currentKeyboardState; } + } + + public MouseState MouseState + { + get { return _currentMouseState; } + } + + public GamePadState VirtualState + { + get { return _currentVirtualState; } + } + + public GamePadState PreviousGamePadState + { + get { return _lastGamePadState; } + } + + public KeyboardState PreviousKeyboardState + { + get { return _lastKeyboardState; } + } + + public MouseState PreviousMouseState + { + get { return _lastMouseState; } + } + + public GamePadState PreviousVirtualState + { + get { return _lastVirtualState; } + } + + public bool ShowCursor + { + get { return _cursorIsVisible && _cursorIsValid; } + set { _cursorIsVisible = value; } + } + + public bool EnableVirtualStick + { + get { return _handleVirtualStick; } + set { _handleVirtualStick = value; } + } + + public Vector2 Cursor + { + get { return _cursor; } + } + + public bool IsCursorMoved + { + get { return _cursorMoved; } + } + + public bool IsCursorValid + { + get { return _cursorIsValid; } + } + + public void LoadContent() + { + + _cursorSprite = new Sprite(_manager.Game.Content.Load("Common/cursor")); +#if WINDOWS_PHONE + // virtual stick content + _phoneStick = new VirtualStick(_manager.Game.Content.Load("Common/socket"), + _manager.Game.Content.Load("Common/stick"), new Vector2(80f, 400f)); + + Texture2D temp = _manager.Game.Content.Load("Common/buttons"); + _phoneA = new VirtualButton(temp, new Vector2(695f, 380f), new Rectangle(0, 0, 40, 40), new Rectangle(0, 40, 40, 40)); + _phoneB = new VirtualButton(temp, new Vector2(745f, 360f), new Rectangle(40, 0, 40, 40), new Rectangle(40, 40, 40, 40)); +#endif + _viewport = _manager.GraphicsDevice.Viewport; + } + + /// + /// Reads the latest state of the keyboard and gamepad and mouse/touchpad. + /// + public void Update(GameTime gameTime) + { + _lastKeyboardState = _currentKeyboardState; + _lastGamePadState = _currentGamePadState; + _lastMouseState = _currentMouseState; + if (_handleVirtualStick) + { + _lastVirtualState = _currentVirtualState; + } + + _currentKeyboardState = Keyboard.GetState(); + _currentGamePadState = GamePad.GetState(PlayerIndex.One); + _currentMouseState = Mouse.GetState(); + + if (_handleVirtualStick) + { +#if XBOX + _currentVirtualState= GamePad.GetState(PlayerIndex.One); +#elif WINDOWS + if (GamePad.GetState(PlayerIndex.One).IsConnected) + { + _currentVirtualState = GamePad.GetState(PlayerIndex.One); + } + else + { + _currentVirtualState = HandleVirtualStickWin(); + } +#elif WINDOWS_PHONE + _currentVirtualState = HandleVirtualStickWP7(); +#endif + } + + _gestures.Clear(); + while (TouchPanel.IsGestureAvailable) + { + _gestures.Add(TouchPanel.ReadGesture()); + } + + // Update cursor + Vector2 oldCursor = _cursor; + if (_currentGamePadState.IsConnected && _currentGamePadState.ThumbSticks.Left != Vector2.Zero) + { + Vector2 temp = _currentGamePadState.ThumbSticks.Left; + _cursor += temp * new Vector2(300f, -300f) * (float)gameTime.ElapsedGameTime.TotalSeconds; + Mouse.SetPosition((int)_cursor.X, (int)_cursor.Y); + } + else + { + _cursor.X = _currentMouseState.X; + _cursor.Y = _currentMouseState.Y; + } + _cursor.X = MathHelper.Clamp(_cursor.X, 0f, _viewport.Width); + _cursor.Y = MathHelper.Clamp(_cursor.Y, 0f, _viewport.Height); + + if (_cursorIsValid && oldCursor != _cursor) + { + _cursorMoved = true; + } + else + { + _cursorMoved = false; + } + +#if WINDOWS + if (_viewport.Bounds.Contains(_currentMouseState.X, _currentMouseState.Y)) + { + _cursorIsValid = true; + } + else + { + _cursorIsValid = false; + } +#elif WINDOWS_PHONE + if (_currentMouseState.LeftButton == ButtonState.Pressed) + { + _cursorIsValid = true; + } + else + { + _cursorIsValid = false; + } +#endif + } + + public void Draw() + { + if (_cursorIsVisible && _cursorIsValid) + { + _manager.SpriteBatch.Begin(); + _manager.SpriteBatch.Draw(_cursorSprite.Texture, _cursor, null, Color.White, 0f, _cursorSprite.Origin, 1f, SpriteEffects.None, 0f); + _manager.SpriteBatch.End(); + } +#if WINDOWS_PHONE + if (_handleVirtualStick) + { + _manager.SpriteBatch.Begin(); + _phoneA.Draw(_manager.SpriteBatch); + _phoneB.Draw(_manager.SpriteBatch); + _phoneStick.Draw(_manager.SpriteBatch); + _manager.SpriteBatch.End(); + } +#endif + } + + private GamePadState HandleVirtualStickWin() + { + Vector2 _leftStick = Vector2.Zero; + List _buttons = new List(); + + if (_currentKeyboardState.IsKeyDown(Keys.A)) + { + _leftStick.X -= 1f; + } + if (_currentKeyboardState.IsKeyDown(Keys.S)) + { + _leftStick.Y -= 1f; + } + if (_currentKeyboardState.IsKeyDown(Keys.D)) + { + _leftStick.X += 1f; + } + if (_currentKeyboardState.IsKeyDown(Keys.W)) + { + _leftStick.Y += 1f; + } + if (_currentKeyboardState.IsKeyDown(Keys.Space)) + { + _buttons.Add(Buttons.A); + } + if (_currentKeyboardState.IsKeyDown(Keys.LeftControl)) + { + _buttons.Add(Buttons.B); + } + if (_leftStick != Vector2.Zero) + { + _leftStick.Normalize(); + } + + return new GamePadState(_leftStick, Vector2.Zero, 0f, 0f, _buttons.ToArray()); + } + + private GamePadState HandleVirtualStickWP7() + { + List _buttons = new List(); + Vector2 _stick = Vector2.Zero; +#if WINDOWS_PHONE + _phoneA.Pressed = false; + _phoneB.Pressed = false; + TouchCollection touchLocations = TouchPanel.GetState(); + foreach (TouchLocation touchLocation in touchLocations) + { + _phoneA.Update(touchLocation); + _phoneB.Update(touchLocation); + _phoneStick.Update(touchLocation); + } + if (_phoneA.Pressed) + { + _buttons.Add(Buttons.A); + } + if (_phoneB.Pressed) + { + _buttons.Add(Buttons.B); + } + _stick = _phoneStick.StickPosition; +#endif + return new GamePadState(_stick, Vector2.Zero, 0f, 0f, _buttons.ToArray()); + } + + /// + /// Helper for checking if a key was newly pressed during this update. + /// + public bool IsNewKeyPress(Keys key) + { + return (_currentKeyboardState.IsKeyDown(key) && + _lastKeyboardState.IsKeyUp(key)); + } + + public bool IsNewKeyRelease(Keys key) + { + return (_lastKeyboardState.IsKeyDown(key) && + _currentKeyboardState.IsKeyUp(key)); + } + + public bool IsNewVirtualButtonPress(Buttons button) + { + return (_lastVirtualState.IsButtonUp(button) && + _currentVirtualState.IsButtonDown(button)); + } + + public bool IsNewVirtualButtonRelease(Buttons button) + { + return (_lastVirtualState.IsButtonDown(button) && + _currentVirtualState.IsButtonUp(button)); + } + + /// + /// Helper for checking if a button was newly pressed during this update. + /// + public bool IsNewButtonPress(Buttons button) + { + return (_currentGamePadState.IsButtonDown(button) && + _lastGamePadState.IsButtonUp(button)); + } + + public bool IsNewButtonRelease(Buttons button) + { + return (_lastGamePadState.IsButtonDown(button) && + _currentGamePadState.IsButtonUp(button)); + } + + /// + /// Helper for checking if a mouse button was newly pressed during this update. + /// + public bool IsNewMouseButtonPress(MouseButtons button) + { + switch (button) + { + case MouseButtons.LeftButton: + return (_currentMouseState.LeftButton == ButtonState.Pressed && + _lastMouseState.LeftButton == ButtonState.Released); + case MouseButtons.RightButton: + return (_currentMouseState.RightButton == ButtonState.Pressed && + _lastMouseState.RightButton == ButtonState.Released); + case MouseButtons.MiddleButton: + return (_currentMouseState.MiddleButton == ButtonState.Pressed && + _lastMouseState.MiddleButton == ButtonState.Released); + case MouseButtons.ExtraButton1: + return (_currentMouseState.XButton1 == ButtonState.Pressed && + _lastMouseState.XButton1 == ButtonState.Released); + case MouseButtons.ExtraButton2: + return (_currentMouseState.XButton2 == ButtonState.Pressed && + _lastMouseState.XButton2 == ButtonState.Released); + default: + return false; + } + } + + + /// + /// Checks if the requested mouse button is released. + /// + /// The button. + public bool IsNewMouseButtonRelease(MouseButtons button) + { + switch (button) + { + case MouseButtons.LeftButton: + return (_lastMouseState.LeftButton == ButtonState.Pressed && + _currentMouseState.LeftButton == ButtonState.Released); + case MouseButtons.RightButton: + return (_lastMouseState.RightButton == ButtonState.Pressed && + _currentMouseState.RightButton == ButtonState.Released); + case MouseButtons.MiddleButton: + return (_lastMouseState.MiddleButton == ButtonState.Pressed && + _currentMouseState.MiddleButton == ButtonState.Released); + case MouseButtons.ExtraButton1: + return (_lastMouseState.XButton1 == ButtonState.Pressed && + _currentMouseState.XButton1 == ButtonState.Released); + case MouseButtons.ExtraButton2: + return (_lastMouseState.XButton2 == ButtonState.Pressed && + _currentMouseState.XButton2 == ButtonState.Released); + default: + return false; + } + } + + /// + /// Checks for a "menu select" input action. + /// + public bool IsMenuSelect() + { + return IsNewKeyPress(Keys.Space) || + IsNewKeyPress(Keys.Enter) || + IsNewButtonPress(Buttons.A) || + IsNewButtonPress(Buttons.Start) || + IsNewMouseButtonPress(MouseButtons.LeftButton); + } + + public bool IsMenuPressed() + { + return _currentKeyboardState.IsKeyDown(Keys.Space) || + _currentKeyboardState.IsKeyDown(Keys.Enter) || + _currentGamePadState.IsButtonDown(Buttons.A) || + _currentGamePadState.IsButtonDown(Buttons.Start) || + _currentMouseState.LeftButton == ButtonState.Pressed; + } + + public bool IsMenuReleased() + { + return IsNewKeyRelease(Keys.Space) || + IsNewKeyRelease(Keys.Enter) || + IsNewButtonRelease(Buttons.A) || + IsNewButtonRelease(Buttons.Start) || + IsNewMouseButtonRelease(MouseButtons.LeftButton); + } + + /// + /// Checks for a "menu cancel" input action. + /// + public bool IsMenuCancel() + { + return IsNewKeyPress(Keys.Escape) || + IsNewButtonPress(Buttons.Back); + } + } +} \ No newline at end of file diff --git a/ScreenSystem/InputState.cs b/ScreenSystem/InputState.cs new file mode 100644 index 0000000..3f1705d --- /dev/null +++ b/ScreenSystem/InputState.cs @@ -0,0 +1,671 @@ +#region File Description +//----------------------------------------------------------------------------- +// InputState.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Input.Touch; +using FarseerPhysics.SamplesFramework; +using Microsoft.Xna.Framework.Graphics; +using System; +#if WINDOWS +using XNACC.BaseTypes; +#endif +using System.Linq; + + +namespace GameStateManagement +{ + + /// + /// an enum of all available mouse buttons. + /// + public enum MouseButtons + { + LeftButton, + MiddleButton, + RightButton, + ExtraButton1, + ExtraButton2 + } + + /// + /// Helper for reading input from keyboard, gamepad, and touch input. This class + /// tracks both the current and previous state of the input devices, and implements + /// query methods for high level input actions such as "move up through the menu" + /// or "pause the game". + /// +#if WINDOWS + public class InputState : IConsoleKeyboard +#else + public class InputState +#endif + { + +#if WINDOWS + #region XNACC + /* + * These are needed for XNACC + * -- Nathan Adams [adamsna@datanethost.net] - 5/26/2012 + */ + + private KeyboardState KeyState; + private List newlyPressedKeys = new List(); + private List heldKeys = new List(); + private Keys[] oldPressedKeys; + private Keys[] newPressedKeys; + + public KeyboardState CurrentKeyboardState + { + get { return KeyState; } + } + + public IList NewlyPressedKeys + { + get { return newlyPressedKeys; } + } + + public IList HeldKeys + { + get { return heldKeys; } + } + + /* + * End XNACC variables + */ + #endregion +#endif + public const int MaxInputs = 4; + + public readonly KeyboardState[] CurrentKeyboardStates; + public readonly GamePadState[] CurrentGamePadStates; + + public readonly KeyboardState[] LastKeyboardStates; + public readonly GamePadState[] LastGamePadStates; + + public readonly bool[] GamePadWasConnected; + + /* + * Needed for virtual stick on WP7 + * -- Nathan Adams [adamsna@datanethost.net] - 4/12/2012 + */ + private GamePadState _currentVirtualState; + private GamePadState _lastVirtualState; + private bool _handleVirtualStick; + /* + * I didn't create an array for the virtual stick because there will only be one + * -- Nathan Adams [adamsna@datanethost.net] - 4/12/2012 + */ + + public bool MoveCursorWithController = false; + + /* + * Adding variables for the cursor + * -- Nathan Adams [adamsna@datanethost.net] - 4/15/2012 + * + */ + private MouseState _currentMouseState; + private MouseState _lastMouseState; + + private Vector2 _cursor; + private bool _cursorIsValid; + private bool _cursorIsVisible; + private bool _cursorMoved; + private Sprite _cursorSprite; + +#if WINDOWS_PHONE + private VirtualStick _phoneStick; + private VirtualButton _phoneA; + private VirtualButton _phoneB; +#endif + + public TouchCollection TouchState; + + public readonly List Gestures = new List(); + + private ScreenManager _manager; + private Viewport _viewport; + + + /// + /// Constructs a new input state. + /// + public InputState(ScreenManager manager) + { + _manager = manager; + CurrentKeyboardStates = new KeyboardState[MaxInputs]; + CurrentGamePadStates = new GamePadState[MaxInputs]; + + LastKeyboardStates = new KeyboardState[MaxInputs]; + LastGamePadStates = new GamePadState[MaxInputs]; + + GamePadWasConnected = new bool[MaxInputs]; + _currentVirtualState = new GamePadState(); + _lastVirtualState = new GamePadState(); + + _cursorIsVisible = false; + _cursorMoved = false; +#if WINDOWS_PHONE + _cursorIsValid = false; +#else + _cursorIsValid = true; +#endif + _cursor = Vector2.Zero; + + _handleVirtualStick = false; + } + + public MouseState MouseState + { + get { return _currentMouseState; } + } + + public GamePadState VirtualState + { + get { return _currentVirtualState; } + } + + public MouseState PreviousMouseState + { + get { return _lastMouseState; } + } + + public GamePadState PreviousVirtualState + { + get { return _lastVirtualState; } + } + + public bool ShowCursor + { + get { return _cursorIsVisible && _cursorIsValid; } + set { _cursorIsVisible = value; } + } + + public bool EnableVirtualStick + { + get { return _handleVirtualStick; } + set { _handleVirtualStick = value; } + } + + public Vector2 Cursor + { + get { return _cursor; } + } + + public bool IsCursorMoved + { + get { return _cursorMoved; } + } + + public bool IsCursorValid + { + get { return _cursorIsValid; } + } + + public void LoadContent() + { + ContentManager man = new ContentManager(_manager.Game.Services, "Content"); + _cursorSprite = new Sprite(man.Load("Common/cursor")); +#if WINDOWS_PHONE + // virtual stick content + _phoneStick = new VirtualStick(man.Load("Common/socket"), + man.Load("Common/stick"), new Vector2(80f, 400f)); + + Texture2D temp = man.Load("Common/buttons"); + _phoneA = new VirtualButton(temp, new Vector2(695f, 380f), new Rectangle(0, 0, 40, 40), new Rectangle(0, 40, 40, 40)); + _phoneB = new VirtualButton(temp, new Vector2(745f, 360f), new Rectangle(40, 0, 40, 40), new Rectangle(40, 40, 40, 40)); +#endif + _viewport = _manager.GraphicsDevice.Viewport; + } + + private GamePadState HandleVirtualStickWin() + { + Vector2 _leftStick = Vector2.Zero; + List _buttons = new List(); + PlayerIndex pout; + if (IsNewKeyPress(Keys.A, PlayerIndex.One, out pout)) + { + _leftStick.X -= 1f; + } + if (IsNewKeyPress(Keys.S, PlayerIndex.One, out pout)) + { + _leftStick.Y -= 1f; + } + if (IsNewKeyPress(Keys.D, PlayerIndex.One, out pout)) + { + _leftStick.X += 1f; + } + if (IsNewKeyPress(Keys.W, PlayerIndex.One, out pout)) + { + _leftStick.Y += 1f; + } + if (IsNewKeyPress(Keys.Space, PlayerIndex.One, out pout)) + { + _buttons.Add(Buttons.A); + } + if (IsNewKeyPress(Keys.LeftControl, PlayerIndex.One, out pout)) + { + _buttons.Add(Buttons.B); + } + if (_leftStick != Vector2.Zero) + { + _leftStick.Normalize(); + } + + return new GamePadState(_leftStick, Vector2.Zero, 0f, 0f, _buttons.ToArray()); + } + + private GamePadState HandleVirtualStickWP7() + { + List _buttons = new List(); + Vector2 _stick = Vector2.Zero; +#if WINDOWS_PHONE + _phoneA.Pressed = false; + _phoneB.Pressed = false; + TouchCollection touchLocations = TouchPanel.GetState(); + foreach (TouchLocation touchLocation in touchLocations) + { + _phoneA.Update(touchLocation); + _phoneB.Update(touchLocation); + _phoneStick.Update(touchLocation); + } + if (_phoneA.Pressed) + { + _buttons.Add(Buttons.A); + } + if (_phoneB.Pressed) + { + _buttons.Add(Buttons.B); + } + _stick = _phoneStick.StickPosition; +#endif + return new GamePadState(_stick, Vector2.Zero, 0f, 0f, _buttons.ToArray()); + } + + public void Draw() + { + if (_cursorIsVisible && _cursorIsValid) + { + _manager.SpriteBatch.Begin(); + _manager.SpriteBatch.Draw(_cursorSprite.Texture, _cursor, null, Color.White, 0f, _cursorSprite.Origin, 1f, SpriteEffects.None, 0f); + _manager.SpriteBatch.End(); + } +#if WINDOWS_PHONE + if (_handleVirtualStick) + { + _manager.SpriteBatch.Begin(); + _phoneA.Draw(_manager.SpriteBatch); + _phoneB.Draw(_manager.SpriteBatch); + _phoneStick.Draw(_manager.SpriteBatch); + _manager.SpriteBatch.End(); + } +#endif + } + + /// + /// Reads the latest state user input. + /// + public void Update(GameTime gameTime) + { + +#if WINDOWS + #region XNACC + KeyState = Keyboard.GetState(); + + oldPressedKeys = newPressedKeys; + newPressedKeys = KeyState.GetPressedKeys(); + + newlyPressedKeys.Clear(); + heldKeys.Clear(); + + foreach (Keys key in newPressedKeys) + { + if (oldPressedKeys.Contains(key)) + heldKeys.Add(key); + else + newlyPressedKeys.Add(key); + } + #endregion +#endif + + //PlayerIndex p; + _lastMouseState = _currentMouseState; + if (_handleVirtualStick) + { + _lastVirtualState = _currentVirtualState; + } + + _currentMouseState = Mouse.GetState(); + + if (_handleVirtualStick) + { +#if XBOX + _currentVirtualState= GamePad.GetState(PlayerIndex.One); +#elif WINDOWS + if (GamePad.GetState(PlayerIndex.One).IsConnected) + { + _currentVirtualState = GamePad.GetState(PlayerIndex.One); + } + else + { + _currentVirtualState = HandleVirtualStickWin(); + } +#elif WINDOWS_PHONE + _currentVirtualState = HandleVirtualStickWP7(); +#endif + } + for (int i = 0; i < MaxInputs; i++) + { + LastKeyboardStates[i] = CurrentKeyboardStates[i]; + LastGamePadStates[i] = CurrentGamePadStates[i]; + + CurrentKeyboardStates[i] = Keyboard.GetState((PlayerIndex)i); + CurrentGamePadStates[i] = GamePad.GetState((PlayerIndex)i); + + // Keep track of whether a gamepad has ever been + // connected, so we can detect if it is unplugged. + if (CurrentGamePadStates[i].IsConnected) + { + GamePadWasConnected[i] = true; + } + } + + // Get the raw touch state from the TouchPanel + TouchState = TouchPanel.GetState(); + + // Read in any detected gestures into our list for the screens to later process + Gestures.Clear(); + while (TouchPanel.IsGestureAvailable) + { + //System.Diagnostics.Debugger.Break(); + Gestures.Add(TouchPanel.ReadGesture()); + } + //System.Diagnostics.Debugger.Break(); + + // Update cursor + Vector2 oldCursor = _cursor; + + if (CurrentGamePadStates[0].IsConnected && CurrentGamePadStates[0].ThumbSticks.Left != Vector2.Zero && MoveCursorWithController) + { + Vector2 temp = CurrentGamePadStates[0].ThumbSticks.Left; + _cursor += temp * new Vector2(300f, -300f) * (float)gameTime.ElapsedGameTime.TotalSeconds; + Mouse.SetPosition((int)_cursor.X, (int)_cursor.Y); + } + else + { + _cursor.X = _currentMouseState.X; + _cursor.Y = _currentMouseState.Y; + } + + //if (this.IsNewKeyPress(Keys.P, PlayerIndex.One, out p)) + // Console.WriteLine(_cursor.ToString()); + + _cursor.X = MathHelper.Clamp(_cursor.X, 0f, _viewport.Width); + _cursor.Y = MathHelper.Clamp(_cursor.Y, 0f, _viewport.Height); + + //if (this.IsNewKeyPress(Keys.P, PlayerIndex.One, out p)) + // Console.WriteLine(_cursor.ToString()); + + if (_cursorIsValid && oldCursor != _cursor) + { + _cursorMoved = true; + } + else + { + _cursorMoved = false; + } + +#if WINDOWS + if (_viewport.Bounds.Contains(_currentMouseState.X, _currentMouseState.Y)) + { + _cursorIsValid = true; + } + else + { + _cursorIsValid = false; + } +#elif WINDOWS_PHONE + if (_currentMouseState.LeftButton == ButtonState.Pressed) + { + _cursorIsValid = true; + } + else + { + _cursorIsValid = false; + } +#endif + + //if (this.IsNewKeyPress(Keys.P, PlayerIndex.One, out p)) + // Console.WriteLine(_viewport.ToString()); + } + + + /// + /// Helper for checking if a key was pressed during this update. The + /// controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a keypress + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsKeyPressed(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return CurrentKeyboardStates[i].IsKeyDown(key); + } + else + { + // Accept input from any player. + return (IsKeyPressed(key, PlayerIndex.One, out playerIndex) || + IsKeyPressed(key, PlayerIndex.Two, out playerIndex) || + IsKeyPressed(key, PlayerIndex.Three, out playerIndex) || + IsKeyPressed(key, PlayerIndex.Four, out playerIndex)); + } + } + + /// + /// Helper for checking if a button was pressed during this update. + /// The controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a button press + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsButtonPressed(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return CurrentGamePadStates[i].IsButtonDown(button); + } + else + { + // Accept input from any player. + return (IsButtonPressed(button, PlayerIndex.One, out playerIndex) || + IsButtonPressed(button, PlayerIndex.Two, out playerIndex) || + IsButtonPressed(button, PlayerIndex.Three, out playerIndex) || + IsButtonPressed(button, PlayerIndex.Four, out playerIndex)); + } + } + + /// + /// Helper for checking if a key was newly pressed during this update. The + /// controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a keypress + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentKeyboardStates[i].IsKeyDown(key) && + LastKeyboardStates[i].IsKeyUp(key)); + } + else + { + // Accept input from any player. + return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) || + IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) || + IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) || + IsNewKeyPress(key, PlayerIndex.Four, out playerIndex)); + } + } + + public bool IsNewKeyRelease(Keys key, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentKeyboardStates[i].IsKeyUp(key) && + LastKeyboardStates[i].IsKeyDown(key)); + } + else + { + // Accept input from any player. + return (IsNewKeyRelease(key, PlayerIndex.One, out playerIndex) || + IsNewKeyRelease(key, PlayerIndex.Two, out playerIndex) || + IsNewKeyRelease(key, PlayerIndex.Three, out playerIndex) || + IsNewKeyRelease(key, PlayerIndex.Four, out playerIndex)); + } + } + + /// + /// Helper for checking if a button was newly pressed during this update. + /// The controllingPlayer parameter specifies which player to read input for. + /// If this is null, it will accept input from any player. When a button press + /// is detected, the output playerIndex reports which player pressed it. + /// + public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentGamePadStates[i].IsButtonDown(button) && + LastGamePadStates[i].IsButtonUp(button)); + } + else + { + // Accept input from any player. + return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) || + IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) || + IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) || + IsNewButtonPress(button, PlayerIndex.Four, out playerIndex)); + } + } + + public bool IsNewButtonRelease(Buttons button, PlayerIndex? controllingPlayer, out PlayerIndex playerIndex) + { + if (controllingPlayer.HasValue) + { + // Read input from the specified player. + playerIndex = controllingPlayer.Value; + + int i = (int)playerIndex; + + return (CurrentGamePadStates[i].IsButtonUp(button) && + LastGamePadStates[i].IsButtonDown(button)); + } + else + { + // Accept input from any player. + return (IsNewButtonRelease(button, PlayerIndex.One, out playerIndex) || + IsNewButtonRelease(button, PlayerIndex.Two, out playerIndex) || + IsNewButtonRelease(button, PlayerIndex.Three, out playerIndex) || + IsNewButtonRelease(button, PlayerIndex.Four, out playerIndex)); + } + } + + public bool IsNewVirtualButtonPress(Buttons button) + { + return (_lastVirtualState.IsButtonUp(button) && + _currentVirtualState.IsButtonDown(button)); + } + + public bool IsNewVirtualButtonRelease(Buttons button) + { + return (_lastVirtualState.IsButtonDown(button) && + _currentVirtualState.IsButtonUp(button)); + } + + /// + /// Helper for checking if a mouse button was newly pressed during this update. + /// + public bool IsNewMouseButtonPress(MouseButtons button) + { + switch (button) + { + case MouseButtons.LeftButton: + return (_currentMouseState.LeftButton == ButtonState.Pressed && + _lastMouseState.LeftButton == ButtonState.Released); + case MouseButtons.RightButton: + return (_currentMouseState.RightButton == ButtonState.Pressed && + _lastMouseState.RightButton == ButtonState.Released); + case MouseButtons.MiddleButton: + return (_currentMouseState.MiddleButton == ButtonState.Pressed && + _lastMouseState.MiddleButton == ButtonState.Released); + case MouseButtons.ExtraButton1: + return (_currentMouseState.XButton1 == ButtonState.Pressed && + _lastMouseState.XButton1 == ButtonState.Released); + case MouseButtons.ExtraButton2: + return (_currentMouseState.XButton2 == ButtonState.Pressed && + _lastMouseState.XButton2 == ButtonState.Released); + default: + return false; + } + } + + /// + /// Checks if the requested mouse button is released. + /// + /// The button. + public bool IsNewMouseButtonRelease(MouseButtons button) + { + switch (button) + { + case MouseButtons.LeftButton: + return (_lastMouseState.LeftButton == ButtonState.Pressed && + _currentMouseState.LeftButton == ButtonState.Released); + case MouseButtons.RightButton: + return (_lastMouseState.RightButton == ButtonState.Pressed && + _currentMouseState.RightButton == ButtonState.Released); + case MouseButtons.MiddleButton: + return (_lastMouseState.MiddleButton == ButtonState.Pressed && + _currentMouseState.MiddleButton == ButtonState.Released); + case MouseButtons.ExtraButton1: + return (_lastMouseState.XButton1 == ButtonState.Pressed && + _currentMouseState.XButton1 == ButtonState.Released); + case MouseButtons.ExtraButton2: + return (_lastMouseState.XButton2 == ButtonState.Pressed && + _currentMouseState.XButton2 == ButtonState.Released); + default: + return false; + } + } + } +} diff --git a/ScreenSystem/LogoScreen.cs b/ScreenSystem/LogoScreen.cs new file mode 100644 index 0000000..7b545ac --- /dev/null +++ b/ScreenSystem/LogoScreen.cs @@ -0,0 +1,94 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using FarseerPhysics.SamplesFramework; + +namespace GameStateManagement +{ + public class LogoScreen : GameScreen + { + private const float LogoScreenHeightRatio = 4f / 6f; + private const float LogoWidthHeightRatio = 1.4f; + + private ContentManager _content; + private Rectangle _destination; + private TimeSpan _duration; + private Texture2D _farseerLogoTexture; + + public LogoScreen(TimeSpan duration) + { + _duration = duration; + TransitionOffTime = TimeSpan.FromSeconds(2.0); + } + + /// + /// Loads graphics content for this screen. The background texture is quite + /// big, so we use our own local ContentManager to load it. This allows us + /// to unload before going from the menus into the game itself, wheras if we + /// used the shared ContentManager provided by the Game class, the content + /// would remain loaded forever. + /// + public override void Activate(bool instancePreserved) + { + if (!instancePreserved) + { + if (_content == null) + { + _content = new ContentManager(ScreenManager.Game.Services, "Content"); + } + + _farseerLogoTexture = _content.Load("Common/logo"); + + Viewport viewport = ScreenManager.GraphicsDevice.Viewport; + int rectHeight = (int)(viewport.Height * LogoScreenHeightRatio); + int rectWidth = (int)(rectHeight * LogoWidthHeightRatio); + int posX = viewport.Bounds.Center.X - rectWidth / 2; + int posY = viewport.Bounds.Center.Y - rectHeight / 2; + + _destination = new Rectangle(posX, posY, rectWidth, rectHeight); + } + } + + /// + /// Unloads graphics content for this screen. + /// + public override void Unload() + { + _content.Unload(); + } + + public override void HandleInput(GameTime gameTime, InputState input) + { + //input. + if (input.CurrentKeyboardStates[0].GetPressedKeys().Length > 0 || + input.CurrentGamePadStates[0].IsButtonDown(Buttons.A | Buttons.Start | Buttons.Back) || + input.MouseState.LeftButton == ButtonState.Pressed) + { + _duration = TimeSpan.Zero; + } + } + + public override void Update(GameTime gameTime, bool otherScreenHasFocus, + bool coveredByOtherScreen) + { + _duration -= gameTime.ElapsedGameTime; + if (_duration <= TimeSpan.Zero) + { + ExitScreen(); + } + + base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + } + + public override void Draw(GameTime gameTime) + { + ScreenManager.GraphicsDevice.Clear(Color.White); + + ScreenManager.SpriteBatch.Begin(); + ScreenManager.SpriteBatch.Draw(_farseerLogoTexture, _destination, Color.White); + ScreenManager.SpriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/ScreenSystem/MenuScreen.cs b/ScreenSystem/MenuScreen.cs new file mode 100644 index 0000000..f8442ff --- /dev/null +++ b/ScreenSystem/MenuScreen.cs @@ -0,0 +1,329 @@ +#region File Description +//----------------------------------------------------------------------------- +// MenuScreen.cs +// +// XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input.Touch; +using Microsoft.Xna.Framework.Input; +using FarseerPhysics.SamplesFramework; +#endregion + +namespace GameStateManagement +{ + /// + /// Base class for screens that contain a menu of options. The user can + /// move up and down to select an entry, or cancel to back out of the screen. + /// + public class MenuScreen : GameScreen + { + #region Fields + + // the number of pixels to pad above and below menu entries for touch input + const int menuEntryPadding = 10; + + private List menuEntries = new List(); + int selectedEntry = 0; + string menuTitle; + + InputAction menuUp; + InputAction menuDown; + InputAction menuSelect; + InputAction menuCancel; + + #endregion + + #region Properties + + + /// + /// Gets the list of menu entries, so derived classes can add + /// or change the menu contents. + /// + protected IList MenuEntries + { + get { return menuEntries; } + } + + + #endregion + + #region Initialization + + + /// + /// Constructor. + /// + public MenuScreen(string menuTitle) + { + this.menuTitle = menuTitle; + // menus generally only need Tap for menu selection + EnabledGestures = GestureType.Tap; + + TransitionOnTime = TimeSpan.FromSeconds(0.5); + TransitionOffTime = TimeSpan.FromSeconds(0.5); + + menuUp = new InputAction( + new Buttons[] { Buttons.DPadUp, Buttons.LeftThumbstickUp }, + new Keys[] { Keys.Up }, + true); + menuDown = new InputAction( + new Buttons[] { Buttons.DPadDown, Buttons.LeftThumbstickDown }, + new Keys[] { Keys.Down }, + true); + menuSelect = new InputAction( + new Buttons[] { Buttons.A, Buttons.Start }, + new Keys[] { Keys.Enter, Keys.Space }, + true); + menuCancel = new InputAction( + new Buttons[] { Buttons.B, Buttons.Back }, + new Keys[] { Keys.Escape }, + true); + } + + + #endregion + + public void AddMenuItem(string name) + { + + menuEntries.Add(new MenuEntry(name)); + } + + #region Handle Input + + /// + /// Allows the screen to create the hit bounds for a particular menu entry. + /// + protected virtual Rectangle GetMenuEntryHitBounds(MenuEntry entry) + { + // the hit bounds are the entire width of the screen, and the height of the entry + // with some additional padding above and below. + return new Rectangle( + 0, + (int)entry.Position.Y - menuEntryPadding, + ScreenManager.GraphicsDevice.Viewport.Width, + entry.GetHeight(this) + (menuEntryPadding * 2)); + } + + /// + /// Responds to user input, changing the selected entry and accepting + /// or cancelling the menu. + /// + public override void HandleInput(GameTime gameTime, InputState input) + { + // For input tests we pass in our ControllingPlayer, which may + // either be null (to accept input from any player) or a specific index. + // If we pass a null controlling player, the InputState helper returns to + // us which player actually provided the input. We pass that through to + // OnSelectEntry and OnCancel, so they can tell which player triggered them. + + +#if WINDOWS || XBOX360 + PlayerIndex playerIndex; + // Move to the previous menu entry? + if (menuUp.Evaluate(input, ControllingPlayer, out playerIndex)) + { + selectedEntry--; + + if (selectedEntry < 0) + selectedEntry = menuEntries.Count - 1; + } + + // Move to the next menu entry? + if (menuDown.Evaluate(input, ControllingPlayer, out playerIndex)) + { + selectedEntry++; + + if (selectedEntry >= menuEntries.Count) + selectedEntry = 0; + } + + if (menuSelect.Evaluate(input, ControllingPlayer, out playerIndex)) + { + OnSelectEntry(selectedEntry, playerIndex); + } + else if (menuCancel.Evaluate(input, ControllingPlayer, out playerIndex)) + { + OnCancel(playerIndex); + } +#endif + +#if WINDOWS_PHONE + //selectedEntry = 1; + + PlayerIndex player; + if (input.IsNewButtonPress(Buttons.Back, ControllingPlayer, out player)) + { + OnCancel(player); + } + + // look for any taps that occurred and select any entries that were tapped + foreach (GestureSample gesture in input.Gestures) + { + //System.Diagnostics.Debugger.Break(); + if (gesture.GestureType == GestureType.Tap) + { + // convert the position to a Point that we can test against a Rectangle + Point tapLocation = new Point((int)gesture.Position.X, (int)gesture.Position.Y); + + // iterate the entries to see if any were tapped + for (int i = 0; i < menuEntries.Count; i++) + { + MenuEntry menuEntry = menuEntries[i]; + + if (GetMenuEntryHitBounds(menuEntry).Contains(tapLocation)) + { + // select the entry. since gestures are only available on Windows Phone, + // we can safely pass PlayerIndex.One to all entries since there is only + // one player on Windows Phone. + OnSelectEntry(i, PlayerIndex.One); + } + } + } + } +#endif + } + + + /// + /// Handler for when the user has chosen a menu entry. + /// + protected virtual void OnSelectEntry(int entryIndex, PlayerIndex playerIndex) + { + menuEntries[entryIndex].OnSelectEntry(playerIndex); + } + + + /// + /// Handler for when the user has cancelled the menu. + /// + protected virtual void OnCancel(PlayerIndex playerIndex) + { + ExitScreen(); + } + + + /// + /// Helper overload makes it easy to use OnCancel as a MenuEntry event handler. + /// + protected void OnCancel(object sender, PlayerIndexEventArgs e) + { + OnCancel(e.PlayerIndex); + } + + + #endregion + + #region Update and Draw + + + /// + /// Allows the screen the chance to position the menu entries. By default + /// all menu entries are lined up in a vertical list, centered on the screen. + /// + protected virtual void UpdateMenuEntryLocations() + { + // Make the menu slide into place during transitions, using a + // power curve to make things look more interesting (this makes + // the movement slow down as it nears the end). + float transitionOffset = (float)Math.Pow(TransitionPosition, 2); + + // start at Y = 175; each X value is generated per entry + Vector2 position = new Vector2(0f, 175f); + + // update each menu entry's location in turn + for (int i = 0; i < menuEntries.Count; i++) + { + MenuEntry menuEntry = menuEntries[i]; + + // each entry is to be centered horizontally + position.X = ScreenManager.GraphicsDevice.Viewport.Width / 2 - menuEntry.GetWidth(this) / 2; + + if (ScreenState == ScreenState.TransitionOn) + position.X -= transitionOffset * 256; + else + position.X += transitionOffset * 512; + + // set the entry's position + menuEntry.Position = position; + + // move down for the next entry the size of this entry + position.Y += menuEntry.GetHeight(this); + } + } + + + /// + /// Updates the menu. + /// + public override void Update(GameTime gameTime, bool otherScreenHasFocus, + bool coveredByOtherScreen) + { + base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + + // Update each nested MenuEntry object. + for (int i = 0; i < menuEntries.Count; i++) + { + bool isSelected = IsActive && (i == selectedEntry); + + menuEntries[i].Update(this, isSelected, gameTime); + } + } + + + /// + /// Draws the menu. + /// + public override void Draw(GameTime gameTime) + { + // make sure our entries are in the right place before we draw them + UpdateMenuEntryLocations(); + + GraphicsDevice graphics = ScreenManager.GraphicsDevice; + SpriteBatch spriteBatch = ScreenManager.SpriteBatch; + SpriteFont font = ScreenManager.Font; + + spriteBatch.Begin(); + + // Draw each menu entry in turn. + for (int i = 0; i < menuEntries.Count; i++) + { + MenuEntry menuEntry = menuEntries[i]; + + bool isSelected = IsActive && (i == selectedEntry); + + menuEntry.Draw(this, isSelected, gameTime); + } + + // Make the menu slide into place during transitions, using a + // power curve to make things look more interesting (this makes + // the movement slow down as it nears the end). + float transitionOffset = (float)Math.Pow(TransitionPosition, 2); + + // Draw the menu title centered on the screen + Vector2 titlePosition = new Vector2(graphics.Viewport.Width / 2, 80); + Vector2 titleOrigin = font.MeasureString(menuTitle) / 2; + Color titleColor = new Color(192, 192, 192) * TransitionAlpha; + float titleScale = 1.25f; + + titlePosition.Y -= transitionOffset * 100; + + spriteBatch.DrawString(font, menuTitle, titlePosition, titleColor, 0, + titleOrigin, titleScale, SpriteEffects.None, 0); + + spriteBatch.End(); + } + + + #endregion + } +} diff --git a/ScreenSystem/PhysicsGameScreen.cs b/ScreenSystem/PhysicsGameScreen.cs new file mode 100644 index 0000000..7dcb9a3 --- /dev/null +++ b/ScreenSystem/PhysicsGameScreen.cs @@ -0,0 +1,348 @@ +using System; +using FarseerPhysics; +using FarseerPhysics.DebugViews; +using FarseerPhysics.Dynamics; +using FarseerPhysics.Dynamics.Joints; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Input; +using FarseerPhysics.SamplesFramework; +using Axios.Engine; + +namespace GameStateManagement +{ + public class PhysicsGameScreen : GameScreen + { + public Camera2D Camera; + protected DebugViewXNA DebugView; + public World World; + + private float _agentForce; + private float _agentTorque; + private FixedMouseJoint _fixedMouseJoint; + private Body _userAgent; + public bool UseSecondStep = false; + protected PhysicsGameScreen() + { + TransitionOnTime = TimeSpan.FromSeconds(0.75); + TransitionOffTime = TimeSpan.FromSeconds(0.75); +#if DEBUG + EnableCameraControl = true; + HasCursor = true; +#else + EnableCameraControl = false; + HasCursor = false; +#endif + _userAgent = null; + World = null; + Camera = null; + DebugView = null; + } + + public bool EnableCameraControl { get; set; } + + protected void SetUserAgent(Body agent, float force, float torque) + { + _userAgent = agent; + _agentForce = force; + _agentTorque = torque; + } + + + + public override void Activate(bool instancePreserved) + { + if (!instancePreserved) + { + base.Activate(instancePreserved); + + //We enable diagnostics to show get values for our performance counters. + FarseerPhysics.Settings.EnableDiagnostics = true; + + if (World == null) + { + World = new World(Vector2.Zero); + } + else + { + World.Clear(); + } + + if (DebugView == null) + { + if (!Axios.Settings.ScreenSaver) + { + ContentManager man = new ContentManager(this.ScreenManager.Game.Services, "Content"); + DebugView = new DebugViewXNA(World); + DebugView.RemoveFlags(DebugViewFlags.Shape); + DebugView.RemoveFlags(DebugViewFlags.Joint); + DebugView.DefaultShapeColor = Color.White; + DebugView.SleepingShapeColor = Color.LightGray; + DebugView.LoadContent(ScreenManager.GraphicsDevice, man); + } + } + + if (Camera == null) + { + Camera = new Camera2D(ScreenManager.GraphicsDevice); + } + else + { + Camera.ResetCamera(); + } + + // Loading may take a while... so prevent the game from "catching up" once we finished loading + ScreenManager.Game.ResetElapsedTime(); + } + } + + public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen) + { + if (!coveredByOtherScreen && !otherScreenHasFocus && ScreenState != GameStateManagement.ScreenState.TransitionOff) + { + // variable time step but never less then 30 Hz + if (UseSecondStep) + World.Step(Math.Min((float)gameTime.ElapsedGameTime.TotalSeconds, (1f / 30f))); + else + World.Step((float)(gameTime.ElapsedGameTime.TotalMilliseconds * 0.001)); + } + else + { + World.Step(0f); + } + Camera.Update(gameTime); + base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + } + + public virtual void CleanUp() + { + + } + public override void HandleInput(GameTime gameTime, InputState input) + { + +#if DEBUG + // Control debug view + PlayerIndex player; + if (input.IsNewButtonPress(Buttons.Start, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.Shape); + EnableOrDisableFlag(DebugViewFlags.DebugPanel); + EnableOrDisableFlag(DebugViewFlags.PerformanceGraph); + EnableOrDisableFlag(DebugViewFlags.Joint); + EnableOrDisableFlag(DebugViewFlags.ContactPoints); + EnableOrDisableFlag(DebugViewFlags.ContactNormals); + EnableOrDisableFlag(DebugViewFlags.Controllers); + } + + if (input.IsNewKeyPress(Keys.F1, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.Shape); + } + if (input.IsNewKeyPress(Keys.F2, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.DebugPanel); + EnableOrDisableFlag(DebugViewFlags.PerformanceGraph); + } + if (input.IsNewKeyPress(Keys.F3, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.Joint); + } + if (input.IsNewKeyPress(Keys.F4, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.ContactPoints); + EnableOrDisableFlag(DebugViewFlags.ContactNormals); + } + if (input.IsNewKeyPress(Keys.F5, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.PolygonPoints); + } + if (input.IsNewKeyPress(Keys.F6, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.Controllers); + } + if (input.IsNewKeyPress(Keys.F7, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.CenterOfMass); + } + if (input.IsNewKeyPress(Keys.F8, ControllingPlayer.Value, out player)) + { + EnableOrDisableFlag(DebugViewFlags.AABB); + } + +#endif + + if (_userAgent != null) + { + HandleUserAgent(input); + } + + if (EnableCameraControl) + { + HandleCamera(input, gameTime); + } + + + if (HasCursor) + { + HandleCursor(input); + } + + PlayerIndex i; + if (input.IsNewButtonPress(Buttons.Back, PlayerIndex.One, out i) || input.IsNewKeyPress(Keys.Escape, PlayerIndex.One, out i)) + { + //if (this.ScreenState == GameStateManagement.ScreenState.Active && this.TransitionPosition == 0 && this.TransitionAlpha == 1) + //{ //Give the screens a chance to transition + CleanUp(); + ExitScreen(); + + //} + } + base.HandleInput(gameTime, input); + } + + public virtual void HandleCursor(InputState input) + { + PlayerIndex player; + Vector2 position = Camera.ConvertScreenToWorld(input.Cursor); + + if ((input.IsNewButtonPress(Buttons.A, PlayerIndex.One, out player) || + input.IsNewMouseButtonPress(MouseButtons.LeftButton)) && + _fixedMouseJoint == null) + { + Fixture savedFixture = World.TestPoint(position); + if (savedFixture != null && savedFixture.UserData is SimpleAxiosGameObject && ((SimpleAxiosGameObject)(savedFixture.UserData)).AllowAutomaticMouseJoint) + { + Body body = savedFixture.Body; + _fixedMouseJoint = new FixedMouseJoint(body, position); + _fixedMouseJoint.MaxForce = 1000.0f * body.Mass; + World.AddJoint(_fixedMouseJoint); + body.Awake = true; + } + } + + + if ((input.IsNewButtonRelease(Buttons.A, ControllingPlayer.Value, out player) || + input.IsNewMouseButtonRelease(MouseButtons.LeftButton)) && + _fixedMouseJoint != null) + { + World.RemoveJoint(_fixedMouseJoint); + _fixedMouseJoint = null; + } + + if (_fixedMouseJoint != null) + { + _fixedMouseJoint.WorldAnchorB = position; + } + + } + + private void HandleCamera(InputState input, GameTime gameTime) + { + Vector2 camMove = Vector2.Zero; + + + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.Up)) + { + camMove.Y -= 10f * (float)gameTime.ElapsedGameTime.TotalSeconds; + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.Down)) + { + camMove.Y += 10f * (float)gameTime.ElapsedGameTime.TotalSeconds; + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.Left)) + { + camMove.X -= 10f * (float)gameTime.ElapsedGameTime.TotalSeconds; + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.Right)) + { + camMove.X += 10f * (float)gameTime.ElapsedGameTime.TotalSeconds; + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.PageUp)) + { + Camera.Zoom += 5f * (float)gameTime.ElapsedGameTime.TotalSeconds * Camera.Zoom / 20f; + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.PageDown)) + { + Camera.Zoom -= 5f * (float)gameTime.ElapsedGameTime.TotalSeconds * Camera.Zoom / 20f; + } + if (camMove != Vector2.Zero) + { + Camera.MoveCamera(camMove); + } + PlayerIndex i; + if (input.IsNewKeyPress(Keys.Home, PlayerIndex.One, out i)) + { + Camera.ResetCamera(); + } + + } + + private void HandleUserAgent(InputState input) + { + + Vector2 force = _agentForce * new Vector2(input.CurrentGamePadStates[0].ThumbSticks.Right.X, + -input.CurrentGamePadStates[0].ThumbSticks.Right.Y); + float torque = _agentTorque * (input.CurrentGamePadStates[0].Triggers.Right - input.CurrentGamePadStates[0].Triggers.Left); + + _userAgent.ApplyForce(force); + _userAgent.ApplyTorque(torque); + + float forceAmount = _agentForce * 0.6f; + + force = Vector2.Zero; + torque = 0; + + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.A)) + { + force += new Vector2(-forceAmount, 0); + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.S)) + { + force += new Vector2(0, forceAmount); + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.D)) + { + force += new Vector2(forceAmount, 0); + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.W)) + { + force += new Vector2(0, -forceAmount); + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.Q)) + { + torque -= _agentTorque; + } + if (input.CurrentKeyboardStates[0].IsKeyDown(Keys.E)) + { + torque += _agentTorque; + } + + _userAgent.ApplyForce(force); + _userAgent.ApplyTorque(torque); + + } + + private void EnableOrDisableFlag(DebugViewFlags flag) + { + if ((DebugView.Flags & flag) == flag) + { + DebugView.RemoveFlags(flag); + } + else + { + DebugView.AppendFlags(flag); + } + } + + public override void Draw(GameTime gameTime) + { + Matrix projection = Camera.SimProjection; + Matrix view = Camera.SimView; + + if (!Axios.Settings.ScreenSaver) + DebugView.RenderDebugData(ref projection, ref view); + base.Draw(gameTime); + } + } +} \ No newline at end of file diff --git a/ScreenSystem/ScreenManager.cs b/ScreenSystem/ScreenManager.cs new file mode 100644 index 0000000..85927ea --- /dev/null +++ b/ScreenSystem/ScreenManager.cs @@ -0,0 +1,504 @@ +#region File Description +//----------------------------------------------------------------------------- +// ScreenManager.cs +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#endregion + +#region Using Statements +using System; +using System.Diagnostics; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input.Touch; +using System.IO; +using System.IO.IsolatedStorage; +using System.Xml.Linq; +using FarseerPhysics.SamplesFramework; +using Axios.Engine; +#endregion + +namespace GameStateManagement +{ + /// + /// The screen manager is a component which manages one or more GameScreen + /// instances. It maintains a stack of screens, calls their Update and Draw + /// methods at the appropriate times, and automatically routes input to the + /// topmost active screen. + /// + public class ScreenManager : DrawableGameComponent + { + #region Fields + + private const string StateFilename = "ScreenManagerState.xml"; + + List screens = new List(); + List tempScreensList = new List(); + + InputState input; + + SpriteBatch spriteBatch; + SpriteFont font; + Texture2D blankTexture; + + bool isInitialized; + + bool traceEnabled; + + /// + /// Contains all the fonts avaliable for use. + /// + private SpriteFonts _spriteFonts; + + #endregion + + #region Properties + + public InputState InputState + { + get { return input; } + private set { input = value; } + } + + public SpriteFonts Fonts + { + get { return _spriteFonts; } + } + + /// + /// A default SpriteBatch shared by all the screens. This saves + /// each screen having to bother creating their own local instance. + /// + public SpriteBatch SpriteBatch + { + get { return spriteBatch; } + } + + + /// + /// A default font shared by all the screens. This saves + /// each screen having to bother loading their own local copy. + /// + public SpriteFont Font + { + get { return font; } + } + + + /// + /// If true, the manager prints out a list of all the screens + /// each time it is updated. This can be useful for making sure + /// everything is being added and removed at the right times. + /// + public bool TraceEnabled + { + get { return traceEnabled; } + set { traceEnabled = value; } + } + + + /// + /// Gets a blank texture that can be used by the screens. + /// + public Texture2D BlankTexture + { + get { return blankTexture; } + } + + + #endregion + + #region Initialization + + + /// + /// Constructs a new screen manager component. + /// + public ScreenManager(Game game) + : base(game) + { + // we must set EnabledGestures before we can query for them, but + // we don't assume the game wants to read them. + TouchPanel.EnabledGestures = GestureType.None; + this.input = new InputState(this); + } + + + /// + /// Initializes the screen manager component. + /// + public override void Initialize() + { + base.Initialize(); + + isInitialized = true; + } + + + /// + /// Load your graphics content. + /// + protected override void LoadContent() + { + // Load content belonging to the screen manager. + ContentManager content = new ContentManager(this.Game.Services, "Content/Fonts"); + _spriteFonts = new SpriteFonts(content); + spriteBatch = new SpriteBatch(GraphicsDevice); + font = content.Load("menufont"); + blankTexture = Game.Content.Load("Materials/blank"); + GameServices.AddService(this.Game.GraphicsDevice); + GameServices.AddService(this.Game.Content); + + // It is advised to use one instance of Random + // because Random is seeded with the current time + // initilizing random objects too quickly can cause + // the impression of generating the same value + // http://stackoverflow.com/questions/2727538/random-encounter-not-so-random + GameServices.AddService(new Random()); + AxiosRandom.init(); + + + input.LoadContent(); + // Tell each of the screens to load their content. + foreach (GameScreen screen in screens) + { + screen.Activate(false); + } + } + + + /// + /// Unload your graphics content. + /// + protected override void UnloadContent() + { + // Tell each of the screens to unload their content. + foreach (GameScreen screen in screens) + { + screen.Unload(); + } + } + + + #endregion + + #region Update and Draw + + + /// + /// Allows each screen to run logic. + /// + public override void Update(GameTime gameTime) + { + // Read the keyboard and gamepad. + input.Update(gameTime); + + // Make a copy of the master screen list, to avoid confusion if + // the process of updating one screen adds or removes others. + tempScreensList.Clear(); + + foreach (GameScreen screen in screens) + tempScreensList.Add(screen); + + bool otherScreenHasFocus = !Game.IsActive; + bool coveredByOtherScreen = false; + + // Loop as long as there are screens waiting to be updated. + while (tempScreensList.Count > 0) + { + // Pop the topmost screen off the waiting list. + GameScreen screen = tempScreensList[tempScreensList.Count - 1]; + + tempScreensList.RemoveAt(tempScreensList.Count - 1); + + // Update the screen. + screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + + if (screen.ScreenState == ScreenState.TransitionOn || + screen.ScreenState == ScreenState.Active) + { + // If this is the first active screen we came across, + // give it a chance to handle input. + if (!otherScreenHasFocus) + { + // The default implementation of screens aren't aware that it's + // being woke up + input.ShowCursor = screen.HasCursor; + input.EnableVirtualStick = screen.HasVirtualStick; + screen.HandleInput(gameTime, input); + + otherScreenHasFocus = true; + } + + // If this is an active non-popup, inform any subsequent + // screens that they are covered by it. + if (!screen.IsPopup) + coveredByOtherScreen = true; + } + } + + // Print debug trace? + if (traceEnabled) + TraceScreens(); + } + + + /// + /// Prints a list of all the screens, for debugging. + /// + void TraceScreens() + { + List screenNames = new List(); + + foreach (GameScreen screen in screens) + screenNames.Add(screen.GetType().Name); + + Debug.WriteLine(string.Join(", ", screenNames.ToArray())); + } + + + /// + /// Tells each screen to draw itself. + /// + public override void Draw(GameTime gameTime) + { + + foreach (GameScreen screen in screens) + { + if (screen.ScreenState == ScreenState.Hidden) + continue; + + screen.Draw(gameTime); + } + input.Draw(); + } + + + #endregion + + #region Public Methods + + + /// + /// Adds a new screen to the screen manager. + /// + public void AddScreen(GameScreen screen, PlayerIndex? controllingPlayer) + { + screen.ControllingPlayer = controllingPlayer; + screen.ScreenManager = this; + screen.IsExiting = false; + + // If we have a graphics device, tell the screen to load content. + if (isInitialized) + { + screen.Activate(false); + } + + screens.Add(screen); + + // update the TouchPanel to respond to gestures this screen is interested in + TouchPanel.EnabledGestures = screen.EnabledGestures; + } + + /// + /// Adds a new screen to the screen manager with a default PlayerIndex of one + /// + public void AddScreen(GameScreen screen) + { + screen.ControllingPlayer = PlayerIndex.One; + screen.ScreenManager = this; + screen.IsExiting = false; + + // If we have a graphics device, tell the screen to load content. + if (isInitialized) + { + screen.Activate(false); + } + + screens.Add(screen); + + // update the TouchPanel to respond to gestures this screen is interested in + TouchPanel.EnabledGestures = screen.EnabledGestures; + } + + + /// + /// Removes a screen from the screen manager. You should normally + /// use GameScreen.ExitScreen instead of calling this directly, so + /// the screen can gradually transition off rather than just being + /// instantly removed. + /// + public void RemoveScreen(GameScreen screen) + { + // If we have a graphics device, tell the screen to unload content. + if (isInitialized) + { + screen.Unload(); + } + + screens.Remove(screen); + tempScreensList.Remove(screen); + + // if there is a screen still in the manager, update TouchPanel + // to respond to gestures that screen is interested in. + if (screens.Count > 0) + { + TouchPanel.EnabledGestures = screens[screens.Count - 1].EnabledGestures; + } + } + + + /// + /// Expose an array holding all the screens. We return a copy rather + /// than the real master list, because screens should only ever be added + /// or removed using the AddScreen and RemoveScreen methods. + /// + public GameScreen[] GetScreens() + { + return screens.ToArray(); + } + + + /// + /// Helper draws a translucent black fullscreen sprite, used for fading + /// screens in and out, and for darkening the background behind popups. + /// + public void FadeBackBufferToBlack(float alpha) + { + spriteBatch.Begin(); + spriteBatch.Draw(blankTexture, GraphicsDevice.Viewport.Bounds, Color.Black * alpha); + spriteBatch.End(); + } + + /// + /// Informs the screen manager to serialize its state to disk. + /// + public void Deactivate() + { +#if !WINDOWS_PHONE + return; +#else + // Open up isolated storage + using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) + { + // Create an XML document to hold the list of screen types currently in the stack + XDocument doc = new XDocument(); + XElement root = new XElement("ScreenManager"); + doc.Add(root); + + // Make a copy of the master screen list, to avoid confusion if + // the process of deactivating one screen adds or removes others. + tempScreensList.Clear(); + foreach (GameScreen screen in screens) + tempScreensList.Add(screen); + + // Iterate the screens to store in our XML file and deactivate them + foreach (GameScreen screen in tempScreensList) + { + // Only add the screen to our XML if it is serializable + if (screen.IsSerializable) + { + // We store the screen's controlling player so we can rehydrate that value + string playerValue = screen.ControllingPlayer.HasValue + ? screen.ControllingPlayer.Value.ToString() + : ""; + + root.Add(new XElement( + "GameScreen", + new XAttribute("Type", screen.GetType().AssemblyQualifiedName), + new XAttribute("ControllingPlayer", playerValue))); + } + + // Deactivate the screen regardless of whether we serialized it + screen.Deactivate(); + } + + // Save the document + using (IsolatedStorageFileStream stream = storage.CreateFile(StateFilename)) + { + doc.Save(stream); + } + } +#endif + } + + public bool Activate(bool instancePreserved) + { +#if !WINDOWS_PHONE + return false; +#else + // If the game instance was preserved, the game wasn't dehydrated so our screens still exist. + // We just need to activate them and we're ready to go. + if (instancePreserved) + { + // Make a copy of the master screen list, to avoid confusion if + // the process of activating one screen adds or removes others. + tempScreensList.Clear(); + + foreach (GameScreen screen in screens) + tempScreensList.Add(screen); + + foreach (GameScreen screen in tempScreensList) + screen.Activate(true); + } + + // Otherwise we need to refer to our saved file and reconstruct the screens that were present + // when the game was deactivated. + else + { + // Try to get the screen factory from the services, which is required to recreate the screens + IScreenFactory screenFactory = Game.Services.GetService(typeof(IScreenFactory)) as IScreenFactory; + if (screenFactory == null) + { + throw new InvalidOperationException( + "Game.Services must contain an IScreenFactory in order to activate the ScreenManager."); + } + + // Open up isolated storage + using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) + { + // Check for the file; if it doesn't exist we can't restore state + if (!storage.FileExists(StateFilename)) + return false; + + // Read the state file so we can build up our screens + using (IsolatedStorageFileStream stream = storage.OpenFile(StateFilename, FileMode.Open)) + { + XDocument doc = XDocument.Load(stream); + + // Iterate the document to recreate the screen stack + foreach (XElement screenElem in doc.Root.Elements("GameScreen")) + { + // Use the factory to create the screen + Type screenType = Type.GetType(screenElem.Attribute("Type").Value); + GameScreen screen = screenFactory.CreateScreen(screenType); + + // Rehydrate the controlling player for the screen + PlayerIndex? controllingPlayer = screenElem.Attribute("ControllingPlayer").Value != "" + ? (PlayerIndex)Enum.Parse(typeof(PlayerIndex), screenElem.Attribute("ControllingPlayer").Value, true) + : (PlayerIndex?)null; + screen.ControllingPlayer = controllingPlayer; + + // Add the screen to the screens list and activate the screen + screen.ScreenManager = this; + screens.Add(screen); + screen.Activate(false); + + // update the TouchPanel to respond to gestures this screen is interested in + TouchPanel.EnabledGestures = screen.EnabledGestures; + } + } + } + } + + return true; +#endif + } + + #endregion + } +} diff --git a/ScreenSystem/ScreenManagerComponent.cs b/ScreenSystem/ScreenManagerComponent.cs new file mode 100644 index 0000000..15b1a86 --- /dev/null +++ b/ScreenSystem/ScreenManagerComponent.cs @@ -0,0 +1,305 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input.Touch; +using FarseerPhysics.SamplesFramework; + +namespace GameStateManagement +{ + /* + /// + /// The screen manager is a component which manages one or more GameScreen + /// instances. It maintains a stack of screens, calls their Update and Draw + /// methods at the appropriate times, and automatically routes input to the + /// topmost active screen. + /// + public class ScreenManager : DrawableGameComponent + { + private AssetCreator _assetCreator; + private ContentManager _contentManager; + + private InputHelper _input; + private bool _isInitialized; + private LineBatch _lineBatch; + + private List _screens; + private List _screensToUpdate; + + private SpriteBatch _spriteBatch; + + /// + /// Contains all the fonts avaliable for use. + /// + private SpriteFonts _spriteFonts; + + private List _transitions; + + /// + /// Constructs a new screen manager component. + /// + public ScreenManager(Game game) + : base(game) + { + // we must set EnabledGestures before we can query for them, but + // we don't assume the game wants to read them. + //game.Components. + TouchPanel.EnabledGestures = GestureType.None; + _contentManager = game.Content; + _contentManager.RootDirectory = "Content"; + _input = new InputHelper(this); + + _screens = new List(); + _screensToUpdate = new List(); + _transitions = new List(); + } + + /// + /// A default SpriteBatch shared by all the screens. This saves + /// each screen having to bother creating their own local instance. + /// + public SpriteBatch SpriteBatch + { + get { return _spriteBatch; } + } + + public LineBatch LineBatch + { + get { return _lineBatch; } + } + + public ContentManager Content + { + get { return _contentManager; } + } + + public SpriteFonts Fonts + { + get { return _spriteFonts; } + } + + public AssetCreator Assets + { + get { return _assetCreator; } + } + + /// + /// Initializes the screen manager component. + /// + public override void Initialize() + { + if (!Axios.Settings.ScreenSaver) + _spriteFonts = new SpriteFonts(_contentManager); + base.Initialize(); + + _isInitialized = true; + } + + /// + /// Load your graphics content. + /// + protected override void LoadContent() + { + _spriteBatch = new SpriteBatch(GraphicsDevice); + _lineBatch = new LineBatch(GraphicsDevice); + + if (!Axios.Settings.ScreenSaver) + { + _assetCreator = new AssetCreator(GraphicsDevice); + _assetCreator.LoadContent(_contentManager); + + _input.LoadContent(); + } + // Tell each of the screens to load their content. + foreach (GameScreen screen in _screens) + { + screen.LoadContent(); + } + } + + /// + /// Unload your graphics content. + /// + protected override void UnloadContent() + { + // Tell each of the screens to unload their content. + foreach (GameScreen screen in _screens) + { + screen.UnloadContent(); + } + } + + /// + /// Allows each screen to run logic. + /// + public override void Update(GameTime gameTime) + { + // Read the keyboard and gamepad. + _input.Update(gameTime); + + // Make a copy of the master screen list, to avoid confusion if + // the process of updating one screen adds or removes others. + _screensToUpdate.Clear(); + + if (_screens.Count == 0 || (_screens.Count == 1 && _screens[0] is BackgroundScreen)) + //I'm done, exit + this.Game.Exit(); + + foreach (GameScreen screen in _screens) + { + _screensToUpdate.Add(screen); + } + + bool otherScreenHasFocus = !Game.IsActive; + bool coveredByOtherScreen = false; + + // Loop as long as there are screens waiting to be updated. + while (_screensToUpdate.Count > 0) + { + // Pop the topmost screen off the waiting list. + GameScreen screen = _screensToUpdate[_screensToUpdate.Count - 1]; + + _screensToUpdate.RemoveAt(_screensToUpdate.Count - 1); + + // Update the screen. + screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen); + + if (screen.ScreenState == ScreenState.TransitionOn || + screen.ScreenState == ScreenState.Active) + { + // If this is the first active screen we came across, + // give it a chance to handle input. + if (!otherScreenHasFocus || screen.AlwaysHasFocus) + { + + if (!otherScreenHasFocus) + { + _input.ShowCursor = screen.HasCursor; + _input.EnableVirtualStick = screen.HasVirtualStick; + + otherScreenHasFocus = true; + } + screen.HandleInput(_input, gameTime); + } + + // If this is an active non-popup, inform any subsequent + // screens that they are covered by it. + if (!screen.IsPopup) + { + coveredByOtherScreen = true; + } + } + } + } + + /// + /// Tells each screen to draw itself. + /// + public override void Draw(GameTime gameTime) + { + int transitionCount = 0; + foreach (GameScreen screen in _screens) + { + if (screen.ScreenState == ScreenState.TransitionOn || + screen.ScreenState == ScreenState.TransitionOff) + { + ++transitionCount; + if (_transitions.Count < transitionCount) + { + PresentationParameters _pp = GraphicsDevice.PresentationParameters; + _transitions.Add(new RenderTarget2D(GraphicsDevice, _pp.BackBufferWidth, _pp.BackBufferHeight, + false, + SurfaceFormat.Color, _pp.DepthStencilFormat, + _pp.MultiSampleCount, + RenderTargetUsage.DiscardContents)); + } + GraphicsDevice.SetRenderTarget(_transitions[transitionCount - 1]); + GraphicsDevice.Clear(Color.Transparent); + screen.Draw(gameTime); + GraphicsDevice.SetRenderTarget(null); + } + } + + //GraphicsDevice.Clear(Color.Black); + + transitionCount = 0; + foreach (GameScreen screen in _screens) + { + if (screen.ScreenState == ScreenState.Hidden) + { + continue; + } + + if (screen.ScreenState == ScreenState.TransitionOn || + screen.ScreenState == ScreenState.TransitionOff) + { + _spriteBatch.Begin(0, BlendState.AlphaBlend); + _spriteBatch.Draw(_transitions[transitionCount], Vector2.Zero, Color.White * screen.TransitionAlpha); + _spriteBatch.End(); + + ++transitionCount; + } + else + { + screen.Draw(gameTime); + } + } + _input.Draw(); + } + + /// + /// Adds a new screen to the screen manager. + /// + public void AddScreen(GameScreen screen) + { + screen.ScreenManager = this; + screen.IsExiting = false; + + // If we have a graphics device, tell the screen to load content. + if (_isInitialized) + { + screen.LoadContent(); + } + + _screens.Add(screen); + + // update the TouchPanel to respond to gestures this screen is interested in + TouchPanel.EnabledGestures = screen.EnabledGestures; + } + + /// + /// Removes a screen from the screen manager. You should normally + /// use GameScreen.ExitScreen instead of calling this directly, so + /// the screen can gradually transition off rather than just being + /// instantly removed. + /// + public void RemoveScreen(GameScreen screen) + { + // If we have a graphics device, tell the screen to unload content. + if (_isInitialized) + { + screen.UnloadContent(); + } + + _screens.Remove(screen); + _screensToUpdate.Remove(screen); + + // if there is a screen still in the manager, update TouchPanel + // to respond to gestures that screen is interested in. + if (_screens.Count > 0) + { + TouchPanel.EnabledGestures = _screens[_screens.Count - 1].EnabledGestures; + } + } + + /// + /// Expose an array holding all the screens. We return a copy rather + /// than the real master list, because screens should only ever be added + /// or removed using the AddScreen and RemoveScreen methods. + /// + public GameScreen[] GetScreens() + { + return _screens.ToArray(); + } + }*/ +} \ No newline at end of file diff --git a/ScreenSystem/SpriteFonts.cs b/ScreenSystem/SpriteFonts.cs new file mode 100644 index 0000000..d46f213 --- /dev/null +++ b/ScreenSystem/SpriteFonts.cs @@ -0,0 +1,19 @@ +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace FarseerPhysics.SamplesFramework +{ + public class SpriteFonts + { + public SpriteFont DetailsFont; + public SpriteFont FrameRateCounterFont; + public SpriteFont MenuSpriteFont; + + public SpriteFonts(ContentManager contentManager) + { + MenuSpriteFont = contentManager.Load("menuFont"); + FrameRateCounterFont = contentManager.Load("frameRateCounterFont"); + DetailsFont = contentManager.Load("detailsFont"); + } + } +} \ No newline at end of file diff --git a/ScreenSystem/VirtualButton.cs b/ScreenSystem/VirtualButton.cs new file mode 100644 index 0000000..c8365c4 --- /dev/null +++ b/ScreenSystem/VirtualButton.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input.Touch; + +namespace FarseerPhysics.SamplesFramework +{ + public sealed class VirtualButton + { + private Texture2D _sprite; + private Vector2 _origin; + private Rectangle _normal; + private Rectangle _pressed; + private Vector2 _position; + + public bool Pressed; + + public VirtualButton(Texture2D sprite, Vector2 position, Rectangle normal, Rectangle pressed) + { + _sprite = sprite; + _origin = new Vector2(normal.Width / 2f, normal.Height / 2f); + _normal = normal; + _pressed = pressed; + Pressed = false; + _position = position; + } + + public void Update(TouchLocation touchLocation) + { + if (touchLocation.State == TouchLocationState.Pressed || + touchLocation.State == TouchLocationState.Moved) + { + Vector2 delta = touchLocation.Position - _position; + if (delta.LengthSquared() <= 400f) + { + Pressed = true; + } + } + } + + public void Draw(SpriteBatch batch) + { + batch.Draw(_sprite, _position, Pressed ? _pressed : _normal, Color.White, 0f, _origin, 1f, SpriteEffects.None, 0f); + } + } +} diff --git a/ScreenSystem/VirtualStick.cs b/ScreenSystem/VirtualStick.cs new file mode 100644 index 0000000..b50a359 --- /dev/null +++ b/ScreenSystem/VirtualStick.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input.Touch; + +namespace FarseerPhysics.SamplesFramework +{ + public sealed class VirtualStick + { + private Sprite _socketSprite; + private Sprite _stickSprite; + private int _picked; + private Vector2 _position; + private Vector2 _center; + + public Vector2 StickPosition; + + public VirtualStick(Texture2D socket, Texture2D stick, Vector2 position) + { + _socketSprite = new Sprite(socket); + _stickSprite = new Sprite(stick); + _picked = -1; + _center = position; + _position = position; + StickPosition = Vector2.Zero; + } + + public void Update(TouchLocation touchLocation) + { + if (touchLocation.State == TouchLocationState.Pressed && _picked < 0) + { + Vector2 delta = touchLocation.Position - _position; + if (delta.LengthSquared() <= 2025f) + { + _picked = touchLocation.Id; + } + } + if ((touchLocation.State == TouchLocationState.Pressed || + touchLocation.State == TouchLocationState.Moved) && touchLocation.Id == _picked) + { + Vector2 delta = touchLocation.Position - _center; + if (delta != Vector2.Zero) + { + float _length = delta.Length(); + if (_length > 25f) + { + delta *= (25f / _length); + } + StickPosition = delta / 25f; + StickPosition.Y *= -1f; + _position = _center + delta; + } + } + if (touchLocation.State == TouchLocationState.Released && touchLocation.Id == _picked) + { + _picked = -1; + _position = _center; + StickPosition = Vector2.Zero; + } + } + + public void Draw(SpriteBatch batch) + { + batch.Draw(_socketSprite.Texture, _center, null, Color.White, 0f, + _socketSprite.Origin, 1f, SpriteEffects.None, 0f); + batch.Draw(_stickSprite.Texture, _position, null, Color.White, 0f, + _stickSprite.Origin, 1f, SpriteEffects.None, 0f); + } + } +} diff --git a/ae-physics.csproj b/ae-physics.csproj new file mode 100644 index 0000000..fca03da --- /dev/null +++ b/ae-physics.csproj @@ -0,0 +1,143 @@ + + + + + Debug + AnyCPU + {68607752-5F66-44D4-A8A2-B3E6AF81F411} + Library + Properties + ae_physics + ae-physics + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ae-physics.sln b/ae-physics.sln new file mode 100644 index 0000000..7e62792 --- /dev/null +++ b/ae-physics.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ae-physics", "ae-physics.csproj", "{68607752-5F66-44D4-A8A2-B3E6AF81F411}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {68607752-5F66-44D4-A8A2-B3E6AF81F411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68607752-5F66-44D4-A8A2-B3E6AF81F411}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68607752-5F66-44D4-A8A2-B3E6AF81F411}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68607752-5F66-44D4-A8A2-B3E6AF81F411}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/lib/FNA b/lib/FNA new file mode 160000 index 0000000..4e35338 --- /dev/null +++ b/lib/FNA @@ -0,0 +1 @@ +Subproject commit 4e35338d6c3dfb85752b4b94c6ced3223c8b35f5