Якщо у вас це питання вперше, пропоную прочитати попередньо оновлену частину нижче, а потім цю частину. Ось синтез проблеми, однак:
В основному, у мене є механізм виявлення та вирішення зіткнень із сітковою системою просторового розподілу, де мають значення порядок зіткнення та групи зіткнень. Одне тіло за часом повинне рухатись, потім виявляти зіткнення, потім вирішувати зіткнення. Якщо я переміщу всі тіла одразу, а потім генерую можливі пари зіткнення, це, очевидно, швидше, але роздільна здатність порушується, оскільки порядок зіткнення не дотримується. Якщо я рухаю по одному тілу за раз, я змушений змушувати органи перевіряти зіткнення, і це стає проблемою ^ 2. Покладіть групи в суміш, і ви можете собі уявити, чому це дуже повільно стає дуже швидко з великою кількістю тіл.
Оновлення: над цим я дуже багато працював, але не зміг нічого оптимізувати.
Я також виявив велику проблему: мій двигун залежить від порядку зіткнення.
Я спробував реалізувати унікальну генерацію пари зіткнень , яка напевно пришвидшила все на багато, але порушила порядок зіткнення .
Дозволь пояснити:
у моєму оригінальному дизайні (не генеруючи пар) це відбувається:
- єдине тіло рухається
- після переміщення він оновлює клітини і отримує тіла, проти яких стикається
- якщо це перекриває тіло, яке йому потрібно вирішити, вирішити зіткнення
це означає, що якщо тіло рухається і вдаряється об стіну (або будь-яке інше тіло), тільки тіло, яке перемістилося, вирішить його зіткнення, а інше тіло буде не зачеплене.
Такої поведінки я прагну .
Я розумію, що це не є загальним для фізичних двигунів, але це має багато переваг для ігор в стилі ретро .
у звичайній конструкції сітки (генерації унікальних пар) це відбувається:
- всі тіла рухаються
- після переміщення всіх тіл оновіть усі клітини
- генерують унікальні пари зіткнень
- для кожної пари обробляти виявлення та дозвіл зіткнень
в цьому випадку одночасне переміщення могло б призвести до перекриття двох тіл, і вони вирішаться одночасно - це ефективно змушує тіла "штовхати одне одного навколо" і порушує стійкість до зіткнення з кількома тілами
Така поведінка є загальною для фізичних двигунів, але в моєму випадку вона неприйнятна .
Я також виявив ще одне питання, яке є головним (навіть якщо це, швидше за все, не відбудеться в реальній ситуації):
- розглянути органи групи A, B і W
- Збіг і вирішує проти W і A
- B стикається і вирішується проти W і B
- А нічого не робить проти Б
- Б нічого не робить проти А
може виникнути ситуація, коли багато тіл А та В займають одну клітинку - у цьому випадку між тілами є багато непотрібних ітерацій, які не повинні реагувати один на одного (або лише виявляти зіткнення, але не вирішувати їх) .
Для 100 тіл, що займають одну клітинку, це 100 ^ 100 ітерацій! Це відбувається тому, що унікальні пари не генеруються - але я не можу генерувати унікальні пари , інакше я отримав би поведінку, якої не бажаю.
Чи є спосіб оптимізувати цей тип двигуна зіткнення?
Це рекомендації, яких необхідно дотримуватися:
Порядок зіткнення надзвичайно важливий!
- Органи повинні рухатись по черзі , потім перевіряти на предмет зіткнення по черзі та вирішувати їх після руху по черзі .
Органи повинні мати 3 групові біти
- Групи : групи, до яких належить організм
- GroupsToCheck : групи, в яких організм повинен виявити зіткнення
- GroupsNoResolve : групи, яким організм не повинен вирішувати зіткнення проти
- Можуть бути ситуації, коли я хочу лише виявити зіткнення, але не вирішити його
Попереднє оновлення:
Передмова : Я знаю, що оптимізація цього вузького місця не є необхідністю - двигун вже дуже швидкий. Я, однак, для розважальних та освітніх цілей хотів би знайти спосіб зробити двигун ще швидшим.
Я створюю загальноприйнятий двигун виявлення / реагування на зіткнення C ++ 2D з акцентом на гнучкість та швидкість.
Ось дуже основна схема його архітектури:
В основному, основним класом є World
, який володіє (управляє пам'яттю) a ResolverBase*
, a SpatialBase*
і a vector<Body*>
.
SpatialBase
це чистий віртуальний клас, який займається широкофазним виявленням зіткнень.
ResolverBase
це чистий віртуальний клас, який займається вирішенням колізій.
Органи спілкуються World::SpatialBase*
з SpatialInfo
об'єктами, якими володіють самі органи.
Тут є один просторовий клас:, Grid : SpatialBase
який є базовою фіксованою 2D сіткою. Він має власний інфо - клас, GridInfo : SpatialInfo
.
Ось як виглядає його архітектура:
Grid
Клас володіє 2D масив Cell*
. Cell
Клас містить колекцію (не належить) Body*
: а , vector<Body*>
який містить всі тіла , які знаходяться в клітці.
GridInfo
об'єкти також містять невласні покажчики на клітини, в яких знаходиться організм.
Як я вже говорив раніше, двигун заснований на групах.
Body::getGroups()
повертає astd::bitset
з усіх груп, до складу яких входить тіло.Body::getGroupsToCheck()
повертає astd::bitset
з усіх груп, з якими тіло має перевірити зіткнення.
Органи можуть займати більше однієї клітини. GridInfo завжди зберігає невласні покажчики на зайняті клітини.
Після переміщення одного тіла відбувається виявлення зіткнення. Я припускаю, що всі тіла є осями, що обмежують вісь.
Як працює широкофазне виявлення зіткнень:
Частина 1: оновлення просторової інформації
Для кожного Body
body
:
- Обчислюються клітини верхньої лівої зайнятості та найменші праворуч зайняті клітини.
- Якщо вони відрізняються від попередніх комірок,
body.gridInfo.cells
очищається і заповнюється всіма клітинами, якими займає тіло (2D для циклу від верхньої лівої клітини до крайньої нижньої правої клітини).
body
тепер гарантовано дізнається, які клітини він займає.
Частина 2: фактичні перевірки на зіткнення
Для кожного Body
body
:
body.gridInfo.handleCollisions
називається:
void GridInfo::handleCollisions(float mFrameTime)
{
static int paint{-1};
++paint;
for(const auto& c : cells)
for(const auto& b : c->getBodies())
{
if(b->paint == paint) continue;
base.handleCollision(mFrameTime, b);
b->paint = paint;
}
}
void Body::handleCollision(float mFrameTime, Body* mBody)
{
if(mBody == this || !mustCheck(*mBody) || !shape.isOverlapping(mBody->getShape())) return;
auto intersection(getMinIntersection(shape, mBody->getShape()));
onDetection({*mBody, mFrameTime, mBody->getUserData(), intersection});
mBody->onDetection({*this, mFrameTime, userData, -intersection});
if(!resolve || mustIgnoreResolution(*mBody)) return;
bodiesToResolve.push_back(mBody);
}
Потім зіткнення вирішується для кожного органа в
bodiesToResolve
.Це воно.
Отже, я досить довго намагаюся оптимізувати це широкофазове виявлення зіткнень. Кожен раз, коли я спробую щось інше, ніж поточна архітектура / налаштування, щось не йде так, як планувалося, або я роблю припущення про моделювання, які згодом виявляються помилковими.
Моє запитання: як я можу оптимізувати широкофазний двигун зіткнення ?
Чи є якась магічна оптимізація C ++, яку можна застосувати тут?
Чи можна переробити архітектуру, щоб забезпечити більшу продуктивність?
- Фактична реалізація: SSVSCollsion
- Body.h , Body.cpp
- World.h , World.cpp
- Grid.h , Grid.cpp
- Cell.h , Cell.cpp
- GridInfo.h , GridInfo.cpp
Вихід з виклику для останньої версії: http://txtup.co/rLJgz
getBodiesToCheck()
викликалася 5462334 рази і займала 35,1% всього часу профілювання (Інструкція для читання часу доступу)