using System; using System.Collections.Generic; using System.Linq; using System.Text; using TransportGame.Utils; namespace TransportGame.Model { /// /// Represents a polygon /// public class Polygon : IPositionable { #region Private types private struct Edge { public int U, V; public Edge(int U, int V) { this.U = U; this.V = V; } } #endregion /// /// Gets or sets the points that define the polygon /// public Vector2[] Points { get; set; } /// /// Gets the gravitational center /// public Vector2 Position { get { return Points.Aggregate((x, y) => x + y) / Points.Length; } } /// /// Initializes polygon /// public Polygon() { Points = new Vector2[0]; } /// /// Initializes polygon using given points /// /// Points public Polygon(IEnumerable points) { Points = points.ToArray(); } /// /// Initializes polygon using given points /// /// Points public Polygon(params Vector2[] points) { Points = points; } /// /// Compares angle between two NORMALIZED vectors. /// /// /// /// private static float CompareAngle(Vector2 a, Vector2 b) { float sin = Vector2.Cross(a, b); float cos = Vector2.Dot(a, b); if (sin >= 0 && cos >= 0) return sin; else if (sin >= 0 && cos <= 0) return 1 + Math.Abs(cos); else if (sin <= 0 && cos <= 0) return 2 + Math.Abs(sin); else return 3 + cos; } /// /// Returns the union between given polygons /// /// Polygons /// Polygon representing union public static Polygon Union(params Polygon[] polys) { return Union(polys); } /// /// Returns the union between given polygons /// /// Polygons /// Polygon representing union public static Polygon Union(IEnumerable polys) { List vertices = new List(); List edges = new List(); List union = new List(); foreach (var poly in polys) { // Add all points for (int i = 0; i < poly.Points.Length; i++) { int j = (i + 1) % poly.Points.Length; // Get/add first point int indexi = vertices.IndexOf(poly.Points[i]); if (indexi == -1) { vertices.Add(poly.Points[i]); indexi = vertices.Count - 1; } // Get/add second point int indexj = vertices.IndexOf(poly.Points[j]); if (indexj == -1) { vertices.Add(poly.Points[j]); indexj = vertices.Count - 1; } // Add edge edges.Add(new Edge(indexi, indexj)); } } // Intersect edges for (int i = 0; i < edges.Count; i++) for (int j = i + 1; j < edges.Count; j++) { LineSegment a = new LineSegment(vertices[edges[i].U], vertices[edges[i].V]); LineSegment b = new LineSegment(vertices[edges[j].U], vertices[edges[j].V]); var inters = LineSegment.Intersect(a, b); if (inters.HasValue && inters.Value != a.P0 && inters.Value != a.P1 && inters.Value != b.P0 && inters.Value != b.P1) { vertices.Add(inters.Value); int index = vertices.Count - 1; edges.Add(new Edge(index, edges[i].V)); edges[i] = new Edge(edges[i].U, index); edges.Add(new Edge(index, edges[j].V)); edges[j] = new Edge(edges[j].U, index); } } // Compute union int start = 0; // Find starting point for (int i = 0; i < vertices.Count; i++) if (vertices[i].X + vertices[i].Y < vertices[start].X + vertices[start].Y) start = i; int v = start, vold = -1; Vector2 prev = vertices[v].Normalized; do { union.Add(vertices[v]); int newV = -1; float smallestAngle = -1; Vector2 smallestDir = Vector2.Zero; foreach (var edge in edges) { if ((edge.U == v || edge.V == v) && edge.V != vold && edge.U != vold) { int otherv = (edge.U == v) ? edge.V : edge.U; Vector2 dir = (vertices[otherv] - vertices[v]).Normalized; // Find smallest angle float cmpAngle = CompareAngle(-prev, dir); if (cmpAngle < smallestAngle || smallestAngle < 0) { newV = otherv; smallestAngle = cmpAngle; smallestDir = dir; } } } // Advance prev = smallestDir; vold = v; v = newV; } while (v != start); return new Polygon(union); } public static bool DoPolygonsIntersect(Polygon a, Polygon b) { foreach (var poly in new[] { a, b }) { for (int i = 0; i < poly.Points.Length; i++) { int j = (i + 1) % poly.Points.Length; var normal = new Vector2(poly.Points[j].Y - poly.Points[i].Y, poly.Points[i].X - poly.Points[j].X); double? minA = null, maxA = null; foreach (var p in a.Points) { var projected = Vector2.Dot(normal, p); if (minA == null || projected < minA) minA = projected; if (maxA == null || projected > maxA) maxA = projected; } double? minB = null, maxB = null; foreach (var p in b.Points) { var projected = Vector2.Dot(normal, p); if (minB == null || projected < minB) minB = projected; if (maxB == null || projected > maxB) maxB = projected; } if (maxA <= minB || maxB <= minA) return false; } } return true; } } }