Виявлення зіткнення шестикутника для швидко рухаються об'єктів?


39

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

Помилка зіткнення межі боксу

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

Шестикутник зіткнення Win

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

Тепер поки що добре. Але як я насправді перевіряю, чи перетинаються два шестикутники? Зауважте, що це дуже специфічні шестикутники.

Технічні характеристики шестикутника

Питання про бонус : Чи можна порахувати, де саме (а точніше через скільки часу) зіткнення сталося? Це може бути дуже корисно для виявлення того, що насправді сталося, як, де і з якою потужністю, і для моделювання того, як вони рухалися за час між зіткненням і кінцем кадру.


for (рядки в A) для (рядки в B), якщо (лінії перетинаються) зіткнення - за винятком випадків, що не охоплюють A у B або B у випадках A. Гм. =)
Jari Komppa

4
Ви прихильні до коробки? Намальовані ящики можна представити кружечками з мінімальною втратою точності, але порівняно легким альго зіткненням. Шукайте виявлення зіткнення кругообігу. Якщо співвідношення довжини / ширини відходить від 1, все-таки це буде менш привабливо.
Стів H

@SteveH Я шукаю найбільш гнучке рішення, тому співвідношення довжини / ширини - це щось велике.
API-Beast

1
Ви повинні усвідомити, що те, що шестикутники перетинаються, не означає зіткнення. Навіть за умови, що ви зможете без помилок сказати, чи вони перетинаються, вам все одно доведеться попрацювати, щоб визначити, чи є зіткнення, і, очевидно, де і коли це відбувається. Тож ви ще не можете перейти до свого бонусного питання.
jrsala

2
Я раніше не пробував цього, але здається, що замість шестикутників у просторі 2d ви можете вважати рух у 2d як обсяги в 3d просторі, де одна вісь - час. Потім ви перетинаєте два 3d багатогранника з координатами (x, y, t). Якщо два суцільних об’єкта перетинаються, то потрібно знайти мінімальне значення t. Ви можете трохи спростити, перетворивши всі координати B у відповідні рамки A. Я цього не реалізував, але саме з цього я би почав.
amitp

Відповіді:


34

Рішення насправді простіше, ніж очікувалося. Хитрість полягає у використанні віднімання Мінковського перед технікою шестикутника.

Ось ваші прямокутники A і B з їх швидкістю vAта vB. Зауважте, що vAі vBнасправді не є швидкістю, це відстань, пройдена протягом одного кадру.

крок 1

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

крок 2

Але якщо прямокутник C рухається по вектору vA, а точка P рухається по вектору vB, проста зміна опорного кадру говорить нам, що це те саме, як якщо б прямокутник C був нерухомий, а точка P переміщувалася по вектору vB-vA:

крок 3

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

Останній крок - повернутися до належної системи відліку. Просто розділіть відстань, пройдену точкою, до тих пір, поки не обведеться перетин на довжину вектора, vB-vAі ви отримаєте таке значення s, що 0 < s < 1. Зіткнення відбувається в той час, s * Tколи Tтривалість вашого кадру.

Коментар madshogo : ОГРАНИЧНА
перевага цієї методики над тією, у власній відповіді містера Звіра полягає в тому, що якщо немає повороту, то "віднімання Міньковського" A + (- B) можна обчислити один раз за всі наступні кроки часу !

Тож єдиний алгоритм, який потребує часу у всьому цьому (сума Мінковського, складність O (mn), де m - кількість вершин у A і n кількість вершин у В ), може бути використаний лише один раз, ефективно роблячи виявлення зіткнення постійним- проблема часу!

Пізніше ви зможете викинути суму, як тільки довідаєтесь, що А і В знаходяться в різних частинах сцени (у вашому квадраті?) І більше не зіткнуться.

Навпаки, метод містера Біста вимагає досить багато обчислень на кожному кроці.

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

