Припустимо, що кожен об'єкт Box має властивості x, y, ширину, висоту і в центрі їх походження, і що ні об'єкти, ні обмежувальні коробки не обертаються.
Припустимо, що кожен об'єкт Box має властивості x, y, ширину, висоту і в центрі їх походження, і що ні об'єкти, ні обмежувальні коробки не обертаються.
Відповіді:
(Псевдокод C-ish - адаптуйте мовні оптимізації відповідно)
bool DoBoxesIntersect(Box a, Box b) {
return (abs(a.x - b.x) * 2 < (a.width + b.width)) &&
(abs(a.y - b.y) * 2 < (a.height + b.height));
}
Англійською: На кожній осі перевірте, чи є центри коробок достатньо близькими, щоб вони перетиналися. Якщо вони перетинаються на обох осях, то ящики перетинаються. Якщо вони цього не роблять, то ні.
Ви можете змінити <'s на <=, якщо ви хочете вважати дотик до краю як перетинається. Якщо ви хочете певної формули, що стосується лише дотику, ви не можете використовувати == - це підкаже, чи торкаються кути, а не якщо краї торкаються. Ви хочете зробити щось логічно рівнозначне return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b)
.
Варто зазначити, що ви можете отримати невелике, але значне збільшення швидкості, зберігаючи половину ширини та напіввисоти на додаток до (або замість) повної ширини та повної висоти. З іншого боку, рідкісне перетину 2-огрантовної коробки є вузьким місцем продуктивності.
abs(5 - 10) * 2 < (10 + 4)
=> 10 < 14
. Вам потрібно буде зробити кілька простих налаштувань, щоб змусити його працювати з топлефтом-кутом і розміром.
Це працює для двох прямокутників, вирівняних по осі X і Y.
Кожен прямокутник має властивості:
"ліворуч", х координата його лівої сторони,
"верх", y координата його верхньої сторони,
"права", х координата його правої сторони,
"нижня", координата y її нижня сторона,
function IntersectRect(r1:Rectangle, r2:Rectangle):Boolean {
return !(r2.left > r1.right
|| r2.right < r1.left
|| r2.top > r1.bottom
|| r2.bottom < r1.top);
}
Зауважте, що це розроблено для системи координат, у якій вісь + y спрямована вниз, а вісь + x спрямована вправо (тобто типові координати екрана / пікселя). Щоб пристосувати це до типової декартової системи, в якій + y спрямована вгору, порівняння вздовж вертикальних осей буде перевернуто, наприклад:
return !(r2.left > r1.right
|| r2.right < r1.left
|| r2.top < r1.bottom
|| r2.bottom > r1.top);
Ідея полягає в тому , щоб охопити всі можливі умови , при яких прямокутники будуть НЕ перетинаються, а потім звести на немає відповіді , щоб побачити , якщо вони будуть перекриватися. Незалежно від напрямку осей, легко помітити, що два прямокутники не будуть перетинатися, якщо:
лівий край r2 далі правий, ніж правий край r1
________ ________
| | | |
| r1 | | r2 |
| | | |
|________| |________|
або правий край r2 далі лівий, ніж лівий край r1
________ ________
| | | |
| r2 | | r1 |
| | | |
|________| |________|
або верхній край r2 знаходиться нижче нижнього краю r1
________
| |
| r1 |
| |
|________|
________
| |
| r2 |
| |
|________|
або нижній край r2 вище верхнього краю r1
________
| |
| r2 |
| |
|________|
________
| |
| r1 |
| |
|________|
Оригінальну функцію - та альтернативний опис того, чому це працює - можна знайти тут: http://tekpool.wordpress.com/2006/10/11/rectangle-intersection-determine-if-two-given-rectangles-intersect- один-інший чи ні /
Якщо ви хочете встановити об'єктивні обмежувальні поля, спробуйте цей підручник з теореми осі відокремлення метанетом: http://www.metanetsoftware.com/technique/tutorialA.html
SAT - це не найшвидше рішення, але відносно просте. Ви намагаєтеся знайти одну лінію (або площину, якщо 3D), яка відокремить ваші об'єкти. Якщо ця лінія існує, гарантовано мати паралель до краю однієї з ваших скриньок, тож ви повторіть тестування всіх країв, щоб побачити, чи вона розділяє поля.
Це також працює для вирівнюваних по осі коробок, обмежуючись лише віссю x / y.
Вище DoBoxesIntersect є хорошим попарним рішенням. Однак якщо у вас багато коробок, у вас все ще виникає проблема O (N ^ 2), і вам може здатися, що вам потрібно зробити щось над цим, як те, на що посилається Kaj. (У літературі з 3D-виявлення зіткнень це відоме як з широкофазним, так і з вузькофазним алгоритмом. Ми зробимо щось дійсно швидко, щоб знайти всі можливі пари перекриттів, а потім щось дорожче, щоб побачити, чи можливо наше пари - фактичні пари.)
Широкофазний алгоритм, який я використовував раніше, - це "зачистка та обрізка"; для 2D ви б підтримували два відсортовані списки початку та кінця кожного поля. Поки рух коробки не буде >> масштабом поля від кадру до кадру, порядок цих списків не зміниться сильно, і тому ви можете використовувати сортування бульбашок чи вставок для його підтримки. У книзі "Візуалізація в режимі реального часу" є приємний перелік оптимізацій, які ви можете зробити, але вона зводиться до часу O (N + K) у широкій фазі, для N коробок, K з яких перекриваються, і з відмінним реальним світом продуктивність, якщо ви можете дозволити N ^ 2 булеви, щоб відслідковувати, які пари коробок перетинаються від кадру до кадру. Тоді у вас загальний час O (N + K ^ 2), який становить << O (N ^ 2), якщо у вас багато коробок, але лише кілька перекриттів.
Тут багато математики для дуже простої проблеми, припустимо, що у нас є 4 бали, визначені для прямої, верхньої, лівої, нижньої, правої ...
У випадку визначення того, чи стикаються 2 прямої стрілки, нам потрібно лише подивитися, що всі можливі крайності, які запобігали б зіткненням, якщо жодне з них не дотримано, то ДВІ ПІДТВЕРДЖУЮТЬСЯ стикатися, якщо ви хочете включити граничні зіткнення, просто замініть> та < з відповідними> = і = <.
struct aRect{
float top;
float left;
float bottom;
float right;
};
bool rectCollision(rect a, rect b)
{
return ! ( b.left > a.right || b.right < a.left || b.top < a.bottom || b.bottom > a.top);
}
Альтернативна версія відповіді ZorbaTHut:
bool DoBoxesIntersect(Box a, Box b) {
return (abs(a.x - b.x) < (a.width + b.width) / 2) &&
(abs(a.y - b.y) < (a.height + b.height) / 2);
}
Залежно від проблеми, яку ви намагаєтеся вирішити, вам може бути краще відслідковувати об'єкт під час переміщення, тобто зберігати список відсортованих х початкових та кінцевих позицій та одного для початкових та кінцевих позицій. Якщо вам доведеться зробити багато перевірок на перекриття, і тому вам потрібно оптимізувати, ви можете використовувати це на вашу користь, оскільки ви можете негайно шукати, хто закінчується, закривається зліва, кожен, хто закінчується, знаходиться зліва від цього, може бути обрізаний негайно. Те саме стосується верху, знизу і справа.
Бухгалтерський облік коштує часу, звичайно, тому він більше підходить для ситуації, коли мало об’єктів, що рухаються, але багато чеків перекриття.
Іншим варіантом є просторове хешуваннявання, коли ви збираєте об'єкти на основі приблизної позиції (розмір може містити їх у декількох відрах), але знову ж таки, лише якщо об’єктів багато, причому порівняно мало їх переміщується за ітерацією через вартість бухгалтерського обліку.
В основному все, що уникає (n * n) / 2 (якщо ви перевіряєте об'єкт a проти b, очевидно, вам не доведеться перевіряти b проти), допомагає більше, ніж оптимізувати чеки в обмежувальних полях. Якщо чекі з обмежувальною коробкою є вузьким місцем, я б серйозно порадив вивчити альтернативні варіанти вирішення проблеми.
Відстань між центрами не є такою ж, як відстань між кутами (коли, наприклад, один ящик всередині іншого), тож ЗАГАЛЬНО це рішення є правильним (я думаю).
відстань між центрами (для, скажімо, х): abs(x1+1/2*w1 - x2+1/2*w2)
або1/2 * abs(2*(x1-x2)+(w1-w2)
Мінімальна відстань - це 1/2 w1 + 1/2 w2 or 1/2 (w1+w2)
. половинки скасовують так ..
return
ABS(2*(x1 - x2) + (w1-w2) ) < (w1+w2)) &&
ABS(2*(y1 - y2) + (h1-h2) ) < (h1+h2));
Ось моя реалізація в Java, передбачаючи архітектуру з двома доповненнями . Якщо ви не підтримуєте двійки, замість цього використовуйте стандартний виклик функції Math.abs :
boolean intersects(IntAxisAlignedBox left, IntAxisAlignedBox right) {
return
(
lineDeltaFactor(left.min.x, left.max.x, right.min.x, right.max.x) |
lineDeltaFactor(left.min.y, left.max.y, right.min.y, right.max.y) |
lineDeltaFactor(left.min.z, left.max.z, right.min.z, right.max.z)
) == 0;
}
int lineDeltaFactor(int leftMin, int leftMax, int rightMin, int rightMax) {
final int
leftWidth = leftMax - leftMin,
rightWidth = rightMax - rightMin,
leftMid = leftMin + ((leftMax - leftMin) >> 1),
rightMid = rightMin + ((rightMax - rightMin) >> 1);
return (abs(leftMid - rightMid) << 1) / (leftWidth + rightWidth + 1);
}
int abs(int value) {
final int mask = value >> (Integer.SIZE - 1);
value ^= mask;
value += mask & 1;
return value;
}
Якщо припустити, що напівпристойний компілятор / LLVM вбудований, розширює ці функції, щоб уникнути дорогого жонглювання стека та перегляду v-таблиці. Це не вдасться для вхідних значень, близьких до 32-бітових крайнощів (тобто Integer.MAX_VALUE
і Integer.MIN_VALUE
).
Найшвидший спосіб - об'єднати всі 4 значення в одному векторному регістрі.
Зберігайте поля у векторному з наступними значеннями [ min.x, min.y, -max.x, -max.y ]
. Якщо ви зберігаєте такі коробки, тест перехрестя містить лише 3 інструкції щодо процесора:
_mm_shuffle_ps
змінити порядок другого поля, гортаючи хв та максимум половини.
_mm_xor_ps
з магічним числом _mm_set1_ps(-0.0f)
перевернути знаки всіх 4 значень у другому полі.
_mm_cmple_ps
для порівняння всіх 4 значень один з одним, порівнюючи наступні два регістри:
[ a.min.x, a.min.y, -a.max.x, -a.max.y ] < [ b.max.x, b.max.y, -b.min.x, -b.min.y ]
Нарешті, якщо потрібно, _mm_movemask_ps
для отримання результату з векторної одиниці в скалярний регістр. Значення 0 означає поля, що перетинаються. Або якщо у вас більше 2-х коробок, це не потрібно, залиште значення у векторних регістрах та використовуйте побітові операції для об'єднання результатів з декількох полів.
Ви не вказали ні мову, ні платформу, але підтримка SIMD, як ця, або дуже схожа, є на всіх платформах та мовах. У мобільному телефоні ARM має NEON SIMD з дуже подібними речами. .NET має Vector128 у просторі імен System.Runtime.Intrinsics тощо.