/// <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 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> /// 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 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 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)); } }