Алгоритм виявлення перетину двох прямокутників?


143

Я шукаю алгоритм, щоб визначити, чи перетинаються два прямокутники (один під довільним кутом, а другий лише вертикальні / горизонтальні лінії).

Тестування, чи працює кут одного в іншому ALMOST. Він не вдається, якщо прямокутники мають форму хрестоподібної форми.

Здається, гарна ідея уникати використання схилів ліній, що вимагало б особливих випадків для вертикальних ліній.


що робити, якщо ви додасте до свого кутового чека, чека, щоб побачити, чи є другий прямокутник всередині меж (прямокутних) кутового прямокутника?
Уес П

Якою мовою ти це зробиш? Тому що в Java є вбудовані класи, які дозволяють вам це робити.
Martijn

Я думаю, що графіка api та більшість бібліотек графічного інтерфейсу (як swing) це реалізували.
l_39217_l

це може пропустити випадки, коли вони перекриваються, але жоден кут не знаходиться в прямокутнику
Флоріан Бьош

1
Це питання майже те саме, що: stackoverflow.com/questions/306316/… . Хоча, це шукає рішення, яке саме стосується C ++. Прийнята відповідь також досить проста і відверта.
Срібний Гонсалес

Відповіді:


162

Стандартним методом було б зробити тест роздільної осі (здійснити пошук в Google).

Коротко:

  • Два об'єкти не перетинаються, якщо ви можете знайти лінію, яка розділяє два об'єкти. наприклад, об'єкти / всі точки об'єкта знаходяться на різних сторонах лінії.

Найцікавіше, що достатньо лише перевірити всі краї двох прямокутників. Якщо прямокутники не перетинаються, один з ребер буде осі, що розділяє.

У 2D ви можете це зробити без використання нахилів. Ребро просто визначається як різниця між двома вершинами, наприклад

  edge = v(n) - v(n-1)

Ви можете отримати перпендикуляр до цього, обертаючи його на 90 °. У 2D це легко так:

  rotated.x = -unrotated.y
  rotated.y =  unrotated.x

