Если вы впервые отвечаете на этот вопрос, я предлагаю сначала прочитать часть перед обновлением, а затем эту часть. Вот обобщение проблемы:
По сути, у меня есть механизм обнаружения и разрешения коллизий с системой пространственного разбиения сетки, где важен порядок коллизий и группы коллизий. Одно тело за один раз должно двигаться, затем обнаруживать столкновения, а затем разрешать столкновения. Если я перемещаю все тела одновременно, а затем генерирую возможные пары столкновений, это, очевидно, быстрее, но разрешение нарушается, потому что порядок столкновения не соблюдается. Если я перемещаю одно тело за раз, я вынужден заставлять тела проверять столкновения, и это становится проблемой ^ 2. Поместите группы в микс, и вы можете себе представить, почему это происходит очень медленно и очень быстро с большим количеством тел.
Обновление: я очень много работал над этим, но не смог ничего оптимизировать.
Я также обнаружил большой проблему: мой двигатель зависит от порядка столкновения.
Я попробовал реализацию генерации уникальной пары столкновений , которые определенно сильно ускоряют все, но нарушают порядок столкновений .
Позволь мне объяснить:
в моем оригинальном дизайне (не генерация пар) это происходит:
- одно тело движется
- после перемещения он освежает свои клетки и получает тела, с которыми сталкивается
- если он перекрывает тело, против которого нужно разрешить, разрешите столкновение
это означает, что если тело движется и ударяет стену (или любое другое тело), только тело, которое переместилось, разрешит его столкновение, и другое тело не будет затронуто.
Это поведение, которое я желаю .
Я понимаю, что это не характерно для физических движков, но имеет много преимуществ для игр в стиле ретро .
в обычном сеточном дизайне (создание уникальных пар) это происходит:
- все тела движутся
- после того, как все тела переместились, обновите все клетки
- генерировать уникальные пары столкновений
- для каждой пары обрабатывать обнаружение и разрешение столкновений
в этом случае одновременное перемещение могло бы привести к перекрытию двух тел, и они будут разрешаться одновременно - это эффективно заставляет тела «толкать друг друга» и нарушает устойчивость столкновения с несколькими телами
Такое поведение характерно для физических движков, но в моем случае оно неприемлемо .
Я также обнаружил еще одну проблему, которая является серьезной (даже если это вряд ли произойдет в реальной ситуации):
- рассмотреть тела группы A, B и W
- А сталкивается и решает против W и A
- B сталкивается и решает против W и B
- А ничего не делает против В
- B ничего не делает против A
может быть ситуация, когда множество тел A и B занимают одну и ту же ячейку - в этом случае существует много ненужных итераций между телами, которые не должны реагировать друг на друга (или только обнаруживать столкновения, но не разрешать их) ,
Для 100 тел, занимающих одну и ту же клетку, это 100 ^ 100 итераций! Это происходит потому, что уникальные пары не генерируются - но я не могу генерировать уникальные пары , иначе я бы получил поведение, которого я не желаю.
Есть ли способ оптимизировать этот тип двигателя столкновения?
Это руководящие принципы, которые необходимо соблюдать:
Порядок столкновения чрезвычайно важен!
- Тела должны двигаться по одному , затем проверять наличие столкновений по одному и разрешаться после движения по одному .
Тела должны иметь 3 групповых набора битов
- Группы : группы, к которым принадлежит тело
- GroupsToCheck : группы, в которых тело должно обнаружить столкновение с
- GroupsNoResolve : группы, в которых тело не должно разрешать конфликты с
- Могут быть ситуации, когда я только хочу, чтобы столкновение было обнаружено, но не разрешено
Предварительное обновление:
Предисловие : я знаю, что оптимизация этого узкого места не является необходимостью - двигатель уже очень быстрый. Я, однако, для забавных и образовательных целей, хотел бы найти способ сделать двигатель еще быстрее.
Я создаю универсальный C ++ 2D механизм обнаружения / реагирования на столкновения с упором на гибкость и скорость.
Вот очень простая схема его архитектуры:
По сути, основным классом является World
, который владеет (управляет памятью) a ResolverBase*
, a SpatialBase*
и a vector<Body*>
.
SpatialBase
это чисто виртуальный класс, который имеет дело с обнаружением столкновений в широкой фазе.
ResolverBase
это чисто виртуальный класс, который имеет дело с разрешением коллизий.
Тела общаются World::SpatialBase*
с SpatialInfo
объектами, принадлежащими самим телам.
В настоящее время существует один пространственный класс: Grid : SpatialBase
базовая фиксированная двумерная сетка. У него есть свой собственный информационный класс 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
Вывод Callgrind для последней версии: http://txtup.co/rLJgz
getBodiesToCheck()
была вызвана 5462334 раза и заняла 35,1% от всего времени профилирования (время доступа для чтения инструкций)