Але все це працює лише в тому випадку, якщо ні А, ні В не обертаються і якщо обидва є опуклими. Якщо обертання є або якщо ви використовуєте увігнуті форми, то ви повинні використовувати розміщені об'єми / області.
кінець коментаря


4
Схоже, це досить цікавий підхід, однак я ще не сприймаю його на 100%, що відбувається, коли об’єкт дійсно малий і рухається між двома лініями? i.imgur.com/hRolvAF.png
API-Beast

-1: Цей спосіб жодним чином не дозволяє вам бути впевненим у зіткненні. Це дозволяє лише бути впевненим, що цього не відбудеться, якщо сегмент та екструдований об'єм не перетинаються. Але цілком можливо, що вони перетинаються, і все-таки зіткнення немає. Що не так - це частина "Тепер ви можете використовувати [...] просте перетин сегмента на сегмент, щоб визначити, де відбулося зіткнення".
jrsala

2
@madshogo Ви маєте рацію. Я припускав, що часовий крок був досить малим у порівнянні з розмірами об'єктів, що це не буде проблемою, але, звичайно, не дуже надійним у загальному випадку. Я розберуся, як це виправити.
sam hocevar

@SamHocevar Якщо ви зможете переглянути відповідь, це було б чудово.
API-Beast

1
@LuisAlves та й немає ... все логічні роботи, але вам доведеться замінити vB-vAз , g(t)-f(t)де fі gзнаходяться позиції A і B протягом довгого часу. Оскільки це більше не є прямою лінією, вам доведеться вирішити проблему перетину параметричної кривої.
sam hocevar

17

Перш за все, у випадку прямокутників, що вирівнюються по осі, відповідь Кевіна Рейда найкраща, а алгоритм - найшвидший.

По-друге, для простих фігур використовуйте відносні швидкості (як показано нижче) і теорему роздільної осі для виявлення зіткнень. Це буде сказати, чи відбувається зіткнення в разі лінійного руху (без обертання). І якщо є обертання, вам потрібен невеликий часовий крок, щоб він був точним. Тепер, щоб відповісти на запитання:


Як сказати в загальному випадку, чи перетинаються дві опуклі форми?

Я дам вам алгоритм, який працює для всіх опуклих фігур, а не лише для шестикутників.

Припустимо, X і Y - дві опуклі форми. Вони перетинаються тоді і лише тоді, коли мають спільну точку, тобто є точка x X і точка y ∈ Y така, що x = y . Якщо ви розглядаєте простір як векторний простір, то це означає вислів x - y = 0 . А тепер ми переходимо до цієї справи Міньковського:

Сума Маньківського з X і Y являє собою безліч всіх х + у для х ∈ X і Y ∈ Y .


Приклад для X і Y


X, Y та їх сума Minkowski, X + Y

Припустимо, що (-Y) - це множина всіх -y для y ∈ Y , тоді, з огляду на попередній абзац, X і Y перетинаються тоді і тільки тоді, коли X + (-Y) містить 0 , тобто походження .

Побічне зауваження: чому я пишу X + (-Y) замість X - Y ? Отже, оскільки в математиці існує операція, яка називається різницею Міньковського A і B, яка іноді пишеться X - Y, але не має нічого спільного з множиною всіх x - y для x ∈ X і y ∈ Y (справжній Міньковський різниця трохи складніша).

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

Сума Міньковського X і Y має класну властивість, яка полягає в тому, що якщо X і Y опуклі, то X + Y теж. А визначити, чи належить точка до опуклої множини, набагато простіше, ніж якби ця множина не була (як відомо) опуклою.

Ми не можемо обчислити всі x - y для x ∈ X і y ∈ Y, тому що існує нескінченність таких точок x і y , тому, сподіваємось, оскільки X , Y і X + Y є опуклими, ми можемо просто використовувати "зовнішні" точки, що визначають фігури X і Y , які є їх вершинами, і ми отримаємо найвіддаленіші точки X + Y , а також деякі інші.

