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

84

Я хочу узнать, лежит ли точка внутри прямоугольника или нет. Прямоугольник можно ориентировать любым способом, и его не требуется выравнивать по оси.

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

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

avd
источник
Вы можете быстро проверить, попала ли точка в ортогональную ограничивающую рамку повернутого прямоугольника, и быстро потерпеть неудачу, если нет. (Да, это только половина ответа (хммм, есть три ортогональных блока, которые могут быть образованы угловыми точками ... и уже поздно (концептуальная геометрия - первая))).
msw
Но сначала нужно повернуть, верно?
avd
1
Пока вы не расскажете нам, как определяется ваш прямоугольник, ответы не будут иметь большого практического значения. Когда вы работаете с целочисленными координатами, метод, используемый для представления формы, имеет решающее значение при выборе алгоритма.
AnT

Ответы:

81

Как изображен прямоугольник? Три очка? Четыре очка? Точка, стороны и угол? Два очка и сторона? Что-то другое? Не зная этого, любые попытки ответить на ваш вопрос будут иметь чисто академическое значение.

В любом случае для любого выпуклого многоугольника (включая прямоугольник) проверка очень проста: проверьте каждое ребро многоугольника, предполагая, что каждое ребро ориентировано против часовой стрелки, и проверьте, лежит ли точка слева. от края (слева). -ручная полуплоскость). Если все ребра проходят проверку - точка внутри. Если хоть один выйдет из строя - дело во внешнем.

Чтобы проверить, (xp, yp)лежит ли точка слева от края (x1, y1) - (x2, y2), вам просто нужно вычислить

D = (x2 - x1) * (yp - y1) - (xp - x1) * (y2 - y1)

Если D > 0, точка находится слева. Если D < 0, точка находится справа. Если D = 0, то точка находится на линии.


Предыдущая версия этого ответа описывала, казалось бы, другую версию левого теста (см. Ниже). Но легко показать, что он вычисляет то же значение.

... Чтобы проверить, (xp, yp)лежит ли точка на левой стороне кромки (x1, y1) - (x2, y2), необходимо построить линейное уравнение для линии, содержащей кромку. Уравнение выглядит следующим образом

A * x + B * y + C = 0

где

A = -(y2 - y1)
B = x2 - x1
C = -(A * x1 + B * y1)

Теперь все, что вам нужно сделать, это вычислить

D = A * xp + B * yp + C

Если D > 0, точка находится слева. Если D < 0, точка находится справа. Если D = 0, то точка находится на линии.

Однако этот тест, опять же, работает для любого выпуклого многоугольника, а это означает, что он может быть слишком общим для прямоугольника. Прямоугольник может позволить более простой тест ... Например, в прямоугольнике (или в любом другом параллелограмме) значения Aи Bимеют одинаковую величину, но разные знаки для противоположных (т. Е. Параллельных) краев, что может быть использовано для упрощения теста .

Муравей
источник
Это верно только для набора координат математика. На экране ПК и для GPS направления оси различаются, и вы не можете быть уверены, что у вас правильный набор неравенств. Или можете быть уверены, что нет. Мой ответ лучше :-).
Гангнус
AndreyT @Gangnus, быстрая точность, вам нужно только проверить, что знак уравнения одинаковый для всех точек выпуклой формы по отношению к P, это позволяет вам не беспокоиться о системах координат или о том, в каком направлении находится ваша выпуклая форма. определено;))
Джейсон Роджерс
2
Есть несколько расширений, которые позволяют ускорить работу. 1. Поскольку две противоположные стороны прямоугольника параллельны, их коэффициенты A, B могут быть одинаковыми. 2. Так как две другие стороны перпендикулярны им, их коэффициенты A'и B'могут быть заданы как A'=Bи B'=-A. 3. Нет смысла вычислять A xp + B ypоба ребра, поэтому объедините их в один тест. Тогда ваш тест на нахождение в прямоугольнике станет (v_min < A xp + B yp < v_max) && ( w_min < B xp - A yp < w_max ).
Майкл Андерсон,
@MichaelAnderson А что такое v_minи т. Д.?
Anonymous
v_min- минимальное значение A x + B yдля всех значений внутри прямоугольника (которое является минимальным значением при оценке по углам). v_max- соответствующий максимум. Эти w_?значения одинаковы, но Bx - A y.
Майкл Андерсон
43

