Виявлення зіткнень на основі квадрового дерева та сітки


27

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

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

Будь-яка порада вітається. Спасибі.

Відповіді:


31

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

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

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

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

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

foreach actor in actorList:
    foreach target in actorList:
        if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

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

Це працює фантастично добре, доки кількість таких предметів є досить малою, але починає виглядати трохи бідно, коли потрібно перевірити більше кількох сотень об’єктів. 10 об'єктів призводять до 100 перевірок на зіткнення, 100 - до 10000 перевірок. 1000 результатів на мільйон перевірок.

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

foreach actor in actorList:
    foreach target in actorIndex.neighbors(actor.boundingbox):
       if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

Ефективність цього (за умови рівномірного розподілу сутностей): зазвичай O (n ^ 1,5 log (n)), оскільки індекс займає порівняння log (n) порівняння, для порівняння буде близько sqrt (n) сусідів , а для перевірки є n акторів. Реально, однак, кількість сусідів завжди досить обмежена, оскільки якщо зіткнення все-таки відбувається, більшість часу один із об'єктів видаляється або віддаляється від зіткнення. таким чином, ви отримуєте просто O (n log (n)). Для 10 організацій ви робите (приблизно) 10 порівнянь, для 100 ви робите 200, для 1000 - 3000.

Дійсно розумний індекс може навіть поєднувати пошук сусідів з масовою ітерацією та виконувати зворотний виклик на кожному пересічному об'єкті. Це дасть продуктивність приблизно O (n), оскільки індекс сканується один раз, а не запитується n разів.


Я не впевнений, що знаю, на що ви звертаєтесь, коли ви говорите "статичний фон". Що я маю справу - це в основному 2D стрілок, тож це виявлення зіткнень з космічними кораблями та прибульцями, кулями та стінами.
dotminic

2
Ви щойно заробили мій приватний знак "Чудова відповідь"!
Феліксиз

Це може здатися дурним, але як я фактично використовую моє квадратурне дерево для вибору, проти яких інших об'єктів об'єкт повинен перевірити зіткнення? Я не впевнений, як це робиться. Що викликає друге питання. Скажіть, у мене є об’єкт у вузлі, який не є сусідкою іншого вузла, але що об'єкт досить великий, що він охоплює кілька вузлів, як я можу перевірити фактичне зіткнення, оскільки я здогадуюсь, що дерево може вважати, що це не достатньо близько, щоб зіткнутися з об’єктами у «далекому» вузлі? Чи повинні об’єкти, які не повністю вміщуються у вузлі, зберігатись у батьківському вузлі?
dotminic

2
Quat-tree є за своєю суттю неоптимальними для пошуку перекриття обмежувального поля. Найкращий вибір для цього, як правило, R-дерево. Для квадрових дерев, якщо більшість об’єктів мають приблизно точковий характер, тоді так, розумно тримати об’єкти на внутрішніх вузлах і проводити точне тестування на зіткнення при нечіткому пошуку сусідів. Якщо більшість об'єктів в індексі великі і перетинаються без зіткнення, квадрове дерево, мабуть, є поганим вибором. Якщо у вас є додаткові технічні запитання з цього приводу, варто подумати про те, щоб
перенести

Все це досить заплутано! дякую за інформацію.
dotminic

3

Вибачте за відродження древньої нитки, але звичайні старі сітки ІМХО не використовуються досить часто для цих випадків. Є багато переваг сітки в тому, що введення / видалення комірок - бруд дешевий. Ви не повинні турбуватися із звільненням комірки, оскільки сітка не має на меті оптимізувати розріджені зображення. Я кажу, що скоротивши час для розміщення, виберіть купу елементів у застарілій кодовій базі з понад 1200 мс до 20 мс, просто замінивши квадратичне дерево сіткою. Справедливо кажучи, що квадратичне дерево було реально погано реалізовано, зберігаючи окремий динамічний масив на вузол листя для елементів.

Інший, який я вважаю надзвичайно корисним, - це те, що класичні алгоритми растризації для малювання фігур можна використовувати для пошуку в сітці. Наприклад, ви можете використовувати растерізацію ліній Брезенама для пошуку елементів, які перетинають лінію, розширення растрової лінії, щоб знайти, які клітини перетинають полігон і т. Д. Оскільки я багато працюю в обробці зображень, дуже приємно мати можливість використовувати саме те саме оптимізований код, який я використовую для нанесення пікселів на зображення, як і для виявлення перехрестя проти рухомих об'єктів у сітці.

Це означає, що для того, щоб зробити сітку ефективною, вам не потрібно більше 32 біт на одну комірку сітки. Ви повинні мати можливість зберігати мільйон комірок у розмірі менше 4 мегабайт. Кожна комірка сітки може просто індексувати перший елемент у комірці, а перший елемент у комірці може потім індексувати наступний елемент у комірці. Якщо ви зберігаєте який-небудь повнофункціональний контейнер з кожною осередком, він швидко отримує вибухонебезпечне використання та розподілення пам'яті. Натомість ви можете просто зробити:

struct Node
{
    int32_t next;
    ...
};

struct Grid
{
     vector<int32_t> cells;
     vector<Node> nodes;
};

Приблизно так:

введіть тут опис зображення

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

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

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

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

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

З урахуванням сказаного, я дійсно думаю, що це залежить від програміста. З такими речами, як сітка проти квадратичного дерева або octree проти kd-tree проти BVH, я голосую за найбільш плідного розробника, який має записи про створення дуже ефективних рішень незалежно від того, яку структуру даних він / вона використовує. На мікрорівні також багато, як багатопотокова, SIMD, зручна в кеші макет пам'яті та шаблони доступу. Деякі люди можуть розглянути ці мікро, але вони не обов'язково мають мікровплив. Такі речі можуть зробити різницю в 100 разів від одного рішення до іншого. Незважаючи на це, якби мені особисто дали кілька днів і мені сказали, що мені потрібно впровадити структуру даних для швидкого прискорення виявлення зіткнення елементів, що рухаються навколо кожного кадру, я б краще за короткий час застосував сітку, ніж квадроцикл -древо.

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