Чи є спосіб підвищити ефективність перевірки зіткнення системи з n об’єктів?


9

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

Я зробив щось подібне:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

Це має O (n ^ 2), що, як мені кажуть, погано. Як зробити це більш ефективно, це можливо навіть? Я роблю це в Javascript, і n зазвичай буде нижчим за 30, чи буде це проблемою, якщо це залишиться таким же?


3
Ви спробували запустити код, щоб побачити, як він працює?
тедаян

Ні, я не маю, я просто припускаю, що це погано через O (n ^ 2).
jcora

1
Всього 30 об’єктів? Я б рекомендував просторовий розподіл, але це було б безрезультатно лише з 30 об’єктами. Є деякі незначні оптимізації, на які вказували інші, але все це незначні оптимізації в масштабі, про який ви говорите.
Джон Макдональд,

Відповіді:


16

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

Вам знадобиться реалізація просторового розподілу , наприклад, Octree (для 3D-ігор) або Quadtree (для 2D-ігор). Вони поділяють світ на підрозділи, а потім кожний підрозділ ділиться далі в одній садибі, поки вони не поділяться на мінімальний розмір. Це дозволяє дуже швидко перевірити, які ще об’єкти знаходяться в тому ж регіоні світу, що й інший, що обмежує кількість зіткнень, з якими ви повинні перевірити.

На додаток до просторового розподілу я рекомендую створити AABB ( вікно, орієнтоване по осі ) для кожного з ваших фізичних об'єктів. Це дозволяє перевірити AABB одного об'єкта проти іншого, що набагато швидше, ніж детальна перевірка полі-полі між між об'єктами.

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

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

Крім того, багато фізичних двигунів використовують "острівці моделювання", де група фізичних тіл, які знаходяться близько один до одного, об'єднуються. Якщо все на острові імітації знаходиться в спокої, то сам острів моделювання діє. Перевага імітаційного острова полягає в тому, що всі тіла, що знаходяться всередині нього, можуть припинити перевірку на зіткнення, коли острів неактивний, і єдина перевірка кожного кадру полягає у тому, щоб побачити, чи щось увійшло в AABB острова. Тільки після того, як щось ввійде в AABB острова, кожному з органів острова потрібно перевірити на зіткнення. Острів моделювання також знову активізується, якщо будь-яке тіло всередині нього знову почне рухатися самостійно. Якщо тіло рухається досить далеко від центру групи, воно видаляється з острова.

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

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

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

Також переконайтеся, що не змінюйте контейнер AABBNodes під час циклічного перегляду через нього, оскільки це може означати пропущені перевірки зіткнення. Це може здатися здоровим глуздом, але ви здивуєтеся, наскільки легко речі, що реагують на зіткнення, спричиняють зміни, яких ви не очікували. Наприклад, якщо зіткнення призвело до того, що один із об’єктів, що стикаються, змінив позицію достатньо, щоб видалити їх з AABB вузла Octree, який ви перевіряли, то це може змінити цей контейнер. Для вирішення цього питання я рекомендую зберігати список усіх зіткнень, що трапляються під час перевірок, а потім після того, як всі перевірки будуть завершені, проведіть список і надсилайте будь-які події зіткнення.


4
Дуже послідовна відповідь із приємними та корисними технічними вказівками, щоб відкрити читацький розум існуючим методам. +1
Valkea

Що робити, якщо мені потрібно видалити об'єкт, що стикається? Чи можу я змінити контейнер? Я маю на увазі вилучення його з контейнера, оскільки мені вже не потрібен об'єкт, оскільки він "знищений". Мені потрібна ще одна петля, щоб пройти через події зіткнення, якщо я не знімаю її під час виявлення зіткнення.
newguy

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

4

Ваш приклад тестує кожну пару об'єктів кілька разів.

Візьмемо дуже простий приклад з масивом, що містить 0,1,2,3

За допомогою коду ви отримуєте це:

  • У циклі 0 ви протестуєте проти 1, 2 і 3
  • У циклі 1 ви протестуєте проти 0, 2 і 3 ===> (0-1 вже перевірено)
  • У циклі 2 ви протестуєте проти 0, 1 і 3 ===> (0-2 / 1-2 вже протестовано)
  • У циклі 3 ви протестуєте проти 0, 1 і 2 ===> (0-3 / 1-3 / 2-3 вже протестовано)

Тепер переглянемо наступний код:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

Якщо ми знову використаємо масив, що містить 0,1,2,3, ми маємо таку поведінку:

  • У циклі 0 ви протестуєте проти 1, 2, 3
  • У циклі 1 ви протестуєте проти 2, 3
  • У циклі 2 ви протестуєте проти 3
  • У циклі 3 ви протестуєте нічого

З другого алгоритму ми отримали 6 тестів на зіткнення, тоді як попередній просив 12 тестів на зіткнення.


Цей алгоритм проводить N(N-1)/2порівняння, що досі є O (N ^ 2) продуктивністю.
Кай

1
Що ж із 30 запитаними об'єктами, що означає 465 тестів на зіткнення проти 870 ... це, мабуть, схоже з вашої точки зору, але не з моєї. Крім того, рішення, запропоноване в іншій відповіді, є абсолютно таким же алгоритмом :)
Valkea

1
@Valkea: Ну, це частина. :)
Нік Фостер

@NicFoster: так, ти маєш рацію;) Я говорив суворо про тест зіткнення між вибраними об'єктами, а не про роздільну частину алгоритму, що, очевидно, є дуже цінним доповненням, яке я навіть не думав додати у своєму прикладі, коли Я це писав.
Valkea

Це називається амортизацією? У будь-якому випадку, дякую!
jcora

3

Створіть свій алгоритм у відповідності зі своїми потребами, але нехай деталізація реалізації буде інкапсульована. Навіть у Javascript застосовуються основні поняття OOP.

Бо N =~ 30, O(N*N)це не є проблемою, і ваш лінійний пошук, швидше за все, буде таким же швидким, як і будь-яка альтернатива там. Але ви не хочете робити жорсткі припущення щодо свого коду. У псевдокоді у вас буде інтерфейс

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

Це описує, що можна зробити зі списком елементів. Потім ви можете написати клас ArrayContainer, який реалізує цей інтерфейс. У Javascript код буде виглядати приблизно так:

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

Ось приклад коду, який створює 300 обмежувальних коробок і отримує всі перетини. Якщо ви реалізували ArrayContainer і QuadTreeContainer правильно, єдине , що вам потрібно буде змінити в коді зміни var allMyObjects = new ArrayContainer()в var allMyObjects = QuadTreeContainer().

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

Я пішов вперед і здійснив реалізацію для стандартного ArrayContainer тут:

http://jsfiddle.net/SKkN5/1/


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

2

Ви також повинні врахувати типи об'єктів, ніж вони можуть сприйматись стикаються.

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

Для ефективної реалізації цього можливо ви хочете зберігати окремі списки об’єктів, по одному на тип об'єкта.

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