Предполагая, что прямоугольник представлен тремя точками A, B, C с перпендикулярами AB и BC, вам нужно только проверить проекции точки запроса M на AB и BC:

0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)

ABэто вектор AB, с координатами (Bx-Ax, Ay По-), и dot(U,V)является скалярным произведением векторов U и V: Ux*Vx+Uy*Vy.

Обновить . Давайте рассмотрим пример, чтобы проиллюстрировать это: A (5,0) B (0,2) C (1,5) и D (6,3). Из координат точки получаем AB = (- 5,2), BC = (1,3), точка (AB, AB) = 29, точка (BC, BC) = 10.

Для точки запроса M (4,2) у нас AM = (- 1,2), BM = (4,0), точка (AB, AM) = 9, точка (BC, BM) = 4. М находится внутри прямоугольника.

Для точки запроса P (6,1) имеем AP = (1,1), BP = (6, -1), точка (AB, AP) = - 3, точка (BC, BP) = 3. P не находится внутри прямоугольника, потому что его проекция на сторону AB не находится внутри сегмента AB.

Эрик Бейнвилл
источник
1
0,2 - 10,2 - 10,10 - 2,10 не является прямоугольником.
Эрик Бейнвилл
2
Пожалуйста, нанесите точки и подумайте о том, чтобы проверить точность вашего первого комментария.
Эрик Бейнвилл,
3
Это лучший ответ!
Tahlil
1
Мне нравится, что этот ответ краток, сохраняет количество операций более или менее таким же низким, как и другие хорошие ответы здесь, но также имеет то преимущество, что он очень интуитивно понятен и нагляден.
Anonymous
22

Я позаимствовал из ответа Эрика Бейнвилля:

0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(BC,BM) <= dot(BC,BC)

Что в javascript выглядит так:

function pointInRectangle(m, r) {
    var AB = vector(r.A, r.B);
    var AM = vector(r.A, m);
    var BC = vector(r.B, r.C);
    var BM = vector(r.B, m);
    var dotABAM = dot(AB, AM);
    var dotABAB = dot(AB, AB);
    var dotBCBM = dot(BC, BM);
    var dotBCBC = dot(BC, BC);
    return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}

function vector(p1, p2) {
    return {
            x: (p2.x - p1.x),
            y: (p2.y - p1.y)
    };
}

function dot(u, v) {
    return u.x * v.x + u.y * v.y; 
}

например:

var r = {
    A: {x: 50, y: 0},
    B: {x: 0, y: 20},
    C: {x: 10, y: 50},
    D: {x: 60, y: 30}
};

var m = {x: 40, y: 20};

тогда:

pointInRectangle(m, r); // returns true.

Вот код для вывода результатов в качестве визуального теста :) http://codepen.io/mattburns/pen/jrrprN

введите описание изображения здесь

Мэтт Бернс
источник
Привет, @matt Burns. Я использовал ваш метод и поместил его в свой тестовый проект: jsfiddle.net/caymanbruce/06wjp2sk/6 Но я не смог заставить его работать. Понятия не имею, почему он все еще тестирует точку в исходном прямоугольнике без поворота. Я использую mouseoverсобытие в своем проекте, поэтому всякий раз, когда указатель мыши находится над точкой, которая должна находиться внутри прямоугольника, вокруг мыши будет отображаться черная круглая точка, а за пределами прямоугольника ничего не должно отображаться. Мне нужна помощь, чтобы заставить его работать, но я очень запутался.
newguy 09
mouseoverдолжно быть mousemove, просто опечатка.
newguy 09
Ссылка
Zmart
В принципе, ваш метод правильный, но ваш прямоугольник не является прямоугольником в вашем примере. Я сделал вашу улучшенную версию здесь, где я придерживаюсь исходной формулы и схемы именования, и где ввод фактически представляет собой настоящий прямоугольник.
JohannesB
15
# Pseudo code
# Corners in ax,ay,bx,by,dx,dy
# Point in x, y

