Si c'est votre première fois sur cette question, je vous suggère de lire d'abord la partie pré-mise à jour ci-dessous, puis cette partie. Voici cependant une synthèse du problème:
Fondamentalement, j'ai un moteur de détection et de résolution de collision avec un système de partitionnement de grille où les groupes d'ordre de collision et de collision sont importants. Un corps à la fois doit se déplacer, puis détecter la collision, puis résoudre les collisions. Si je déplace tous les corps en même temps, puis que je génère des paires de collisions possibles, c'est évidemment plus rapide, mais la résolution se brise car l'ordre de collision n'est pas respecté. Si je déplace un corps à la fois, je suis obligé de faire contrôler les collisions par les corps et cela devient un problème ^ 2. Mettez les groupes dans le mix, et vous pouvez imaginer pourquoi ça devient très lent très vite avec beaucoup de corps.
Mise à jour: j'ai travaillé très dur sur cela, mais je n'ai pas réussi à optimiser quoi que ce soit.
J'ai également découvert un gros problème: mon moteur dépend de l'ordre de collision.
J'ai essayé une implémentation de génération unique de paires de collisions , qui accélère certainement beaucoup, mais a cassé l' ordre de collision .
Laissez-moi expliquer:
dans ma conception d'origine (ne pas générer de paires), cela se produit:
- un seul corps bouge
- après avoir bougé, il rafraîchit ses cellules et obtient les corps contre lesquels il entre en collision
- s'il chevauche un corps contre lequel il doit se résoudre, résoudre la collision
cela signifie que si un corps bouge et frappe un mur (ou tout autre corps), seul le corps qui a bougé résoudra sa collision et l'autre corps ne sera pas affecté.
C'est le comportement que je désire .
Je comprends que ce n'est pas courant pour les moteurs physiques, mais cela a beaucoup d'avantages pour les jeux de style rétro .
dans la conception de grille habituelle (générant des paires uniques), cela se produit:
- tous les corps bougent
- après que tous les corps se soient déplacés, rafraîchissez toutes les cellules
- générer des paires de collisions uniques
- pour chaque paire, gérer la détection et la résolution des collisions
dans ce cas, un mouvement simultané aurait pu entraîner le chevauchement de deux corps, et ils se résoudraient en même temps - ce qui fait que les corps se «repoussent» et rompent la stabilité de la collision avec plusieurs corps
Ce comportement est courant pour les moteurs physiques, mais il n'est pas acceptable dans mon cas .
J'ai également trouvé un autre problème, qui est majeur (même s'il n'est pas susceptible de se produire dans une situation réelle):
- considérer les corps des groupes A, B et W
- A entre en collision et se résout contre W et A
- B entre en collision et se résout contre W et B
- A ne fait rien contre B
- B ne fait rien contre A
il peut y avoir une situation où beaucoup de corps A et de corps B occupent la même cellule - dans ce cas, il y a beaucoup d'itérations inutiles entre les corps qui ne doivent pas réagir les uns aux autres (ou seulement détecter une collision mais pas les résoudre) .
Pour 100 corps occupant la même cellule, c'est 100 ^ 100 itérations! Cela se produit car des paires uniques ne sont pas générées - mais je ne peux pas générer de paires uniques , sinon j'obtiendrais un comportement que je ne souhaite pas.
Existe-t-il un moyen d'optimiser ce type de moteur de collision?
Ce sont les lignes directrices qui doivent être respectées:
L'ordre de collision est extrêmement important!
- Les corps doivent se déplacer un par un , puis vérifier les collisions un par un et se résoudre après le mouvement un par un .
Les corps doivent avoir 3 groupes de bits de groupe
- Groupes : groupes auxquels appartient le corps
- GroupsToCheck : groupes contre lesquels le corps doit détecter une collision
- GroupsNoResolve : groupes contre lesquels le corps ne doit pas résoudre la collision
- Il peut y avoir des situations où je veux seulement qu'une collision soit détectée mais pas résolue
Pré-mise à jour:
Avant - propos : Je suis conscient que l'optimisation de ce goulot d'étranglement n'est pas une nécessité - le moteur est déjà très rapide. Cependant, pour des raisons amusantes et éducatives, j'aimerais trouver un moyen de rendre le moteur encore plus rapide.
Je crée un moteur de détection / réponse de collision C ++ 2D à usage général, en mettant l'accent sur la flexibilité et la vitesse.
Voici un schéma très basique de son architecture:
Fondamentalement, la classe principale est World
, qui possède (gère la mémoire) de a ResolverBase*
, a SpatialBase*
et a vector<Body*>
.
SpatialBase
est une classe virtuelle pure qui traite de la détection de collision à large phase.
ResolverBase
est une classe virtuelle pure qui traite de la résolution des collisions.
Les corps communiquent World::SpatialBase*
avec les SpatialInfo
objets appartenant aux corps eux-mêmes.
Il existe actuellement une classe spatiale:, Grid : SpatialBase
qui est une grille 2D fixe de base. Il a sa propre classe d'information, GridInfo : SpatialInfo
.
Voici à quoi ressemble son architecture:
La Grid
classe possède un tableau 2D de Cell*
. La Cell
classe contient une collection de (n'appartenant pas) Body*
: a vector<Body*>
qui contient tous les corps qui se trouvent dans la cellule.
GridInfo
les objets contiennent également des pointeurs n'appartenant pas aux cellules dans lesquelles se trouve le corps.
Comme je l'ai dit précédemment, le moteur est basé sur des groupes.
Body::getGroups()
renvoie unstd::bitset
de tous les groupes dont le corps fait partie.Body::getGroupsToCheck()
renvoie unstd::bitset
de tous les groupes contre lesquels le corps doit vérifier la collision.
Les corps peuvent occuper plus d'une seule cellule. GridInfo stocke toujours des pointeurs non propriétaires vers les cellules occupées.
Après le déplacement d'un seul corps, la détection de collision se produit. Je suppose que tous les corps sont des boîtes englobantes alignées sur l'axe.
Fonctionnement de la détection de collisions à large phase:
Partie 1: mise à jour des informations spatiales
Pour chacun Body
body
:
- Les cellules occupées en haut à gauche et les cellules occupées en bas à droite sont calculées.
- S'ils diffèrent des cellules précédentes,
body.gridInfo.cells
est effacé et rempli de toutes les cellules que le corps occupe (2D pour la boucle de la cellule la plus en haut à gauche à la cellule en bas à droite).
body
est désormais assuré de savoir quelles cellules il occupe.
Partie 2: contrôles de collision réels
Pour chacun Body
body
:
body.gridInfo.handleCollisions
est appelé:
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);
}
La collision est alors résolue pour chaque corps
bodiesToResolve
.C'est ça.
Donc, j'essaie d'optimiser cette détection de collision à large phase depuis un bon moment maintenant. Chaque fois que j'essaie autre chose que l'architecture / la configuration actuelle, quelque chose ne se passe pas comme prévu ou je fais des suppositions sur la simulation qui s'avèrent plus tard fausses.
Ma question est: comment puis-je optimiser la phase large de mon moteur de collision ?
Y a-t-il une sorte d'optimisation magique C ++ qui peut être appliquée ici?
L'architecture peut-elle être repensée afin de permettre plus de performances?
- Implémentation réelle: SSVSCollsion
- Body.h , Body.cpp
- World.h , World.cpp
- Grid.h , Grid.cpp
- Cell.h , Cell.cpp
- GridInfo.h , GridInfo.cpp
Sortie Callgrind pour la dernière version: http://txtup.co/rLJgz
getBodiesToCheck()
été appelée 5462334 fois, et a pris 35,1% de tout le temps de profilage (temps d'accès en lecture aux instructions)