Визначення, лежить точка всередині прямокутника чи ні


84

Я хочу з'ясувати, лежить точка всередині прямокутника чи ні. Прямокутник може бути орієнтований будь-яким способом, і його не потрібно вирівнювати по осі.

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

Вищевказаний метод вимагає обертання і, отже, операцій з плаваючою комою. Чи існує інший ефективний спосіб зробити це?


Ви можете швидко перевірити, чи не потрапила точка в ортогональне обмежувальне поле повернутого прямокутника, і швидко не вдатися, якщо ні. (Так, це лише половина відповіді (мммм, є три ортогональні рамки, які можуть бути сформовані кутовими точками ... і вже пізно (концептуальна геометрія йде першою))).
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 напрямки осей різні, і ви не можете бути впевнені, що маєте правильний набір нерівностей. Або ви можете бути впевнені, що ні. Моя відповідь краща :-).
Gangnus

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тощо?
Анонім

v_minє мінімальним значенням A x + B yдля всіх значень у внутрішній частині прямокутника (що є мінімальним значенням при оцінці по кутах). v_max- відповідний максимум. Ці w_?значення однакові, але Bx - A y.
Michael Anderson

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, By-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
Це найкраща відповідь!
Тахліл

1
Мені подобається, що ця відповідь стисла, зменшує кількість операцій більш-менш низькою, як інші хороші відповіді тут, але також має ту перевагу, що є дуже інтуїтивною та наочною.
Анонім

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

mouseoverповинно бути mousemove, просто друкарська помилка.
newguy


В принципі, ваш метод правильний, але ваш прямокутник не є прямокутником у вашому прикладі. Я зробив вашу вдосконалену версію тут, де я дотримуюся оригінальної формули та схеми імен, і де введення фактично є справжнім прямокутником.
Йоханнес,

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
Проблема подібних підходів полягає в тому, що вони працюють теоретично, але на практиці можуть виникнути проблеми. В ОП не сказано, як зображений прямокутник. Ця відповідь передбачає, що вона представлена ​​трьома пунктами - a, bі d. Незважаючи на те, що три точки є теоретичним способом представити довільний прямокутник, на практиці це неможливо зробити точно в міжцифрових координатах, загалом. Як правило, у результаті вийде паралелограм, який дуже близький до прямокутника, але все ще не є прямокутником.
AnT

3
Тобто кути в такій формі не будуть рівно 90 градусів. Потрібно бути дуже обережним, проводячи будь-які тести на основі кута в такій ситуації. Знову ж таки, це залежить від того, як OP визначає «всередині» для неточно представленого «прямокутника». І, знову ж таки, про те, як зображений прямокутник.
AnT

+1 до обох ваших коментарів. Тільки @avd може сказати нам, чи це достатньо добре.
Jonas Elfström

Мені чудово підходить ... Використовуючи тригонометрію та геометрію досить часто, приємно не придумувати формулу для вирішення загальної проблеми. Дякую.
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"

І це все. Це також буде працювати для паралелограм.


Чи можете ви підвести підсумок дискусії там досі? В іншому випадку це, мабуть, мав бути коментарем, а не відповіддю. Трохи більше представників ви зможете залишати коментарі .
Nathan Tuggy,

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 Shader, і зменшив його на 3 бали замість 4. Працював добре! Ура.
Дастін Йенсен,

Це спрацювало для мене, ось реалізація в C #, яку я зробив для Unity DOTS: gist.github.com/rgoupil/04b59be8ddb56c992f25e1489c61b310
JamesDev

6

Якщо вам не вдається вирішити проблему прямокутника, спробуйте розділити задачу на простіші. Поділіть прямокутник на 2 трикутники, щоб перевірити, чи знаходиться точка всередині будь-якого з них, як вони пояснюють тут

По суті, ви прокручуєте ребра на кожних двох парах ліній від точки. Потім за допомогою перехресного добутку перевірте, чи знаходиться точка між двома лініями, використовуючи поперечний добуток. Якщо це перевірено для всіх 3 точок, то точка знаходиться всередині трикутника. Гарна річ цього методу полягає в тому, що він не створює жодних помилок з плаваючою точкою, що трапляється, якщо ви перевіряєте кути.


Це правильний, але жахливо неефективний алгоритм.
Gangnus

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 i (P) = 0.

Р - точка. Для точок, що належать до 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 до н. е. (P) f до н. е (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 Математика сама по собі не має орієнтації. Так. Але алгоритм має кілька моментів, і було б набагато краще мати зрозумілі та розумні (у реальному житті) результати в будь-який момент, щоб мати можливість тестувати. Без цього до кінця другого речення ви навряд чи зможете перевірити, чи правильно ви розв’язуєте нерівності, використовуючи свою космічну уяву.
Gangnus

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});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.