bax = bx - ax
bay = by - ay
dax = dx - ax
day = dy - ay

if ((x - ax) * bax + (y - ay) * bay < 0.0) return false
if ((x - bx) * bax + (y - by) * bay > 0.0) return false
if ((x - ax) * dax + (y - ay) * day < 0.0) return false
if ((x - dx) * dax + (y - dy) * day > 0.0) return false

return true
Йонас Эльфстрём
источник
Прочтите это как: «если мы соединим точку с тремя вершинами прямоугольника, тогда углы между этими сегментами и сторонами должны быть острыми»
Швед Швед
3
Проблема с подобными подходами в том, что они работают теоретически, но в некоторых случаях можно столкнуться с проблемами. OP не сказал, как представлен прямоугольник. Этот ответ предполагает, что он представлен тремя точками - a, bи d. В то время как три точки являются допустимым способом представления произвольного прямоугольника в теории, на практике это невозможно сделать точно в целочисленных координатах в общем случае. В общем случае получается параллелограмм, который очень близок к прямоугольнику, но все же не является прямоугольником.
AnT
3
Т.е. углы в этой форме не будут ровно 90 град. При проведении любых угловых тестов в такой ситуации нужно быть очень осторожным. Опять же, это зависит от того, как OP определяет «внутри» для неточно представленного «прямоугольника». И снова о том, как изображен прямоугольник.
AnT
+1 к обоим вашим комментариям. Только @avd может сказать нам, достаточно ли этого.
Йонас Эльфстрём
У меня отлично работает ... Используя тригонометрию и геометрию довольно часто, приятно не придумывать формулу для решения общей проблемы. Благодарю.
sq2
12

Я понимаю, что это старая тема, но для тех, кто хочет взглянуть на это с чисто математической точки зрения, есть отличная тема по обмену математическим стеком, здесь:

/math/190111/how-to-check-if-a-point-is-inside-a-rectangle

Изменить: Вдохновленный этой веткой, я собрал простой векторный метод для быстрого определения вашей точки зрения.

Предположим, у вас есть прямоугольник с точками в точках p1 = (x1, y1), p2 = (x2, y2), p3 = (x3, y3) и p4 = (x4, y4), идущий по часовой стрелке. Если точка p = (x, y) лежит внутри прямоугольника, то скалярное произведение (p - p1). (P2 - p1) будет лежать между 0 и | p2 - p1 | ^ 2 и (p - p1). (p4 - p1) будет лежать между 0 и | p4 - p1 | ^ 2. Это эквивалентно проецированию вектора p - p1 по длине и ширине прямоугольника с p1 в качестве начала координат.

Это может иметь больше смысла, если я покажу эквивалентный код:

p21 = (x2 - x1, y2 - y1)
p41 = (x4 - x1, y4 - y1)

p21magnitude_squared = p21[0]^2 + p21[1]^2
p41magnitude_squared = p41[0]^2 + p41[1]^2

for x, y in list_of_points_to_test:

    p = (x - x1, y - y1)

    if 0 <= p[0] * p21[0] + p[1] * p21[1] <= p21magnitude_squared:
        if 0 <= p[0] * p41[0] + p[1] * p41[1]) <= p41magnitude_squared:
            return "Inside"
        else:
            return "Outside"
    else:
        return "Outside"

Вот и все. Это также будет работать для параллелограммов.

