How to know if a line intersects a rectangle

28,871

Solution 1

    public static bool LineIntersectsRect(Point p1, Point p2, Rectangle r)
    {
        return LineIntersectsLine(p1, p2, new Point(r.X, r.Y), new Point(r.X + r.Width, r.Y)) ||
               LineIntersectsLine(p1, p2, new Point(r.X + r.Width, r.Y), new Point(r.X + r.Width, r.Y + r.Height)) ||
               LineIntersectsLine(p1, p2, new Point(r.X + r.Width, r.Y + r.Height), new Point(r.X, r.Y + r.Height)) ||
               LineIntersectsLine(p1, p2, new Point(r.X, r.Y + r.Height), new Point(r.X, r.Y)) ||
               (r.Contains(p1) && r.Contains(p2));
    }

    private static bool LineIntersectsLine(Point l1p1, Point l1p2, Point l2p1, Point l2p2)
    {
        float q = (l1p1.Y - l2p1.Y) * (l2p2.X - l2p1.X) - (l1p1.X - l2p1.X) * (l2p2.Y - l2p1.Y);
        float d = (l1p2.X - l1p1.X) * (l2p2.Y - l2p1.Y) - (l1p2.Y - l1p1.Y) * (l2p2.X - l2p1.X);

        if( d == 0 )
        {
            return false;
        }

        float r = q / d;

        q = (l1p1.Y - l2p1.Y) * (l1p2.X - l1p1.X) - (l1p1.X - l2p1.X) * (l1p2.Y - l1p1.Y);
        float s = q / d;

        if( r < 0 || r > 1 || s < 0 || s > 1 )
        {
            return false;
        }

        return true;
    }

Solution 2

