Как узнать, пересекает ли линия прямоугольник
Я проверил этот вопрос, но для меня очень большой ответ:
Как узнать, пересекает ли линия плоскость в С#? - Основная 2D-геометрия
Есть ли какой-либо метод .NET, чтобы узнать, пересекает ли строка, определенную двумя точками, прямоугольник?
public bool Intersects(Point a, Point b, Rectangle r)
{
// return true if the line intersects the rectangle
// false otherwise
}
Спасибо заранее.
Ответы
Ответ 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;
}
Ответ 2
К сожалению, неправильный ответ был проголосован. Очень дорого вычислить фактические точки пересечения, вам нужны только сравнения. Ключевое слово для поиска - "Обрезка линии" (http://en.wikipedia.org/wiki/Line_clipping). Википедия рекомендует алгоритм Коэна-Сазерленда (http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland), когда вам нужны быстрые отклонения, что, вероятно, является наиболее распространенным сценарием. На странице wikipedia есть реализация С++. Если вас не интересует фактическое обрезание линии, вы можете пропустить большую часть ее.
Ответ @Johann очень похож на этот алгоритм, но я не рассматривал его подробно.
Ответ 3
Алгоритм грубой силы...
Сначала проверьте, находится ли прямоугольник слева или справа от конечных точек линии:
- Установите самые левые и самые правые значения 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;
}
Ответ 4
Я принял решение 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. Отмечу, что сначала я написал свою собственную процедуру, которая проверила каждую точку вдоль градиента, и я сделал все, что мог, чтобы максимизировать производительность, но это было сразу намного быстрее.
Ответ 5
Этот код имеет лучшую производительность:
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 and rectangle 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 and rectangle 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);
}
Ответ 6
Нет простого предопределенного метода .NET, который вы можете вызвать для этого. Однако, используя Win32 API, есть довольно простой способ сделать это (легко в смысле реализации, производительность не является сильной стороной): LineDDA
BOOL LineDDA(int nXStart,int nYStart,int nXEnd,int nYEnd,LINEDDAPROC lpLineFunc,LPARAM lpData)
Эти функции вызывают функцию обратного вызова для каждого пикселя линии, которую нужно нарисовать. В этой функции вы можете проверить, находится ли пиксель в вашем прямоугольнике - если вы его найдете, то он пересечет.
Как я понял, это не самое быстрое решение, но довольно легко реализовать. Чтобы использовать его в С#, вам, конечно же, нужно будет ddlimport его из gdi32.dll.
[DllImport("gdi32.dll")] public static extern int LineDDA(int n1,int n2,int n3,int n4,int lpLineDDAProc,int lParam);
Ответ 7
Простейший метод вычислительной геометрии состоит в том, чтобы просто пройти через отрезки многоугольника и посмотреть, пересекается ли он с любым из них, так как тогда он также должен пересекать многоугольник.
Единственное предостережение этого метода (и большая часть CG) заключается в том, что мы должны быть осторожны с случаями краев. Что, если линия пересекает прямоугольник в точке - считаем ли мы это пересечением или нет? Будьте осторожны в своей реализации.
Изменить. Типичным инструментом для вычисления сегментов линии является тест LeftOf(Ray, Point)
, который возвращает, если точка находится слева от луча. Для линии l
(которую мы используем как луч) и сегмента, содержащего точки a
и b
, линия пересекает отрезок, если одна точка находится влево, а одна точка не является:
(LeftOf(l,a) && !LeftOf(l,b)) || (LeftOf(l,b) && !LeftOf(l,a))
Опять же, вам нужно следить за краевыми случаями, когда точка находится на линии, но зависит от того, как вы хотите определить пересечение.
Ответ 8
Для 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;
}
}
}