Мэтт Томпсон
источник
Не могли бы вы подвести итоги дискуссии? В противном случае, вероятно, это должен был быть комментарий, а не ответ. Если немного больше репутации, вы сможете публиковать комментарии .
Натан Тагги,
7
bool pointInRectangle(Point A, Point B, Point C, Point D, Point m ) {
    Point AB = vect2d(A, B);  float C1 = -1 * (AB.y*A.x + AB.x*A.y); float  D1 = (AB.y*m.x + AB.x*m.y) + C1;
    Point AD = vect2d(A, D);  float C2 = -1 * (AD.y*A.x + AD.x*A.y); float D2 = (AD.y*m.x + AD.x*m.y) + C2;
    Point BC = vect2d(B, C);  float C3 = -1 * (BC.y*B.x + BC.x*B.y); float D3 = (BC.y*m.x + BC.x*m.y) + C3;
    Point CD = vect2d(C, D);  float C4 = -1 * (CD.y*C.x + CD.x*C.y); float D4 = (CD.y*m.x + CD.x*m.y) + C4;
    return     0 >= D1 && 0 >= D4 && 0 <= D2 && 0 >= D3;}





Point vect2d(Point p1, Point p2) {
    Point temp;
    temp.x = (p2.x - p1.x);
    temp.y = -1 * (p2.y - p1.y);
    return temp;}

Точки внутри многоугольника

Я только что реализовал ответ AnT с помощью c ++. Я использовал этот код, чтобы проверить, находится ли координация пикселей (X, Y) внутри формы или нет.

Альгиалин
источник
Некоторое объяснение того, что вы здесь делаете, было бы действительно полезно.
Брэд
Просто хотел сказать спасибо. Я преобразовал то, что вам нужно было для работы с шейдером Unity, и уменьшил его на 3 пункта вместо 4. Работало хорошо! Ура.
Дастин Дженсен
У меня это сработало, вот реализация на C #, которую я сделал для Unity DOTS: gist.github.com/rgoupil/04b59be8ddb56c992f25e1489c61b310
JamesDev
6

Если вы не можете решить проблему для прямоугольника, попробуйте разделить ее на более простые. Разделите прямоугольник на 2 треугольника и проверьте, находится ли точка внутри любого из них, как они объясняют здесь.

По сути, вы циклически перебираете края на каждых двух парах линий от точки. Затем используйте кросс-произведение, чтобы проверить, находится ли точка между двумя линиями, используя кросс-произведение. Если это проверено для всех трех точек, то точка находится внутри треугольника. Преимущество этого метода в том, что он не создает ошибок с плавающей запятой, которые возникают, если вы проверяете углы.

Кристиан Т
источник
Это правильный, но ужасно неэффективный алгоритм.
Гангнус
4

Если точка находится внутри прямоугольника. На плоскости. Для математических или геодезических (GPS) координат

  • Пусть прямоугольник задан вершинами A, B, C, D. Точка - P. Координаты прямоугольные: x, y.
  • Продлим стороны прямоугольника. Итак, у нас есть 4 прямые l AB , l BC , l CD , l DA , или, для краткости, l 1 , l 2 , l 3 , l 4 .
  • Составьте уравнение для каждого l i . Уравнение вроде:

    f я (P) = 0.

P - точка. Для точек, принадлежащих l i , уравнение верно.

  • Нам потребуются функции, стоящие в левых частях уравнений. Это f 1 , f 2 , f 3 , f 4 .
  • Обратите внимание, что для каждой точки с одной стороны от l i функция f i больше 0, для точек с другой стороны f i меньше 0.
  • Итак, если мы проверяем, что P находится в прямоугольнике, нам нужно только, чтобы p было на правильных сторонах всех четырех линий. Итак, нам предстоит проверить четыре функции на наличие признаков.
  • Но какая сторона правильной линии, которой принадлежит прямоугольник? Это сторона, на которой лежат вершины прямоугольника, не принадлежащие прямой. Для проверки мы можем выбрать любую из двух не принадлежащих вершин.
  • Итак, мы должны это проверить:

    f AB (P) f AB (C)> = 0

    f BC (P) f BC (D)> = 0

    f CD (P) f CD (A)> = 0

    f DA (P) f DA (B)> = 0