Тож жодна тригонометрія чи схили не задіяні. Нормалізація вектора на одиницю довжини також не потрібна.

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

  // rotated: your rotated edge
  // v(n-1) any point from the edge.
  // testpoint: the point you want to find out which side it's on.

  side = sign (rotated.x * (testpoint.x - v(n-1).x) + 
               rotated.y * (testpoint.y - v(n-1).y);

Тепер протестуйте всі точки прямокутника A по краях прямокутника B і навпаки. Якщо ви знайдете розділовий край, об'єкти не перетинаються (якщо всі інші точки в B знаходяться на іншій стороні краю, на який перевіряється - див. Малюнок нижче). Якщо ви не знайдете відокремлюваного краю, то прямокутники перетинаються, або один прямокутник міститься в іншому.

Тест працює з будь-якими опуклими багатокутниками btw ..

Поправка: Щоб визначити розділовий край, недостатньо протестувати всі точки одного прямокутника проти кожного краю іншого. Край-кандидат E (внизу), як такий, ідентифікується як розділовий край, оскільки всі точки в A знаходяться в одній півплощині E. Однак це не розділювальне ребро, оскільки вершини Vb1 і Vb2 B також знаходяться в тій півплощині. Це було б лише окремим краєм, якби це не було http://www.iassess.com/collision.png


2
Цей алгоритм працює не у всіх випадках. Можна розмістити другий прямокутник, повернутий на 45 градусів до першого прямокутника і зміщений по діагоналі, щоб він виконував вищевказані випробування на перетину, але не перетинався.
Skizz

6
Skizz, перевірте всі вісім країв. Якщо об'єкти не перетинаються , один з восьми ребер буде розділяти їх. Чому ви не опублікуєте зображення, на якому відображається ваша справа? Я можу показати вам вісь ..
Нілс Піпенбрінк

2
Моя помилка, вона справляється з цією умовою.
Skizz

2
Зображення тепер мертве (листопад 2012)
Джон Дворак

2
У мене було багато проблем із візуалізацією цього, тому я створив те, на що я думаю, що виглядало зображення, на яке посилався. imgur.com/bNwrzsv
Rjdlee

16

В основному дивіться наступну картинку:


Якщо два поля зіткнуться, лінії А і В будуть перетинатися.

Зауважте, що це доведеться робити як на осі X, так і на Y, і обидві повинні перекриватись для стикання прямокутників.

У gamasutra.com є гарна стаття, яка відповідає на питання (малюнок із статті). Я робив подібний алгоритм 5 років тому, і я повинен знайти свій фрагмент коду, щоб опублікувати його тут пізніше

Поправка : Теорема роздільної осі стверджує, що дві опуклі форми не перетинаються, якщо існує розділююча вісь (тобто така, де проекції, як показано , не перетинаються). Отже, "Відокремлювальна вісь існує" => "Ніякого перекриття". Це не є двозначним, тому ви не можете зробити висновок про зворотне.


1
Очевидно, як два квадрати (0,0,1,1) та (0,3,1,4) не перетинаються, але їхні проекції на вісь x повністю перекриваються. Обидва тесту необхідні, комбінації достатньо.
MSalters

18
Недостатньо, щоб проекції x та y перекривалися: візьмемо, наприклад, прямокутники [(0,0), (0,3), (3,3), (3,0)] та [(2,5), (5,2), (7,4), (4,7)].
Джоель в Ґо,

4
Я згоден з @Joel в Gö. Цей метод пропускає великий набір випадків, коли прямокутники не перетинаються, проте їх прогнозовані радіуси виконуються як у x, так і у.
Scottie T

5
Ця відповідь не є помилковою, але вводить в оману. Це правда, що: якщо два поля зіткнуться, лінії А і В будуть перетинатися. але також вірно, що: Якщо лінії А і В перетинаються, два поля можуть або не можуть стикатися
матовий спалює

7
@floater: Я б сказав, що це не тільки неправильно, але і вводить в оману, що ще гірше.
BlueRaja - Danny Pflughoeft

4

m_pGladiator відповідь правильна, і я вважаю за краще це. Тест роздільної осі є найпростішим і стандартним методом виявлення перекриття прямокутника. Рядок, для якого інтервали проекції не перетинаються, називаємо розділовою віссю . Рішення Нілса Піпенбрінка є надто загальним. Він використовує крапковий виріб, щоб перевірити, чи одна форма повністю стоїть на одній стороні краю іншої. Це рішення насправді може спонукати до n-реберних опуклих багатокутників. Однак він не оптимізований для двох прямокутників.

критична точка відповіді m_pGladiator полягає в тому, що ми повинні перевірити проекцію двох прямокутників на обидві осі (x і y). Якщо дві проекції перекриваються, то можна сказати, що ці два прямокутники перекриваються. Отже, коментарі до відповіді m_pGladiator є помилковими.

для простої ситуації, якщо два прямокутника не обертаються, ми представляємо прямокутник зі структурою:

struct Rect {
    x, // the center in x axis
    y, // the center in y axis
    width,
    height
}

назвемо прямокутник A, B з rectA, rectB.

    if Math.abs(rectA.x - rectB.x) < (Math.abs(rectA.width + rectB.width) / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(rectA.height + rectB.height) / 2))
    then
        // A and B collide
    end if

якщо будь-який з двох прямокутників повернутий, то для визначення проекції їх на осі x і y можуть знадобитися певні зусилля. Визначте Stru RotatedRect таким чином:

struct RotatedRect : Rect {
    double angle; // the rotating angle oriented to its center
}

різниця полягає в тому, як ширина 'зараз трохи відрізняється: widthA' для rectA: Math.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle) widthB 'для rectB:Math.sqrt(rectB.width*rectB.width + rectB.height*rectB.height) * Math.cos(rectB.angle)

    if Math.abs(rectA.x - rectB.x) < (Math.abs(widthA' + widthB') / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(heightA' + heightB') / 2))
    then
        // A and B collide
    end if

Може посилатися на GDC (Game Development Conference 2007) PPT www.realtimecollisiondetection.net/pubs/GDC07_Ericson_Physics_Tutorial_SAT.ppt


Для чого потрібні Math.abs () у "Math.abs (rectA.width + rectB.width)", щоб обробляти негативні ширини?
AlexWien

Вісь, що відокремлює, не обов'язково є компасом, вона може мати будь-який кут.
Ben Voigt

Не обертаються прямокутники rectA (x = 0, y = 0, ширина = 1, висота = 1) і rectB (x = 2, y = 0, ширина = 100, висота = 1) не перетинаються, але ваш метод говорить, що вони перетинаються. Чи я щось роблю не так?
Kagami Sascha Rosylight

4

У какао ви зможете легко визначити, чи перетинається обрана зона Area переглянуту рамку кадру NSView. Вам навіть не потрібно обчислювати багатокутники, нормальні такі. Просто додайте ці методи до свого підкласу NSView. Наприклад, користувач вибирає область під наглядом NSView, після чого ви викликаєте метод DoesThisRectSelectMe, передаючи обрану дію прямої області. API convertRect: зробить цю роботу. Цей же трюк працює, коли ви натискаєте на NSView, щоб вибрати його. У цьому випадку просто замініть метод hitTest, як показано нижче. API convertPoint: зробить цю роботу ;-)

