/// <summary> /// Determines if polygon 1 and polygon 2 at position 1 and position 2, respectively, intersect along axis. /// </summary> /// <param name="poly1">polygon 1</param> /// <param name="poly2">polygon 2</param> /// <param name="pos1">Origin of polygon 1</param> /// <param name="pos2">Origin of polygon 2</param> /// <param name="rot1">Rotation of the first polygon</param> /// <param name="rot2">Rotation of the second polygon</param> /// <param name="strict">If overlapping is required for intersection</param> /// <param name="axis">The axis to check</param> /// <returns>If poly1 at pos1 intersects poly2 at pos2 along axis</returns> public static bool IntersectsAlongAxis(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, bool strict, Vector2 axis) { var proj1 = ProjectAlongAxis(poly1, pos1, rot1, axis); var proj2 = ProjectAlongAxis(poly2, pos2, rot2, axis); return(AxisAlignedLine2.Intersects(proj1, proj2, strict)); }
/// <summary> /// Determines the distance along axis, if any, that polygon 1 should be shifted by /// to prevent intersection with polygon 2. Null if no intersection along axis. /// </summary> /// <param name="poly1">polygon 1</param> /// <param name="poly2">polygon 2</param> /// <param name="pos1">polygon 1 origin</param> /// <param name="pos2">polygon 2 origin</param> /// <param name="rot1">polygon 1 rotation</param> /// <param name="rot2">polygon 2 rotation</param> /// <param name="axis">Axis to check</param> /// <returns>a number to shift pos1 along axis by to prevent poly1 at pos1 from intersecting poly2 at pos2, or null if no int. along axis</returns> public static float?IntersectMTVAlongAxis(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, Vector2 axis) { var proj1 = ProjectAlongAxis(poly1, pos1, rot1, axis); var proj2 = ProjectAlongAxis(poly2, pos2, rot2, axis); return(AxisAlignedLine2.IntersectMTV(proj1, proj2)); }
/// <summary> /// Determines if the circle at the specified position intersects the line, /// which is at its true position and rotation, when the line is assumed to be horizontal. /// </summary> /// <param name="circle">The circle</param> /// <param name="line">The line</param> /// <param name="circleCenter">The center of the circle</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If the circle with center circleCenter intersects the horizontal line</returns> protected static bool CircleIntersectsHorizontalLine(Circle2 circle, Line2 line, Vector2 circleCenter, bool strict) { // This is exactly the same process as CircleIntersectsLine, except the projetions are easier var lineY = line.Start.Y; // Step 1 - Find closest distance var vecCircleCenterToLine1D = lineY - circleCenter.Y; var closestDistance = Math.Abs(vecCircleCenterToLine1D); // Step 1a if (strict) { if (closestDistance >= circle.Radius) { return(false); } } else { if (closestDistance > circle.Radius) { return(false); } } // Step 2 - Find closest point var closestPointX = circleCenter.X; // Step 3 - Is closest point on line if (AxisAlignedLine2.Contains(line.Start.X, line.End.X, closestPointX, false, true)) { return(true); } // Step 4 - Find edgeClosest float edgeClosestX; if (line.Start.X < line.End.X) { edgeClosestX = (closestPointX <= line.Start.X) ? line.Start.X : line.End.X; } else { edgeClosestX = (closestPointX >= line.Start.X) ? line.Start.X : line.End.X; } // Step 5 - Circle-point intersection on closest point var distClosestEdgeToCircleSq = new Vector2(circleCenter.X - edgeClosestX, circleCenter.Y - lineY).LengthSquared(); if (strict) { return(distClosestEdgeToCircleSq < circle.Radius * circle.Radius); } else { return(distClosestEdgeToCircleSq <= circle.Radius * circle.Radius); } }
/// <summary> /// Determines if the circle at the specified position intersects the line, which /// is at its true position and rotation, when the line is assumed to be vertical /// </summary> /// <param name="circle">The circle</param> /// <param name="line">The line</param> /// <param name="circleCenter">The center of the circle</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If the circle with center circleCenter intersects the line</returns> protected static bool CircleIntersectsVerticalLine(Circle2 circle, Line2 line, Vector2 circleCenter, bool strict) { // Same process as horizontal, but axis flipped var lineX = line.Start.X; // Step 1 - Find closest distance var vecCircleCenterToLine1D = lineX - circleCenter.X; var closestDistance = Math.Abs(vecCircleCenterToLine1D); // Step 1a if (strict) { if (closestDistance >= circle.Radius) { return(false); } } else { if (closestDistance > circle.Radius) { return(false); } } // Step 2 - Find closest point var closestPointY = circleCenter.Y; // Step 3 - Is closest point on line if (AxisAlignedLine2.Contains(line.Start.Y, line.End.Y, closestPointY, false, true)) { return(true); } // Step 4 - Find edgeClosest float edgeClosestY; if (line.Start.Y < line.End.Y) { edgeClosestY = (closestPointY <= line.Start.Y) ? line.Start.Y : line.End.Y; } else { edgeClosestY = (closestPointY >= line.Start.Y) ? line.Start.Y : line.End.Y; } // Step 5 - Circle-point intersection on closest point var distClosestEdgeToCircleSq = new Vector2(circleCenter.X - lineX, circleCenter.Y - edgeClosestY).LengthSquared(); if (strict) { return(distClosestEdgeToCircleSq < circle.Radius * circle.Radius); } else { return(distClosestEdgeToCircleSq <= circle.Radius * circle.Radius); } }
/// <summary> /// Determines the best way for line1 to move to prevent intersection with line2 /// </summary> /// <param name="line1">Line1</param> /// <param name="line2">Line2</param> /// <returns>MTV for line1</returns> public static float?IntersectMTV(AxisAlignedLine2 line1, AxisAlignedLine2 line2) { if (line1.Axis != line2.Axis) { throw new ArgumentException($"Lines {line1} and {line2} are not aligned - you will need to convert to Line2 to check intersection."); } return(IntersectMTV(line1.Min, line1.Max, line2.Min, line2.Max, false)); }
/// <summary> /// Determines if the two polygons intersect using the Separating Axis Theorem. /// The performance of this function depends on the number of unique normals /// between the two polygons. /// </summary> /// <param name="poly1">First polygon</param> /// <param name="poly2">Second polygon</param> /// <param name="pos1">Offset for the vertices of the first polygon</param> /// <param name="pos2">Offset for the vertices of the second polygon</param> /// <param name="rot1">Rotation of the first polygon</param> /// <param name="rot2">Rotation of the second polygon</param> /// <param name="strict"> /// True if the two polygons must overlap a non-zero area for intersection, /// false if they must overlap on at least one point for intersection. /// </param> /// <returns>True if the polygons overlap, false if they do not</returns> public static bool IntersectsSat(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, bool strict) { if (rot1 == Rotation2.Zero && rot2 == Rotation2.Zero) { // This was a serious performance bottleneck so we speed up the fast case HashSet <Vector2> seen = new HashSet <Vector2>(); Vector2[] poly1Verts = poly1.Vertices; Vector2[] poly2Verts = poly2.Vertices; for (int i = 0, len = poly1.Normals.Count; i < len; i++) { var axis = poly1.Normals[i]; var proj1 = ProjectAlongAxis(axis, pos1, poly1Verts); var proj2 = ProjectAlongAxis(axis, pos2, poly2Verts); if (!AxisAlignedLine2.Intersects(proj1, proj2, strict)) { return(false); } seen.Add(axis); } for (int i = 0, len = poly2.Normals.Count; i < len; i++) { var axis = poly2.Normals[i]; if (seen.Contains(axis)) { continue; } var proj1 = ProjectAlongAxis(axis, pos1, poly1Verts); var proj2 = ProjectAlongAxis(axis, pos2, poly2Verts); if (!AxisAlignedLine2.Intersects(proj1, proj2, strict)) { return(false); } } return(true); } foreach (var norm in poly1.Normals.Select((v) => Tuple.Create(v, rot1)).Union(poly2.Normals.Select((v) => Tuple.Create(v, rot2)))) { var axis = Math2.Rotate(norm.Item1, Vector2.Zero, norm.Item2); if (!IntersectsAlongAxis(poly1, poly2, pos1, pos2, rot1, rot2, strict, axis)) { return(false); } } return(true); }
/// <summary> /// Determines if the given line contains the given point. /// </summary> /// <param name="line">The line to check</param> /// <param name="pos">The offset for the line</param> /// <param name="pt">The point to check</param> /// <returns>True if pt is on the line, false otherwise</returns> public static bool Contains(Line2 line, Vector2 pos, Vector2 pt) { // The horizontal/vertical checks are not required but are // very fast to calculate and short-circuit the common case // (false) very quickly if (line.Horizontal) { return(Math2.Approximately(line.Start.Y + pos.Y, pt.Y) && AxisAlignedLine2.Contains(line.MinX, line.MaxX, pt.X - pos.X, false, false)); } if (line.Vertical) { return(Math2.Approximately(line.Start.X + pos.X, pt.X) && AxisAlignedLine2.Contains(line.MinY, line.MaxY, pt.Y - pos.Y, false, false)); } // Our line is not necessarily a linear space, but if we shift // our line to the origin and adjust the point correspondingly // then we have a linear space and the problem remains the same. // Our line at the origin is just the infinite line with slope // Axis. We can form an orthonormal basis of R2 as (Axis, Normal). // Hence we can write pt = line_part * Axis + normal_part * Normal. // where line_part and normal_part are floats. If the normal_part // is 0, then pt = line_part * Axis, hence the point is on the // infinite line. // Since we are working with an orthonormal basis, we can find // components with dot products. // To check the finite line, we consider the start of the line // the origin. Then the end of the line is line.Magnitude * line.Axis. Vector2 lineStart = pos + line.Start; float normalPart = Math2.Dot(pt - lineStart, line.Normal); if (!Math2.Approximately(normalPart, 0)) { return(false); } float axisPart = Math2.Dot(pt - lineStart, line.Axis); return(axisPart > -Math2.DEFAULT_EPSILON && axisPart < line.Magnitude + Math2.DEFAULT_EPSILON); }
/// <summary> /// Calculates the shortest distance and direction to go from poly1 at pos1 to poly2 at pos2. Returns null /// if the polygons intersect. /// </summary> /// <returns>The distance.</returns> /// <param name="poly1">First polygon</param> /// <param name="poly2">Second polygon</param> /// <param name="pos1">Origin of first polygon</param> /// <param name="pos2">Origin of second polygon</param> /// <param name="rot1">Rotation of first polygon</param> /// <param name="rot2">Rotation of second polygon</param> public static Tuple <Vector2, float> MinDistance(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2) { if (rot1.Theta != 0 || rot2.Theta != 0) { throw new NotSupportedException("Finding the minimum distance between polygons requires calculating the rotated polygons. This operation is expensive and should be cached. " + "Create the rotated polygons with Polygon2#GetRotated and call this function with Rotation2.Zero for both rotations."); } var axises = poly1.Normals.Union(poly2.Normals).Union(GetExtraMinDistanceVecsPolyPoly(poly1, poly2, pos1, pos2)); Vector2?bestAxis = null; // note this is the one with the longest distance float bestDist = 0; foreach (var norm in axises) { var proj1 = ProjectAlongAxis(poly1, pos1, rot1, norm); var proj2 = ProjectAlongAxis(poly2, pos2, rot2, norm); var dist = AxisAlignedLine2.MinDistance(proj1, proj2); if (dist.HasValue && (bestAxis == null || dist.Value > bestDist)) { bestDist = dist.Value; if (proj2.Min < proj1.Min && dist > 0) { bestAxis = -norm; } else { bestAxis = norm; } } } if (!bestAxis.HasValue || Math2.Approximately(bestDist, 0)) { return(null); // they intersect } return(Tuple.Create(bestAxis.Value, bestDist)); }
/// <summary> /// Determines if line1 intersects line2, when line1 is offset by pos1 and line2 /// is offset by pos2. /// </summary> /// <param name="line1">Line 1</param> /// <param name="line2">Line 2</param> /// <param name="pos1">Origin of line 1</param> /// <param name="pos2">Origin of line 2</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If line1 intersects line2</returns> public static bool Intersects(Line2 line1, Line2 line2, Vector2 pos1, Vector2 pos2, bool strict) { if (line1.Horizontal && line2.Horizontal) { return(AxisAlignedLine2.Intersects(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2.MinX + pos2.X, line2.MaxX + pos2.X, strict, false)); } else if (line1.Vertical && line2.Vertical) { return(AxisAlignedLine2.Intersects(line1.MinY + pos1.Y, line1.MaxY + pos1.Y, line2.MinY + pos2.Y, line2.MaxY + pos2.Y, strict, false)); } else if (line1.Horizontal || line2.Horizontal) { if (line2.Horizontal) { // swap line 1 and 2 to prevent duplicating everything var tmp = line1; var tmpp = pos1; line1 = line2; pos1 = pos2; line2 = tmp; pos2 = tmpp; } if (line2.Vertical) { return(AxisAlignedLine2.Contains(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2.Start.X + pos2.X, strict, false) && AxisAlignedLine2.Contains(line2.MinY + pos2.Y, line2.MaxY + pos2.Y, line1.Start.Y + pos1.Y, strict, false)); } else { // recalculate line2 y intercept // y = mx + b // b = y - mx var line2YIntInner = line2.Start.Y + pos2.Y - line2.Slope * (line2.Start.X + pos2.X); // check line2.x at line1.y // line2.y = line2.slope * line2.x + line2.yintercept // line1.y = line2.slope * line2.x + line2.yintercept // line1.y - line2.yintercept = line2.slope * line2.x // (line1.y - line2.yintercept) / line2.slope = line2.x var line2XAtLine1Y = (line1.Start.Y + pos1.Y - line2YIntInner) / line2.Slope; return(AxisAlignedLine2.Contains(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2XAtLine1Y, strict, false) && AxisAlignedLine2.Contains(line2.MinX + pos2.X, line2.MaxX + pos2.X, line2XAtLine1Y, strict, false)); } } else if (line1.Vertical) { // vertical line with regular line var line2YIntInner = line2.Start.Y + pos2.Y - line2.Slope * (line2.Start.X + pos2.X); var line2YAtLine1X = line2.Slope * (line1.Start.X + pos1.X) + line2YIntInner; return(AxisAlignedLine2.Contains(line1.MinY + pos1.Y, line1.MaxY + pos1.Y, line2YAtLine1X, strict, false) && AxisAlignedLine2.Contains(line2.MinY + pos2.Y, line2.MaxY + pos2.Y, line2YAtLine1X, strict, false)); } // two non-vertical, non-horizontal lines var line1YInt = line1.Start.Y + pos1.Y - line1.Slope * (line1.Start.X + pos1.X); var line2YInt = line2.Start.Y + pos2.Y - line2.Slope * (line2.Start.X + pos2.X); if (Math.Abs(line1.Slope - line2.Slope) <= Math2.DEFAULT_EPSILON) { // parallel lines if (line1YInt != line2YInt) { return(false); // infinite lines don't intersect } // parallel lines with equal y intercept. Intersect if ever at same X coordinate. return(AxisAlignedLine2.Intersects(line1.MinX + pos1.X, line1.MaxX + pos1.X, line2.MinX + pos2.X, line2.MaxX + pos2.X, strict, false)); } else { // two non-parallel lines. Only one possible intersection point // y1 = y2 // line1.Slope * x + line1.YIntercept = line2.Slope * x + line2.YIntercept // line1.Slope * x - line2.Slope * x = line2.YIntercept - line1.YIntercept // x (line1.Slope - line2.Slope) = line2.YIntercept - line1.YIntercept // x = (line2.YIntercept - line1.YIntercept) / (line1.Slope - line2.Slope) var x = (line2YInt - line1YInt) / (line1.Slope - line2.Slope); return(AxisAlignedLine2.Contains(line1.MinX + pos1.X, line1.MaxX + pos1.X, x, strict, false) && AxisAlignedLine2.Contains(line2.MinX + pos1.X, line2.MaxX + pos2.X, x, strict, false)); } }
/// <summary> /// Determines if the circle whose bounding boxs top left is at the first postion intersects the line /// at the second position who is rotated the specified amount about the specified point. /// </summary> /// <param name="circle">The circle</param> /// <param name="line">The line</param> /// <param name="pos1">The top-left of the circles bounding box</param> /// <param name="pos2">The origin of the line</param> /// <param name="rot2">What rotation the line is under</param> /// <param name="about2">What the line is rotated about</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If the circle at pos1 intersects the line at pos2 rotated rot2 about about2</returns> protected static bool CircleIntersectsLine(Circle2 circle, Line2 line, Vector2 pos1, Vector2 pos2, Rotation2 rot2, Vector2 about2, bool strict) { // Make more math friendly var actualLine = new Line2(Math2.Rotate(line.Start, about2, rot2) + pos2, Math2.Rotate(line.End, about2, rot2) + pos2); var circleCenter = new Vector2(pos1.X + circle.Radius, pos1.Y + circle.Radius); // Check weird situations if (actualLine.Horizontal) { return(CircleIntersectsHorizontalLine(circle, actualLine, circleCenter, strict)); } if (actualLine.Vertical) { return(CircleIntersectsVerticalLine(circle, actualLine, circleCenter, strict)); } // Goal: // 1. Find closest distance, closestDistance, on the line to the circle (assuming the line was infinite) // 1a Determine if closestPoint is intersects the circle according to strict // - If it does not, we've shown there is no intersection. // 2. Find closest point, closestPoint, on the line to the circle (assuming the line was infinite) // 3. Determine if closestPoint is on the line (including edges) // - If it is, we've shown there is intersection. // 4. Determine which edge, edgeClosest, is closest to closestPoint // 5. Determine if edgeClosest intersects the circle according to strict // - If it does, we've shown there is intersection // - If it does not, we've shown there is no intersection // Step 1 // We're trying to find closestDistance // Recall that the shortest line from a line to a point will be normal to the line // Thus, the shortest distance from a line to a point can be found by projecting // the line onto it's own normal vector and projecting the point onto the lines // normal vector; the distance between those points is the shortest distance from // the two points. // The projection of a line onto its normal will be a single point, and will be same // for any point on that line. So we pick a point that's convienent (the start or end). var lineProjectedOntoItsNormal = Vector2.Dot(actualLine.Start, actualLine.Normal); var centerOfCircleProjectedOntoNormalOfLine = Vector2.Dot(circleCenter, actualLine.Normal); var closestDistance = Math.Abs(centerOfCircleProjectedOntoNormalOfLine - lineProjectedOntoItsNormal); // Step 1a if (strict) { if (closestDistance >= circle.Radius) { return(false); } } else { if (closestDistance > circle.Radius) { return(false); } } // Step 2 // We're trying to find closestPoint // We can just walk the vector from the center to the closest point, which we know is on // the normal axis and the distance closestDistance. However it's helpful to get the signed // version End - Start to walk. var signedDistanceCircleCenterToLine = lineProjectedOntoItsNormal - centerOfCircleProjectedOntoNormalOfLine; var closestPoint = circleCenter - actualLine.Normal * signedDistanceCircleCenterToLine; // Step 3 // Determine if closestPoint is on the line (including edges) // We're going to accomplish this by projecting the line onto it's own axis and the closestPoint onto the lines // axis. Then we have a 1D comparison. var lineStartProjectedOntoLineAxis = Vector2.Dot(actualLine.Start, actualLine.Axis); var lineEndProjectedOntoLineAxis = Vector2.Dot(actualLine.End, actualLine.Axis); var closestPointProjectedOntoLineAxis = Vector2.Dot(closestPoint, actualLine.Axis); if (AxisAlignedLine2.Contains(lineStartProjectedOntoLineAxis, lineEndProjectedOntoLineAxis, closestPointProjectedOntoLineAxis, false, true)) { return(true); } // Step 4 // We're trying to find edgeClosest. // // We're going to reuse those projections from step 3. // // (for each "point" in the next paragraph I mean "point projected on the lines axis" but that's wordy) // // We know that the start is closest iff EITHER the start is less than the end and the // closest point is less than the start, OR the start is greater than the end and // closest point is greater than the end. var closestEdge = Vector2.Zero; if (lineStartProjectedOntoLineAxis < lineEndProjectedOntoLineAxis) { closestEdge = (closestPointProjectedOntoLineAxis <= lineStartProjectedOntoLineAxis) ? actualLine.Start : actualLine.End; } else { closestEdge = (closestPointProjectedOntoLineAxis >= lineEndProjectedOntoLineAxis) ? actualLine.Start : actualLine.End; } // Step 5 // Circle->Point intersection for closestEdge var distToCircleFromClosestEdgeSq = (circleCenter - closestEdge).LengthSquared(); if (strict) { return(distToCircleFromClosestEdgeSq < (circle.Radius * circle.Radius)); } else { return(distToCircleFromClosestEdgeSq <= (circle.Radius * circle.Radius)); } // If you had trouble following, see the horizontal and vertical cases which are the same process but the projections // are simpler }
/// <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> /// Determines if the specified line contains the specified point. /// </summary> /// <param name="line">The line</param> /// <param name="point">The point</param> /// <param name="strict">If the edges of the line are excluded</param> /// <returns>if line contains point</returns> public static bool Contains(AxisAlignedLine2 line, float point, bool strict) { return(Contains(line.Min, line.Max, point, strict, false)); }
/// <summary> /// Determines the shortest distance for line1 to go to touch line2. Returns /// null if line1 and line 2 intersect (not strictly) /// </summary> /// <returns>The distance.</returns> /// <param name="line1">Line1.</param> /// <param name="line2">Line2.</param> public static float?MinDistance(AxisAlignedLine2 line1, AxisAlignedLine2 line2) { return(MinDistance(line1.Min, line1.Max, line2.Min, line2.Max, false)); }
/// <summary> /// Detrmines the shortest distance from the line to get to point. Returns /// null if the point is on the line (not strict). Always returns a positive value. /// </summary> /// <returns>The distance.</returns> /// <param name="line">Line.</param> /// <param name="point">Point.</param> public static float?MinDistance(AxisAlignedLine2 line, float point) { return(MinDistance(line.Min, line.Max, point, false)); }
/// <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 the minimum translation that must be applied the specified polygon (at the given position /// and rotation) to prevent intersection with the circle (at its given rotation). If the two are not overlapping, /// returns null. /// /// Returns a tuple of the axis to move the polygon in (unit vector) and the distance to move the polygon. /// </summary> /// <param name="poly">The polygon</param> /// <param name="circle">The circle</param> /// <param name="pos1">The origin of the polygon</param> /// <param name="pos2">The top-left of the circles bounding box</param> /// <param name="rot1">The rotation of the polygon</param> /// <returns></returns> public static Tuple <Vector2, float> IntersectMTV(Polygon2 poly, Circle2 circle, Vector2 pos1, Vector2 pos2, Rotation2 rot1) { // We have two situations, either the circle is not strictly intersecting the polygon, or // there exists at least one shortest line that you could push the polygon to prevent // intersection with the circle. // That line will either go from a vertix of the polygon to a point on the edge of the circle, // or it will go from a point on a line of the polygon to the edge of the circle. // If the line comes from a vertix of the polygon, the MTV will be along the line produced // by going from the center of the circle to the vertix, and the distance can be found by // projecting the cirle on that axis and the polygon on that axis and doing 1D overlap. // If the line comes from a point on the edge of the polygon, the MTV will be along the // normal of that line, and the distance can be found by projecting the circle on that axis // and the polygon on that axis and doing 1D overlap. // As with all SAT, if we find any axis that the circle and polygon do not overlap, we've // proven they do not intersect. // The worst case performance is related to 2x the number of vertices of the polygon, the same speed // as for 2 polygons of equal number of vertices. 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 polyProj = Polygon2.ProjectAlongAxis(poly, pos1, rot1, axis); var circleProj = Circle2.ProjectAlongAxis(circle, pos2, axis); var mtv = AxisAlignedLine2.IntersectMTV(polyProj, circleProj); if (!mtv.HasValue) { return(false); } if (Math.Abs(mtv.Value) < Math.Abs(shortestOverlap)) { bestAxis = axis; shortestOverlap = mtv.Value; } } return(true); }; var circleCenter = new Vector2(pos2.X + circle.Radius, pos2.Y + circle.Radius); int last = poly.Vertices.Length - 1; var lastVec = Math2.Rotate(poly.Vertices[last], poly.Center, rot1) + pos1; for (int curr = 0; curr < poly.Vertices.Length; curr++) { var currVec = Math2.Rotate(poly.Vertices[curr], poly.Center, rot1) + pos1; // 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 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)); }