Неравенства не являются строгими, поскольку, если точка находится на границе, она также принадлежит прямоугольнику. Если точки на границе не нужны, можно сменить неравенства на строгие. Но пока вы работаете с операциями с плавающей запятой, выбор не имеет значения.

  • Для точки, находящейся в прямоугольнике, все четыре неравенства верны. Обратите внимание, что это работает также для каждого выпуклого многоугольника, будет отличаться только количество линий / уравнений.
  • Осталось только получить уравнение для прямой, проходящей через две точки. Это известное линейное уравнение. Запишем это для прямой AB и точки P:

    f AB (P) ≡ (x A -x B ) (y P -y B ) - (y A -y B ) (x P -x B )

Проверку можно было бы упростить - пройдемся по прямоугольнику по часовой стрелке - A, B, C, D, A. Тогда все правильные стороны будут справа от линий. Итак, нам не нужно сравнивать со стороной, где находится другая вершина. И нам нужно проверить набор более коротких неравенств:

f AB (P)> = 0

f BC (P)> = 0

f CD (P)> = 0

f DA (P)> = 0

Но это верно для обычного математического (из школьной математики) набора координат, где X находится справа, а Y сверху. А для геодезических координат, которые используются в GPS, где X - вверху, а Y - вправо, мы должны изменить неравенства:

f AB (P) <= 0

f BC (P) <= 0

f CD (P) <= 0

f DA (P) <= 0

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

Гангнус
источник
«где X находится вверху, а Y - вправо, мы должны изменить неравенства:» Это зависит от того, как вы определяете «по часовой стрелке». Если вы считаете координаты математическими, то по часовой стрелке автоматически изменится на против часовой стрелки, и вы все равно можете использовать первый такой же набор неравенств. Другими словами, вы можете обойтись без этого беспорядка, если будете рассматривать координаты как математические во всех аспектах. Изменение или изменение координат не повлияет на этот предикат.
Palo
@Palo Mathematics не ориентирована сама по себе. Да. Но у алгоритма есть несколько моментов, и было бы намного лучше иметь понятные и разумные (в реальной жизни) результаты на любом его этапе, чтобы иметь возможность протестировать. Без этого до конца второго предложения вы вряд ли сможете проверить, правильно ли вы решаете неравенства, используя свое космическое воображение.
Гангнус
0

Самый простой способ, который я придумал, - это просто спроецировать точку на ось прямоугольника. Позволь мне объяснить:

Если вы можете получить вектор от центра прямоугольника к верхнему или нижнему краю, а также к левому или правому краю. И у вас также есть вектор от центра прямоугольника до вашей точки, вы можете проецировать эту точку на свои векторы ширины и высоты.

P = точечный вектор, H = вектор высоты, W = вектор ширины

Получите единичный вектор W ', H', разделив векторы на их величину