Ці додаткові точки "оточені" самими зовнішніми з X + Y, щоб вони не сприяли визначенню щойно отриманої опуклої форми. Ми говоримо, що вони не визначають " опуклий корпус " набору точок. Тож, що ми робимо, це те, що ми позбавляємось їх під час підготовки до остаточного алгоритму, який повідомляє нам, чи є походження всередині опуклого корпусу.


Опуклий корпус X + Y. Ми видалили вершини "всередині".

Тому ми отримуємо

Перший, наївний алгоритм

boolean intersect(Shape X, Shape Y) {

  SetOfVertices minkowski = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      minkowski.addVertice(x-y);
    }
  }
  return contains(convexHull(minkowski), Vector2D(0,0));

}

Петлі, очевидно, мають складність O (mn), де m і n - кількість вершин кожної форми. minkoswkiНабір млн на більшість елементів. convexHullАлгоритм має складність , яка залежить від алгоритму, який використовується , і ви можете прагнути до O (до балці (к)) , де до є розміром безлічі точок, так що в нашому випадку ми отримуємо O (млн журналу (млн) ) . containsАлгоритм має складність, лінійно з числом ребер (в 2D) або особа (в 3D) опуклої оболонки, так що це дійсно залежить від ваших вихідних форм, але це буде не більше , ніж O (млн) .

Я дозволю вам google для containsалгоритму опуклих фігур, це досить поширений. Я можу поставити його тут, якщо матиму час.


Але це ми виявляємо зіткнення, тому ми можемо оптимізувати це багато

Ми спочатку мали два тіла і B переміщення без обертання під час Timestep DT (від того, що я можу сказати, дивлячись на ваші фотографії). Назвемо v A і v B відповідні швидкості A і B , постійні протягом нашого часового кроку тривалості dt . Ми отримуємо наступне:

і, як ви вказуєте на своїх фотографіях, ці тіла під час руху рухаються по областях (або томах у 3D):

і вони закінчуються як A ' і B' після часового кроку.

Щоб застосувати тут наш наївний алгоритм, нам слід було б лише обчислити нечисті обсяги. Але ми цього не робимо.

У системі відліку B , B не рухається (так!). І A має певну швидкість відносно B, яку ви отримуєте, обчислюючи v A - v B (ви можете зробити зворотне, обчислити відносну швидкість B у референтному кадрі A ).

Відносний рух

Зліва направо: швидкості в базовій системі відліку; відносні швидкості; обчислення відносних швидкостей.

Розглядаючи B як нерухомий у своїй власній системі відліку, ви тільки обчислити об'єм, А мете через , як вона рухається під час ДТ з його відносної швидкості про - про B .

Це зменшує кількість вершин, які будуть використані при обчисленні суми Міньковського (іноді сильно).

Інша можлива оптимізація - це місце, коли ви обчислюєте об'єм, пронесений одним із тіл, скажімо, A. Не потрібно перекладати всі вершини, що складаються з A. Тільки ті, що належать до ребер (граней у 3D), чиї зовнішнє нормальне «обличчя» напрям підмітання. Звичайно, ви це помітили вже під час обчислення розміщених площ для площ. Ви можете сказати, чи нормальна в напрямку змітання, використовуючи крапковий добуток із напрямком зміщення, який повинен бути позитивним.

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

Припустимо , ви знаєте радіуси в A і B щодо їх центрів мас (тобто, відстань між центром мас і вершиною далекому від нього), як це:

Зіткнення може відбутися тільки в тому випадку, можливо , що обмежує коло А зустрічаються у B . Тут ми бачимо , що він не буде, і так би мовити , комп'ютер , який повинен обчислити відстань від C B до I , як на малюнку нижче і переконайтеся , що це більше , ніж сума радіусів A і B . Якщо він більший, ніяких зіткнень. Якщо вона менша, то зіткнення.

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

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

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

