Se questa è la prima volta su questa domanda, ti suggerisco di leggere prima la parte di pre-aggiornamento di seguito, quindi questa parte. Ecco una sintesi del problema, però:
Fondamentalmente, ho un motore di rilevamento e risoluzione delle collisioni con un sistema di partizionamento spaziale della griglia in cui sono importanti l'ordine di collisione e i gruppi di collisione. Un corpo alla volta deve muoversi, quindi rilevare la collisione, quindi risolvere le collisioni. Se muovo tutti i corpi contemporaneamente, quindi generi possibili coppie di collisione, è ovviamente più veloce, ma la risoluzione si interrompe perché l'ordine di collisione non viene rispettato. Se muovo un corpo alla volta, sono costretto a chiedere ai corpi di controllare le collisioni e questo diventa un problema ^ 2. Inserisci i gruppi nel mix e puoi immaginare perché diventa molto lento molto velocemente con molti corpi.
Aggiornamento: ho lavorato molto duramente su questo, ma non sono riuscito a ottimizzare nulla.
Ho anche scoperto un grosso problema: il mio motore dipende dall'ordine di collisione.
Ho provato un'implementazione della generazione unica delle coppie di collisioni , che ha sicuramente accelerato molto di molto, ma ha rotto l' ordine di collisione .
Lasciatemi spiegare:
nel mio progetto originale (non generare coppie), ciò accade:
- si muove un solo corpo
- dopo che si è mosso, rinfresca le sue cellule e ottiene i corpi contro i quali si scontra
- se si sovrappone a un corpo che deve risolvere, risolvere la collisione
questo significa che se un corpo si muove e colpisce un muro (o qualsiasi altro corpo), solo il corpo che si è mosso risolverà la sua collisione e l'altro corpo non sarà interessato.
Questo è il comportamento che desidero .
Capisco che non è comune per i motori fisici, ma ha molti vantaggi per i giochi in stile retrò .
nel solito disegno a griglia (che genera coppie uniche), ciò accade:
- tutti i corpi si muovono
- dopo che tutti i corpi si sono mossi, aggiorna tutte le celle
- generare coppie di collisioni uniche
- per ogni coppia, gestire il rilevamento e la risoluzione delle collisioni
in questo caso una mossa simultanea avrebbe potuto causare la sovrapposizione di due corpi, e si risolveranno allo stesso tempo - questo fa sì che i corpi "si spingano l'un l'altro" e rompe la stabilità della collisione con più corpi
Questo comportamento è comune per i motori fisici, ma non è accettabile nel mio caso .
Ho anche trovato un altro problema, che è importante (anche se non è probabile che accada in una situazione del mondo reale):
- considerare i corpi dei gruppi A, B e W
- A si scontra e si risolve contro W e A
- B si scontra e si risolve contro W e B
- A non fa nulla contro B
- B non fa nulla contro A
può esserci una situazione in cui molti corpi A e corpi B occupano la stessa cellula - in quel caso, c'è molta iterazione non necessaria tra corpi che non devono reagire tra loro (o rilevare solo la collisione ma non risolverli) .
Per 100 corpi che occupano la stessa cella, sono 100 ^ 100 iterazioni! Ciò accade perché non vengono generate coppie uniche , ma non riesco a generare coppie uniche , altrimenti otterrei un comportamento che non desidero.
C'è un modo per ottimizzare questo tipo di motore di collisione?
Queste sono le linee guida che devono essere rispettate:
L'ordine di collisione è estremamente importante!
- I corpi devono muoversi uno alla volta , quindi controllare le collisioni uno alla volta e risolvere dopo il movimento uno alla volta .
I corpi devono avere 3 bitset di gruppo
- Gruppi : gruppi a cui appartiene il corpo
- GroupsToCheck : gruppi contro i quali il corpo deve rilevare la collisione
- GroupsNoResolve : gruppi contro i quali il corpo non deve risolvere la collisione
- Ci possono essere situazioni in cui voglio solo rilevare una collisione ma non risolverla
Pre-update:
Premessa : sono consapevole che l'ottimizzazione di questo collo di bottiglia non è una necessità: il motore è già molto veloce. Tuttavia, per scopi educativi e divertenti, mi piacerebbe trovare un modo per rendere il motore ancora più veloce.
Sto creando un motore di rilevamento / risposta collisione 2D C ++ per scopi generali, con particolare attenzione alla flessibilità e alla velocità.
Ecco un diagramma di base della sua architettura:
Fondamentalmente, la classe principale è World
, che possiede (gestisce la memoria) di a ResolverBase*
, a SpatialBase*
e a vector<Body*>
.
SpatialBase
è una pura classe virtuale che si occupa del rilevamento di collisioni a fase larga.
ResolverBase
è una pura classe virtuale che si occupa della risoluzione delle collisioni.
I corpi comunicano World::SpatialBase*
con gli SpatialInfo
oggetti, di proprietà dei corpi stessi.
Attualmente esiste una classe spaziale: Grid : SpatialBase
che è una griglia 2D fissa di base. Essa ha il proprio Informazioni classe, GridInfo : SpatialInfo
.
Ecco come appare la sua architettura:
La Grid
classe possiede un array 2D di Cell*
. La Cell
classe contiene una raccolta di (non di proprietà) Body*
: a vector<Body*>
che contiene tutti i corpi che si trovano nella cella.
GridInfo
gli oggetti contengono anche puntatori non proprietari delle celle in cui si trova il corpo.
Come ho già detto, il motore si basa su gruppi.
Body::getGroups()
restituisce unostd::bitset
di tutti i gruppi di cui fa parte il corpo.Body::getGroupsToCheck()
restituisce unostd::bitset
di tutti i gruppi contro i quali il corpo deve verificare la collisione.
I corpi possono occupare più di una singola cella. GridInfo memorizza sempre puntatori non proprietari sulle celle occupate.
Dopo che un singolo corpo si muove, si verifica il rilevamento delle collisioni. Presumo che tutti i corpi siano scatole di delimitazione allineate agli assi.
Come funziona il rilevamento di collisioni a fase larga:
Parte 1: aggiornamento delle informazioni spaziali
Per ciascuno Body
body
:
- Vengono calcolate la cella occupata più in alto a sinistra e le celle occupate più in basso a destra.
- Se differiscono dalle celle precedenti,
body.gridInfo.cells
vengono cancellate e riempite con tutte le celle occupate dal corpo (2D per loop dalla cella più in alto a sinistra alla cella in basso a destra).
body
ora è garantito per sapere quali celle occupa.
Parte 2: controlli di collisione effettivi
Per ciascuno Body
body
:
body.gridInfo.handleCollisions
è chiamato:
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 collisione viene quindi risolta per ogni corpo in
bodiesToResolve
.Questo è tutto.
Quindi, da un po 'di tempo cerco di ottimizzare questo rilevamento di collisioni a fase larga. Ogni volta che provo qualcos'altro rispetto all'attuale architettura / configurazione, qualcosa non va come previsto o faccio ipotesi sulla simulazione che in seguito si dimostreranno false.
La mia domanda è: come posso ottimizzare la fase larga del mio motore di collisione ?
Esiste una sorta di ottimizzazione magica del C ++ che può essere applicata qui?
L'architettura può essere riprogettata per consentire maggiori prestazioni?
- Implementazione effettiva: SSVSCollsion
- Body.h , Body.cpp
- World.h , World.cpp
- Grid.h , Grid.cpp
- Cell.h , Cell.cpp
- GridInfo.h , GridInfo.cpp
Uscita Callgrind per l'ultima versione: http://txtup.co/rLJgz
getBodiesToCheck()
stata chiamata 5462334 volte e ha impiegato il 35,1% dell'intero tempo di profilazione (tempo di accesso alla lettura delle istruzioni)