proj_P, H = P - (P.H ') H' proj_P, W = P - (P.W ') W'

Если я не ошибаюсь, чего я не думаю ... (поправьте меня, если я ошибаюсь), но если величина проекции вашей точки на вектор высоты меньше, чем величина вектора высоты (которая равна половина высоты прямоугольника) и величина проекции вашей точки на вектор ширины равна, тогда у вас есть точка внутри вашего прямоугольника.

Если у вас универсальная система координат, вам, возможно, придется вычислить векторы высоты / ширины / точки с помощью векторного вычитания. Векторные проекции потрясающие! запомни это.

Мэтью
источник
0

В продолжение мат ответить. нам нужно использовать решение /math/190111/how-to-check-if-a-point-is-inside-a-rectangle/190373#190373, чтобы оно работало

Ниже не работает
0 <= точка (AB, AM) <= точка (AB, AB) && 0 <= точка (BC, BM) <= точка (BC, BC)

Ниже работает
0 <= точка (AB, AM) <= точка (AB, AB) && 0 <= точка (AM, AC) <= точка (AC, AC)

вы проверяете, вставив ниже в консоль javascript // Решение Javascript для того же

            var screenWidth = 320;
            var screenHeight = 568;
            var appHeaderWidth = 320;
            var AppHeaderHeight = 65;
            var regionWidth = 200;
            var regionHeight = 200;

            this.topLeftBoundary = {
                A: {x: 0, y: AppHeaderHeight},
                B: {x: regionWidth, y: AppHeaderHeight},
                C: {x: 0, y: regionHeight + AppHeaderHeight},
                D: {x: regionWidth, y: regionHeight + AppHeaderHeight}
            }

            this.topRightBoundary = {
                A: {x: screenWidth, y: AppHeaderHeight},
                B: {x: screenWidth - regionWidth, y: AppHeaderHeight},
                C: {x: screenWidth, y: regionHeight + AppHeaderHeight},
                D: {x: screenWidth - regionWidth, y: regionHeight + AppHeaderHeight}
            }

            this.bottomRightBoundary = {
                A: {x: screenWidth, y: screenHeight},
                B: {x: screenWidth - regionWidth, y: screenHeight},
                C: {x: screenWidth, y: screenHeight - regionHeight},
                D: {x: screenWidth - regionWidth, y: screenHeight - regionHeight}
            }

            this.bottomLeftBoundary = {
                A: {x: 0, y: screenHeight},
                B: {x: regionWidth, y: screenHeight},
                C: {x: 0, y: screenHeight - regionHeight},
                D: {x: regionWidth, y: screenHeight - regionHeight}
            }
            console.log(this.topLeftBoundary);
            console.log(this.topRightBoundary);
            console.log(this.bottomRightBoundary);
            console.log(this.bottomLeftBoundary);

            checkIfTapFallsInBoundary = function (region, point) {
                console.log("region " + JSON.stringify(region));
                console.log("point" + JSON.stringify(point));

                var r = region;
                var m = point;

                function vector(p1, p2) {
                    return {
                        x: (p2.x - p1.x),
                        y: (p2.y - p1.y)
                    };
                }

                function dot(u, v) {
                    console.log("DOT " + (u.x * v.x + u.y * v.y));
                    return u.x * v.x + u.y * v.y;
                }

                function pointInRectangle(m, r) {
                    var AB = vector(r.A, r.B);
                    var AM = vector(r.A, m);
                    var AC = vector(r.A, r.C);
                    var BC = vector(r.B, r.C);
                    var BM = vector(r.B, m);

                    console.log("AB " + JSON.stringify(AB));
                    console.log("AM " + JSON.stringify(AM));
                    console.log("AM " + JSON.stringify(AC));
                    console.log("BC " + JSON.stringify(BC));
                    console.log("BM " + JSON.stringify(BM));

                    var dotABAM = dot(AB, AM);
                    var dotABAB = dot(AB, AB);
                    var dotBCBM = dot(BC, BM);
                    var dotBCBC = dot(BC, BC);
                    var dotAMAC = dot(AM, AC);
                    var dotACAC = dot(AC, AC);

                    console.log("ABAM " + JSON.stringify(dotABAM));
                    console.log("ABAB " + JSON.stringify(dotABAB));
                    console.log("BCBM " + JSON.stringify(dotBCBM));
                    console.log("BCBC " + JSON.stringify(dotBCBC));
                    console.log("AMAC " + JSON.stringify(dotAMAC));
                    console.log("ACAC" + JSON.stringify(dotACAC));

                    var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotBCBM && dotBCBM <= dotBCBC));
                    console.log(" first check" + check);
                    var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotAMAC && dotAMAC <= dotACAC));
                    console.log("second check" + check);
                    return check;
                }

                return pointInRectangle(m, r);
            }

        //var point = {x: 136, y: 342};

            checkIfTapFallsInBoundary(topLeftBoundary, {x: 136, y: 342});
            checkIfTapFallsInBoundary(topRightBoundary, {x: 136, y: 274});
            checkIfTapFallsInBoundary(bottomRightBoundary, {x: 141, y: 475});
            checkIfTapFallsInBoundary(bottomRightBoundary, {x: 131, y: 272});
            checkIfTapFallsInBoundary(bottomLeftBoundary, {x: 131, y: 272});
Codeninja
источник