- (BOOL)DoesThisRectSelectMe:(NSRect)selectedArea
{
    NSRect localArea = [self convertRect:selectedArea fromView:self.superview];

    return NSIntersectsRect(localArea, self.bounds);
}


- (NSView *)hitTest:(NSPoint)aPoint
{
    NSPoint localPoint = [self convertPoint:aPoint fromView:self.superview];
    return NSPointInRect(localPoint, self.bounds) ? self : nil;
}

2
Цей код працює лише для прямокутників, квадратних до екрана. Це тривіальний випадок. Припущення полягає в тому, що ми маємо справу з прямокутниками, які не знаходяться під кутом 90 градусів до екрана чи один до одного.
Duncan C

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

Це не описує алгоритм, однак він просто згадує бібліотеку, яка вже використовує його.
Ben Voigt

2

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

Якщо вам потрібна більша швидкість, існують розширені алгоритми перетину відрізка лінії (розгортка). Дивіться http://en.wikipedia.org/wiki/Line_segment_intersection


4
Обережно! Не забувайте про випадок, коли один прямокутник повністю закриває інший
Pitarou

2

Одне рішення - використовувати щось, що називається No Fit Polygon. Цей многокутник обчислюється з двох многокутників (концептуально ковзаючи один навколо іншого) і він визначає площу, на яку багатокутники перекриваються, враховуючи їх відносне зміщення. Після того, як у вас є цей NFP, ви просто повинні зробити тест на включення з точкою, заданою відносним зміщенням двох многокутників. Цей тест на включення швидкий і простий, але вам потрібно створити NFP спочатку.

Пошукайте в Інтернеті No Fit Polygon і подивіться, чи зможете ви знайти алгоритм для опуклих багатокутників (він набагато складніший, якщо у вас є увігнуті багатокутники). Якщо ви нічого не можете знайти, то напишіть мені електронною поштою howard dot J dot, можливо, gmail dot com


1

Ось що, на мою думку, піклується про всі можливі випадки. Зробіть наступні тести.

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

Якщо вищевказані 2 тести повертають помилково, ці 2 прямокутники не перетинаються.


0

Якщо ви використовуєте Java, всі реалізації інтерфейсу Shape мають метод перетину, який займає прямокутник.


На жаль, я використовую C #. Клас «Прямокутник» має метод Contains (), але він застосовується лише для прямокутників, що не повертаються.
користувач20493

метод intersects () є досить марним, оскільки, напевно, він повертає булевий замість перетину.
ZZ 5

0

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

Математична відповідь полягає у формуванні рівнянь, що описують кожен край обох прямокутників. Тепер ви можете просто знайти, чи перетинає будь-який із чотирьох прямих прямокутника A будь-який з прямокутників B, який повинен бути простим (швидким) лінійним рівнянням рівняння.

-Адам


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

Для кожного рішення є кутові випадки.
Адам Девіс

2
і один квадрат повністю огортає інший.
Олівер Халлам

0

Ви можете знайти перетин кожної сторони кутового прямокутника з кожною стороною вирівняного осі. Зробіть це, знайшовши рівняння нескінченної прямої, на якій лежить кожна сторона (тобто v1 + t (v2-v1) і v'1 + t '(v'2-v'1) в основному), знаходячи точку, в якій рядки відповідають, вирішуючи для t, коли ці два рівняння рівні (якщо вони паралельні, ви можете перевірити це), а потім перевірити, чи лежить ця точка на відрізку лінії між двома вершинами, тобто чи правда, що 0 <= t <= 1 і 0 <= t '<= 1.

Однак це не стосується випадку, коли один прямокутник повністю покриває інший. Це ви можете покрити, перевіривши, чи лежать усі чотири точки прямокутника всередині іншого прямокутника.


0

Ось що я зробив би для 3D- версії цієї проблеми:

Моделюйте 2 прямокутники як площини, описані рівнянням P1 і P2, потім запишіть P1 = P2 і виводите з цього рівняння рівняння перетину, якого не буде, якщо площини паралельні (немає перетину) або знаходяться в одній площині, в такому випадку ви отримуєте 0 = 0. У такому випадку вам потрібно буде використовувати алгоритм перетину 2D прямокутника.

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

