// // this code is from the great farseer project // http://farseerphysics.codeplex.com/ // // it is used for the calculation of the origin of a texture, // when DefaultTextureOriginMethod "Centroid" is set in the Settings. // using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; namespace GLEED2D { /// /// Provides an implementation of a strongly typed List with Vector2 /// public class Vertices : List { private Vector2 _res; private Vector2 _vectorTemp1 = Vector2.Zero; private Vector2 _vectorTemp2 = Vector2.Zero; private Vector2 _vectorTemp3 = Vector2.Zero; private Vector2 _vectorTemp4 = Vector2.Zero; private Vector2 _vectorTemp5 = Vector2.Zero; public Vertices() { } public Vertices(Vector2[] vector2) { for (int i = 0; i < vector2.Length; i++) { Add(vector2[i]); } } public Vertices(Vertices vertices) { for (int i = 0; i < vertices.Count; i++) { Add(vertices[i]); } } /// /// Gets an array of vertices. /// /// public Vector2[] GetVerticesArray() { return ToArray(); } /// /// Gets the next index. /// /// The index. /// public int NextIndex(int index) { if (index == Count - 1) { return 0; } return index + 1; } /// /// Gets the previous index. /// /// The index. /// public int PreviousIndex(int index) { if (index == 0) { return Count - 1; } return index - 1; } /// /// Gets the edge. /// /// The index. /// public Vector2 GetEdge(int index) { int nextIndex = NextIndex(index); _vectorTemp2 = this[nextIndex]; _vectorTemp3 = this[index]; Vector2.Subtract(ref _vectorTemp2, ref _vectorTemp3, out _vectorTemp1); return _vectorTemp1; } /// /// Gets the edge. /// /// The index. /// The edge. public void GetEdge(int index, out Vector2 edge) { int nextIndex = NextIndex(index); _vectorTemp2 = this[nextIndex]; _vectorTemp3 = this[index]; Vector2.Subtract(ref _vectorTemp2, ref _vectorTemp3, out edge); } /// /// Gets the edge mid point. /// /// The index. /// public Vector2 GetEdgeMidPoint(int index) { GetEdge(index, out _vectorTemp1); Vector2.Multiply(ref _vectorTemp1, .5f, out _vectorTemp2); _vectorTemp3 = this[index]; Vector2.Add(ref _vectorTemp3, ref _vectorTemp2, out _vectorTemp1); return _vectorTemp1; } /// /// Gets the edge mid point. /// /// The index. /// The mid point. public void GetEdgeMidPoint(int index, out Vector2 midPoint) { GetEdge(index, out _vectorTemp1); Vector2.Multiply(ref _vectorTemp1, .5f, out _vectorTemp2); _vectorTemp3 = this[index]; Vector2.Add(ref _vectorTemp3, ref _vectorTemp2, out midPoint); } /// /// Gets the edge normal. /// /// The index. /// public Vector2 GetEdgeNormal(int index) { GetEdge(index, out _vectorTemp1); _vectorTemp2.X = -_vectorTemp1.Y; _vectorTemp2.Y = _vectorTemp1.X; Vector2.Normalize(ref _vectorTemp2, out _vectorTemp3); return _vectorTemp3; } /// /// Gets the edge normal. /// /// The index. /// The edge normal. public void GetEdgeNormal(int index, out Vector2 edgeNormal) { GetEdge(index, out _vectorTemp4); _vectorTemp5.X = -_vectorTemp4.Y; _vectorTemp5.Y = _vectorTemp4.X; Vector2.Normalize(ref _vectorTemp5, out edgeNormal); } /// /// Gets the vertex normal. /// /// The index. /// public Vector2 GetVertexNormal(int index) { GetEdgeNormal(index, out _vectorTemp1); int prevIndex = PreviousIndex(index); GetEdgeNormal(prevIndex, out _vectorTemp2); Vector2.Add(ref _vectorTemp1, ref _vectorTemp2, out _vectorTemp3); Vector2.Normalize(ref _vectorTemp3, out _vectorTemp1); return _vectorTemp1; } /// /// Gets the vertex normal. /// /// The index. /// The vertex normal. public void GetVertexNormal(int index, out Vector2 vertexNormal) { GetEdgeNormal(index, out _vectorTemp1); int prevIndex = PreviousIndex(index); GetEdgeNormal(prevIndex, out _vectorTemp2); Vector2.Add(ref _vectorTemp1, ref _vectorTemp2, out _vectorTemp3); Vector2.Normalize(ref _vectorTemp3, out vertexNormal); } /// /// Finds the shortest edge. /// /// public float GetShortestEdge() { float shortestEdge = float.MaxValue; for (int i = 0; i < Count; i++) { GetEdge(i, out _vectorTemp1); float length = _vectorTemp1.Length(); if (length < shortestEdge) { shortestEdge = length; } } return shortestEdge; } /// /// Divides the edges up into the specified length. /// /// Length of the max edge. public void SubDivideEdges(float maxEdgeLength) { Vertices verticesTemp = new Vertices(); for (int i = 0; i < Count; i++) { Vector2 vertA = this[i]; Vector2 vertB = this[NextIndex(i)]; Vector2 edge; Vector2.Subtract(ref vertA, ref vertB, out edge); float edgeLength = edge.Length(); verticesTemp.Add(vertA); if (edgeLength > maxEdgeLength) //need to subdivide { double edgeCount = Math.Ceiling(edgeLength / (double)maxEdgeLength); for (int j = 0; j < edgeCount - 1; j++) { Vector2 vert = Vector2.Lerp(vertA, vertB, (j + 1) / (float)edgeCount); verticesTemp.Add(vert); } } } Clear(); for (int k = 0; k < verticesTemp.Count; k++) { Add(verticesTemp[k]); } } /// /// Forces counter clock wise order. /// public void ForceCounterClockWiseOrder() { // the sign of the 'area' of the polygon is all // we are interested in. float area = GetSignedArea(); if (area > 0) { Reverse(); } } /// /// Gets the signed area. /// /// private 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() { float area = GetArea(); return GetCentroid(area); } /// /// Gets the centroid. /// /// The area. /// public Vector2 GetCentroid(float area) { //calc centroid on counter clockwise verts. Vertices verts = new Vertices(this); verts.ForceCounterClockWiseOrder(); float cx = 0, cy = 0; int i; float factor; for (i = 0; i < Count; i++) { int j = (i + 1) % Count; factor = -(verts[i].X * verts[j].Y - verts[j].X * verts[i].Y); cx += (verts[i].X + verts[j].X) * factor; cy += (verts[i].Y + verts[j].Y) * factor; } area *= 6.0f; factor = 1 / area; cx *= factor; cy *= factor; _res.X = cx; _res.Y = cy; return _res; } /// /// Gets the moment of inertia from the vertices /// /// /// Can't calculate MOI on zero vertices public float GetMomentOfInertia() { Vertices verts = new Vertices(this); //Make sure that the vertices are in counter clockwise order. verts.ForceCounterClockWiseOrder(); //Get the centroid and center the vertices around the centroid. Vector2 centroid = verts.GetCentroid(); Vector2.Multiply(ref centroid, -1, out centroid); verts.Translate(ref centroid); if (verts.Count == 0) throw new ArgumentException("Can't calculate MOI on zero vertices"); if (verts.Count == 1) return 0; float denom = 0; float numer = 0; Vector2 v2; Vector2 v1 = verts[verts.Count - 1]; for (int index = 0; index < verts.Count; index++, v1 = v2) { v2 = verts[index]; float a; Vector2.Dot(ref v2, ref v2, out a); float b; Vector2.Dot(ref v2, ref v1, out b); float c; Vector2.Dot(ref v1, ref v1, out c); //Vector2.Cross(ref v1, ref v2, out d); float d; Calculator.Cross(ref v1, ref v2, out d); d = Math.Abs(d); numer += d; denom += (a + b + c) * d; } return denom / (numer * 6); } /// /// 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; } } } } /// /// 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); } /// /// Determines whether this instance is convex. /// /// /// true if this instance is convex; otherwise, false. /// public bool IsConvex() { bool isPositive = false; for (int i = 0; i < Count; ++i) { int lower = (i == 0) ? (Count - 1) : (i - 1); int middle = i; int upper = (i == Count - 1) ? (0) : (i + 1); float dx0 = this[middle].X - this[lower].X; float dy0 = this[middle].Y - this[lower].Y; float dx1 = this[upper].X - this[middle].X; float dy1 = this[upper].Y - this[middle].Y; float cross = dx0 * dy1 - dx1 * dy0; // Cross product should have same sign // for each vertex if poly is convex. bool newIsP = (cross >= 0) ? true : false; if (i == 0) { isPositive = newIsP; } else if (isPositive != newIsP) { return false; } } return true; } /// /// Creates a rectangle with the specified width and height /// with automatic subdivsion. /// /// The width. /// The height. /// The vertices that define a rectangle public static Vertices CreateRectangle(float width, float height) { //Note: The rectangle has vertices along the edges. This is to support the distance grid better. Vertices vertices = new Vertices(); vertices.Add(new Vector2(-width * .5f, -height * .5f)); vertices.Add(new Vector2(-width * .5f, -height * .25f)); vertices.Add(new Vector2(-width * .5f, 0)); vertices.Add(new Vector2(-width * .5f, height * .25f)); vertices.Add(new Vector2(-width * .5f, height * .5f)); vertices.Add(new Vector2(-width * .25f, height * .5f)); vertices.Add(new Vector2(0, height * .5f)); vertices.Add(new Vector2(width * .25f, height * .5f)); vertices.Add(new Vector2(width * .5f, height * .5f)); vertices.Add(new Vector2(width * .5f, height * .25f)); vertices.Add(new Vector2(width * .5f, 0)); vertices.Add(new Vector2(width * .5f, -height * .25f)); vertices.Add(new Vector2(width * .5f, -height * .5f)); vertices.Add(new Vector2(width * .25f, -height * .5f)); vertices.Add(new Vector2(0, -height * .5f)); vertices.Add(new Vector2(-width * .25f, -height * .5f)); return vertices; } /// /// Creates a rectangle with the specified width and height /// with no subdivision. /// /// The width. /// The height. /// The vertices that define a rectangle public static Vertices CreateSimpleRectangle(float width, float height) { Vertices vertices = new Vertices(); vertices.Add(new Vector2(-width * .5f, -height * .5f)); vertices.Add(new Vector2(-width * .5f, height * .5f)); vertices.Add(new Vector2(width * .5f, height * .5f)); vertices.Add(new Vector2(width * .5f, -height * .5f)); 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 = 1; i < numberOfEdges; i++) vertices.Add(new Vector2(xRadius * Calculator.Cos(stepSize * i), -yRadius * Calculator.Sin(stepSize * i))); 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; float toothTipStepSize = (stepSize / 2f) * tipPercentage; float toothAngleStepSize = (stepSize - (toothTipStepSize * 2f)) / 2f; for (int i = 0; i < numberOfTeeth; i++) { vertices.Add(new Vector2((radius) * Calculator.Cos(stepSize * i), -(radius) * Calculator.Sin(stepSize * i))); vertices.Add(new Vector2((radius + toothHeight) * Calculator.Cos((stepSize * i) + toothAngleStepSize), -(radius + toothHeight) * Calculator.Sin((stepSize * i) + toothAngleStepSize))); vertices.Add(new Vector2((radius + toothHeight) * Calculator.Cos((stepSize * i) + toothAngleStepSize + toothTipStepSize), -(radius + toothHeight) * Calculator.Sin((stepSize * i) + toothAngleStepSize + toothTipStepSize))); vertices.Add(new Vector2((radius) * Calculator.Cos((stepSize * i) + (toothAngleStepSize * 2f) + toothTipStepSize), -(radius) * Calculator.Sin((stepSize * i) + (toothAngleStepSize * 2f) + toothTipStepSize))); } return vertices; } 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(); } #region Sickbattery's Extension /// /// TODO: /// 1.) Das Array welches ich bekomme am besten in einen bool array verwandeln. Würde die Geschwindigkeit verbessern /// private static readonly int[,] _closePixels = new int[8, 2] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } }; /// /// Creates vertices from the texture data. /// /// The data. /// The width. /// The height. /// public static Vertices CreatePolygon(uint[] data, int width, int height) { PolygonCreationAssistance pca = new PolygonCreationAssistance(data, width, height); List verts = CreatePolygon(ref pca); return verts[0]; } /// /// Creates a list of vertices from the texture data. /// /// The data. /// The width. /// The height. /// The hull tolerance. This argument controls the amount of details found in the detection. /// The alpha tolerance. /// if set to true [multi part detection]. /// if set to true [hole detection]. /// public static List CreatePolygon(uint[] data, int width, int height, float hullTolerance, byte alphaTolerance, bool multiPartDetection, bool holeDetection) { PolygonCreationAssistance pca = new PolygonCreationAssistance(data, width, height); pca.HullTolerance = hullTolerance; pca.AlphaTolerance = alphaTolerance; pca.MultipartDetection = multiPartDetection; pca.HoleDetection = holeDetection; return CreatePolygon(ref pca); } /// /// Creates a list of vertices. Create a PolygonCreationAssistance that contains all the data needed for detection. /// /// The pca. /// public static List CreatePolygon(ref PolygonCreationAssistance pca) { List polygons = new List(); Vertices polygon; Vertices holePolygon; int vertex1Index; int vertex2Index; Vector2? holeEntrance = null; Vector2? polygonEntrance = null; List blackList = new List(); bool inPolygon; bool searchOn; // First of all: Check the array you just got. if (pca.IsValid()) { do { if (polygons.Count == 0) { polygon = CreateSimplePolygon(ref pca, Vector2.Zero, Vector2.Zero); if (polygon != null && polygon.Count > 2) { polygonEntrance = GetTopMostVertex(ref polygon); } } else if (polygonEntrance.HasValue) { polygon = CreateSimplePolygon(ref pca, polygonEntrance.Value, new Vector2(polygonEntrance.Value.X - 1f, polygonEntrance.Value.Y)); } else { break; } searchOn = false; if (polygon != null && polygon.Count > 2) { if (pca.HoleDetection) { do { holeEntrance = GetHoleHullEntrance(ref pca, ref polygon, holeEntrance); if (holeEntrance.HasValue) { if (!blackList.Contains(holeEntrance.Value)) { blackList.Add(holeEntrance.Value); holePolygon = CreateSimplePolygon(ref pca, holeEntrance.Value, new Vector2(holeEntrance.Value.X + 1, holeEntrance.Value.Y)); if (holePolygon != null && holePolygon.Count > 2) { holePolygon.Add(holePolygon[0]); if (SplitPolygonEdge(ref polygon, EdgeAlignment.Vertical, holeEntrance.Value, out vertex1Index, out vertex2Index)) { polygon.InsertRange(vertex2Index, holePolygon); } } } else { break; } } else { break; } } while (true); } polygons.Add(polygon); if (pca.MultipartDetection) { /// 1: 95 / 151 /// 2: 232 / 252 /// while (GetNextHullEntrance(ref pca, polygonEntrance.Value, out polygonEntrance)) { inPolygon = false; for (int i = 0; i < polygons.Count; i++) { polygon = polygons[i]; if (InPolygon(ref pca, ref polygon, polygonEntrance.Value)) { inPolygon = true; break; } } if (!inPolygon) { searchOn = true; break; } } } } } while (searchOn); } else { throw new Exception("Sizes don't match: Color array must contain texture width * texture height elements."); } return polygons; } private static Vector2? GetHoleHullEntrance(ref PolygonCreationAssistance pca, ref Vertices polygon, Vector2? startVertex) { List edges = new List(); Vector2? entrance; int startLine; int endLine; int lastSolid = 0; bool foundSolid; bool foundTransparent; if (polygon != null && polygon.Count > 0) { if (startVertex.HasValue) { startLine = (int)startVertex.Value.Y; } else { startLine = (int)GetTopMostCoord(ref polygon); } endLine = (int)GetBottomMostCoord(ref polygon); if (startLine > 0 && startLine < pca.Height && endLine > 0 && endLine < pca.Height) { // go from top to bottom of the polygon for (int y = startLine; y <= endLine; y += pca.HoleDetectionLineStepSize) { // get x-coord of every polygon edge which crosses y edges = GetCrossingEdges(ref polygon, EdgeAlignment.Vertical, y); // we need an even number of crossing edges if (edges.Count > 1 && edges.Count % 2 == 0) { for (int i = 0; i < edges.Count; i += 2) { foundSolid = false; foundTransparent = false; for (int x = (int)edges[i].CrossingPoint.X; x <= (int)edges[i + 1].CrossingPoint.X; x++) { if (pca.IsSolid(x, y)) { if (!foundTransparent) { foundSolid = true; lastSolid = x; } if (foundSolid && foundTransparent) { entrance = new Vector2(lastSolid, y); if (DistanceToHullAcceptable(ref pca, ref polygon, entrance.Value, true)) { return entrance; } entrance = null; break; } } else { if (foundSolid) { foundTransparent = true; } } } } } } } } return null; } private static bool DistanceToHullAcceptable(ref PolygonCreationAssistance pca, ref Vertices polygon, Vector2 point, bool higherDetail) { Vector2 edgeVertex1; Vector2 edgeVertex2; if (polygon != null && polygon.Count > 2) { edgeVertex2 = polygon[polygon.Count - 1]; if (higherDetail) { for (int i = 0; i < polygon.Count; i++) { edgeVertex1 = polygon[i]; if (Calculator.DistanceBetweenPointAndLineSegment(point, edgeVertex1, edgeVertex2) <= pca.HullTolerance || Calculator.DistanceBetweenPointAndPoint(ref point, ref edgeVertex1) <= pca.HullTolerance) { return false; } edgeVertex2 = polygon[i]; } return true; } else { for (int i = 0; i < polygon.Count; i++) { edgeVertex1 = polygon[i]; if (Calculator.DistanceBetweenPointAndLineSegment(point, edgeVertex1, edgeVertex2) <= pca.HullTolerance) { return false; } edgeVertex2 = polygon[i]; } return true; } } return false; } private static bool InPolygon(ref PolygonCreationAssistance pca, ref Vertices polygon, Vector2 point) { bool inPolygon = !DistanceToHullAcceptable(ref pca, ref polygon, point, true); if (!inPolygon) { List edges = GetCrossingEdges(ref polygon, EdgeAlignment.Vertical, (int)point.Y); if (edges.Count > 0 && edges.Count % 2 == 0) { for (int i = 0; i < edges.Count; i += 2) { if (edges[i].CrossingPoint.X <= point.X && edges[i + 1].CrossingPoint.X >= point.X) { return true; } } return false; } return false; } return inPolygon; } private static Vector2? GetTopMostVertex(ref 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 static float GetTopMostCoord(ref 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 static float GetBottomMostCoord(ref 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 static List GetCrossingEdges(ref Vertices polygon, EdgeAlignment edgeAlign, int checkLine) { List edges = new List(); Vector2 slope; Vector2 edgeVertex1; Vector2 edgeVertex2; Vector2 slopePreview; Vector2 edgeVertexPreview; Vector2 crossingPoint; bool addCrossingPoint; if (polygon.Count > 1) { edgeVertex2 = polygon[polygon.Count - 1]; switch (edgeAlign) { case EdgeAlignment.Vertical: for (int i = 0; i < polygon.Count; i++) { edgeVertex1 = polygon[i]; if ((edgeVertex1.Y >= checkLine && edgeVertex2.Y <= checkLine) || (edgeVertex1.Y <= checkLine && edgeVertex2.Y >= checkLine)) { if (edgeVertex1.Y != edgeVertex2.Y) { addCrossingPoint = true; slope = edgeVertex2 - edgeVertex1; if (edgeVertex1.Y == checkLine) { edgeVertexPreview = polygon[(i + 1) % polygon.Count]; slopePreview = edgeVertex1 - edgeVertexPreview; if (slope.Y > 0) { addCrossingPoint = (slopePreview.Y <= 0); } else { addCrossingPoint = (slopePreview.Y >= 0); } } if (addCrossingPoint) { crossingPoint = new Vector2((checkLine - edgeVertex1.Y) / slope.Y * slope.X + edgeVertex1.X, (float)checkLine); edges.Add(new CrossingEdgeInfo(edgeVertex1, edgeVertex2, crossingPoint, edgeAlign)); } } } edgeVertex2 = edgeVertex1; } break; case EdgeAlignment.Horizontal: throw new Exception("EdgeAlignment.Horizontal isn't implemented yet. Sorry."); } } edges.Sort(); return edges; } private static bool SplitPolygonEdge(ref Vertices polygon, EdgeAlignment edgeAlign, Vector2 coordInsideThePolygon, out int vertex1Index, out int vertex2Index) { List edges = new List(); Vector2 slope; int edgeVertex1Index; int edgeVertex2Index; int nearestEdgeVertex1Index = 0; int nearestEdgeVertex2Index = 0; bool edgeFound = false; float distance; float shortestDistance = float.MaxValue; bool edgeCoordFound = false; Vector2 foundEdgeCoord = Vector2.Zero; vertex1Index = 0; vertex2Index = 0; switch (edgeAlign) { case EdgeAlignment.Vertical: edges = GetCrossingEdges(ref polygon, EdgeAlignment.Vertical, (int)coordInsideThePolygon.Y); foundEdgeCoord.Y = coordInsideThePolygon.Y; if (edges != null && edges.Count > 1 && edges.Count % 2 == 0) { for (int i = 0; i < edges.Count; i++) { if (edges[i].CrossingPoint.X < coordInsideThePolygon.X) { distance = coordInsideThePolygon.X - edges[i].CrossingPoint.X; if (distance < shortestDistance) { shortestDistance = distance; foundEdgeCoord.X = edges[i].CrossingPoint.X; edgeCoordFound = true; } } } if (edgeCoordFound) { shortestDistance = float.MaxValue; edgeVertex2Index = polygon.Count - 1; for (edgeVertex1Index = 0; edgeVertex1Index < polygon.Count; edgeVertex1Index++) { distance = Calculator.DistanceBetweenPointAndLineSegment(foundEdgeCoord, polygon[edgeVertex1Index], polygon[edgeVertex2Index]); if (distance < shortestDistance) { shortestDistance = distance; nearestEdgeVertex1Index = edgeVertex1Index; nearestEdgeVertex2Index = edgeVertex2Index; edgeFound = true; } edgeVertex2Index = edgeVertex1Index; } if (edgeFound) { slope = polygon[nearestEdgeVertex2Index] - polygon[nearestEdgeVertex1Index]; slope.Normalize(); distance = Calculator.DistanceBetweenPointAndPoint(polygon[nearestEdgeVertex1Index], foundEdgeCoord); vertex1Index = nearestEdgeVertex1Index; vertex2Index = nearestEdgeVertex1Index + 1; polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex1Index]); polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex2Index]); return true; } } } break; case EdgeAlignment.Horizontal: throw new Exception("EdgeAlignment.Horizontal isn't implemented yet. Sorry."); } return false; } private static Vertices CreateSimplePolygon(ref PolygonCreationAssistance pca, Vector2 entrance, Vector2 last) { bool entranceFound = false; Vertices polygon = new Vertices(); Vertices hullArea = new Vertices(); Vector2 current = Vector2.Zero; Vector2 next; #region Entrance check // Get the entrance point. //todo: alle möglichkeiten testen if (entrance == Vector2.Zero || !pca.InBounds(entrance)) { entranceFound = GetHullEntrance(ref pca, out entrance); if (entranceFound) { current = new Vector2(entrance.X - 1f, entrance.Y); } } else { if (pca.IsSolid(entrance)) { if (IsNearPixel(ref pca, entrance, last)) { current = last; entranceFound = true; } else { Vector2 temp; if (SearchNearPixels(ref pca, false, entrance, out temp)) { current = temp; entranceFound = true; } else { entranceFound = false; } } } } #endregion if (entranceFound) { // next has to be set to entrance so it'll be added as the // first point in the list. next = entrance; // Fast bugfix XD. The entrance point of course has to be added first to the polygon XD. Damn I forgot that! polygon.Add(entrance); do { Vector2 outstanding; // Add the vertex to a hull pre vision list. hullArea.Add(next); // Search in the pre vision list for an outstanding point. if (SearchForOutstandingVertex(ref hullArea, pca.HullTolerance, out outstanding)) { // 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 pca, ref last, ref current, out next)) { next = entrance; } } // Exit loop if next piont is the entrance point. The hull is complete now! while (next != entrance); } return polygon; } private static bool SearchNearPixels(ref PolygonCreationAssistance pca, bool searchingForSolidPixel, Vector2 current, out Vector2 foundPixel) { int x; int y; for (int i = 0; i < 8; i++) { x = (int)current.X + _closePixels[i, 0]; y = (int)current.Y + _closePixels[i, 1]; if (!searchingForSolidPixel ^ pca.IsSolid(x, y)) { foundPixel = new Vector2(x, y); return true; } } // Nothing found. foundPixel = Vector2.Zero; return false; } private static bool IsNearPixel(ref PolygonCreationAssistance pca, Vector2 current, Vector2 near) { for (int i = 0; i < 8; i++) { int x = (int)current.X + _closePixels[i, 0]; int y = (int)current.Y + _closePixels[i, 1]; if (x >= 0 && x <= pca.Width && y >= 0 && y <= pca.Height) { if (x == (int)near.X && y == (int)near.Y) { return true; } } } return false; } private static bool GetHullEntrance(ref PolygonCreationAssistance pca, out Vector2 entrance) { // Search for first solid pixel. for (int y = 0; y <= pca.Height; y++) { for (int x = 0; x <= pca.Width; x++) { if (pca.IsSolid(x, y)) { entrance = new Vector2(x, y); return true; } } } // If there are no solid pixels. entrance = Vector2.Zero; return false; } private static bool GetNextHullEntrance(ref PolygonCreationAssistance pca, Vector2 start, out Vector2? entrance) { // Search for first solid pixel. int size = pca.Height * pca.Width; int x; bool foundTransparent = false; for (int i = (int)start.X + (int)start.Y * pca.Width; i <= size; i++) { if (pca.IsSolid(i)) { if (foundTransparent) { x = i % pca.Width; entrance = new Vector2(x, (i - x) / pca.Width); return true; } } else { foundTransparent = true; } } // If there are no solid pixels. entrance = null; return false; } private static bool GetNextHullPoint(ref PolygonCreationAssistance pca, ref Vector2 last, ref Vector2 current, out Vector2 next) { int x; int y; int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(last, current); int indexOfPixelToCheck; const int pixelsToCheck = 8;// _closePixels.Length; for (int i = 0; i < pixelsToCheck; i++) { indexOfPixelToCheck = (indexOfFirstPixelToCheck + i) % pixelsToCheck; x = (int)current.X + _closePixels[indexOfPixelToCheck, 0]; y = (int)current.Y + _closePixels[indexOfPixelToCheck, 1]; if (x >= 0 && x < pca.Width && y >= 0 && y <= pca.Height) { if (pca.IsSolid(x, y)) //todo { next = new Vector2(x, y); return true; } } } next = Vector2.Zero; return false; } private static bool SearchForOutstandingVertex(ref Vertices hullArea, float hullTolerance, out Vector2 outstanding) { int hullAreaLastPoint = hullArea.Count - 1; Vector2 outstandingResult = Vector2.Zero; bool found = false; // Search between the first and last hull point. for (int i = 1; i < hullAreaLastPoint; i++) { // Check if the distance is over the one that's tolerable. if (Calculator.DistanceBetweenPointAndLineSegment(hullArea[i], hullArea[0], hullArea[hullAreaLastPoint]) >= hullTolerance) { outstandingResult = hullArea[i]; found = true; break; } } outstanding = outstandingResult; return found; } private static int GetIndexOfFirstPixelToCheck(Vector2 last, 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 first pixel. 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; } #endregion #region DrDeth's Extension /// /// Merges two polygons, given that they intersect. /// /// The first polygon. /// The second polygon. /// The error returned from union /// The union of the two polygons, or null if there was an error. public static Vertices Union(Vertices polygon1, Vertices polygon2, out PolyUnionError error) { Vertices poly1; Vertices poly2; List intersections; int startingIndex = PreparePolygons(polygon1, polygon2, out poly1, out poly2, out intersections, out error); if (startingIndex == -1) { switch (error) { case PolyUnionError.NoIntersections: return null; case PolyUnionError.Poly1InsidePoly2: return polygon2; } } Vertices union = new Vertices(); Vertices currentPoly = poly1; Vertices otherPoly = poly2; // Store the starting vertex so we can refer to it later. Vector2 startingVertex = poly1[startingIndex]; int currentIndex = startingIndex; do { // Add the current vertex to the final union union.Add(currentPoly[currentIndex]); foreach (EdgeIntersectInfo intersect in intersections) { // If the current point is an intersection point if (currentPoly[currentIndex] == intersect.IntersectionPoint) { // Make sure we want to swap polygons here. int otherIndex = otherPoly.IndexOf(intersect.IntersectionPoint); // If the next vertex, if we do swap, is not inside the current polygon, // then its safe to swap, otherwise, just carry on with the current poly. if (!PointInPolygonAngle(otherPoly[otherPoly.NextIndex(otherIndex)], currentPoly)) { // switch polygons if (currentPoly == poly1) { currentPoly = poly2; otherPoly = poly1; } else { currentPoly = poly1; otherPoly = poly2; } // set currentIndex currentIndex = otherIndex; // Stop checking intersections for this point. break; } } } // Move to next index currentIndex = currentPoly.NextIndex(currentIndex); } while ((currentPoly[currentIndex] != startingVertex) && (union.Count <= (poly1.Count + poly2.Count))); // If the number of vertices in the union is more than the combined vertices // of the input polygons, then something is wrong and the algorithm will // loop forever. Luckily, we check for that. if (union.Count > (poly1.Count + poly2.Count)) { error = PolyUnionError.InfiniteLoop; } return union; } /// /// Subtracts one polygon from another. /// /// The base polygon. /// The polygon to subtract from the base. /// The error. /// /// The result of the polygon subtraction, or null if there was an error. /// public static Vertices Subtract(Vertices polygon1, Vertices polygon2, out PolyUnionError error) { Vertices poly1; Vertices poly2; List intersections; int startingIndex = PreparePolygons(polygon1, polygon2, out poly1, out poly2, out intersections, out error); if (startingIndex == -1) { switch (error) { case PolyUnionError.NoIntersections: return null; case PolyUnionError.Poly1InsidePoly2: return null; } } Vertices subtract = new Vertices(); Vertices currentPoly = poly1; Vertices otherPoly = poly2; // Store the starting vertex so we can refer to it later. Vector2 startingVertex = poly1[startingIndex]; int currentIndex = startingIndex; // Trace direction bool forward = true; do { // Add the current vertex to the final union subtract.Add(currentPoly[currentIndex]); foreach (EdgeIntersectInfo intersect in intersections) { // If the current point is an intersection point if (currentPoly[currentIndex] == intersect.IntersectionPoint) { // Make sure we want to swap polygons here. int otherIndex = otherPoly.IndexOf(intersect.IntersectionPoint); Vector2 otherVertex; if (forward) { otherVertex = otherPoly[otherPoly.PreviousIndex(otherIndex)]; // If the next vertex, if we do swap, is inside the current polygon, // then its safe to swap, otherwise, just carry on with the current poly. if (PointInPolygonAngle(otherVertex, currentPoly)) { // switch polygons if (currentPoly == poly1) { currentPoly = poly2; otherPoly = poly1; } else { currentPoly = poly1; otherPoly = poly2; } // set currentIndex currentIndex = otherIndex; // Reverse direction forward = !forward; // Stop checking intersections for this point. break; } } else { otherVertex = otherPoly[otherPoly.NextIndex(otherIndex)]; // If the next vertex, if we do swap, is outside the current polygon, // then its safe to swap, otherwise, just carry on with the current poly. if (!PointInPolygonAngle(otherVertex, currentPoly)) { // switch polygons if (currentPoly == poly1) { currentPoly = poly2; otherPoly = poly1; } else { currentPoly = poly1; otherPoly = poly2; } // set currentIndex currentIndex = otherIndex; // Reverse direction forward = !forward; // Stop checking intersections for this point. break; } } } } if (forward) { // Move to next index currentIndex = currentPoly.NextIndex(currentIndex); } else { currentIndex = currentPoly.PreviousIndex(currentIndex); } } while ((currentPoly[currentIndex] != startingVertex) && (subtract.Count <= (poly1.Count + poly2.Count))); // If the number of vertices in the union is more than the combined vertices // of the input polygons, then something is wrong and the algorithm will // loop forever. Luckily, we check for that. if (subtract.Count > (poly1.Count + poly2.Count)) { error = PolyUnionError.InfiniteLoop; } return subtract; } /// /// Finds the intersection between two polygons. /// /// The first polygon. /// The second polygon. /// The error. /// /// The intersection of the two polygons, or null if there was an error. /// public static Vertices Intersect(Vertices polygon1, Vertices polygon2, out PolyUnionError error) { error = PolyUnionError.None; Vertices poly1; Vertices poly2; List intersections; PolyUnionError gotError; int startingIndex = PreparePolygons(polygon1, polygon2, out poly1, out poly2, out intersections, out gotError); if (startingIndex == -1) { switch (gotError) { case PolyUnionError.NoIntersections: return null; case PolyUnionError.Poly1InsidePoly2: return polygon2; } } Vertices intersectOut = new Vertices(); Vertices currentPoly = poly1; Vertices otherPoly = poly2; // Store the starting vertex so we can refer to it later. int currentIndex = poly1.IndexOf(intersections[0].IntersectionPoint); Vector2 startingVertex = poly1[currentIndex]; do { // Add the current vertex to the final union intersectOut.Add(currentPoly[currentIndex]); foreach (EdgeIntersectInfo intersect in intersections) { // If the current point is an intersection point if (currentPoly[currentIndex] == intersect.IntersectionPoint) { // Make sure we want to swap polygons here. int otherIndex = otherPoly.IndexOf(intersect.IntersectionPoint); // If the next vertex, if we do swap, is inside the current polygon, // then its safe to swap, otherwise, just carry on with the current poly. if (PointInPolygonAngle(otherPoly[otherPoly.NextIndex(otherIndex)], currentPoly)) { // switch polygons if (currentPoly == poly1) { currentPoly = poly2; otherPoly = poly1; } else { currentPoly = poly1; otherPoly = poly2; } // set currentIndex currentIndex = otherIndex; // Stop checking intersections for this point. break; } } } // Move to next index currentIndex = currentPoly.NextIndex(currentIndex); } while ((currentPoly[currentIndex] != startingVertex) && (intersectOut.Count <= (poly1.Count + poly2.Count))); // If the number of vertices in the union is more than the combined vertices // of the input polygons, then something is wrong and the algorithm will // loop forever. Luckily, we check for that. if (intersectOut.Count > (poly1.Count + poly2.Count)) { error = PolyUnionError.InfiniteLoop; } return intersectOut; } /// /// Prepares the polygons. /// /// The polygon1. /// The polygon2. /// The poly1. /// The poly2. /// The intersections. /// The error. /// private static int PreparePolygons(Vertices polygon1, Vertices polygon2, out Vertices poly1, out Vertices poly2, out List intersections, out PolyUnionError error) { error = PolyUnionError.None; // Make a copy of the polygons so that we dont modify the originals, and // force vertices to integer (pixel) values. poly1 = Round(polygon1); poly2 = Round(polygon2); // Find intersection points intersections = new List(); if (!VerticesIntersect(poly1, poly2, ref intersections)) { // No intersections found - polygons do not overlap. error = PolyUnionError.NoIntersections; return -1; } // Add intersection points to original polygons, ignoring existing points. foreach (EdgeIntersectInfo intersect in intersections) { if (!poly1.Contains(intersect.IntersectionPoint)) { poly1.Insert(poly1.IndexOf(intersect.EdgeOne.EdgeStart) + 1, intersect.IntersectionPoint); } if (!poly2.Contains(intersect.IntersectionPoint)) { poly2.Insert(poly2.IndexOf(intersect.EdgeTwo.EdgeStart) + 1, intersect.IntersectionPoint); } } // Find starting point on the edge of polygon1 // that is outside of the intersected area // to begin polygon trace. int startingIndex = -1; int currentIndex = 0; do { if (!PointInPolygonAngle(poly1[currentIndex], poly2)) { startingIndex = currentIndex; break; } currentIndex = poly1.NextIndex(currentIndex); } while (currentIndex != 0); // If we dont find a point on polygon1 thats outside of the // intersect area, the polygon1 must be inside of polygon2, // in which case, polygon2 IS the union of the two. if (startingIndex == -1) { error = PolyUnionError.Poly1InsidePoly2; } return startingIndex; } /// /// Check and return polygon intersections /// /// /// /// /// private static bool VerticesIntersect(Vertices polygon1, Vertices polygon2, ref List intersections) { // Make sure the output is clear before we start. intersections.Clear(); // Iterate through polygon1's edges for (int i = 0; i < polygon1.Count; i++) { // Get edge vertices Vector2 p1 = polygon1[i]; Vector2 p2 = polygon1[polygon1.NextIndex(i)]; // Get intersections between this edge and polygon2 for (int j = 0; j < polygon2.Count; j++) { Vector2 point = Vector2.Zero; Vector2 p3 = polygon2[j]; Vector2 p4 = polygon2[polygon2.NextIndex(j)]; // _defaultFloatTolerance = .00001f (Perhaps this should be made available publically from CollisionHelper? // Check if the edges intersect if (true) //(CollisionHelper.LineIntersect(p1, p2, p3, p4, true, true, 0.00001f, out point)) { //throw new NotImplementedException("Commented out"); // Here, we round the returned intersection point to its nearest whole number. // This prevents floating point anomolies where 99.9999-> is returned instead of 100. point = new Vector2((float)Math.Round(point.X, 0), (float)Math.Round(point.Y, 0)); // Record the intersection intersections.Add(new EdgeIntersectInfo(new Edge(p1, p2), new Edge(p3, p4), point)); } } } // true if any intersections were found. return (intersections.Count > 0); } /// /// * ref: http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ - Solution 2 /// * 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. /// private static bool PointInPolygonAngle(Vector2 point, Vertices polygon) { double angle = 0; // Iterate through polygon's edges for (int i = 0; i < polygon.Count; i++) { /* p1.h = polygon[i].h - p.h; p1.v = polygon[i].v - p.v; p2.h = polygon[(i + 1) % n].h - p.h; p2.v = polygon[(i + 1) % n].v - p.v; */ // Get points Vector2 p1 = polygon[i] - point; Vector2 p2 = polygon[polygon.NextIndex(i)] - point; angle += VectorAngle(p1, p2); } if (Math.Abs(angle) < Math.PI) { return false; } return true; } /// /// 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 /// private static double VectorAngle(Vector2 p1, 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); } /// /// Rounds vertices X and Y values to whole numbers. /// /// The polygon whose vertices should be rounded. /// A new polygon with rounded vertices. public static Vertices Round(Vertices polygon) { Vertices returnPoly = new Vertices(); for (int i = 0; i < polygon.Count; i++) returnPoly.Add(new Vector2((float)Math.Round(polygon[i].X, 0), (float)Math.Round(polygon[i].Y, 0))); return returnPoly; } /// /// Determines if three vertices are collinear (ie. on a straight line) /// /// Vertex 1 /// Vertex 2 /// Vertex 3 /// private static bool VerticesAreCollinear(Vector2 p1, Vector2 p2, Vector2 p3) { double collinearity = (p3.X - p1.X) * (p2.Y - p1.Y) + (p3.Y - p1.Y) * (p1.X - p2.X); return (collinearity == 0); } /// /// Simple polygon simplification. /// /// The polygon that needs simplification. /// The distance bias (in pixels) between points. Points closer than this will be 'joined'. /// A simplified polygon. public static Vertices Simplify(Vertices polygon, int bias) { //We can't simplify polygons under 3 vertices if (polygon.Count < 3) return polygon; Vertices simplified = new Vertices(); Vertices roundPolygon = Round(polygon); for (int curr = 0; curr < roundPolygon.Count; curr++) { int prev = roundPolygon.PreviousIndex(curr); int next = roundPolygon.NextIndex(curr); if ((roundPolygon[prev] - roundPolygon[curr]).Length() <= bias) continue; if (!VerticesAreCollinear(roundPolygon[prev], roundPolygon[curr], roundPolygon[next])) simplified.Add(roundPolygon[curr]); } return simplified; } /// /// Simple polygon simplification. /// /// The polygon that needs simplification. /// A simplified polygon. public static Vertices Simplify(Vertices polygon) { return Simplify(polygon, 0); } #endregion #region Yobiv's Extension /// /// 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 * Calculator.Cos(stepSize * i), topRadius * Calculator.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 * Calculator.Cos(stepSize * i), -bottomRadius * Calculator.Sin(stepSize * i) - newHeight)); } vertices.Add(new Vector2(bottomRadius, -newHeight)); return vertices; } #endregion #region Matt Bettcher's Extension #region Fields static readonly IndexableCyclicalLinkedList polygonVertices = new IndexableCyclicalLinkedList(); static readonly IndexableCyclicalLinkedList earVertices = new IndexableCyclicalLinkedList(); static readonly CyclicalList convexVertices = new CyclicalList(); static readonly CyclicalList reflexVertices = new CyclicalList(); #endregion #region Vertex struct Vertex { public readonly Vector2 Position; public readonly short Index; public Vertex(Vector2 position, short index) { Position = position; Index = index; } public override bool Equals(object obj) { if (obj.GetType() != typeof(Vertex)) return false; return Equals((Vertex)obj); } public bool Equals(Vertex obj) { return obj.Position.Equals(Position) && obj.Index == Index; } public override int GetHashCode() { unchecked { return (Position.GetHashCode() * 397) ^ Index; } } public override string ToString() { return string.Format("{0} ({1})", Position, Index); } } #endregion #region LineSegment struct LineSegment { public Vertex A; public Vertex B; public LineSegment(Vertex a, Vertex b) { A = a; B = b; } public float? IntersectsWithRay(Vector2 origin, Vector2 direction) { float largestDistance = MathHelper.Max(A.Position.X - origin.X, B.Position.X - origin.X) * 2f; LineSegment raySegment = new LineSegment(new Vertex(origin, 0), new Vertex(origin + (direction * largestDistance), 0)); Vector2? intersection = FindIntersection(this, raySegment); float? value = null; if (intersection != null) value = Vector2.Distance(origin, intersection.Value); return value; } public static Vector2? FindIntersection(LineSegment a, LineSegment b) { float x1 = a.A.Position.X; float y1 = a.A.Position.Y; float x2 = a.B.Position.X; float y2 = a.B.Position.Y; float x3 = b.A.Position.X; float y3 = b.A.Position.Y; float x4 = b.B.Position.X; float y4 = b.B.Position.Y; float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); float uaNum = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); float ubNum = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); float ua = uaNum / denom; float ub = ubNum / denom; if (MathHelper.Clamp(ua, 0f, 1f) != ua || MathHelper.Clamp(ub, 0f, 1f) != ub) return null; return a.A.Position + (a.B.Position - a.A.Position) * ua; } } #endregion #region Triangle /// /// A basic triangle structure that holds the three vertices that make up a given triangle. /// struct Triangle { public readonly Vertex A; public readonly Vertex B; public readonly Vertex C; public Triangle(Vertex a, Vertex b, Vertex c) { A = a; B = b; C = c; } public bool ContainsPoint(Vertex point) { //return true if the point to test is one of the vertices if (point.Equals(A) || point.Equals(B) || point.Equals(C)) return true; bool oddNodes = false; if (checkPointToSegment(C, A, point)) oddNodes = !oddNodes; if (checkPointToSegment(A, B, point)) oddNodes = !oddNodes; if (checkPointToSegment(B, C, point)) oddNodes = !oddNodes; return oddNodes; } public static bool ContainsPoint(Vertex a, Vertex b, Vertex c, Vertex point) { return new Triangle(a, b, c).ContainsPoint(point); } static bool checkPointToSegment(Vertex sA, Vertex sB, Vertex point) { if ((sA.Position.Y < point.Position.Y && sB.Position.Y >= point.Position.Y) || (sB.Position.Y < point.Position.Y && sA.Position.Y >= point.Position.Y)) { float x = sA.Position.X + (point.Position.Y - sA.Position.Y) / (sB.Position.Y - sA.Position.Y) * (sB.Position.X - sA.Position.X); if (x < point.Position.X) return true; } return false; } public override bool Equals(object obj) { if (obj.GetType() != typeof(Triangle)) return false; return Equals((Triangle)obj); } public bool Equals(Triangle obj) { return obj.A.Equals(A) && obj.B.Equals(B) && obj.C.Equals(C); } public override int GetHashCode() { unchecked { int result = A.GetHashCode(); result = (result * 397) ^ B.GetHashCode(); result = (result * 397) ^ C.GetHashCode(); return result; } } } #endregion #region CyclicalList /// /// Implements a List structure as a cyclical list where indices are wrapped. /// /// The Type to hold in the list. class CyclicalList : List { public new T this[int index] { get { //perform the index wrapping while (index < 0) index = Count + index; if (index >= Count) index %= Count; return base[index]; } set { //perform the index wrapping while (index < 0) index = Count + index; if (index >= Count) index %= Count; base[index] = value; } } public CyclicalList() { } public CyclicalList(IEnumerable collection) : base(collection) { } public new void RemoveAt(int index) { Remove(this[index]); } } #endregion #region IndexableCyclicalLinkedList /// /// Implements a LinkedList that is both indexable as well as cyclical. Thus /// indexing into the list with an out-of-bounds index will automatically cycle /// around the list to find a valid node. /// class IndexableCyclicalLinkedList : LinkedList { /// /// Gets the LinkedListNode at a particular index. /// /// The index of the node to retrieve. /// The LinkedListNode found at the index given. public LinkedListNode this[int index] { get { //perform the index wrapping while (index < 0) index = Count + index; if (index >= Count) index %= Count; //find the proper node LinkedListNode node = First; for (int i = 0; i < index; i++) node = node.Next; return node; } } /// /// Removes the node at a given index. /// /// The index of the node to remove. public void RemoveAt(int index) { Remove(this[index]); } /// /// Finds the index of a given item. /// /// The item to find. /// The index of the item if found; -1 if the item is not found. public int IndexOf(T item) { for (int i = 0; i < Count; i++) if (this[i].Value.Equals(item)) return i; return -1; } } #endregion #region Public Methods #region Triangulate /// /// Triangulates a 2D polygon produced the indexes required to render the points as a triangle list. /// /// The polygon vertices in counter-clockwise winding order. /// The desired output winding order. /// The resulting vertices that include any reversals of winding order and holes. /// The resulting indices for rendering the shape as a triangle list. public static void Triangulate( Vector2[] inputVertices, WindingOrder desiredWindingOrder, out Vector2[] outputVertices, out short[] indices) { //Log("\nBeginning triangulation..."); List triangles = new List(); //make sure we have our vertices wound properly if (DetermineWindingOrder(inputVertices) == WindingOrder.Clockwise) outputVertices = ReverseWindingOrder(inputVertices); else outputVertices = (Vector2[])inputVertices.Clone(); //clear all of the lists polygonVertices.Clear(); earVertices.Clear(); convexVertices.Clear(); reflexVertices.Clear(); //generate the cyclical list of vertices in the polygon for (int i = 0; i < outputVertices.Length; i++) polygonVertices.AddLast(new Vertex(outputVertices[i], (short)i)); //categorize all of the vertices as convex, reflex, and ear FindConvexAndReflexVertices(); FindEarVertices(); //clip all the ear vertices while (polygonVertices.Count > 3 && earVertices.Count > 0) ClipNextEar(triangles); //if there are still three points, use that for the last triangle if (polygonVertices.Count == 3) triangles.Add(new Triangle( polygonVertices[0].Value, polygonVertices[1].Value, polygonVertices[2].Value)); //add all of the triangle indices to the output array indices = new short[triangles.Count * 3]; //move the if statement out of the loop to prevent all the //redundant comparisons if (desiredWindingOrder == WindingOrder.CounterClockwise) { for (int i = 0; i < triangles.Count; i++) { indices[(i * 3)] = triangles[i].A.Index; indices[(i * 3) + 1] = triangles[i].B.Index; indices[(i * 3) + 2] = triangles[i].C.Index; } } else { for (int i = 0; i < triangles.Count; i++) { indices[(i * 3)] = triangles[i].C.Index; indices[(i * 3) + 1] = triangles[i].B.Index; indices[(i * 3) + 2] = triangles[i].A.Index; } } } #endregion #region CutHoleInShape /// /// Cuts a hole into a shape. /// /// An array of vertices for the primary shape. /// An array of vertices for the hole to be cut. It is assumed that these vertices lie completely within the shape verts. /// The new array of vertices that can be passed to Triangulate to properly triangulate the shape with the hole. public static Vector2[] CutHoleInShape(Vector2[] shapeVerts, Vector2[] holeVerts) { Log("\nCutting hole into shape..."); //make sure the shape vertices are wound counter clockwise and the hole vertices clockwise shapeVerts = EnsureWindingOrder(shapeVerts, WindingOrder.CounterClockwise); holeVerts = EnsureWindingOrder(holeVerts, WindingOrder.Clockwise); //clear all of the lists polygonVertices.Clear(); earVertices.Clear(); convexVertices.Clear(); reflexVertices.Clear(); //generate the cyclical list of vertices in the polygon for (int i = 0; i < shapeVerts.Length; i++) polygonVertices.AddLast(new Vertex(shapeVerts[i], (short)i)); CyclicalList holePolygon = new CyclicalList(); for (int i = 0; i < holeVerts.Length; i++) holePolygon.Add(new Vertex(holeVerts[i], (short)(i + polygonVertices.Count))); #if DEBUG StringBuilder vString = new StringBuilder(); foreach (Vertex v in polygonVertices) vString.Append(string.Format("{0}, ", v)); Log("Shape Vertices: {0}", vString); vString = new StringBuilder(); foreach (Vertex v in holePolygon) vString.Append(string.Format("{0}, ", v)); Log("Hole Vertices: {0}", vString); #endif FindConvexAndReflexVertices(); FindEarVertices(); //find the hole vertex with the largest X value Vertex rightMostHoleVertex = holePolygon[0]; foreach (Vertex v in holePolygon) if (v.Position.X > rightMostHoleVertex.Position.X) rightMostHoleVertex = v; //construct a list of all line segments where at least one vertex //is to the right of the rightmost hole vertex with one vertex //above the hole vertex and one below List segmentsToTest = new List(); for (int i = 0; i < polygonVertices.Count; i++) { Vertex a = polygonVertices[i].Value; Vertex b = polygonVertices[i + 1].Value; if ((a.Position.X > rightMostHoleVertex.Position.X || b.Position.X > rightMostHoleVertex.Position.X) && ((a.Position.Y >= rightMostHoleVertex.Position.Y && b.Position.Y <= rightMostHoleVertex.Position.Y) || (a.Position.Y <= rightMostHoleVertex.Position.Y && b.Position.Y >= rightMostHoleVertex.Position.Y))) segmentsToTest.Add(new LineSegment(a, b)); } //now we try to find the closest intersection point heading to the right from //our hole vertex. float? closestPoint = null; LineSegment closestSegment = new LineSegment(); foreach (LineSegment segment in segmentsToTest) { float? intersection = segment.IntersectsWithRay(rightMostHoleVertex.Position, Vector2.UnitX); if (intersection != null) { if (closestPoint == null || closestPoint.Value > intersection.Value) { closestPoint = intersection; closestSegment = segment; } } } //if closestPoint is null, there were no collisions (likely from improper input data), //but we'll just return without doing anything else if (closestPoint == null) return shapeVerts; //otherwise we can find our mutually visible vertex to split the polygon Vector2 I = rightMostHoleVertex.Position + Vector2.UnitX * closestPoint.Value; Vertex P = (closestSegment.A.Position.X > closestSegment.B.Position.X) ? closestSegment.A : closestSegment.B; //construct triangle MIP Triangle mip = new Triangle(rightMostHoleVertex, new Vertex(I, 1), P); //see if any of the reflex vertices lie inside of the MIP triangle List interiorReflexVertices = new List(); foreach (Vertex v in reflexVertices) if (mip.ContainsPoint(v)) interiorReflexVertices.Add(v); //if there are any interior reflex vertices, find the one that, when connected //to our rightMostHoleVertex, forms the line closest to Vector2.UnitX if (interiorReflexVertices.Count > 0) { float closestDot = -1f; foreach (Vertex v in interiorReflexVertices) { //compute the dot product of the vector against the UnitX Vector2 d = Vector2.Normalize(v.Position - rightMostHoleVertex.Position); float dot = Vector2.Dot(Vector2.UnitX, d); //if this line is the closest we've found if (dot > closestDot) { //save the value and save the vertex as P closestDot = dot; P = v; } } } //now we just form our output array by injecting the hole vertices into place //we know we have to inject the hole into the main array after point P going from //rightMostHoleVertex around and then back to P. int mIndex = holePolygon.IndexOf(rightMostHoleVertex); int injectPoint = polygonVertices.IndexOf(P); Log("Inserting hole at injection point {0} starting at hole vertex {1}.", P, rightMostHoleVertex); for (int i = mIndex; i <= mIndex + holePolygon.Count; i++) { Log("Inserting vertex {0} after vertex {1}.", holePolygon[i], polygonVertices[injectPoint].Value); polygonVertices.AddAfter(polygonVertices[injectPoint++], holePolygon[i]); } polygonVertices.AddAfter(polygonVertices[injectPoint], P); #if DEBUG vString = new StringBuilder(); foreach (Vertex v in polygonVertices) vString.Append(string.Format("{0}, ", v)); Log("New Shape Vertices: {0}\n", vString); #endif //finally we write out the new polygon vertices and return them out Vector2[] newShapeVerts = new Vector2[polygonVertices.Count]; for (int i = 0; i < polygonVertices.Count; i++) newShapeVerts[i] = polygonVertices[i].Value.Position; return newShapeVerts; } #endregion #region EnsureWindingOrder /// /// Ensures that a set of vertices are wound in a particular order, reversing them if necessary. /// /// The vertices of the polygon. /// The desired winding order. /// A new set of vertices if the winding order didn't match; otherwise the original set. public static Vector2[] EnsureWindingOrder(Vector2[] vertices, WindingOrder windingOrder) { //Log("\nEnsuring winding order of {0}...", windingOrder); if (DetermineWindingOrder(vertices) != windingOrder) { //Log("Reversing vertices..."); return ReverseWindingOrder(vertices); } //Log("No reversal needed."); return vertices; } #endregion #region ReverseWindingOrder /// /// Reverses the winding order for a set of vertices. /// /// The vertices of the polygon. /// The new vertices for the polygon with the opposite winding order. public static Vector2[] ReverseWindingOrder(Vector2[] vertices) { //Log("\nReversing winding order..."); Vector2[] newVerts = new Vector2[vertices.Length]; #if DEBUG //StringBuilder vString = new StringBuilder(); //foreach (Vector2 v in vertices) // vString.Append(string.Format("{0}, ", v)); //Log("Original Vertices: {0}", vString); #endif newVerts[0] = vertices[0]; for (int i = 1; i < newVerts.Length; i++) newVerts[i] = vertices[vertices.Length - i]; #if DEBUG //vString = new StringBuilder(); //foreach (Vector2 v in newVerts) // vString.Append(string.Format("{0}, ", v)); //Log("New Vertices After Reversal: {0}\n", vString); #endif return newVerts; } #endregion #region DetermineWindingOrder /// /// Determines the winding order of a polygon given a set of vertices. /// /// The vertices of the polygon. /// The calculated winding order of the polygon. public static WindingOrder DetermineWindingOrder(Vector2[] vertices) { int clockWiseCount = 0; int counterClockWiseCount = 0; Vector2 p1 = vertices[0]; for (int i = 1; i < vertices.Length; i++) { Vector2 p2 = vertices[i]; Vector2 p3 = vertices[(i + 1) % vertices.Length]; Vector2 e1 = p1 - p2; Vector2 e2 = p3 - p2; if (e1.X * e2.Y - e1.Y * e2.X >= 0) clockWiseCount++; else counterClockWiseCount++; p1 = p2; } return (clockWiseCount > counterClockWiseCount) ? WindingOrder.Clockwise : WindingOrder.CounterClockwise; } #endregion #endregion #region Private Methods #region ClipNextEar private static void ClipNextEar(ICollection triangles) { //find the triangle Vertex ear = earVertices[0].Value; Vertex prev = polygonVertices[polygonVertices.IndexOf(ear) - 1].Value; Vertex next = polygonVertices[polygonVertices.IndexOf(ear) + 1].Value; triangles.Add(new Triangle(ear, next, prev)); //remove the ear from the shape earVertices.RemoveAt(0); polygonVertices.RemoveAt(polygonVertices.IndexOf(ear)); //Log("\nRemoved Ear: {0}", ear); //validate the neighboring vertices ValidateAdjacentVertex(prev); ValidateAdjacentVertex(next); //write out the states of each of the lists #if DEBUG /*StringBuilder rString = new StringBuilder(); foreach (Vertex v in reflexVertices) rString.Append(string.Format("{0}, ", v.Index)); Log("Reflex Vertices: {0}", rString); StringBuilder cString = new StringBuilder(); foreach (Vertex v in convexVertices) cString.Append(string.Format("{0}, ", v.Index)); Log("Convex Vertices: {0}", cString); StringBuilder eString = new StringBuilder(); foreach (Vertex v in earVertices) eString.Append(string.Format("{0}, ", v.Index)); Log("Ear Vertices: {0}", eString);*/ #endif } #endregion #region ValidateAdjacentVertex private static void ValidateAdjacentVertex(Vertex vertex) { //Log("Validating: {0}...", vertex); if (reflexVertices.Contains(vertex)) { if (IsConvex(vertex)) { reflexVertices.Remove(vertex); convexVertices.Add(vertex); //Log("Vertex: {0} now convex", vertex); } else { //Log("Vertex: {0} still reflex", vertex); } } if (convexVertices.Contains(vertex)) { bool wasEar = earVertices.Contains(vertex); bool isEar = IsEar(vertex); if (wasEar && !isEar) { earVertices.Remove(vertex); //Log("Vertex: {0} no longer ear", vertex); } else if (!wasEar && isEar) { earVertices.AddFirst(vertex); //Log("Vertex: {0} now ear", vertex); } else { //Log("Vertex: {0} still ear", vertex); } } } #endregion #region FindConvexAndReflexVertices private static void FindConvexAndReflexVertices() { for (int i = 0; i < polygonVertices.Count; i++) { Vertex v = polygonVertices[i].Value; if (IsConvex(v)) { convexVertices.Add(v); //Log("Convex: {0}", v); } else { reflexVertices.Add(v); //Log("Reflex: {0}", v); } } } #endregion #region FindEarVertices private static void FindEarVertices() { for (int i = 0; i < convexVertices.Count; i++) { Vertex c = convexVertices[i]; if (IsEar(c)) { earVertices.AddLast(c); //Log("Ear: {0}", c); } } } #endregion #region IsEar private static bool IsEar(Vertex c) { Vertex p = polygonVertices[polygonVertices.IndexOf(c) - 1].Value; Vertex n = polygonVertices[polygonVertices.IndexOf(c) + 1].Value; //Log("Testing vertex {0} as ear with triangle {1}, {0}, {2}...", c, p, n); foreach (Vertex t in reflexVertices) { if (t.Equals(p) || t.Equals(c) || t.Equals(n)) continue; if (Triangle.ContainsPoint(p, c, n, t)) { //Log("\tTriangle contains vertex {0}...", t); return false; } } return true; } #endregion #region IsConvex private static bool IsConvex(Vertex c) { Vertex p = polygonVertices[polygonVertices.IndexOf(c) - 1].Value; Vertex n = polygonVertices[polygonVertices.IndexOf(c) + 1].Value; Vector2 d1 = Vector2.Normalize(c.Position - p.Position); Vector2 d2 = Vector2.Normalize(n.Position - c.Position); Vector2 n2 = new Vector2(-d2.Y, d2.X); return (Vector2.Dot(d1, n2) <= 0f); } #endregion #region IsReflex private static bool IsReflex(Vertex c) { return !IsConvex(c); } #endregion #region Log //[Conditional("DEBUG")] private static void Log(string format, params object[] parameters) { Console.WriteLine(format, parameters); } #endregion #endregion #region WindingOrder /// /// Specifies a desired winding order for the shape vertices. /// public enum WindingOrder { Clockwise, CounterClockwise } #endregion #endregion #region SAT Extensions /// /// Decomposes a set of vertices into a set of Geoms all /// attached to one body. /// /// Vertices to decompose. /// Body to attach too. /// Maximum Geoms to return. /// A list of Geoms. //public static List DecomposeGeom(Vertices vertices, Body body, int maxPolysToFind) //{ // Vertices[] verts = Polygon.DecomposeVertices(vertices, maxPolysToFind); // List geomList = new List(); // Vector2 mainCentroid = vertices.GetCentroid(); // foreach (Vertices v in verts) // { // //Vector2 subCentroid = v.GetCentroid(); // geomList.Add(new Geom(body, v, -mainCentroid, 0, 1.0f)); // } // return geomList; //} #endregion } #region Sickbattery's Extension - Enums & Classes public enum EdgeAlignment { Vertical = 0, Horizontal = 1 } public class CrossingEdgeInfo : IComparable { #region Attributes private Vector2 _egdeVertex1; private Vector2 _edgeVertex2; private EdgeAlignment _alignment; private Vector2 _crossingPoint; #endregion #region Properties public Vector2 EdgeVertex1 { get { return _egdeVertex1; } set { _egdeVertex1 = value; } } public Vector2 EdgeVertex2 { get { return _edgeVertex2; } set { _edgeVertex2 = value; } } public EdgeAlignment CheckLineAlignment { get { return _alignment; } set { _alignment = value; } } public Vector2 CrossingPoint { get { return _crossingPoint; } set { _crossingPoint = value; } } #endregion #region Constructor public CrossingEdgeInfo(Vector2 edgeVertex1, Vector2 edgeVertex2, Vector2 crossingPoint, EdgeAlignment checkLineAlignment) { _egdeVertex1 = edgeVertex1; _edgeVertex2 = edgeVertex2; _alignment = checkLineAlignment; _crossingPoint = crossingPoint; } #endregion #region IComparable Member public int CompareTo(object obj) { CrossingEdgeInfo cei = (CrossingEdgeInfo)obj; int result = 0; switch (_alignment) { case EdgeAlignment.Vertical: if (_crossingPoint.X < cei.CrossingPoint.X) { result = -1; } else if (_crossingPoint.X > cei.CrossingPoint.X) { result = 1; } break; case EdgeAlignment.Horizontal: if (_crossingPoint.Y < cei.CrossingPoint.Y) { result = -1; } else if (_crossingPoint.Y > cei.CrossingPoint.Y) { result = 1; } break; } return result; } #endregion } /// /// Class used as a data container and helper for the texture-to-vertices code. /// public class PolygonCreationAssistance { private uint[] _data; private int _width; private int _height; private byte _alphaTolerance; private uint _alphaToleranceRealValue; private float _hullTolerance; private int _holeDetectionLineStepSize; private bool _holeDetection; private bool _multipartDetection; public uint[] Data { get { return _data; } } public int Width { get { return _width; } } public int Height { get { return _height; } } public byte AlphaTolerance { get { return _alphaTolerance; } set { _alphaTolerance = value; _alphaToleranceRealValue = (uint)value << 24; } } public float HullTolerance { get { return _hullTolerance; } set { float hullTolerance = value; if (hullTolerance > 4f) hullTolerance = 4f; if (hullTolerance < 0.9f) hullTolerance = 0.9f; _hullTolerance = hullTolerance; } } public int HoleDetectionLineStepSize { get { return _holeDetectionLineStepSize; } set { if (value < 1) { _holeDetectionLineStepSize = 1; } else { if (value > 10) { _holeDetectionLineStepSize = 10; } else { _holeDetectionLineStepSize = value; } } } } public bool HoleDetection { get { return _holeDetection; } set { _holeDetection = value; } } public bool MultipartDetection { get { return _multipartDetection; } set { _multipartDetection = value; } } public PolygonCreationAssistance(uint[] data, int width, int height) { _data = data; _width = width; _height = height; AlphaTolerance = 20; HullTolerance = 1.5f; HoleDetectionLineStepSize = 1; _holeDetection = false; _multipartDetection = false; } public bool IsSolid(Vector2 pixel) { return IsSolid((int)pixel.X, (int)pixel.Y); } public bool IsSolid(int x, int y) { if (x >= 0 && x < _width && y >= 0 && y < _height) return ((_data[x + y * _width] & 0xFF000000) >= _alphaToleranceRealValue); return false; } public bool IsSolid(int index) { if (index >= 0 && index < _width * _height) return ((_data[index] & 0xFF000000) >= _alphaToleranceRealValue); return false; } public bool InBounds(Vector2 coord) { return (coord.X >= 0f && coord.X < _width && coord.Y >= 0f && coord.Y < _height); } public bool IsValid() { if (_data != null && _data.Length > 0) return _data.Length == _width * _height; return false; } ~PolygonCreationAssistance() { _data = null; } } #endregion #region SAT Extensions /* * C# Version Ported by Matt Bettcher 2009 * * 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. */ internal 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() //{ // x = new float[3]; // y = new float[3]; //} 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 void Set(ref Triangle toMe) //{ // for (int i = 0; i < 3; ++i) // { // x[i] = toMe.x[i]; // y[i] = toMe.y[i]; // } //} 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)); } } internal class Polygon { private const int maxVerticesPerPolygon = 32; //private static readonly float linearSlop = 0.005f; // 0.5 cm private const float angularSlop = 1.0f / 180.0f * (float)Math.PI; // 1 degrees private float[] x; //vertex arrays private float[] y; private int nVertices; private bool areaIsSet; private float area; /// /// 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. /// /// The a0. /// The a1. /// The b0. /// The b1. /// The intersection point. /// //private bool Intersect(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) < float.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; //} /// /// True if line from a0->a1 intersects b0->b1 /// /// The a0. /// The a1. /// The b0. /// The b1. /// //private bool Intersect(Vector2 a0, Vector2 a1, Vector2 b0, Vector2 b1) //{ // Vector2 temp; // return Intersect(a0, a1, b0, b1, out temp); //} private Polygon(float[] _x, float[] _y, int nVert) { nVertices = nVert; x = new float[nVertices]; y = new float[nVertices]; for (int i = 0; i < nVertices; ++i) { x[i] = _x[i]; y[i] = _y[i]; } areaIsSet = false; } private Polygon(Vector2[] v, int nVert) { nVertices = nVert; x = new float[nVertices]; y = new float[nVertices]; for (int i = 0; i < nVertices; ++i) { x[i] = v[i].X; y[i] = v[i].Y; } areaIsSet = false; } private Polygon() { x = null; y = null; nVertices = 0; areaIsSet = false; } private float GetArea() { // TODO: fix up the areaIsSet caching so that it can be used //if (areaIsSet) return area; area = 0.0f; //First do wraparound area += x[nVertices - 1] * y[0] - x[0] * y[nVertices - 1]; for (int i = 0; i < nVertices - 1; ++i) { area += x[i] * y[i + 1] - x[i + 1] * y[i]; } area *= .5f; areaIsSet = true; if (areaIsSet) areaIsSet = true; return area; } private bool IsCCW() { return (GetArea() > 0.0f); } private void MergeParallelEdges(float tolerance) { if (nVertices <= 3) return; //Can't do anything useful here to a triangle bool[] mergeMe = new bool[nVertices]; int newNVertices = nVertices; for (int i = 0; i < nVertices; ++i) { int lower = (i == 0) ? (nVertices - 1) : (i - 1); int middle = i; int upper = (i == nVertices - 1) ? (0) : (i + 1); float dx0 = x[middle] - x[lower]; float dy0 = y[middle] - y[lower]; float dx1 = x[upper] - x[middle]; float dy1 = y[upper] - y[middle]; 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 == nVertices || newNVertices == 0) { return; } float[] newx = new float[newNVertices]; float[] newy = new float[newNVertices]; int currIndex = 0; for (int i = 0; i < nVertices; ++i) { if (mergeMe[i] || newNVertices == 0 || currIndex == newNVertices) continue; //b2Assert(currIndex < newNVertices); newx[currIndex] = x[i]; newy[currIndex] = y[i]; ++currIndex; } x = newx; y = newy; nVertices = newNVertices; // printf("%d \n", newNVertices); } /// /// Allocates and returns pointer to vector vertex array. /// Length of array is nVertices. /// /// //public Vector2[] GetVertexVecs() //{ // Vector2[] output = new Vector2[nVertices]; // for (int i = 0; i < nVertices; ++i) // { // output[i] = new Vector2(x[i], y[i]); // } // return output; //} private Polygon(Triangle t) { nVertices = 3; x = new float[nVertices]; y = new float[nVertices]; for (int i = 0; i < nVertices; ++i) { x[i] = t.x[i]; y[i] = t.y[i]; } } private Polygon(Polygon p) { nVertices = p.nVertices; x = new float[nVertices]; y = new float[nVertices]; for (int i = 0; i < nVertices; ++i) { x[i] = p.x[i]; y[i] = p.y[i]; } } private void Set(Polygon p) { if (nVertices != p.nVertices) { nVertices = p.nVertices; x = new float[nVertices]; y = new float[nVertices]; } for (int i = 0; i < nVertices; ++i) { x[i] = p.x[i]; y[i] = p.y[i]; } areaIsSet = false; } /// /// Assuming the polygon is simple, checks if it is convex. /// /// /// true if this instance is convex; otherwise, false. /// private bool IsConvex() { bool isPositive = false; for (int i = 0; i < nVertices; ++i) { int lower = (i == 0) ? (nVertices - 1) : (i - 1); int middle = i; int upper = (i == nVertices - 1) ? (0) : (i + 1); float dx0 = x[middle] - x[lower]; float dy0 = y[middle] - y[lower]; float dx1 = x[upper] - x[middle]; float dy1 = y[upper] - y[middle]; float cross = dx0 * dy1 - dx1 * dy0; // Cross product should have same sign // for each vertex if poly is convex. bool newIsP = (cross >= 0) ? true : false; if (i == 0) { isPositive = newIsP; } else if (isPositive != newIsP) { return false; } } return true; } // Pulled from b2Shape.cpp, assertions removed //private static Vector2 PolyCentroid(Vector2[] vs, int count) //{ // Vector2 c = new Vector2(0.0f, 0.0f); // float area = 0.0f; // float inv3 = 1.0f / 3.0f; // Vector2 pRef = new Vector2(0.0f, 0.0f); // for (int i = 0; i < count; ++i) // { // // Triangle vertices. // Vector2 p1 = pRef; // Vector2 p2 = vs[i]; // Vector2 p3 = i + 1 < count ? vs[i + 1] : vs[0]; // Vector2 e1 = p2 - p1; // Vector2 e2 = p3 - p1; // float D = Calculator.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; //} /// /// 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. /// /// if set to true [print errors]. /// /// true if the specified print errors is usable; otherwise, false. /// //private bool IsUsable(bool printErrors) //{ // int error = -1; // bool noError = true; // if (nVertices < 3 || nVertices > maxVerticesPerPolygon) { noError = false; error = 0; } // if (!IsConvex()) { noError = false; error = 1; } // if (!IsSimple()) { noError = false; error = 2; } // if (GetArea() < float.Epsilon) { noError = false; error = 3; } // //Compute normals // Vector2[] normals = new Vector2[nVertices]; // Vector2[] vertices = new Vector2[nVertices]; // for (int i = 0; i < nVertices; ++i) // { // vertices[i] = new Vector2(x[i], y[i]); // int i1 = i; // int i2 = i + 1 < nVertices ? i + 1 : 0; // Vector2 edge = new Vector2(x[i2] - x[i1], y[i2] - y[i1]); // normals[i] = Calculator.Cross(edge, 1.0f); // normals[i].Normalize(); // } // //Required side checks // for (int i = 0; i < nVertices; ++i) // { // int iminus = (i == 0) ? nVertices - 1 : i - 1; // //int32 iplus = (i==nVertices-1)?0:i+1; // //Parallel sides check // float cross = Calculator.Cross(normals[iminus], normals[i]); // cross = Calculator.Clamp(cross, -1.0f, 1.0f); // float angle = (float)Math.Asin(cross); // if (angle <= angularSlop) // { // noError = false; // error = 4; // break; // } // //Too skinny check // for (int j = 0; j < nVertices; ++j) // { // if (j == i || j == (i + 1) % nVertices) // { // continue; // } // float s = Vector2.Dot(normals[i], vertices[j] - vertices[i]); // if (s >= -linearSlop) // { // noError = false; // error = 5; // } // } // Vector2 centroid = PolyCentroid(vertices, nVertices); // Vector2 n1 = normals[iminus]; // Vector2 n2 = normals[i]; // Vector2 v = vertices[i] - centroid; ; // Vector2 d; // d.X = Vector2.Dot(n1, v) - 0.0f; // d.Y = Vector2.Dot(n2, v) - 0.0f; // // Shifting the edge inward by b2_0.0f should // // not cause the plane to pass the centroid. // if ((d.X < 0.0f) || (d.Y < 0.0f)) // { // noError = false; // error = 6; // } // } // if (!noError && printErrors) // { // //printf("Found invalid polygon, "); // switch (error) // { // case 0: // //printf("must have between 3 and %d vertices.\n",b2_maxPolygonVertices); // break; // case 1: // //printf("must be convex.\n"); // break; // case 2: // //printf("must be simple (cannot intersect itself).\n"); // break; // case 3: // //printf("area is too small.\n"); // break; // case 4: // //printf("sides are too close to parallel.\n"); // break; // case 5: // //printf("polygon is too thin.\n"); // break; // case 6: // //printf("core shape generation would move edge past centroid (too thin).\n"); // break; // default: // //printf("don't know why.\n"); // break; // } // } // return noError; //} //public bool IsUsable() //{ // return IsUsable(false); //} //Check for edge crossings //private bool IsSimple() //{ // for (int i = 0; i < nVertices; ++i) // { // int iplus = (i + 1 > nVertices - 1) ? 0 : i + 1; // Vector2 a1 = new Vector2(x[i], y[i]); // Vector2 a2 = new Vector2(x[iplus], y[iplus]); // for (int j = i + 1; j < nVertices; ++j) // { // int jplus = (j + 1 > nVertices - 1) ? 0 : j + 1; // Vector2 b1 = new Vector2(x[j], y[j]); // Vector2 b2 = new Vector2(x[jplus], y[jplus]); // if (Intersect(a1, a2, b1, b2)) // { // return false; // } // } // } // return true; //} /// /// Tries to add a triangle to the polygon. Returns null if it can't connect /// properly, otherwise returns a pointer to the new Polygon. Assumes bitwise /// equality of joined vertex positions. /// /// Remember to delete the pointer afterwards. /// Todo: Make this return a b2Polygon instead /// of a pointer to a heap-allocated one. /// /// For internal use. /// /// The triangle to add. /// private Polygon Add(Triangle t) { // float32 equalTol = .001f; // First, find vertices that connect int firstP = -1; int firstT = -1; int secondP = -1; int secondT = -1; for (int i = 0; i < nVertices; i++) { if (t.x[0] == x[i] && t.y[0] == y[i]) { if (firstP == -1) { firstP = i; firstT = 0; } else { secondP = i; secondT = 0; } } else if (t.x[1] == x[i] && t.y[1] == y[i]) { if (firstP == -1) { firstP = i; firstT = 1; } else { secondP = i; secondT = 1; } } else if (t.x[2] == x[i] && t.y[2] == y[i]) { 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 == nVertices - 1) { firstP = nVertices - 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; float[] newx = new float[nVertices + 1]; float[] newy = new float[nVertices + 1]; int currOut = 0; for (int i = 0; i < nVertices; i++) { newx[currOut] = x[i]; newy[currOut] = y[i]; if (i == firstP) { ++currOut; newx[currOut] = t.x[tipT]; newy[currOut] = t.y[tipT]; } ++currOut; } Polygon result = new Polygon(newx, newy, nVertices + 1); return result; } /// /// 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. /// /// The pin. /// The pout A. /// The pout B. /// private static bool ResolvePinchPoint(Polygon pin, out Polygon poutA, out Polygon poutB) { poutA = new Polygon(); poutB = new Polygon(); if (pin.nVertices < 3) return false; const float tol = .001f; bool hasPinchPoint = false; int pinchIndexA = -1; int pinchIndexB = -1; for (int i = 0; i < pin.nVertices; ++i) { for (int j = i + 1; j < pin.nVertices; ++j) { //Don't worry about pinch points where the points //are actually just dupe neighbors if (Math.Abs(pin.x[i] - pin.x[j]) < tol && Math.Abs(pin.y[i] - pin.y[j]) < tol && j != i + 1) { pinchIndexA = i; pinchIndexB = j; //printf("pinch: %f, %f == %f, %f\n",pin.x[i],pin.y[i],pin.x[j],pin.y[j]); //printf("at indexes %d, %d\n",i,j); hasPinchPoint = true; break; } } if (hasPinchPoint) break; } if (hasPinchPoint) { //printf("Found pinch point\n"); int sizeA = pinchIndexB - pinchIndexA; if (sizeA == pin.nVertices) return false;//has dupe points at wraparound, not a problem here float[] xA = new float[sizeA]; float[] yA = new float[sizeA]; for (int i = 0; i < sizeA; ++i) { int ind = Remainder(pinchIndexA + i, pin.nVertices); // is this right xA[i] = pin.x[ind]; yA[i] = pin.y[ind]; } Polygon tempA = new Polygon(xA, yA, sizeA); poutA.Set(tempA); int sizeB = pin.nVertices - sizeA; float[] xB = new float[sizeB]; float[] yB = new float[sizeB]; for (int i = 0; i < sizeB; ++i) { int ind = Remainder(pinchIndexB + i, pin.nVertices); // is this right xB[i] = pin.x[ind]; yB[i] = pin.y[ind]; } Polygon tempB = new Polygon(xB, yB, sizeB); poutB.Set(tempB); //printf("Size of a: %d, size of b: %d\n",sizeA,sizeB); } return hasPinchPoint; } /// /// 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. /// /// Returns: /// -1 if algorithm fails (self-intersection most likely) /// 0 if there are not enough vertices to triangulate anything. /// Number of triangles if triangulation was successful. /// /// results will be filled with results - ear clipping always creates vNum - 2 /// or fewer (due to pinch point polygon snipping), so allocate an array of /// this size. /// /// The xv. /// The yv. /// The v num. /// The results. /// private static int TriangulatePolygon(float[] xv, float[] yv, int vNum, out Triangle[] results) { results = new Triangle[175]; if (vNum < 3) return 0; //Recurse and split on pinch points Polygon pA, pB; Polygon pin = new Polygon(xv, yv, vNum); if (ResolvePinchPoint(pin, out pA, out pB)) { Triangle[] mergeA = new Triangle[pA.nVertices]; Triangle[] mergeB = new Triangle[pB.nVertices]; int nA = TriangulatePolygon(pA.x, pA.y, pA.nVertices, out mergeA); int nB = TriangulatePolygon(pB.x, pB.y, pB.nVertices, out mergeB); if (nA == -1 || nB == -1) { return -1; } for (int i = 0; i < nA; ++i) { results[i] = new Triangle(mergeA[i]); } for (int i = 0; i < nB; ++i) { results[nA + i] = new Triangle(mergeB[i]); } return (nA + nB); } Triangle[] buffer = new Triangle[vNum - 2]; int bufferSize = 0; float[] xrem = new float[vNum]; float[] yrem = new float[vNum]; for (int i = 0; i < vNum; ++i) { xrem[i] = xv[i]; yrem[i] = yv[i]; } while (vNum > 3) { // Find an ear int earIndex = -1; //float32 earVolume = -1.0f; 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 = Math.Abs(Calculator.Cross(d1, d2)); float cross23 = Math.Abs(Calculator.Cross(d2, d3)); float cross31 = Math.Abs(Calculator.Cross(d3, d1)); //Find the maximum minimum angle float minCross = Math.Min(cross12, Math.Min(cross23, cross31)); if (minCross > earMaxMinCross) { earIndex = i; earMaxMinCross = minCross; } /*//This bit chooses the ear with greatest volume first float32 testVol = b2Abs( d1.x*d2.y-d2.x*d1.y ); if (testVol > earVolume){ earIndex = i; earVolume = testVol; }*/ } } // 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[i] = new Triangle(buffer[i]); } if (bufferSize > 0) return bufferSize; return -1; } // 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] = new Triangle(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] = new Triangle(tooAdd); ++bufferSize; //b2Assert(bufferSize == xremLength-2); for (int i = 0; i < bufferSize; i++) { results[i] = new Triangle(buffer[i]); } return bufferSize; } /// /// 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; } /// /// 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. /// Length of the triangulated. /// The polys. /// Length of the polys. /// private static int PolygonizeTriangles(Triangle[] triangulated, int triangulatedLength, out Polygon[] polys, int polysLength) { int polyIndex = 0; polys = new Polygon[50]; if (triangulatedLength <= 0) { return 0; } bool[] covered = new bool[triangulatedLength]; for (int i = 0; i < triangulatedLength; ++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 < triangulatedLength; ++i) { if (covered[i]) continue; currTri = i; break; } if (currTri == -1) { notDone = false; } else { Polygon poly = new Polygon(triangulated[currTri]); covered[currTri] = true; int index = 0; for (int i = 0; i < 2 * triangulatedLength; ++i, ++index) { while (index >= triangulatedLength) index -= triangulatedLength; if (covered[index]) { continue; } Polygon newP = poly.Add(triangulated[index]); if (newP == null) { // is this right continue; } if (newP.nVertices > maxVerticesPerPolygon) { newP = null; continue; } if (newP.IsConvex()) { //Or should it be IsUsable? Maybe re-write IsConvex to apply the angle threshold from Box2d poly = new Polygon(newP); newP = null; covered[index] = true; } else { newP = null; } } if (polyIndex < polysLength) { poly.MergeParallelEdges(angularSlop); //If identical points are present, a triangle gets //borked by the MergeParallelEdges function, hence //the vertex number check if (poly.nVertices >= 3) polys[polyIndex] = new Polygon(poly); //else printf("Skipping corrupt poly\n"); } if (poly.nVertices >= 3) polyIndex++; //Must be outside (polyIndex < polysLength) test } } return polyIndex; } /// /// 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; } private static void ReversePolygon(float[] x, float[] y, int n) { if (n == 1) return; int low = 0; int high = n - 1; while (low < high) { float buffer = x[low]; x[low] = x[high]; x[high] = buffer; buffer = y[low]; y[low] = y[high]; y[high] = buffer; ++low; --high; } } /// /// Decomposes a non-convex polygon into a number of convex polygons, up /// to maxPolys (remaining pieces are thrown out, but the total number /// is returned, so the return value can be greater than maxPolys). /// /// Each resulting polygon will have no more than maxVerticesPerPolygon /// vertices (set to b2MaxPolyVertices by default, though you can change /// this). /// /// Returns -1 if operation fails (usually due to self-intersection of /// polygon). /// /// The p. /// The results. /// The max polys. /// private static int DecomposeConvex(Polygon p, out Polygon[] results, int maxPolys) { results = new Polygon[1]; if (p.nVertices < 3) return 0; Triangle[] triangulated = new Triangle[p.nVertices - 2]; int nTri; if (p.IsCCW()) { //printf("It is ccw \n"); Polygon tempP = new Polygon(p); ReversePolygon(tempP.x, tempP.y, tempP.nVertices); nTri = TriangulatePolygon(tempP.x, tempP.y, tempP.nVertices, out triangulated); // ReversePolygon(p->x, p->y, p->nVertices); //reset orientation } else { //printf("It is not ccw \n"); nTri = TriangulatePolygon(p.x, p.y, p.nVertices, out triangulated); } if (nTri < 1) { //Still no luck? Oh well... return -1; } int nPolys = PolygonizeTriangles(triangulated, nTri, out results, maxPolys); return nPolys; } public static Vertices[] DecomposeVertices(Vertices v, int max) { Polygon p = new Polygon(v.ToArray(), v.Count); // convert the vertices to a polygon Polygon[] output; DecomposeConvex(p, out output, max); Vertices[] verticesOut = new Vertices[output.Length]; int i; for (i = 0; i < output.Length; i++) { if (output[i] != null) { verticesOut[i] = new Vertices(); for (int j = 0; j < output[i].nVertices; j++) verticesOut[i].Add(new Vector2(output[i].x[j], output[i].y[j])); } else break; } Vertices[] verts = new Vertices[i]; for (int k = 0; k < i; k++) { verts[k] = new Vertices(verticesOut[k]); } return verts; } } #endregion #region DrDeth's Extension /// /// Enumerator to specify errors with Polygon functions. /// public enum PolyUnionError { None, NoIntersections, Poly1InsidePoly2, InfiniteLoop } public 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 class EdgeIntersectInfo { public EdgeIntersectInfo(Edge edgeOne, Edge edgeTwo, Vector2 intersectionPoint) { EdgeOne = edgeOne; EdgeTwo = edgeTwo; IntersectionPoint = intersectionPoint; } public Edge EdgeOne { get; private set; } public Edge EdgeTwo { get; private set; } public Vector2 IntersectionPoint { get; private set; } } #endregion public static class Calculator { public const float DegreesToRadiansRatio = 57.29577957855f; public const float RadiansToDegreesRatio = 1f / 57.29577957855f; //NOTE: Commented line, use MathHelper.TwoPi instead //public const float TwoPi = 6.28318531f; private static Vector2 _curveEnd; private static Random _random = new Random(); private static Vector2 _startCurve; private static Vector2 _temp; /// Temp variables to speed up the following code. private static float _tPow2; private static float _wayToGo; private static float _wayToGoPow2; public static float Sin(float angle) { return (float)Math.Sin(angle); } public static float Cos(float angle) { return (float)Math.Cos(angle); } public static float ACos(float value) { return (float)Math.Acos(value); } public static float ATan2(float y, float x) { return (float)Math.Atan2(y, x); } //performs bilinear interpolation of a point public static float BiLerp(Vector2 point, Vector2 min, Vector2 max, float value1, float value2, float value3, float value4, float minValue, float maxValue) { float x = point.X; float y = point.Y; x = MathHelper.Clamp(x, min.X, max.X); y = MathHelper.Clamp(y, min.Y, max.Y); float xRatio = (x - min.X) / (max.X - min.X); float yRatio = (y - min.Y) / (max.Y - min.Y); float top = MathHelper.Lerp(value1, value4, xRatio); float bottom = MathHelper.Lerp(value2, value3, xRatio); float value = MathHelper.Lerp(top, bottom, yRatio); value = MathHelper.Clamp(value, minValue, maxValue); return value; } public static float Clamp(float value, float low, float high) { return Math.Max(low, Math.Min(value, high)); } public static float DistanceBetweenPointAndPoint(Vector2 point1, Vector2 point2) { Vector2 v = Vector2.Subtract(point1, point2); return v.Length(); } 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(Vector2 point, Vector2 lineEndPoint1, 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(point, lineEndPoint1); float c2 = Vector2.Dot(v, v); if (c2 <= c1) return DistanceBetweenPointAndPoint(point, lineEndPoint2); float b = c1 / c2; Vector2 pointOnLine = Vector2.Add(lineEndPoint1, Vector2.Multiply(v, b)); return DistanceBetweenPointAndPoint(point, pointOnLine); } public static float Cross(Vector2 value1, Vector2 value2) { return value1.X * value2.Y - value1.Y * value2.X; } public static Vector2 Cross(Vector2 value1, float value2) { return new Vector2(value2 * value1.Y, -value2 * value1.X); } public static Vector2 Cross(float value2, Vector2 value1) { return new Vector2(-value2 * value1.Y, value2 * value1.X); } public static void Cross(ref Vector2 value1, ref Vector2 value2, out float ret) { ret = value1.X * value2.Y - value1.Y * value2.X; } public static void Cross(ref Vector2 value1, ref float value2, out Vector2 ret) { ret = value1; //necassary to get past a compile error on 360 ret.X = value2 * value1.Y; ret.Y = -value2 * value1.X; } public static void Cross(ref float value2, ref Vector2 value1, out Vector2 ret) { ret = value1; //necassary to get past a compile error on 360 ret.X = -value2 * value1.Y; ret.Y = value2 * value1.X; } public static Vector2 Project(Vector2 projectVector, Vector2 onToVector) { float multiplier = 0; float numerator = (onToVector.X * projectVector.X + onToVector.Y * projectVector.Y); float denominator = (onToVector.X * onToVector.X + onToVector.Y * onToVector.Y); if (denominator != 0) { multiplier = numerator / denominator; } return Vector2.Multiply(onToVector, multiplier); } public static void Truncate(ref Vector2 vector, float maxLength, out Vector2 truncatedVector) { float length = vector.Length(); length = Math.Min(length, maxLength); if (length > 0) { vector.Normalize(); } Vector2.Multiply(ref vector, length, out truncatedVector); } public static float DegreesToRadians(float degrees) { return degrees * RadiansToDegreesRatio; } public static float RandomNumber(float min, float max) { return (float)((max - min) * _random.NextDouble() + min); } public static bool IsBetweenNonInclusive(float number, float min, float max) { if (number > min && number < max) { return true; } return false; } public static float VectorToRadians(Vector2 vector) { return (float)Math.Atan2(vector.X, -(double)vector.Y); } public static Vector2 RadiansToVector(float radians) { return new Vector2((float)Math.Sin(radians), -(float)Math.Cos(radians)); } public static void RadiansToVector(float radians, ref Vector2 vector) { vector.X = (float)Math.Sin(radians); vector.Y = -(float)Math.Cos(radians); } public static void RotateVector(ref Vector2 vector, float radians) { float length = vector.Length(); float newRadians = (float)Math.Atan2(vector.X, -(double)vector.Y) + radians; vector.X = (float)Math.Sin(newRadians) * length; vector.Y = -(float)Math.Cos(newRadians) * length; } /// /// /// /// /// /// Value between 0.0f and 1.0f. /// public static Vector2 LinearBezierCurve(Vector2 start, Vector2 end, float t) { return start + (end - start) * t; } /// /// /// /// /// /// /// Value between 0.0f and 1.0f. /// public static Vector2 QuadraticBezierCurve(Vector2 start, Vector2 curve, Vector2 end, float t) { _wayToGo = 1.0f - t; return _wayToGo * _wayToGo * start + 2.0f * t * _wayToGo * curve + t * t * end; } public static Vector2 QuadraticBezierCurve(Vector2 start, Vector2 curve, Vector2 end, float t, ref float radians) { _startCurve = start + (curve - start) * t; _curveEnd = curve + (end - curve) * t; _temp = _curveEnd - _startCurve; radians = (float)Math.Atan2(_temp.X, -(double)_temp.Y); return _startCurve + _temp * t; } public static Vector2 CubicBezierCurve2(Vector2 start, Vector2 startPointsTo, Vector2 end, Vector2 endPointsTo, float t) { return CubicBezierCurve(start, start + startPointsTo, end + endPointsTo, end, t); } public static Vector2 CubicBezierCurve2(Vector2 start, Vector2 startPointsTo, Vector2 end, Vector2 endPointsTo, float t, ref float radians) { return CubicBezierCurve(start, start + startPointsTo, end + endPointsTo, end, t, ref radians); } public static Vector2 CubicBezierCurve2(Vector2 start, float startPointDirection, float startPointLength, Vector2 end, float endPointDirection, float endPointLength, float t, ref float radians) { return CubicBezierCurve(start, RadiansToVector(startPointDirection) * startPointLength, RadiansToVector(endPointDirection) * endPointLength, end, t, ref radians); } public static Vector2 CubicBezierCurve(Vector2 start, Vector2 curve1, Vector2 curve2, Vector2 end, float t) { _tPow2 = t * t; _wayToGo = 1.0f - t; _wayToGoPow2 = _wayToGo * _wayToGo; return _wayToGo * _wayToGoPow2 * start + 3.0f * t * _wayToGoPow2 * curve1 + 3.0f * _tPow2 * _wayToGo * curve2 + t * _tPow2 * end; } public static Vector2 CubicBezierCurve(Vector2 start, Vector2 curve1, Vector2 curve2, Vector2 end, float t, ref float radians) { return QuadraticBezierCurve(start + (curve1 - start) * t, curve1 + (curve2 - curve1) * t, curve2 + (end - curve2) * t, t, ref radians); } //Interpolate normal vectors ... public static Vector2 InterpolateNormal(Vector2 vector1, Vector2 vector2, float t) { vector1 += (vector2 - vector1) * t; vector1.Normalize(); return vector1; } public static void InterpolateNormal(Vector2 vector1, Vector2 vector2, float t, out Vector2 vector) { vector = vector1 + (vector2 - vector1) * t; vector.Normalize(); } public static void InterpolateNormal(ref Vector2 vector1, Vector2 vector2, float t) { vector1 += (vector2 - vector1) * t; vector1.Normalize(); } public static float InterpolateRotation(float radians1, float radians2, float t) { Vector2 vector1 = new Vector2((float)Math.Sin(radians1), -(float)Math.Cos(radians1)); Vector2 vector2 = new Vector2((float)Math.Sin(radians2), -(float)Math.Cos(radians2)); vector1 += (vector2 - vector1) * t; vector1.Normalize(); return (float)Math.Atan2(vector1.X, -(double)vector1.Y); } public static void ProjectToAxis(ref Vector2[] points, ref Vector2 axis, out float min, out float max) { // To project a point on an axis use the dot product axis.Normalize(); float dotProduct = Vector2.Dot(axis, points[0]); min = dotProduct; max = dotProduct; for (int i = 0; i < points.Length; i++) { dotProduct = Vector2.Dot(points[i], axis); if (dotProduct < min) { min = dotProduct; } else { if (dotProduct > max) { max = dotProduct; } } } } } }