Как узнать, пересекает ли линия прямоугольник - PullRequest
14 голосов
/ 01 апреля 2011

Я проверил этот вопрос, но ответ для меня очень большой:

Как узнать, пересекает ли линия плоскость в C #? - Базовая 2D геометрия

Существует ли какой-либо метод .NET, чтобы узнать, пересекает ли прямоугольник прямая, определяемая двумя точками?

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

Заранее спасибо.

Ответы [ 8 ]

26 голосов
/ 01 апреля 2011
    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;
    }
13 голосов
/ 14 мая 2014

К сожалению, за неправильный ответ проголосовали. Вычисление фактических точек пересечения обходится слишком дорого, вам нужны только сравнения. Ключевое слово, которое нужно искать, это «Line Clipping» (http://en.wikipedia.org/wiki/Line_clipping). Википедия рекомендует алгоритм Коэна-Сазерленда (http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland), если вы хотите быстро отказаться, что, вероятно, является наиболее распространенным сценарием. Существует C ++ реализация на странице википедии. Если вы не заинтересованы в том, чтобы на самом деле обрезать строку, вы можете пропустить большую ее часть. Ответ @Johann выглядит очень похоже на этот алгоритм, но я не рассматривал его подробно.

6 голосов
/ 01 апреля 2011

Алгоритм перебора ...

Сначала проверьте, находится ли прямоугольник слева или справа от конечных точек линии:

  • Установить крайние левые и крайние правые значения X конечных точек линии: XMIN и XMAX
  • Если Rect.Left> XMAX, то пересечения нет.
  • Если Rect.Right

Затем, если вышеприведенного недостаточно для исключения пересечения, проверьте, находится ли прямоугольник выше или ниже конечных точек линии:

  • Установить самые верхние и самые нижние значения Y конечных точек линии: YMAX и YMIN
  • Если Rect.Bottom> YMAX, то пересечения нет.
  • Если Rect.Top

Затем, если вышеприведенного недостаточно для исключения пересечения, вам нужно проверить уравнение прямой, y = m * x + b, чтобы увидеть, находится ли прямоугольник над линией:

  • Установить значение Y линии в Rect.Left и Rect.Right: LINEYRECTLEFT и LINEYRECTRIGHT
  • Если Rect.Bottom> LINEYRECTRIGHT && Rect.Bottom> LINEYRECTLEFT, то пересечения нет.

Затем, если вышеприведенного недостаточно для исключения пересечения, вам нужно проверить, находится ли прямоугольник ниже линии:

  • Если Rect.Top

Тогда, если вы попали сюда:

  • Пересечение.

N.B. Я уверен, что есть более элегантное алгебраическое решение, но выполнить эти шаги геометрически с ручкой и бумагой легко.

Несколько непроверенных и не скомпилированных кодов, которые можно использовать для этого:

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;
}
4 голосов
/ 24 февраля 2017

Этот код имеет лучшую производительность:

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

Вы также можете проверить, как это работает в демоверсии JS: http://jsfiddle.net/77eej/2/

Если у вас есть две точки и Rect, вы можете вызвать эту функцию следующим образом:

    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);
    }
3 голосов
/ 23 ноября 2012

Я взял решение HABJAN, которое работало хорошо, и преобразовал его в Objective-C. Код Objective-C выглядит следующим образом:

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

Большое спасибо HABJAN. Я отмечу, что сначала я написал свою собственную процедуру, которая проверяла каждую точку вдоль градиента, и я делал все, что мог, чтобы максимизировать производительность, но это было сразу намного быстрее.

0 голосов
/ 01 февраля 2018

Для Unity (инвертирует y!). Это решает проблему деления на ноль, которую имеют другие подходы:

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 голосов
/ 01 апреля 2011

Самый простой метод вычислительной геометрии состоит в том, чтобы просто пройти через сегменты многоугольника и посмотреть, пересекается ли он с каким-либо из них, поскольку тогда он также должен пересекать многоугольник.

Единственное предостережение этого метода (и большей части компьютерной графики) в том, что мы должны быть осторожны с крайними случаями. Что если линия пересекает прямоугольник в точке - мы считаем это пересечением или нет? Будьте осторожны в своей реализации.

Редактировать : Типичным инструментом для вычисления отрезка прямой является отрезок LeftOf(Ray, Point), который возвращается, если точка находится слева от луча. Для данной линии l (которую мы используем как луч) и отрезка, содержащего точки a и b, линия пересекает отрезок, если одна точка находится слева, а одна точка - нет:

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

Опять же, вам нужно следить за краевыми случаями, когда точка находится на прямой, но зависит от того, как вы хотите определить пересечение.

0 голосов
/ 01 апреля 2011

Нет простого предопределенного метода .NET, который можно вызвать для этого. Однако, используя Win32 API, есть довольно простой способ сделать это (простой в смысле реализации, производительность не является сильной стороной): LineDDA

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

Эта функция вызывает функцию обратного вызова для каждого пикселя рисуемой линии. В этой функции вы можете проверить, находится ли пиксель внутри вашего прямоугольника - если вы его найдете, он пересекается.

Как я уже сказал, это не самое быстрое решение, но довольно простое в реализации. Чтобы использовать его в C #, вам, конечно, нужно будет ddlimport из gdi32.dll.

[DllImport("gdi32.dll")] public static extern int LineDDA(int n1,int n2,int n3,int n4,int lpLineDDAProc,int lParam);
...