Щоб дізнатись, чи проходить лінія через прямокутник в одній площині, я знайшов би 2 точки перетину прямої та сторони прямокутника (моделюючи їх за допомогою рівнянь ліній), а потім переконайтесь, що точки перетину знаходяться у дальність.

Це математичні описи, на жаль, я не маю коду робити вище.


Ви пропустили частину, де, якщо ви знайдете площинну лінію перетину, ви повинні переконатися, що частина її існує в обох прямокутниках.
Лі Лув'є

0

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

Цей алгоритм є дещо більш звивистим, ніж тест з роздільною віссю, але більш швидкий, оскільки він вимагає лише тесту на півплощини, якщо краї перетинають два квадранти (на відміну від до 32 тестів методом роздільної осі)

Алгоритм має ще одну перевагу в тому, що його можна використовувати для перевірки перекриття будь-якого багатокутника (опуклого або увігнутого). Наскільки мені відомо, алгоритм працює лише у 2D просторі.


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

Чи можуть вони, з прямокутниками? Як? Мені здається, що для перетину 2 прямокутників хоча б одна вершина одного з прямокутників повинна лежати на іншій прямокутнику.
Duncan C

@DuncanC: Так, вони можуть. Зразок контрприкладу - це хрест, і він навіть був зазначений у первісному питанні.
Ben Voigt

@BenVoigt Це дуже стара тема, але ти абсолютно прав.
Дункан C

0

Чи я пропускаю щось інше, чому це зробити так складно?

якщо (x1, y1) і (X1, Y1) кути прямокутників, то для знаходження перетину зробіть:

    xIntersect = false;
    yIntersect = false;
    if (!(Math.min(x1, x2, x3, x4) > Math.max(X1, X2, X3, X4) || Math.max(x1, x2, x3, x4) < Math.min(X1, X2, X3, X4))) xIntersect = true;
    if (!(Math.min(y1, y2, y3, y4) > Math.max(Y1, Y2, Y3, Y4) || Math.max(y1, y2, y3, y4) < Math.min(Y1, Y2, Y3, Y4))) yIntersect = true;
    if (xIntersect && yIntersect) {alert("Intersect");}

3
Ви пропускаєте, що він хоче повернути його під будь-яким кутом.
Роботи

0

Я реалізував це так:

bool rectCollision(const CGRect &boundsA, const Matrix3x3 &mB, const CGRect &boundsB)
{
    float Axmin = boundsA.origin.x;
    float Axmax = Axmin + boundsA.size.width;
    float Aymin = boundsA.origin.y;
    float Aymax = Aymin + boundsA.size.height;

    float Bxmin = boundsB.origin.x;
    float Bxmax = Bxmin + boundsB.size.width;
    float Bymin = boundsB.origin.y;
    float Bymax = Bymin + boundsB.size.height;

    // find location of B corners in A space
    float B0x = mB(0,0) * Bxmin + mB(0,1) * Bymin + mB(0,2);
    float B0y = mB(1,0) * Bxmin + mB(1,1) * Bymin + mB(1,2);

    float B1x = mB(0,0) * Bxmax + mB(0,1) * Bymin + mB(0,2);
    float B1y = mB(1,0) * Bxmax + mB(1,1) * Bymin + mB(1,2);

    float B2x = mB(0,0) * Bxmin + mB(0,1) * Bymax + mB(0,2);
    float B2y = mB(1,0) * Bxmin + mB(1,1) * Bymax + mB(1,2);

    float B3x = mB(0,0) * Bxmax + mB(0,1) * Bymax + mB(0,2);
    float B3y = mB(1,0) * Bxmax + mB(1,1) * Bymax + mB(1,2);

    if(B0x<Axmin && B1x<Axmin && B2x<Axmin && B3x<Axmin)
        return false;
    if(B0x>Axmax && B1x>Axmax && B2x>Axmax && B3x>Axmax)
        return false;
    if(B0y<Aymin && B1y<Aymin && B2y<Aymin && B3y<Aymin)
        return false;
    if(B0y>Aymax && B1y>Aymax && B2y>Aymax && B3y>Aymax)
        return false;

    float det = mB(0,0)*mB(1,1) - mB(0,1)*mB(1,0);
    float dx = mB(1,2)*mB(0,1) - mB(0,2)*mB(1,1);
    float dy = mB(0,2)*mB(1,0) - mB(1,2)*mB(0,0);

    // find location of A corners in B space
    float A0x = (mB(1,1) * Axmin - mB(0,1) * Aymin + dx)/det;
    float A0y = (-mB(1,0) * Axmin + mB(0,0) * Aymin + dy)/det;

    float A1x = (mB(1,1) * Axmax - mB(0,1) * Aymin + dx)/det;
    float A1y = (-mB(1,0) * Axmax + mB(0,0) * Aymin + dy)/det;

    float A2x = (mB(1,1) * Axmin - mB(0,1) * Aymax + dx)/det;
    float A2y = (-mB(1,0) * Axmin + mB(0,0) * Aymax + dy)/det;

    float A3x = (mB(1,1) * Axmax - mB(0,1) * Aymax + dx)/det;
    float A3y = (-mB(1,0) * Axmax + mB(0,0) * Aymax + dy)/det;

    if(A0x<Bxmin && A1x<Bxmin && A2x<Bxmin && A3x<Bxmin)
        return false;
    if(A0x>Bxmax && A1x>Bxmax && A2x>Bxmax && A3x>Bxmax)
        return false;
    if(A0y<Bymin && A1y<Bymin && A2y<Bymin && A3y<Bymin)
        return false;
    if(A0y>Bymax && A1y>Bymax && A2y>Bymax && A3y>Bymax)
        return false;

    return true;
}