Unfortunately the wrong answer has been voted up. It is much to expensive to compute the actual intersection points, you only need comparisons. The keyword to look for is "Line Clipping" (http://en.wikipedia.org/wiki/Line_clipping). Wikipedia recommends the Cohen-Sutherland algorithm (http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland) when you want fast rejects, which is probably the most common scenario. There is a C++-implementation on the wikipedia page. If you are not interested in actually clipping the line, you can skip most of it. The answer of @Johann looks very similar to that algorithm, but I didn't look at it in detail.

Solution 3

Brute force algorithm...

First check if the rect is to the left or right of the line endpoints:

  • Establish the leftmost and rightmost X values of the line endpoints: XMIN and XMAX
  • If Rect.Left > XMAX, then no intersection.
  • If Rect.Right < XMIN, then no intersection.

Then, if the above wasn't enough to rule out intersection, check if the rect is above or below the line endpoints:

  • Establish the topmost and bottommost Y values of the line endpoints: YMAX and YMIN
  • If Rect.Bottom > YMAX, then no intersection.
  • If Rect.Top < YMIN, then no intersection.

Then, if the above wasn't enough to rule out intersection, you need to check the equation of the line, y = m * x + b, to see if the rect is above the line:

  • Establish the line's Y-value at Rect.Left and Rect.Right: LINEYRECTLEFT and LINEYRECTRIGHT
  • If Rect.Bottom > LINEYRECTRIGHT && Rect.Bottom > LINEYRECTLEFT, then no intersection.

Then, if the above wasn't enough to rule out intersection, you need to check if the rect is below the line:

  • If Rect.Top < LINEYRECTRIGHT && Rect.Top < LINEYRECTLEFT, then no intersection.

Then, if you get here:

  • Intersection.

N.B. I'm sure there's a more elegant algebraic solution, but performing these steps geometrically with pen and paper is easy to follow.

Some untested and uncompiled code to go with that:

public struct Line
{
    public int XMin { get { ... } }
    public int XMax { get { ... } }

    public int YMin { get { ... } }
    public int YMax { get { ... } }

    public Line(Point a, Point b) { ... }

    public float CalculateYForX(int x) { ... }
}

public bool Intersects(Point a, Point b, Rectangle r)
{
    var line = new Line(a, b);

    if (r.Left > line.XMax || r.Right < line.XMin)
    {
        return false;
    }

    if (r.Top < line.YMin || r.Bottom > line.YMax)
    {
        return false;
    }

    var yAtRectLeft = line.CalculateYForX(r.Left);
    var yAtRectRight = line.CalculateYForX(r.Right);

    if (r.Bottom > yAtRectLeft && r.Bottom > yAtRectRight)
    {
        return false;
    }

    if (r.Top < yAtRectLeft && r.Top < yAtRectRight)
    {
        return false;
    }

    return true;
}

Solution 4

This code has better performance:

    public static bool SegmentIntersectRectangle(
        double rectangleMinX,
        double rectangleMinY,
        double rectangleMaxX,
        double rectangleMaxY,
        double p1X,
        double p1Y,
        double p2X,
        double p2Y)
    {
        // Find min and max X for the segment
        double minX = p1X;
        double maxX = p2X;

        if (p1X > p2X)
        {
            minX = p2X;
            maxX = p1X;
        }

        // Find the intersection of the segment's and rectangle's x-projections
        if (maxX > rectangleMaxX)
        {
            maxX = rectangleMaxX;
        }

        if (minX < rectangleMinX)
        {
            minX = rectangleMinX;
        }

        if (minX > maxX) // If their projections do not intersect return false
        {
            return false;
        }

        // Find corresponding min and max Y for min and max X we found before
        double minY = p1Y;
        double maxY = p2Y;

        double dx = p2X - p1X;

        if (Math.Abs(dx) > 0.0000001)
        {
            double a = (p2Y - p1Y)/dx;
            double b = p1Y - a*p1X;
            minY = a*minX + b;
            maxY = a*maxX + b;
        }

        if (minY > maxY)
        {
            double tmp = maxY;
            maxY = minY;
            minY = tmp;
        }

        // Find the intersection of the segment's and rectangle's y-projections
        if (maxY > rectangleMaxY)
        {
            maxY = rectangleMaxY;
        }

        if (minY < rectangleMinY)
        {
            minY = rectangleMinY;
        }

        if (minY > maxY) // If Y-projections do not intersect return false
        {
            return false;
        }

        return true;
    }

You can also check how it's work in JS demo: http://jsfiddle.net/77eej/2/

If you have two Points and Rect you can call this function like that:

    public static bool LineIntersectsRect(Point p1, Point p2, Rect r)
    {
        return SegmentIntersectRectangle(r.X, r.Y, r.X + r.Width, r.Y + r.Height, p1.X, p1.Y, p2.X, p2.Y);
    }

Solution 5

I took HABJAN's solution, which worked well, and converted it to Objective-C. The Objective-C code is as follows:

bool LineIntersectsLine(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1, CGPoint l2p2)
{
    CGFloat q = (l1p1.y - l2p1.y) * (l2p2.x - l2p1.x) - (l1p1.x - l2p1.x) * (l2p2.y - l2p1.y);
    CGFloat d = (l1p2.x - l1p1.x) * (l2p2.y - l2p1.y) - (l1p2.y - l1p1.y) * (l2p2.x - l2p1.x);

    if( d == 0 )
    {
        return false;
    }

    float r = q / d;

    q = (l1p1.y - l2p1.y) * (l1p2.x - l1p1.x) - (l1p1.x - l2p1.x) * (l1p2.y - l1p1.y);
    float s = q / d;

    if( r < 0 || r > 1 || s < 0 || s > 1 )
    {
        return false;
    }

    return true;
}

bool LineIntersectsRect(CGPoint p1, CGPoint p2, CGRect r)
{
    return LineIntersectsLine(p1, p2, CGPointMake(r.origin.x, r.origin.y), CGPointMake(r.origin.x + r.size.width, r.origin.y)) ||
    LineIntersectsLine(p1, p2, CGPointMake(r.origin.x + r.size.width, r.origin.y), CGPointMake(r.origin.x + r.size.width, r.origin.y + r.size.height)) ||
    LineIntersectsLine(p1, p2, CGPointMake(r.origin.x + r.size.width, r.origin.y + r.size.height), CGPointMake(r.origin.x, r.origin.y + r.size.height)) ||
    LineIntersectsLine(p1, p2, CGPointMake(r.origin.x, r.origin.y + r.size.height), CGPointMake(r.origin.x, r.origin.y)) ||
    (CGRectContainsPoint(r, p1) && CGRectContainsPoint(r, p2));
}

Many thanks HABJAN. I will note that at first I wrote my own routine which checked each point along the gradient, and I did everything I could do to maximize performance, but this was immediately far faster.

Share:
28,871
Daniel Peñalba
Author by

Daniel Peñalba

Software Engineer at Unity Technologies, Valladolid, Spain. Currently developing gmaster, Plastic SCM and SemanticMerge. Areas: C# GUI development Winforms and WPF expert ASP .NET Core Multiplatform UI development with Mono (Linux and OSX, GTK# and MonoMac) Eclipse plugin, Java Automated testing, NUnit, Moq, PNUnit and TestComplete Email: dpenalba[AT]codicesoftware[DOT]com I play the guitar at Sharon Bates, the greatest Spanish rock band.

Updated on July 09, 2022

Comments

  • Daniel Peñalba
    Daniel Peñalba almost 2 years

    I have checked out this question, but the answer is very large for me:

    How to know if a line intersects a plane in C#? - Basic 2D geometry

    Is there any .NET method to know if a line defined by two points intersects a rectangle?

    public bool Intersects(Point a, Point b, Rectangle r)
    {
       // return true if the line intersects the rectangle
       // false otherwise
    }
    

    Thanks in advance.

  • Dan Puzey
    Dan Puzey about 13 years
    Surely if you intersect any segment of the polygon then you must intersect the polygon? A line that crosses one side of the rectangle but does not reach the other side still intersects the rectangle...
  • ceyko
    ceyko about 13 years
    But it is a line. It is impossible for it to cross one segment and then just stop in the middle of a rectangle. You're thinking of a segment. I suppose I could have misinterpreted what the question asked, however...
  • Dave Rager
    Dave Rager about 13 years
    I think you are describing the algorithm for determining if a point is contained within a polygon by calculating the intersections of the edges of a polygon with a line from the point in question to a point known to be outside the polygon.
  • Dan Puzey
    Dan Puzey about 13 years
    I woudl define a line as the straight connection of two points. Are you defining a line in the classical geometric way (i.e. indefinitely?).
  • Dave Rager
    Dave Rager about 13 years
    If it's a line with no endpoints the number of intersections can never be odd.
  • ceyko
    ceyko about 13 years
    @Dave Eek, you're right. I'll just remove the bit about odd/even - clearly if the line intersects any segment then it must intersect the polygon. @Dan Yes, I have more of a math background, so I think line == infinite length segment.
  • Dan Puzey
    Dan Puzey about 13 years
    Your edit further confuses the issue: now you're talking about using a (presumably geometric) line as a ray (which it is not). For a ray, your calculation is invalid (you can pass from left to right of a ray without crossing it if you pass "behind" the origin - the calculation is correct for a line, assuming your line isn't running due left-to-right...). I'm not sure this answer is helping to solve the original question!
  • Daniel Peñalba
    Daniel Peñalba about 13 years
    @Habjan: Thanks, simple, and concrete. The best answer. Is it tested?
  • ceyko
    ceyko about 13 years
    It is still LeftOf the ray, even if it doesn't actually cross the ray. I mentioned rays to give a better definition of what LeftOf meant; it's not exactly clear if we say something is left of a line.
  • HABJAN
    HABJAN about 13 years
    @Daniel Peñalba: I did couple of tests :-)
  • Dave Rager
    Dave Rager about 13 years
    @Daniel, What if the line segment is contained entirely within the rectangle? Is it considered "intersecting"?
  • HABJAN
    HABJAN about 13 years
    I updated the code, added (r.Contains(p1) && r.Contains(p2)) in LineIntersectsRect. Now it does @Dave Rager
  • Daniel Peñalba
    Daniel Peñalba about 13 years
    @HABJAN: Thanks for the update. I think that condition is (r.Contains(p1) || r.Contains(p2)). If any point is inside the rectangle, the line intersects.
  • Marko
    Marko over 10 years
    can someone explain implementation, it is not totally clear...thanks
  • Stanislav Pankevich
    Stanislav Pankevich over 10 years
    Thanks from Objective-C guys!
  • Xtro
    Xtro about 9 years
    Very good answer. And yes Johann's code look much more better. Thank you for explanation.
  • Xtro
    Xtro about 9 years
    As JPE mentioned in his answer, your code is much better than the upvoted answer.
  • naught101
    naught101 over 8 years
    @Marko: I agree, the variable naming especially makes it unclear. But a simple algorithm (assuming your rectangle has horizontal and vertical lines) is: get x values of verticals, check the y value of the line at each x. Then get the y values of the horizontals. If both line y values are greater than the top rectangle y, or both are less than the bottom rectangle y, then your line doesn't intersect. Otherwise, it does.
  • naught101
    naught101 over 8 years
    This answer seems to be for a line segment though, not an (infinite) line.
  • naught101
    naught101 over 8 years
    I think the original answer was better, actually: if one of the sides of the rectangle is a sub-line of the line, then the line will only intersect with one side of the rectangle, and will not pass through the rectangle - e.g. it's a tangent of the rectangle.
  • Johann Gerell
    Johann Gerell over 8 years
    @naught101 - Which is what was asked for.
  • naught101
    naught101 over 8 years
    The question doesn't mention line segment. Two points can also define an actual line. But yeah, the question is a bit ambiguous.
  • Gerard
    Gerard almost 7 years
    A gotcha for this answer is that, although for X we almost certainly expect X to increase going RIGHT, for Y this is less sure. In some cases Y increases going DOWNWARDS, in other situations Y increases going UPWARDS like it is assumed in this answer.
  • chenzhongpu
    chenzhongpu almost 5 years
    what if the line intersects one of the corner points while this MBR lies in one side of this line? The method above cannot handle this case.