boolean mayCollide(Body A, Body B) {

  Vector2D relativeVelocity = A.velocity - B.velocity;
  if (radiiHeuristic(A, B, relativeVelocity)) {
    return false; // there is a separating axis between them
  }

  Volume sweptA = sweptVolume(A, relativeVelocity);
  return contains(convexHull(minkowskiMinus(sweptA, B)), Vector2D(0,0));

}

boolean radiiHeuristic(A, B, relativeVelocity)) {
  // the code here
}

Volume convexHull(SetOfVertices s) {
  // the code here
}

boolean contains(Volume v, Vector2D p) {
  // the code here
}

SetOfVertices minkowskiMinus(Body X, Body Y) {

  SetOfVertices result = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      result.addVertice(x-y);
    }
  }
  return result;

}

2

Я не думаю, що використання «шестикутника» все це корисно. Ось ескіз способу отримати точні зіткнення прямокутників, розташованих по осі:

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

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


3
Ви забули свій ескіз.
MichaelHouse

2
@ Byte56 Ні, я маю на увазі це ескіз алгоритму, навіть не псевдокод.
Кевін Рейд

О Я бачу. Моя помилка.
MichaelHouse

Це насправді найпростіший метод. Я додав відповідний код для його реалізації.
Паша

1

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

function objectsWillCollide(object1,object2) {
    var lineA, lineB, lineC, lineD;
    //get projected paths of objects and store them in the 'line' variables

    var AC = lineCollision(lineA,lineC);
    var AD = lineCollision(lineA,lineD);
    var BC = lineCollision(lineB,lineC);
    var BD = lineCollision(lineB,lineD);
    var objectToObjectCollision = rectangleCollision(object1.getRectangle(), object2.getRectangle());

    return (AC || AD || BC || BD || objectToObjectCollision);
}

ілюстрація шляхів та кінцевих станів об'єктів

Зверніть увагу, як я ігнорую стартовий стан кожного об'єкта, як це слід було перевірити під час попереднього обчислення.


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

0

Теорема окремої осі

Теорема окремої осі говорить: "Якщо ми зможемо знайти вісь, на якій дві опуклі форми не перетинаються, то дві форми не перетинаються" або більш практичні для ІТ:

"Дві опуклі форми перетинаються лише тоді, коли вони перетинаються на всіх можливих осях."

Для прямокутників, вирівнюваних по осі, існує рівно 2 можливі осі: x і y. Але теорема не обмежується прямокутниками, вона може застосовуватися до будь-якої опуклої форми, просто додаючи інші осі, на яких фігури могли перетинатися. Щоб отримати докладніші відомості про тему, ознайомтеся з цим посібником розробником програми N: http://www.metanetsoftware.com/technique/tutorialA.html#section1

Реалізовано це виглядає приблизно так:

axes = [... possible axes ...];
collision = true;
for every index i of axes
{
  range1[i] = shape1.getRangeOnAxis(axes[i]);
  range2[i] = shape2.getRangeOnAxis(axes[i]);
  rangeIntersection[i] = range1[i].intersectionWith(range2[i]);
  if(rangeIntersection[i].length() <= 0)
  {
    collision = false;
    break;
  }
}

Оси можуть бути представлені як нормалізовані вектори.

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

Застосовуючи його до «підмітаного» Прямокутника

Шестикутник у питанні виробляється шляхом "підмітання" AABB об'єкта. Підмітання додає рівно одну можливу вісь зіткнення до будь-якої форми: вектора руху.

shape1 = sweep(originalShape1, movementVectorOfShape1);
shape2 = sweep(originalShape2, movementVectorOfShape2);

axes[0] = vector2f(1.0, 0.0); // X-Axis
axes[1] = vector2f(0.0, 1.0); // Y-Axis
axes[2] = movementVectorOfShape1.normalized();
axes[3] = movementVectorOfShape2.normalized();

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

Це рішення працюватиме для будь-яких опуклих фігур (наприклад, трикутників) та будь-яких підметаних опуклих фігур (наприклад, підмітаних восьмикутників). Однак чим складніша форма, тим менш ефективною вона буде.