Матриця mB - це будь-яка афінна матриця перетворення, яка перетворює точки в B просторі в точки в просторі A. Сюди входить просте обертання та переклад, обертання плюс масштабування та повні афінні основи, але не перспективні основи.

Це може бути не максимально оптимальним. Швидкість не викликала великого занепокоєння. Однак, здається, це працює добре для мене.


0

Ось реалізація прийнятої відповіді за програмою matlab:

function olap_flag = ol(A,B,sub)

%A and B should be 4 x 2 matrices containing the xy coordinates of the corners in clockwise order

if nargin == 2
  olap_flag = ol(A,B,1) && ol(B,A,1);
  return;
end

urdl = diff(A([1:4 1],:));
s = sum(urdl .* A, 2);
sdiff = B * urdl' - repmat(s,[1 4]);

olap_flag = ~any(max(sdiff)<0);

0

Це звичайний метод, перейдіть по черзі і перевірте, чи перетинаються лінії. Це код у MATLAB.

C1 = [0, 0];    % Centre of rectangle 1 (x,y)
C2 = [1, 1];    % Centre of rectangle 2 (x,y)
W1 = 5; W2 = 3; % Widths of rectangles 1 and 2
H1 = 2; H2 = 3; % Heights of rectangles 1 and 2
% Define the corner points of the rectangles using the above
R1 = [C1(1) + [W1; W1; -W1; -W1]/2, C1(2) + [H1; -H1; -H1; H1]/2];
R2 = [C2(1) + [W2; W2; -W2; -W2]/2, C2(2) + [H2; -H2; -H2; H2]/2];

R1 = [R1 ; R1(1,:)] ;
R2 = [R2 ; R2(1,:)] ;

plot(R1(:,1),R1(:,2),'r')
hold on
plot(R2(:,1),R2(:,2),'b')


%% lines of Rectangles 
L1 = [R1(1:end-1,:) R1(2:end,:)] ;
L2 = [R2(1:end-1,:) R2(2:end,:)] ;
%% GEt intersection points
P = zeros(2,[]) ;
count = 0 ;
for i = 1:4
    line1 = reshape(L1(i,:),2,2) ;
    for j = 1:4
        line2 = reshape(L2(j,:),2,2) ;
        point = InterX(line1,line2) ;
        if ~isempty(point)
            count = count+1 ;
            P(:,count) = point ;
        end
    end
end
%%
if ~isempty(P)
    fprintf('Given rectangles intersect at %d points:\n',size(P,2))
    plot(P(1,:),P(2,:),'*k')
end

функцію InterX можна завантажити з: https://in.mathworks.com/matlabcentral/fileexchange/22441-curve-intersections?focused=5165138&tab=function


0

У мене є більш спрощений метод, якщо у нас є 2 прямокутника:

R1 = (min_x1, max_x1, min_y1, max_y1)

R2 = (min_x2, max_x2, min_y2, max_y2)

Вони перетинаються, якщо і тільки якщо:

Перекриття = (max_x1> min_x2) і (max_x2> min_x1) і (max_y1> min_y2) і (max_y2> min_y1)

Ви можете зробити це і для 3D-коробок, адже це працює для будь-якої кількості вимірів.


0

Досить сказано в інших відповідях, тому я просто додам псевдокод-один вкладиш:

!(a.left > b.right || b.left > a.right || a.top > b.bottom || b.top > a.bottom);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.