Как узнать, пересекает ли линия прямоугольник

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

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

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

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

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


person Daniel Peñalba    schedule 01.04.2011    source источник
comment
Вы говорите о линии или сегменте линии? например если две точки находятся внутри прямоугольника, пересекается ли линия   -  person naught101    schedule 20.10.2015


Ответы (8)


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

person JPE    schedule 13.05.2014
comment
Очень хороший ответ. И да, код Иоганна выглядит намного лучше. Спасибо за объяснение. - person Xtro; 12.05.2015

Алгоритм грубой силы ...

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

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

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

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

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

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

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

  • Если Rect.Top ‹LINEYRECTRIGHT && Rect.Top‹ LINEYRECTLEFT, то пересечения нет.

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

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

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;
}
person Johann Gerell    schedule 01.04.2011
comment
Как сказал JPE в своем ответе, ваш код намного лучше, чем ответ, за который проголосовали. - person Xtro; 12.05.2015
comment
Этот ответ, похоже, относится к отрезку линии, а не к (бесконечной) линии. - person naught101; 20.10.2015
comment
@ naught101 - Это то, о чем просили. - person Johann Gerell; 20.10.2015
comment
В вопросе не упоминается линейный сегмент. Две точки также могут определять реальную линию. Но да, вопрос немного двусмысленный. - person naught101; 20.10.2015
comment
Уловка для этого ответа заключается в том, что, хотя для X мы почти наверняка ожидаем, что X увеличится в ПРАВИЛЬНОМ направлении, для Y это менее уверенно. В некоторых случаях Y увеличивается при движении ВНИЗ, в других случаях Y увеличивается при движении ВВЕРХ, как предполагается в этом ответе. - person Gerard; 24.08.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);
    }
person Wojtpl2    schedule 24.02.2017

Я взял решение 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. Замечу, что сначала я написал свою собственную процедуру, которая проверяла каждую точку по градиенту, и я сделал все, что мог, чтобы максимизировать производительность, но это сразу же было намного быстрее.

person John Bushnell    schedule 23.11.2012
comment
Спасибо ребятам из Objective-C! - person Stanislav Pankevich; 07.12.2013

Не существует простого предопределенного метода .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);
person Thalur    schedule 01.04.2011

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

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

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

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

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

person ceyko    schedule 01.04.2011
comment
Конечно, если вы пересекаете любой сегмент многоугольника, вы должны пересекать многоугольник? Линия, которая пересекает одну сторону прямоугольника, но не достигает другой стороны, по-прежнему пересекает прямоугольник ... - person Dan Puzey; 01.04.2011
comment
Но это строка. Он не может пересечь один сегмент и затем просто остановиться в середине прямоугольника. Вы думаете о сегменте. Однако я полагаю, что мог неправильно истолковать заданный вопрос ... - person ceyko; 01.04.2011
comment
Я думаю, вы описываете алгоритм определения того, содержится ли точка внутри многоугольника, путем вычисления пересечений краев многоугольника с линией от рассматриваемой точки до точки, заведомо находящейся за пределами многоугольника. - person Dave Rager; 01.04.2011
comment
Я определю линию как прямое соединение двух точек. Вы определяете линию классическим геометрическим способом (т.е. бесконечно?). - person Dan Puzey; 01.04.2011
comment
Если это линия без конечных точек, количество пересечений никогда не может быть нечетным. - person Dave Rager; 01.04.2011
comment
@ Дэйв Ик, ты прав. Я просто уберу бит о нечетном / четном - очевидно, если линия пересекает любой сегмент, тогда она должна пересекать многоугольник. @Dan Да, у меня больше математического опыта, поэтому я думаю, что линия == отрезок бесконечной длины. - person ceyko; 01.04.2011
comment
Ваше редактирование еще больше запутывает проблему: теперь вы говорите об использовании (предположительно геометрической) линии в качестве луча (а это не так). Для луча ваш расчет недействителен (вы можете пройти слева направо от луча, не пересекая его, если вы пройдете за исходную точку - расчет верен для линии, если ваша линия не проходит из-за слева направо ...). Я не уверен, что этот ответ помогает решить исходный вопрос! - person Dan Puzey; 01.04.2011
comment
Он по-прежнему остается левым от луча, даже если он фактически не пересекает луч. Я упомянул лучи, чтобы лучше понять, что означает LeftOf; не совсем ясно, говорим ли мы, что что-то осталось от строки. - person ceyko; 01.04.2011
comment
Я думаю, что исходный ответ был лучше, на самом деле: если одна из сторон прямоугольника является подстрокой линии, тогда линия будет пересекаться только с одной стороной прямоугольника и не будет проходить через прямоугольник - например, это касательная к прямоугольнику. - person naught101; 20.10.2015

Для 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;
        }
    }
}
person vincent    schedule 01.02.2018

person    schedule
comment
@Habjan: Спасибо, просто и конкретно. Лучший ответ. Это проверено? - person Daniel Peñalba; 01.04.2011
comment
@ Даниэль Пеньяльба: Я сделал пару тестов :-) - person HABJAN; 01.04.2011
comment
@Daniel, а что, если отрезок линии целиком находится внутри прямоугольника? Считается ли это пересекающимся? - person Dave Rager; 01.04.2011
comment
Я обновил код, добавил (r.Contains (p1) && r.Contains (p2)) в LineIntersectsRect. Теперь это делает @Dave Rager - person HABJAN; 01.04.2011
comment
@HABJAN: Спасибо за обновление. Я думаю, что это условие (r.Contains (p1) || r.Contains (p2)). Если какая-либо точка находится внутри прямоугольника, линия пересекается. - person Daniel Peñalba; 04.04.2011
comment
может кто-нибудь объяснить реализацию, это не совсем понятно ... спасибо - person Marko; 27.11.2013
comment
@Marko: Согласен, особенно непонятно именование переменных. Но простой алгоритм (при условии, что ваш прямоугольник имеет горизонтальные и вертикальные линии): получить значения x вертикалей, проверить значение y линии на каждом x. Затем получите значения y горизонталей. Если оба значения линии y больше верхнего прямоугольника y или оба меньше нижнего прямоугольника y, то ваша линия не пересекается. В противном случае это так. - person naught101; 20.10.2015
comment
что, если линия пересекает одну из угловых точек, в то время как MBR находится по одну сторону от этой линии? Вышеупомянутый метод не может справиться с этим случаем. - person chenzhongpu; 23.07.2019