/// <summary> /// Initializes a polygon with the specified vertices /// </summary> /// <param name="vertices">Vertices</param> /// <exception cref="ArgumentNullException">If vertices is null</exception> public Polygon2(Vector2[] vertices) { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } Vertices = vertices; Normals = new List <Vector2>(); Vector2 tmp; for (int i = 1; i < vertices.Length; i++) { tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[i] - vertices[i - 1]))); if (!Normals.Contains(tmp)) { Normals.Add(tmp); } } tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[0] - vertices[vertices.Length - 1]))); if (!Normals.Contains(tmp)) { Normals.Add(tmp); } var min = new Vector2(vertices[0].X, vertices[0].Y); var max = new Vector2(min.X, min.Y); for (int i = 1; i < vertices.Length; i++) { min.X = Math.Min(min.X, vertices[i].X); min.Y = Math.Min(min.Y, vertices[i].Y); max.X = Math.Max(max.X, vertices[i].X); max.Y = Math.Max(max.Y, vertices[i].Y); } AABB = new Rect2(min, max); Center = new Vector2(0, 0); foreach (var vert in Vertices) { Center += vert; } Center *= (1.0f / Vertices.Length); // Find longest axis float longestAxisLenSq = -1; for (int i = 1; i < vertices.Length; i++) { var vec = vertices[i] - vertices[i - 1]; longestAxisLenSq = Math.Max(longestAxisLenSq, vec.LengthSquared()); } longestAxisLenSq = Math.Max(longestAxisLenSq, (vertices[0] - vertices[vertices.Length - 1]).LengthSquared()); LongestAxisLength = (float)Math.Sqrt(longestAxisLenSq); // Area and lines float area = 0; Lines = new Line2[Vertices.Length]; var last = Vertices[Vertices.Length - 1]; for (int i = 0; i < Vertices.Length; i++) { var next = Vertices[i]; Lines[i] = new Line2(last, next); area += Math2.AreaOfTriangle(last, next, Center); last = next; } Area = area; last = Vertices[Vertices.Length - 1]; var centToLast = (last - Center); var angLast = Math.Atan2(centToLast.Y, centToLast.X); var cwCounter = 0; var ccwCounter = 0; var foundDefinitiveResult = false; for (int i = 0; i < Vertices.Length; i++) { var curr = Vertices[i]; var centToCurr = (curr - Center); var angCurr = Math.Atan2(centToCurr.Y, centToCurr.X); var clockwise = angCurr < angLast; if (clockwise) { cwCounter++; } else { ccwCounter++; } Clockwise = clockwise; if (Math.Abs(angLast - angCurr) > Math2.DEFAULT_EPSILON) { foundDefinitiveResult = true; break; } last = curr; centToLast = centToCurr; angLast = angCurr; } if (!foundDefinitiveResult) { Clockwise = cwCounter > ccwCounter; } }
/// <summary> /// Initializes a polygon with the specified vertices /// </summary> /// <param name="vertices">Vertices</param> /// <exception cref="ArgumentNullException">If vertices is null</exception> public Polygon2(Vector2[] vertices) { Vertices = vertices ?? throw new ArgumentNullException(nameof(vertices)); Normals = new List <Vector2>(); Vector2 tmp; for (int i = 1; i < vertices.Length; i++) { tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[i] - vertices[i - 1]))); if (!Normals.Contains(tmp)) { Normals.Add(tmp); } } tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[0] - vertices[vertices.Length - 1]))); if (!Normals.Contains(tmp)) { Normals.Add(tmp); } var min = new Vector2(vertices[0].X, vertices[0].Y); var max = new Vector2(min.X, min.Y); for (int i = 1; i < vertices.Length; i++) { min.X = Math.Min(min.X, vertices[i].X); min.Y = Math.Min(min.Y, vertices[i].Y); max.X = Math.Max(max.X, vertices[i].X); max.Y = Math.Max(max.Y, vertices[i].Y); } AABB = new Rect2(min, max); _LongestAxisLength = -1; // Center, area, and lines TrianglePartition = new Triangle2[Vertices.Length - 2]; float[] triangleSortKeys = new float[TrianglePartition.Length]; float area = 0; Lines = new Line2[Vertices.Length]; Lines[0] = new Line2(Vertices[Vertices.Length - 1], Vertices[0]); var last = Vertices[0]; Center = new Vector2(0, 0); for (int i = 1; i < Vertices.Length - 1; i++) { var next = Vertices[i]; var next2 = Vertices[i + 1]; Lines[i] = new Line2(last, next); var tri = new Triangle2(new Vector2[] { Vertices[0], next, next2 }); TrianglePartition[i - 1] = tri; triangleSortKeys[i - 1] = -tri.Area; area += tri.Area; Center += tri.Center * tri.Area; last = next; } Lines[Vertices.Length - 1] = new Line2(Vertices[Vertices.Length - 2], Vertices[Vertices.Length - 1]); Array.Sort(triangleSortKeys, TrianglePartition); Area = area; Center /= area; last = Vertices[Vertices.Length - 1]; var centToLast = (last - Center); var angLast = Rotation2.Standardize((float)Math.Atan2(centToLast.Y, centToLast.X)); var cwCounter = 0; var ccwCounter = 0; var foundDefinitiveResult = false; for (int i = 0; i < Vertices.Length; i++) { var curr = Vertices[i]; var centToCurr = (curr - Center); var angCurr = Rotation2.Standardize((float)Math.Atan2(centToCurr.Y, centToCurr.X)); var clockwise = (angCurr < angLast && (angCurr - angLast) < Math.PI) || (angCurr - angLast) > Math.PI; if (clockwise) { cwCounter++; } else { ccwCounter++; } Clockwise = clockwise; if (Math.Abs(angLast - angCurr) > Math2.DEFAULT_EPSILON) { foundDefinitiveResult = true; break; } last = curr; centToLast = centToCurr; angLast = angCurr; } if (!foundDefinitiveResult) { Clockwise = cwCounter > ccwCounter; } }
/// <summary> /// Determines if the specified rectangle at pos1 intersects the specified polygon at pos2 with /// no rotation. /// </summary> /// <param name="rect">The rectangle</param> /// <param name="poly">The polygon</param> /// <param name="pos1">Origin of rectangle</param> /// <param name="pos2">Origin of polygon</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If rect at pos1 no rotation intersects poly at pos2</returns> public static bool Intersects(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, bool strict) { return(Intersects(rect, poly, pos1, pos2, Rotation2.Zero, strict)); }
/// <summary> /// Determines the minimum translation vector to be applied to the rect to prevent /// intersection with the specified polygon, when they are at the given positions. /// </summary> /// <param name="rect">The rect</param> /// <param name="poly">The polygon</param> /// <param name="pos1">The origin of the rect</param> /// <param name="pos2">The origin of the polygon</param> /// <returns>MTV to move rect at pos1 to prevent overlap with poly at pos2</returns> public static Tuple <Vector2, float> IntersectMTV(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2) { return(IntersectMTV(rect, poly, pos1, pos2, Rotation2.Zero)); }
/// <summary> /// Determines the minimum translation vector to be applied to the circle to /// prevent overlap with the rectangle, when they are at their given positions. /// </summary> /// <param name="circle">The circle</param> /// <param name="rect">The rectangle</param> /// <param name="pos1">The top-left of the circles bounding box</param> /// <param name="pos2">The rectangles origin</param> /// <returns>MTV for circle at pos1 to prevent overlap with rect at pos2</returns> public static Tuple <Vector2, float> IntersectMTV(Circle2 circle, Rect2 rect, Vector2 pos1, Vector2 pos2) { // Same as polygon rect, just converted to rects points HashSet <Vector2> checkedAxis = new HashSet <Vector2>(); Vector2 bestAxis = Vector2.Zero; float shortestOverlap = float.MaxValue; Func <Vector2, bool> checkAxis = (axis) => { var standard = Math2.MakeStandardNormal(axis); if (!checkedAxis.Contains(standard)) { checkedAxis.Add(standard); var circleProj = Circle2.ProjectAlongAxis(circle, pos1, axis); var rectProj = Rect2.ProjectAlongAxis(rect, pos2, axis); var mtv = AxisAlignedLine2.IntersectMTV(circleProj, rectProj); if (!mtv.HasValue) { return(false); } if (Math.Abs(mtv.Value) < Math.Abs(shortestOverlap)) { bestAxis = axis; shortestOverlap = mtv.Value; } } return(true); }; var circleCenter = new Vector2(pos1.X + circle.Radius, pos1.Y + circle.Radius); int last = 4; var lastVec = rect.UpperRight + pos2; for (int curr = 0; curr < 4; curr++) { Vector2 currVec = Vector2.Zero; switch (curr) { case 0: currVec = rect.Min + pos2; break; case 1: currVec = rect.LowerLeft + pos2; break; case 2: currVec = rect.Max + pos2; break; case 3: currVec = rect.UpperRight + pos2; break; } // Test along circle center -> vector if (!checkAxis(Vector2.Normalize(currVec - circleCenter))) { return(null); } // Test along line normal if (!checkAxis(Vector2.Normalize(Math2.Perpendicular(currVec - lastVec)))) { return(null); } last = curr; lastVec = currVec; } return(Tuple.Create(bestAxis, shortestOverlap)); }
/// <summary> /// Determines the vector, if any, to move poly at pos1 rotated rot1 to prevent intersection of rect /// at pos2. /// </summary> /// <param name="poly">Polygon</param> /// <param name="rect">Rectangle</param> /// <param name="pos1">Origin of polygon</param> /// <param name="pos2">Origin of rectangle</param> /// <param name="rot1">Rotation of the polygon.</param> /// <returns>The vector to move pos1 by or null</returns> public static Tuple <Vector2, float> IntersectMTV(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2, Rotation2 rot1) { bool checkedX = false, checkedY = false; Vector2 bestAxis = Vector2.Zero; float bestMagn = float.MaxValue; for (int i = 0; i < poly.Normals.Count; i++) { var norm = Math2.Rotate(poly.Normals[i], Vector2.Zero, rot1); var mtv = IntersectMTVAlongAxis(poly, rect, pos1, pos2, rot1, norm); if (!mtv.HasValue) { return(null); } if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) { bestAxis = norm; bestMagn = mtv.Value; } if (norm.X == 0) { checkedY = true; } if (norm.Y == 0) { checkedX = true; } } if (!checkedX) { var mtv = IntersectMTVAlongAxis(poly, rect, pos1, pos2, rot1, Vector2.UnitX); if (!mtv.HasValue) { return(null); } if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) { bestAxis = Vector2.UnitX; bestMagn = mtv.Value; } } if (!checkedY) { var mtv = IntersectMTVAlongAxis(poly, rect, pos1, pos2, rot1, Vector2.UnitY); if (!mtv.HasValue) { return(null); } if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) { bestAxis = Vector2.UnitY; bestMagn = mtv.Value; } } return(Tuple.Create(bestAxis, bestMagn)); }
/// <summary> /// Determines if the specified rectangle and polygon where rect is at pos1 and poly is at pos2 intersect /// along the specified axis. /// </summary> /// <param name="rect">Rectangle</param> /// <param name="poly">Polygon</param> /// <param name="pos1">Origin of rectangle</param> /// <param name="pos2">Origin of polygon</param> /// <param name="rot2">Rotation of polygon</param> /// <param name="strict"></param> /// <param name="axis"></param> /// <returns></returns> public static bool IntersectsAlongAxis(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2, bool strict, Vector2 axis) { return(IntersectsAlongAxis(poly, rect, pos2, pos1, rot2, strict, axis)); }
/// <summary> /// Determines if the specified rectangle and circle intersect at their given positions. /// </summary> /// <param name="rect">The rectangle</param> /// <param name="circle">The circle</param> /// <param name="pos1">The origin of the rectangle</param> /// <param name="pos2">The top-left of the circles bounding box</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns></returns> public static bool Intersects(Rect2 rect, Circle2 circle, Vector2 pos1, Vector2 pos2, bool strict) { return(Intersects(circle, rect, pos2, pos1, strict)); }
/// <summary> /// Multiply our min with original min and our max with original max and return /// as a rect /// </summary> /// <param name="original">the original</param> /// <returns>scaled rect</returns> public Rect2 ToRect(Rect2 original) { return(new Rect2(original.Min * Min, original.Max * Max)); }
/// <summary> /// Determines the vector to move pos1 to get rect not to intersect poly at pos2 rotated /// by rot2 radians. /// </summary> /// <param name="rect">The rectangle</param> /// <param name="poly">The polygon</param> /// <param name="pos1">Origin of rectangle</param> /// <param name="pos2">Origin of </param> /// <param name="rot2">Rotation of the polygon</param> /// <returns>Offset of pos1 to get rect not to intersect poly</returns> public static Tuple <Vector2, float> IntersectMTV(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2) { var res = IntersectMTV(poly, rect, pos2, pos1, rot2); return(res != null?Tuple.Create(-res.Item1, res.Item2) : res); }
/// <summary> /// Determines if the box when at pos contains point. /// </summary> /// <param name="box">The box</param> /// <param name="pos">Origin of box</param> /// <param name="point">Point to check</param> /// <param name="strict">true if the edges do not count</param> /// <returns>If the box at pos contains point</returns> public static bool Contains(Rect2 box, Vector2 pos, Vector2 point, bool strict) { return(AxisAlignedLine2.Contains(box.Min.X + pos.X, box.Max.X + pos.X, point.X, strict, false) && AxisAlignedLine2.Contains(box.Min.Y + pos.Y, box.Max.Y + pos.Y, point.Y, strict, false)); }
/// <summary> /// Determines if box1 with origin pos1 intersects box2 with origin pos2. /// </summary> /// <param name="box1">Box 1</param> /// <param name="box2">Box 2</param> /// <param name="pos1">Origin of box 1</param> /// <param name="pos2">Origin of box 2</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If box1 intersects box2 when box1 is at pos1 and box2 is at pos2</returns> public static bool Intersects(Rect2 box1, Rect2 box2, Vector2 pos1, Vector2 pos2, bool strict) { return(AxisAlignedLine2.Intersects(box1.Min.X + pos1.X, box1.Max.X + pos1.X, box2.Min.X + pos2.X, box2.Max.X + pos2.X, strict, false) && AxisAlignedLine2.Intersects(box1.Min.Y + pos1.Y, box1.Max.Y + pos1.Y, box2.Min.Y + pos2.Y, box2.Max.Y + pos2.Y, strict, false)); }
/// <summary> /// Projects the rectangle at pos along axis. /// </summary> /// <param name="rect">The rectangle to project</param> /// <param name="pos">The origin of the rectangle</param> /// <param name="axis">The axis to project on</param> /// <returns>The projection of rect at pos along axis</returns> public static AxisAlignedLine2 ProjectAlongAxis(Rect2 rect, Vector2 pos, Vector2 axis) { return(ProjectAlongAxis(axis, pos, Rotation2.Zero, rect.Center, rect.Min, rect.UpperRight, rect.LowerLeft, rect.Max)); }
/// <summary> /// Deterimines in the box contains the specified polygon /// </summary> /// <param name="box">The box</param> /// <param name="poly">The polygon</param> /// <param name="boxPos">Where the box is located</param> /// <param name="polyPos">Where the polygon is located</param> /// <param name="strict">true if we return false if the any part of the polygon is on the edge, false otherwise</param> /// <returns>true if the poly is contained in box, false otherwise</returns> public static bool Contains(Rect2 box, Polygon2 poly, Vector2 boxPos, Vector2 polyPos, bool strict) { return(Contains(box, poly.AABB, boxPos, polyPos, strict)); }
/// <summary> /// Determines if innerBox is contained entirely in outerBox /// </summary> /// <param name="outerBox">the (bigger) box that you want to check contains the inner box</param> /// <param name="innerBox">the (smaller) box that you want to check is contained in the outer box</param> /// <param name="posOuter">where the outer box is located</param> /// <param name="posInner">where the inner box is located</param> /// <param name="strict">true to return false if innerBox touches an edge of outerBox, false otherwise</param> /// <returns>true if innerBox is contained in outerBox, false otherwise</returns> public static bool Contains(Rect2 outerBox, Rect2 innerBox, Vector2 posOuter, Vector2 posInner, bool strict) { return(Contains(outerBox, posOuter, innerBox.Min + posInner, strict) && Contains(outerBox, posOuter, innerBox.Max + posInner, strict)); }