248 lines
7.6 KiB
C#
248 lines
7.6 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using TransportGame.Utils;
|
|||
|
|
|||
|
namespace TransportGame.Model
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Represents a polygon
|
|||
|
/// </summary>
|
|||
|
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
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets or sets the points that define the polygon
|
|||
|
/// </summary>
|
|||
|
public Vector2[] Points { get; set; }
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the gravitational center
|
|||
|
/// </summary>
|
|||
|
public Vector2 Position
|
|||
|
{
|
|||
|
get { return Points.Aggregate((x, y) => x + y) / Points.Length; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes polygon
|
|||
|
/// </summary>
|
|||
|
public Polygon()
|
|||
|
{
|
|||
|
Points = new Vector2[0];
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes polygon using given points
|
|||
|
/// </summary>
|
|||
|
/// <param name="points">Points</param>
|
|||
|
public Polygon(IEnumerable<Vector2> points)
|
|||
|
{
|
|||
|
Points = points.ToArray();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initializes polygon using given points
|
|||
|
/// </summary>
|
|||
|
/// <param name="points">Points</param>
|
|||
|
public Polygon(params Vector2[] points)
|
|||
|
{
|
|||
|
Points = points;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Compares angle between two NORMALIZED vectors.
|
|||
|
/// </summary>
|
|||
|
/// <param name="a"></param>
|
|||
|
/// <param name="b"></param>
|
|||
|
/// <returns></returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the union between given polygons
|
|||
|
/// </summary>
|
|||
|
/// <param name="polys">Polygons</param>
|
|||
|
/// <returns>Polygon representing union</returns>
|
|||
|
public static Polygon Union(params Polygon[] polys)
|
|||
|
{
|
|||
|
return Union(polys);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the union between given polygons
|
|||
|
/// </summary>
|
|||
|
/// <param name="polys">Polygons</param>
|
|||
|
/// <returns>Polygon representing union</returns>
|
|||
|
public static Polygon Union(IEnumerable<Polygon> polys)
|
|||
|
{
|
|||
|
List<Vector2> vertices = new List<Vector2>();
|
|||
|
List<Edge> edges = new List<Edge>();
|
|||
|
List<Vector2> union = new List<Vector2>();
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
}
|