Guest User

Polygon

a guest
Aug 20th, 2016
198
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 17.60 KB | None | 0 0
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3.  
  4. namespace AdvancedUtilities.Geometry
  5. {
  6.     /// <summary>
  7.     /// Represents a 2D polygon.
  8.     ///
  9.     /// See more Advanced Utilities @
  10.     /// https://www.assetstore.unity3d.com/en/#!/search/page=1/sortby=popularity/query=publisher:18832
  11.     /// </summary>
  12.     public class Polygon
  13.     {
  14.         /// <summary>
  15.         /// The vertices that represent the edges of the polygon.
  16.         /// Each vertices forms a line with the next vertex, and the last one with the first.
  17.         /// </summary>
  18.         public IList<Vector2> Vertices { get; private set; }
  19.  
  20.         #region Constructors
  21.  
  22.         /// <summary>
  23.         /// Constructs a polygon using the provied vertices.
  24.         /// </summary>
  25.         /// <param name="vertices">vertices in order. Each vertex forms a line with the next vertex.</param>
  26.         public Polygon(IList<Vector2> vertices)
  27.         {
  28.             this.Vertices = vertices;
  29.         }
  30.  
  31.         /// <summary>
  32.         /// Constructs a polygon using the provied vertices.
  33.         /// </summary>
  34.         /// <param name="vertices">vertices in order. Each vertex forms a line with the next vertex.</param>
  35.         public Polygon(IEnumerable<Vector2> vertices)
  36.         {
  37.             this.Vertices = new List<Vector2>();
  38.             foreach (var vertex in vertices)
  39.             {
  40.                 this.Vertices.Add(vertex);
  41.             }
  42.         }
  43.  
  44.         /// <summary>
  45.         /// Constructs a polygon from a rect.
  46.         /// </summary>
  47.         /// <param name="rect">Rectangle that will be converted into a polygon object.</param>
  48.         public Polygon(Rect rect)
  49.         {
  50.             this.Vertices = new List<Vector2>
  51.             {
  52.                 new Vector2(rect.x, rect.y),
  53.                 new Vector2(rect.x + rect.width, rect.y),
  54.                 new Vector2(rect.x + rect.width, rect.y + rect.height),
  55.                 new Vector2(rect.x, rect.y + rect.height)
  56.             };
  57.         }
  58.  
  59.         /// <summary>
  60.         /// Constructs a polygon given a box collider and a scale and rotation for that collider.
  61.         /// </summary>
  62.         /// <param name="boxCollider">The box collider defining the shape of the </param>
  63.         public Polygon(BoxCollider2D boxCollider)
  64.         {
  65.             Vector3 right = (boxCollider.transform.right * (boxCollider.transform.lossyScale.x * boxCollider.size.x * 0.5f));
  66.             Vector3 up = (boxCollider.transform.up * (boxCollider.transform.lossyScale.y * boxCollider.size.y * 0.5f));
  67.             this.Vertices = new List<Vector2>()
  68.             {
  69.                 boxCollider.transform.position + right + up,
  70.                 boxCollider.transform.position + right - up,
  71.                 boxCollider.transform.position - right - up,
  72.                 boxCollider.transform.position - right + up
  73.             };
  74.         }
  75.  
  76.         #endregion
  77.  
  78.         /// <summary>
  79.         /// Uses Unity's Debug class to draw the polygon in 2d space.
  80.         /// Defaults to color white for drawing.
  81.         /// </summary>
  82.         public void DebugDraw()
  83.         {
  84.             DebugDraw(Color.white);
  85.         }
  86.  
  87.         /// <summary>
  88.         /// Uses Unity's Debug class to draw the polygon in 2d space.
  89.         /// </summary>
  90.         /// <param name="color">The color of the drawn polygon.</param>
  91.         public void DebugDraw(Color color)
  92.         {
  93.             Vector2 v1 = Vertices[Vertices.Count - 1];
  94.             Vector2 v2 = Vertices[0];
  95.  
  96.             for (int i = 0; i < Vertices.Count; i++)
  97.             {
  98.                 Debug.DrawLine(v1, v2, color);
  99.  
  100.                 if (i == Vertices.Count - 1)
  101.                 {
  102.                     break;
  103.                 }
  104.                 v1 = v2;
  105.                 v2 = Vertices[i + 1];
  106.             }
  107.         }
  108.  
  109.         /// <summary>
  110.         /// Returns whether or not this polygon is actually convex.
  111.         /// </summary>
  112.         /// <returns>Is this polygon convex?</returns>
  113.         public bool IsConvex()
  114.         {
  115.             if (Vertices.Count < 3)
  116.             {
  117.                 Debug.LogError("This convex polygon only has 2 vertices...");
  118.                 return false;
  119.             }
  120.            
  121.             // See "Gift Wrapping Algorithm". This isn't exactly that, but it is similar.
  122.             // Basically going around the polygon and measuring that all outer angles are greater than or equal to 90 degrees.
  123.             Vector2 first = (Vertices[Vertices.Count - 1] - Vertices[Vertices.Count - 2]).normalized;
  124.             Vector2 second = (Vertices[0] - Vertices[Vertices.Count - 1]).normalized;
  125.             for (int i = 0; i < Vertices.Count; i++)
  126.             {
  127.                 // Clockwise angle
  128.                 float dot = first.x * second.x + first.y * second.y;
  129.                 float det = first.x * second.y - first.y * second.x;
  130.                 float angle = Mathf.Atan2(det, dot) * Mathf.Rad2Deg;
  131.                 // If there is a bad angle, it actually appears as a negative, but I'm saying "below 90" since that's really what it should be and it still works great.
  132.                 if (angle < 90)
  133.                 {
  134.                     return false;
  135.                 }
  136.  
  137.                 if (i == Vertices.Count - 1)
  138.                 {
  139.                     break;
  140.                 }
  141.                 first = second;
  142.                 second = (Vertices[i + 1] - Vertices[i]).normalized;
  143.             }
  144.  
  145.             return true;
  146.         }
  147.  
  148.         /// <summary>
  149.         /// Returns whether or not this convex polygon intersects another polygon.
  150.         ///
  151.         /// This method does not work properly if both this polygon and the other are not convex.
  152.         /// This method does not check if they are convex.
  153.         /// </summary>
  154.         /// <param name="other">The other polygon that is being checked for intersection.</param>
  155.         /// <returns>Whether the this and the other polygon intersect.</returns>
  156.         public bool Intersects(Polygon other)
  157.         {
  158.             if (other == null)
  159.             {
  160.                 return false;
  161.             }
  162.            
  163.             // Try finding a seperating axis
  164.             if (FindSeperatingAxis(this, other))
  165.             {
  166.                 return false;
  167.             }
  168.  
  169.             // And switch up the roles if you don't find one.
  170.             if (FindSeperatingAxis(other, this))
  171.             {
  172.                 return false;
  173.             }
  174.  
  175.             return true;
  176.         }
  177.  
  178.         /// <summary>
  179.         /// Returns whether or not this convex polygon intersects a rectangle.
  180.         ///
  181.         /// This method does not work properly if this polygon is not convex.
  182.         /// This method does not check if this polygon is convex.
  183.         /// </summary>
  184.         /// <param name="other">The rectangle to see if it intersects with.</param>
  185.         /// <returns>Whether or not the rectangle intersects this polygon.</returns>
  186.         public bool Intersects(Rect other)
  187.         {
  188.             Polygon rectPolygon = new Polygon(other);
  189.             return Intersects(rectPolygon);
  190.         }
  191.  
  192.         /// <summary>
  193.         /// Returns whether or not this convex polygon intersects a rectangle.
  194.         ///
  195.         /// This method does not work properly if this polygon is not convex.
  196.         /// This method does not check if this polygon is convex.
  197.         /// </summary>
  198.         /// <param name="other">The rectangle to see if it intersects with.</param>
  199.         /// <param name="rectPolygon">A polygon created from the rect for intersecting purposes.</param>
  200.         /// <returns>Whether or not the rectangle intersects this polygon.</returns>
  201.         public bool Intersects(Rect other, out Polygon rectPolygon)
  202.         {
  203.             rectPolygon = new Polygon(other);
  204.             return Intersects(rectPolygon);
  205.         }
  206.  
  207.         /// <summary>
  208.         /// Returns whether or not this convex polygon intersects a box collider.
  209.         ///
  210.         /// This method does not work properly if this polygon is not convex.
  211.         /// This method does not check if this polygon is convex.
  212.         /// </summary>
  213.         /// <param name="boxCollider2D">Box colldier being checked.</param>
  214.         /// <returns>Whether or not the box collider intersects this polygon.</returns>
  215.         public bool Intersects(BoxCollider2D boxCollider2D)
  216.         {
  217.             Polygon rectPolygon = new Polygon(boxCollider2D);
  218.             return Intersects(rectPolygon);
  219.         }
  220.  
  221.         /// <summary>
  222.         /// Returns whether or not this convex polygon intersects a box collider.
  223.         ///
  224.         /// This method does not work properly if this polygon is not convex.
  225.         /// This method does not check if this polygon is convex.
  226.         /// </summary>
  227.         /// <param name="boxCollider2D">Box colldier being checked.</param>
  228.         /// <param name="boxColliderPolygon">A polygon created for intersecting purposes.</param>
  229.         /// <returns>Whether or not the box collider intersects this polygon.</returns>
  230.         public bool Intersects(BoxCollider2D boxCollider2D, out Polygon boxColliderPolygon)
  231.         {
  232.             boxColliderPolygon = new Polygon(boxCollider2D);
  233.             return Intersects(boxColliderPolygon);
  234.         }
  235.  
  236.         /// <summary>
  237.         /// Returns whether or not this polygon intersects a circle collider.
  238.         /// </summary>
  239.         /// <param name="circleCollider2D">The circle collider in question.</param>
  240.         /// <returns>Whether or not this polygon intersects the circle collider.</returns>
  241.         public bool Intersects(CircleCollider2D circleCollider2D)
  242.         {
  243.             float radius = circleCollider2D.radius * Mathf.Max(circleCollider2D.transform.lossyScale.x, circleCollider2D.transform.lossyScale.y);
  244.  
  245.             return WithinDistanceOf(circleCollider2D.transform.position, radius);
  246.         }
  247.  
  248.         /// <summary>
  249.         /// Returns whether the polygon is within a distance of the given point.
  250.         /// </summary>
  251.         /// <param name="point">Point to check.</param>
  252.         /// <param name="distance">Distance to see if it's close enough.</param>
  253.         /// <returns>Whether or not the point is within distance of the point.</returns>
  254.         public bool WithinDistanceOf(Vector2 point, float distance)
  255.         {
  256.             return Distance(point) <= distance;
  257.         }
  258.  
  259.         /// <summary>
  260.         /// Returns the shortest distance from the given point to the polygon.
  261.         /// </summary>
  262.         /// <param name="point">The point to get the distance from.</param>
  263.         /// <returns>The shortest </returns>
  264.         public float Distance(Vector2 point)
  265.         {
  266.             if (Contains(point))
  267.             {
  268.                 return 0f;
  269.             }
  270.  
  271.             float distance = float.MaxValue;
  272.            
  273.             Vector2 v1 = Vertices[Vertices.Count - 1];
  274.             Vector2 v2 = Vertices[0];
  275.             for (int i = 0; i < Vertices.Count; i++)
  276.             {
  277.                 float lineLength = Vector2.Distance(v1, v2);
  278.                 Vector2 line = v2 - v1;
  279.  
  280.                 // Have to try 2 perpendicular vectors to get the one that lies on the line.
  281.                 Vector2 perp1 = new Vector2(line.y, -line.x).normalized;
  282.                 Vector2 perp2 = -perp1;
  283.  
  284.                 // This is the actual distance from the infinite line.
  285.                 float distanceFromLine =
  286.                     Mathf.Abs((v2.x - v1.x) * (v1.y - point.y) - (v1.x - point.x) * (v2.y - v1.y))
  287.                     /
  288.                     Mathf.Sqrt((v2.x - v1.x) * (v2.x - v1.x) + (v2.y - v1.y) * (v2.y - v1.y));
  289.  
  290.                 // This represents the point if it were projected onto the line itself.
  291.                 Vector2 onLinePointTry1 = point + perp1 * distanceFromLine;
  292.                 Vector2 onLinePointTry2 = point + perp2 * distanceFromLine;
  293.                 Vector2 onLinePoint = Vector2.Distance(onLinePointTry1, v1) < Vector2.Distance(onLinePointTry2, v1) ? onLinePointTry1 : onLinePointTry2;
  294.  
  295.                 float distFromPoint1 = Vector2.Distance(onLinePoint, v1);
  296.                 float distFromPoint2 = Vector2.Distance(onLinePoint, v2);
  297.  
  298.                 // If one of the distances of our on line point is greater than the length of the line, that means it doesn't actually fall on the limited line.
  299.                 // So we know the distance is actually the distance to one of the points that make up the line since those are the furthest points off of the line.
  300.                 // If they are both shorter than line length, then we know that the distanceFromLine originally calculated is true.
  301.                 if (distFromPoint1 > lineLength || distFromPoint2 > lineLength)
  302.                 {
  303.                     float actualDistanceFromPoint1 = Vector2.Distance(v1, point);
  304.                     float actualDistanceFromPoint2 = Vector2.Distance(v2, point);
  305.                     distanceFromLine = actualDistanceFromPoint1 < actualDistanceFromPoint2 ? actualDistanceFromPoint1 : actualDistanceFromPoint2;
  306.                 }
  307.  
  308.                 if (distanceFromLine < distance)
  309.                 {
  310.                     distance = distanceFromLine;
  311.                 }
  312.  
  313.                 if (i == Vertices.Count - 1)
  314.                 {
  315.                     break;
  316.                 }
  317.                 v1 = v2;
  318.                 v2 = Vertices[i + 1];
  319.             }
  320.  
  321.             return distance;
  322.         }
  323.  
  324.         /// <summary>
  325.         /// Checks if the polygon contains the given point.
  326.         /// This is not inclusive of greater values on both sides. A 1x1 square from (0,0) to (1,1) would not contain the value (1,1), (0,1), or (1,0), but would contain (0,0), (0.999f,0.999f), (0.999f,0), and (0,0.999f).
  327.         /// </summary>
  328.         /// <param name="point">Point to check.</param>
  329.         /// <returns>Polygon contains the point.</returns>
  330.         public bool Contains(Vector2 point)
  331.         {
  332.             int i;
  333.             int j;
  334.             bool result = false;
  335.             for (i = 0, j = Vertices.Count - 1; i < Vertices.Count; j = i++)
  336.             {
  337.                 if ((Vertices[i].y > point.y) != (Vertices[j].y > point.y) &&
  338.                     (point.x < (Vertices[j].x - Vertices[i].x) * (point.y - Vertices[i].y) / (Vertices[j].y - Vertices[i].y) + Vertices[i].x))
  339.                 {
  340.                     result = !result;
  341.                 }
  342.             }
  343.             return result;
  344.         }
  345.  
  346.         /// <summary>
  347.         /// Returns whether or not thw two polygons intersect each other.
  348.         ///
  349.         /// This method does not work properly if both polygons are not convex.
  350.         /// This method does not check if they are convex.
  351.         /// </summary>
  352.         /// <param name="first">The first polygon to check.</param>
  353.         /// <param name="second">The second polygon to check.</param>
  354.         /// <returns>Whether the polygons intersect eachother.</returns>
  355.         public static bool Intersects(Polygon first, Polygon second)
  356.         {
  357.             if (first == null || second == null)
  358.             {
  359.                 return false;
  360.             }
  361.  
  362.             return first.Intersects(second);
  363.         }
  364.  
  365.         #region Helper methods
  366.  
  367.         /// <summary>
  368.         /// Returns whether or not there is a seperating axis from p1 to p2.
  369.         /// Order of polygons into the function matters.
  370.         /// </summary>
  371.         /// <param name="p1">First polygon.</param>
  372.         /// <param name="p2">Second polygon.</param>
  373.         /// <returns>p1 can be seperated from p2.</returns>
  374.         private static bool FindSeperatingAxis(Polygon p1, Polygon p2)
  375.         {
  376.             // Iterate over all the edges
  377.             int prev = p1.Vertices.Count - 1;
  378.             for (int cur = 0; cur < p1.Vertices.Count; ++cur)
  379.             {
  380.                 Vector2 edge = p1.Vertices[cur] - p1.Vertices[prev];
  381.  
  382.                 // Rotate vector 90 degrees (doesn't matter which way) to get
  383.                 // candidate separating axis.
  384.                 Vector2 projectOnto = new Vector2(edge.y, -edge.x);
  385.  
  386.                 // Gather extents of both polygons projected onto this axis
  387.                 float aMin, aMax, bMin, bMax;
  388.                 GatherPolygonProjectionExtents(p1.Vertices, projectOnto, out aMin, out aMax);
  389.                 GatherPolygonProjectionExtents(p2.Vertices, projectOnto, out bMin, out bMax);
  390.  
  391.                 // Is this a separating axis?
  392.                 if (aMax < bMin) return true;
  393.                 if (bMax < aMin) return true;
  394.  
  395.                 // Next edge, please
  396.                 prev = cur;
  397.             }
  398.  
  399.             return false;
  400.         }
  401.  
  402.         /// <summary>
  403.         /// Gather up one-dimensional extents of the projection of the polygon onto this axis.
  404.         /// </summary>
  405.         /// <param name="vertices">The vertices of a polygon. It's vertices.</param>
  406.         /// <param name="projectOnto">A vector to project onto.</param>
  407.         /// <param name="outMin">Minimum projection value for the edge.</param>
  408.         /// <param name="outMax">Maximum projection value for the edge.</param>
  409.         private static void GatherPolygonProjectionExtents(IList<Vector2> vertices, Vector2 projectOnto, out float outMin, out float outMax)
  410.         {
  411.             outMin = outMax = Vector2.Dot(projectOnto, vertices[0]);
  412.  
  413.             // Now scan all the rest, growing extents to include them
  414.             for (int i = 1; i < vertices.Count; ++i)
  415.             {
  416.                 float d = Vector2.Dot(projectOnto, vertices[i]);
  417.                 if (d < outMin)
  418.                 {
  419.                     outMin = d;
  420.                 }
  421.                 else if (d > outMax)
  422.                 {
  423.                     outMax = d;
  424.                 }
  425.             }
  426.         }
  427.  
  428.         #endregion
  429.     }
  430. }
Advertisement
Add Comment
Please, Sign In to add comment