Esiste un modo per aumentare l'efficienza del controllo delle collisioni di un sistema di n oggetti?


9

Sto realizzando un gioco composto da molti oggetti su schermo, uno dei quali è il giocatore. Devo sapere quali oggetti si scontrano ad ogni iterazione.

Ho fatto qualcosa del genere:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

Questo ha O (n ^ 2), che mi è stato detto che è male. Come posso farlo in modo più efficiente, è anche possibile? Lo sto facendo in Javascript, e di solito n sarà inferiore a 30, sarà un problema se questo rimane lo stesso?


3
Hai provato a eseguire il codice per vedere come funziona?
thedaian,

No, non ho, sto solo pensando che sia male a causa della O (n ^ 2).
jcora,

1
Solo 30 oggetti? Avrei raccomandato il partizionamento spaziale, ma sarebbe inutile con solo 30 oggetti. Ci sono alcune ottimizzazioni minori che altri hanno sottolineato, ma sono tutte ottimizzazioni minori sulla scala di cui stai parlando.
John McDonald,

Risposte:


16

Con solo 30 oggetti al massimo, non dovresti aver bisogno di molta ottimizzazione se non quella di non controllare le stesse due coppie una contro l'altra più di una volta per fotogramma. Di cui verrà coperto l'esempio di codice riportato di seguito. Ma se sei interessante in diverse ottimizzazioni che un motore fisico userebbe, continua a leggere il resto di questo post.

Ciò di cui avrai bisogno è un'implementazione del partizionamento spaziale , come un Octree (per i giochi 3D) o Quadtree (per giochi 2D). Questi suddividono il mondo in sottosezioni, e quindi ogni sottosezione viene ulteriormente suddivisa nello stesso maniero, fino a quando non si suddividono in una dimensione minima. Ciò consente di verificare rapidamente quali altri oggetti si trovano nella stessa regione del mondo di un'altra, il che limita la quantità di collisioni che è necessario verificare.

Oltre al partizionamento spaziale, consiglierei di creare un AABB ( riquadro di selezione allineato agli assi ) per ciascuno dei tuoi oggetti di fisica. Ciò consente di controllare l'AABB di un oggetto rispetto a un altro, che è molto più veloce di un controllo dettagliato per poli tra oggetti.

Questo può essere fatto un ulteriore passo avanti per oggetti fisici complessi o di grandi dimensioni, in cui è possibile suddividere la mesh fisica stessa, dando ad ogni sottoforma il proprio AABB che è possibile verificare solo se due AABB di oggetti si sovrappongono.

La maggior parte dei motori di fisica disattiverà la simulazione di fisica attiva sui corpi fisici una volta che si fermeranno. Quando un corpo fisico è disattivato, deve solo controllare la collisione con il suo AABB per ogni fotogramma e se qualcosa si scontra con l'AABB, allora si riattiverà e farà un controllo di collisione più granulare. Ciò mantiene bassi i tempi di simulazione.

Inoltre, molti motori fisici usano le "isole di simulazione", dove un gruppo di corpi fisici vicini è raggruppato. Se tutto nell'isola di simulazione è a riposo, l'isola di simulazione stessa si disattiva. Il vantaggio dell'isola di simulazione è che tutti i corpi al suo interno possono smettere di controllare le collisioni una volta che l'isola è inattiva, e l'unico controllo di ogni fotogramma è vedere se qualcosa è entrato nella AABB dell'isola. Solo una volta che qualcosa entra nell'ABAB dell'isola, ciascuno dei corpi all'interno dell'isola dovrà controllare le collisioni. L'isola di simulazione si riattiva anche se un corpo al suo interno inizia a muoversi di nuovo da solo. Se un corpo si sposta abbastanza lontano dal centro del gruppo, viene rimosso dall'isola.

Alla fine ti rimane qualcosa di simile (in pseudo-codice):

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

Consiglierei anche di non avere così tanti loop all'interno di loop come questo, l'esempio sopra è stato solo così hai avuto l'idea, lo spezzerei in più funzioni che ti danno la stessa funzionalità di qualcosa come quello mostrato sopra.

Inoltre, assicurarsi di non alterare il contenitore AABBNodes durante il ciclo, in quanto ciò potrebbe significare controlli di collisione persi. Può sembrare un senso comune, ma rimarrai sorpreso dalla facilità con cui le cose che reagiscono alle collisioni causano cambiamenti che non ti aspetti. Ad esempio, se una collisione ha causato una modifica della posizione di uno degli oggetti in collisione per rimuoverli dall'AABB del nodo Octree che si stava verificando, potrebbe alterare quel contenitore. Per risolvere questo problema, ti consiglio di tenere un elenco di tutti gli eventi di collisione che si verificano durante i controlli, quindi dopo aver completato tutti i controlli esegui l'elenco e invia tutti gli eventi di collisione.


