How to know if a line intersects a rectangle

后端 未结 8 544
梦谈多话
梦谈多话 2020-12-01 14:51

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

相关标签:
8条回答
  • 2020-12-01 15:02

    The simplest computational geometry technique is to just walk through the segments of the polygon and see if it intersects with any of them, as it then must also intersect the polygon.

    The only caveat of this method (and most of CG) is that we have to be careful about edge cases. What if the line crosses the rectangle at a point - do we count that as intersection or not? Be careful in your implementation.

    Edit: The typical tool for the line-intersects-segment calculation is a LeftOf(Ray, Point) test, which returns if the point is the to the left of the ray. Given a line l (which we use as a ray) and a segment containing points a and b, the line intersects the segment if one point is to the left and one point is not:

    (LeftOf(l,a) && !LeftOf(l,b)) || (LeftOf(l,b) && !LeftOf(l,a))
    

    Again, you need to watch out for edge-cases, when the point is on the line, but depends how you wish to actually define intersection.

    0 讨论(0)
  • 2020-12-01 15:11

    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);
        }
    
    0 讨论(0)
  • 2020-12-01 15:11

    For Unity (inverts y!). This takes care of division by zero problem that other approaches here have:

    using System;
    using UnityEngine;
    
    namespace Util {
        public static class Math2D {
    
            public static bool Intersects(Vector2 a, Vector2 b, Rect r) {
                var minX = Math.Min(a.x, b.x);
                var maxX = Math.Max(a.x, b.x);
                var minY = Math.Min(a.y, b.y);
                var maxY = Math.Max(a.y, b.y);
    
                if (r.xMin > maxX || r.xMax < minX) {
                    return false;
                }
    
                if (r.yMin > maxY || r.yMax < minY) {
                    return false;
                }
    
                if (r.xMin < minX && maxX < r.xMax) {
                    return true;
                }
    
                if (r.yMin < minY && maxY < r.yMax) {
                    return true;
                }
    
                Func<float, float> yForX = x => a.y - (x - a.x) * ((a.y - b.y) / (b.x - a.x));
    
                var yAtRectLeft = yForX(r.xMin);
                var yAtRectRight = yForX(r.xMax);
    
                if (r.yMax < yAtRectLeft && r.yMax < yAtRectRight) {
                    return false;
                }
    
                if (r.yMin > yAtRectLeft && r.yMin > yAtRectRight) {
                    return false;
                }
    
                return true;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 15:16

    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;
    }
    
    0 讨论(0)
  • 2020-12-01 15:20
        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;
        }
    
    0 讨论(0)
  • 2020-12-01 15:20

    There is no simple predefined .NET method you can call to accomplish that. However, using the Win32 API, there is a pretty easy way to do this (easy in the sense of implementation, performance is not the strong point): LineDDA

    BOOL LineDDA(int nXStart,int nYStart,int nXEnd,int nYEnd,LINEDDAPROC lpLineFunc,LPARAM lpData)
    

    This functions calls the callback function for every pixel of the line to be drawn. In this function, you can check if the pixel is within your rectangle - if you find one, then it intersects.

    As I sais, this is not the fastest solution, but pretty easy to implement. To use it in C#, you will of course need to ddlimport it from gdi32.dll.

    [DllImport("gdi32.dll")] public static extern int LineDDA(int n1,int n2,int n3,int n4,int lpLineDDAProc,int lParam);
    
    0 讨论(0)
提交回复
热议问题