/// <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 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 the specified circle, at the given position, intersects the specified polygon, /// at the given position and rotation. /// </summary> /// <param name="circle">The circle</param> /// <param name="poly">The polygon</param> /// <param name="pos1">The top-left of the circles bounding box</param> /// <param name="pos2">The origin of the polygon</param> /// <param name="rot2">The rotation of the polygon</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If circle at pos1 intersects poly at pos2 with rotation rot2</returns> public static bool Intersects(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2, bool strict) { return(Intersects(poly, circle, pos2, pos1, rot2, strict)); }
/// <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> /// Determines if the specified polygon at the specified position and rotation /// intersects the specified circle at it's respective position. /// </summary> /// <param name="poly">The polygon</param> /// <param name="circle">The circle</param> /// <param name="pos1">The origin for the polygon</param> /// <param name="pos2">The top-left of the circles bounding box</param> /// <param name="rot1">The rotation of the polygon</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If poly at pos1 with rotation rot1 intersects the circle at pos2</returns> public static bool Intersects(Polygon2 poly, Circle2 circle, Vector2 pos1, Vector2 pos2, Rotation2 rot1, bool strict) { // look at pictures of https://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection if you don't // believe this is true return(poly.Lines.Any((l) => CircleIntersectsLine(circle, l, pos2, pos1, rot1, poly.Center, strict)) || Polygon2.Contains(poly, pos1, rot1, new Vector2(pos2.X + circle.Radius, pos2.Y + circle.Radius), strict)); }
/// <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> /// Projects the specified circle with the upper-left at the specified position onto /// the specified axis. /// </summary> /// <param name="circle">The circle</param> /// <param name="pos">The position of the circle</param> /// <param name="axis">the axis to project along</param> /// <returns>Projects circle at pos along axis</returns> public static AxisAlignedLine2 ProjectAlongAxis(Circle2 circle, Vector2 pos, Vector2 axis) { return(ProjectAlongAxis(circle.Radius, pos, axis)); }
/// <summary> /// Determines the shortest axis and overlap for which the first circle at the specified position /// overlaps the second circle at the specified position. If the circles do not overlap, returns null. /// </summary> /// <param name="circle1">First circle</param> /// <param name="circle2">Second circle</param> /// <param name="pos1">Top-left of the first circles bounding box</param> /// <param name="pos2">Top-left of the second circles bounding box</param> /// <returns></returns> public static Tuple <Vector2, float> IntersectMTV(Circle2 circle1, Circle2 circle2, Vector2 pos1, Vector2 pos2) { return(IntersectMTV(circle1.Radius, circle2.Radius, pos1, pos2)); }
/// <summary> /// Determines if the first circle at the specified position intersects the second circle /// at the specified position. /// </summary> /// <param name="circle1">First circle</param> /// <param name="circle2">Second circle</param> /// <param name="pos1">Top-left of the bounding box of the first circle</param> /// <param name="pos2">Top-left of the bounding box of the second circle</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If circle1 at pos1 intersects circle2 at pos2</returns> public static bool Intersects(Circle2 circle1, Circle2 circle2, Vector2 pos1, Vector2 pos2, bool strict) { return(Intersects(circle1.Radius, circle2.Radius, pos1, pos2, strict)); }
/// <summary> /// Determines the minimum translation vector to be applied to the circle to prevent /// intersection with the specified polyogn, when they are at the given positions. /// </summary> /// <param name="circle">The circle</param> /// <param name="poly">The polygon</param> /// <param name="pos1">The top-left of the circles bounding box</param> /// <param name="pos2">The origin of the polygon</param> /// <returns></returns> public static Tuple <Vector2, float> IntersectMTV(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2) { return(IntersectMTV(circle, poly, pos1, pos2, Rotation2.Zero)); }
/// <summary> /// Determines if the circle and polygon intersect when at the given positions. /// </summary> /// <param name="circle">The circle</param> /// <param name="poly">The polygon</param> /// <param name="pos1">The top-left of the circles bounding box</param> /// <param name="pos2">The origin of the polygon</param> /// <param name="strict">If overlap is required for intersection</param> /// <returns>If circle at pos1 intersects poly at pos2</returns> public static bool Intersects(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2, bool strict) { return(Intersects(circle, poly, pos1, pos2, Rotation2.Zero, strict)); }