4
Risposta molto coerente con precisioni tecniche utili e utili per aprire la mente del lettore ai metodi esistenti. +1
Valkea,

Cosa succede se devo rimuovere l'oggetto in collisione? Posso modificare il contenitore? Intendo rimuoverlo dal contenitore in quanto non ho più bisogno dell'oggetto perché è "distrutto". Ho bisogno di un altro loop per eseguire gli eventi di collisione se non lo rimuovo durante il rilevamento delle collisioni.
Newguy

La rimozione dell'oggetto in collisione va bene, ma consiglierei di attendere fino a quando non è stato effettuato il passaggio di collisione sull'intera simulazione. Di solito si contrassegna solo gli oggetti che devono essere rimossi o si genera un elenco di oggetti da rimuovere, quindi dopo aver eseguito la simulazione della collisione si applicano tali modifiche.
Nic Foster,

4

Il tuo esempio testa ogni coppia di oggetti più volte.

Facciamo un esempio molto semplice con un array contenente 0,1,2,3

Con il tuo codice ottieni questo:

  • Al loop 0 testerai contro 1, 2 e 3
  • Al loop 1 si esegue il test contro 0, 2 e 3 ===> (0-1 già testato)
  • Al loop 2 si esegue il test contro 0, 1 e 3 ===> (0-2 / 1-2 già testato)
  • Al loop 3 si esegue il test con 0, 1 e 2 ===> (0-3 / 1-3 / 2-3 già testato)

Ora vediamo il seguente codice:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

Se utilizziamo nuovamente l'array contenente 0,1,2,3, abbiamo il seguente comportamento:

  • Al loop 0 si verifica contro 1, 2, 3
  • Al loop 1 si verifica contro 2, 3
  • Al loop 2 si verifica contro 3
  • Al loop 3 si esegue il test contro nulla

Con il secondo algoritmo abbiamo 6 prove di collisione mentre la precedente ha richiesto 12 prove di collisione.


Questo algoritmo fa N(N-1)/2confronti che sono ancora prestazioni O (N ^ 2).
Kai,

1
Bene con 30 oggetti come richiesto ciò significa 465 test di collisione contro 870 ... è probabilmente simile dal tuo punto di vista, ma non dal mio. Inoltre, la soluzione offerta nell'altra risposta è esattamente lo stesso algoritmo :)
Valkea,

1
@Valkea: Beh, in parte lo è. :)
Nic Foster,

@NicFoster: sì hai ragione;) Stavo parlando rigorosamente del test di collisione tra gli oggetti selezionati, non della parte di partizionamento dell'algoritmo che è ovviamente un'aggiunta molto preziosa che non avevo nemmeno pensato di aggiungere nel mio esempio quando Lo stavo scrivendo.
Valkea,

Si chiama ammortamento? Comunque grazie!
jcora,

3

Progetta il tuo algoritmo in base alle tue esigenze, ma incapsula i dettagli di implementazione. Anche in Javascript, si applicano i concetti base di OOP.

Perché N =~ 30, O(N*N)non è un problema, e la tua ricerca lineare sarà probabilmente altrettanto veloce di qualsiasi alternativa là fuori. Ma non vuoi inserire nel tuo codice ipotesi hard-code. In pseudocodice, avresti un'interfaccia

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

Descrive cosa può fare il tuo elenco di elementi. Quindi è possibile scrivere una classe ArrayContainer che implementa questa interfaccia. In Javascript, il codice sarebbe simile al seguente:

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

Ed ecco un codice di esempio che crea 300 riquadri di delimitazione e ottiene tutte le intersezioni. Se hai implementato correttamente ArrayContainer e QuadTreeContainer, l'unica cosa che avrebbe bisogno di cambiare il codice è il cambiamento var allMyObjects = new ArrayContainer()a var allMyObjects = QuadTreeContainer().

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

Sono andato avanti e ho implementato l'implementazione per lo standard ArrayContainer qui:

http://jsfiddle.net/SKkN5/1/


Nota: questa risposta è stata motivata dalla denuncia di Bane secondo cui la sua base di codice stava diventando troppo grande, disordinata e difficile da gestire. Sebbene non aggiunga molto alla discussione sull'uso di un array contro un albero, spero che sia una risposta pertinente su come in particolare possa organizzare meglio il suo codice.
Jimmy,

2

Dovresti anche considerare i tipi di oggetti che possono scontrarsi sensibilmente.

Ad esempio, probabilmente il giocatore deve essere controllato per la collisione con tutto tranne i suoi proiettili. Tuttavia, i nemici potrebbero aver bisogno solo di controllare i proiettili del giocatore. Quasi sicuramente i proiettili non devono scontrarsi.

Per implementarlo in modo efficiente, probabilmente vorrai mantenere elenchi separati di oggetti, uno per tipo di oggetto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.