Бонус: Де відбувається магія.

Як я вже сказав, єдині додаткові осі - це вектори руху. Рух - це час, помножений на швидкість, тому в певному сенсі вони не просто космічні осі, це осі простору часу.

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

shapeRange1 = originalShape1.getRangeOnAxis(axes[2]);
shapeRange2 = originalShape2.getRangeOnAxis(axes[3]);
// Project them on a scale from 0-1 so we can compare the time ranges
timeFrame1 = (rangeIntersection[2] - shapeRange1.center())/movementVectorOfShape1.project(axes[2]);
timeFrame2 = (rangeIntersection[3] - shapeRange2.center())/movementVectorOfShape2.project(axes[3]);
timeIntersection = timeFrame1.intersectionWith(timeFrame2);

Коли я задав це питання, я начебто прийняв компроміс, що з цим методом буде кілька рідкісних помилкових позитивних результатів. Але я помилявся, перевіряючи цей перехрестя, ми можемо перевірити, чи сталося зіткнення "насправді", і ми зможемо з ним виправити помилкові позитиви:

if(collision)
{
  [... timeIntersection = see above ...]
  if(timeIntersection.length() <= 0)
    collision = false;
  else
    collisionTime = timeIntersection.start; // 0: Start of the frame, 1: End of the frame
}

Якщо ви помітили помилки в прикладах коду, дайте мені знати, я ще не реалізував її, і тому не зміг її перевірити.


1
Вітаємо, що знайшли рішення! Але, як я вже говорив: тільки тому, що шестикутники перетинаються, це не означає, що відбудеться зіткнення. Ви можете використовувати свій метод для обчислення часу зіткнення всього, що вам потрібно, якщо зіткнення немає, це не дуже корисно. По-друге, ви можете використовувати відносні швидкості так, щоб потрібно було обчислити лише один промітний об'єм та спростити обчислення при використанні SAT. Нарешті, у мене є лише приблизне уявлення про те, як працює трюк "час перетину", тому що, можливо, ти змішав свої індекси, бачачи, як shapeRange1 == shapeRange2з кодом, чи не так?
jrsala

@madshogo Зараз має мати більше сенсу.
API-Beast

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

-2

Поки зачислені ділянки будуть закритими (відсутні прогалини в межі, утвореній крайовими лініями), буде працювати наступне (просто зменшіть тести зіткнення до прямої та точки прямої / точки-три):

  1. Чи торкаються їх краї? (зіткнення між лініями) Перевірте, чи перетинається будь-яка лінія краю проміжної області з будь-якою лінією другої області прокоти. Кожна зона підмітання має 6 сторін.

  2. Чи є маленька всередині великої? (Використовуйте фігури, вирівняні по осі (точка-пряма та крапка-три) повинно бути все або немає) знаходяться в межах витягнутої по осі області). Це робиться за допомогою розкладання вашого шестигранника на трис і ректи.

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

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


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

@madshogo Я щойно додав свою відповідь. Зараз має бути повним рішенням.
аксон

1
"Використовуйте фігури, вирівняні по осі (точка-пряма і точка-три)": Як ви навіть вирівнюєте трикутник або "трикутник" (що б це не означало) з осями? "щоб більша була вирівняна по осі": як ви можете сказати, яка з них більша за іншу? Чи обчислюєте ви їх площі? "Це робиться за допомогою розкладання вашого шестигранника на трис і ректи.": Який шестигранник? Є два. "(або підтверджуйте цю відповідь, якщо ви хочете, щоб я проілюстрував це для вас)": Ви серйозно ??
jrsala

"Як ви навіть вирівнюєте трикутник з осями?" A: Вирівняйте шлях obj, зробивши зачищену область. Підберіть край і використовуйте триггер. "як ви можете сказати, який з них більший за інший?" A: Наприклад, використовуйте відстань між двома діагонально протилежними точками прямої частини (середина шестигранника). "який шестигранник?" A: Великий.
аксон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.