Quad tree vs Grid based rilevamento delle collisioni


27

Sto realizzando un gioco di tipo r cooperativo per 4 giocatori e sto per implementare il codice di rilevamento delle collisioni. Ho letto molti articoli e cose su come gestire il rilevamento delle collisioni, ma sto facendo fatica a capire cosa fare. Sembra che l'albero dei quad sia il modo più comune di procedere, ma in alcune risorse menzionano la soluzione basata sulla griglia. Per aver utilizzato una griglia per i rilevamenti in un gioco precedente, mi sento a mio agio con questo, ma in realtà è meglio di un albero quad? Non sono sicuro di quale offra le migliori prestazioni e ho anche eseguito un piccolo benchmark, non c'è molta differenza tra le due soluzioni.

Uno è migliore dell'altro? o più elegante? Non sono davvero sicuro di quale dovrei usare.

Qualunque consiglio è il benvenuto. Grazie.

Risposte:


31

La risposta giusta dipende un po 'dal gioco reale che stai progettando e la scelta dell'uno richiede davvero l'implementazione di entrambi e la profilazione per scoprire quale è più efficiente in termini di tempo o spazio sul tuo gioco specifico.

Il rilevamento della griglia sembra applicarsi solo al rilevamento di collisioni tra oggetti in movimento e uno sfondo statico. Il più grande vantaggio di ciò è che lo sfondo statico è rappresentato come un array di memoria contiguo e ogni ricerca di collisioni è O (1) con una buona località se è necessario eseguire più letture (poiché le entità coprono più di una cella nella griglia). Lo svantaggio, se lo sfondo statico è ampio, è che la griglia può essere piuttosto dispendiosa di spazio.

Se invece rappresenti lo sfondo statico come quadrifoglio, il costo delle singole ricerche aumenta, ma poiché i grandi blocchi dello sfondo occupano una piccola quantità di spazio, i requisiti di memoria diminuiscono e quindi più dello sfondo può stare nella cache. anche se ci vogliono 10 volte più letture per effettuare una ricerca in una tale struttura, se è tutto nella cache, sarà comunque 10 volte più veloce di una singola ricerca con una mancanza della cache.

Se dovessi affrontare la scelta? Andrei con l'implementazione della griglia, perché è stupido semplice da fare, meglio dedicare il mio tempo ad altri problemi più interessanti. Se noto che il mio gioco è un po 'lento, farò un po' di profilazione e vedrò cosa potrebbe essere di aiuto. Se sembra che il gioco stia impiegando molto tempo a rilevare le collisioni, proverei un'altra implementazione, come un quadrifoglio (dopo aver esaurito tutte le semplici correzioni), e scoprire se ciò ha aiutato.

Modifica: non ho idea di come il rilevamento delle collisioni della griglia si riferisca al rilevamento di collisioni di più entità mobili, ma risponderò invece come un indice spaziale (Quadtree) migliora le prestazioni di rilevamento rispetto alla soluzione iterativa. La soluzione ingenua (e in genere perfettamente perfetta) sembra in questo modo:

foreach actor in actorList:
    foreach target in actorList:
        if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

Questo ovviamente ha delle prestazioni intorno a O (n ^ 2), con n il numero di attori che sono attualmente vivi nel gioco, inclusi proiettili, astronavi e alieni. Può anche includere piccoli ostacoli statici.

Funziona in modo fantastico fintanto che il numero di tali oggetti è ragionevolmente piccolo, ma inizia a sembrare un po 'scarso quando ci sono più di alcune centinaia di oggetti da controllare. 10 oggetti producono solo 100 controlli di collisione, 100 risultati in 10.000 controlli. 1000 risultati in un milione di controlli.

Un indice spaziale (come i quadrifici) può enumerare in modo efficiente gli oggetti che raccoglie in base alle relazioni geometriche. questo cambierebbe l'algoritmo di collisione in qualcosa del genere:

foreach actor in actorList:
    foreach target in actorIndex.neighbors(actor.boundingbox):
       if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

L'efficienza di questo (presupponendo una distribuzione uniforme delle entità): di solito è O (n ^ 1.5 log (n)), poiché l'indice impiega circa i confronti log (n) per attraversare, ci saranno circa sqrt (n) vicini da confrontare e ci sono n attori da controllare. Realisticamente, tuttavia, il numero di vicini è sempre piuttosto limitato, poiché se si verifica una collisione, il più delle volte uno degli oggetti viene eliminato o spostato dalla collisione. quindi ottieni solo O (n log (n)). Per 10 entità, fai (circa) 10 confronti, per 100, fai 200, per 1000 fai 3000.

Un indice davvero intelligente può anche combinare la ricerca del vicino con l'iterazione di massa ed eseguire una richiamata su ciascuna entità intersecante. Ciò fornirà una prestazione di circa O (n), poiché l'indice viene scansionato una volta anziché interrogato n volte.


Non sono sicuro di sapere a cosa ti riferisci quando dici "sfondo statico". Quello di cui mi occupo è fondamentalmente uno sparatutto in 2D, quindi è il rilevamento delle collisioni con astronavi e alieni, proiettili e muri.
dotminic

2
Hai appena guadagnato il mio badge privato "Ottima risposta"!
Felixyz,

Questo potrebbe sembrare stupido, ma come posso effettivamente utilizzare il mio quadrifoglio per selezionare contro quali altri oggetti un oggetto dovrebbe testare le collisioni? Non sono sicuro di come sia fatto. Il che fa apparire una seconda domanda. Supponiamo di avere un oggetto nel nodo che non è un vicino di un altro nodo, ma che l'oggetto è abbastanza grande da coprire alcuni nodi, come posso verificare la presenza di una collisione effettiva, dato che immagino che l'albero potrebbe considerare che non lo è abbastanza vicino da scontrarsi con oggetti in un nodo "lontano"? Gli oggetti che non rientrano completamente in un nodo dovrebbero essere mantenuti nel nodo principale?
dotminic

2
Gli alberi di quat sono intrinsecamente non ottimali per le ricerche sovrapposte ai rettangoli. La scelta migliore per questo è di solito un R-Tree. Per i quad-alberi, se la maggior parte degli oggetti è approssimativamente simile a un punto, quindi sì, è ragionevole mantenere gli oggetti nei nodi interni ed eseguire esatti test di collisione in una ricerca confusa nei vicini. Se la maggior parte degli oggetti nell'indice sono grandi e si sovrappongono senza scontrarsi, un albero quad probabilmente è una scelta sbagliata. Se hai domande più tecniche a riguardo, dovresti prendere in considerazione l'
idea di

Tutto ciò è piuttosto confuso! grazie per le informazioni.
dotminic,

3

Ci scusiamo per la resurrezione del filo antico, ma le griglie vecchie semplici IMHO non sono usate abbastanza spesso per questi casi. Ci sono molti vantaggi in una griglia in quanto l'inserimento / rimozione delle celle è sporco a buon mercato. Non devi preoccuparti di liberare una cella poiché la griglia non ha l'obiettivo di ottimizzare le rappresentazioni sparse. Dico che dopo aver ridotto i tempi di selezione selezionare un gruppo di elementi in una base di codice legacy da oltre 1200ms fino a 20ms semplicemente sostituendo il quad-tree con una griglia. In tutta onestà, tuttavia, quel quad-albero è stato implementato in modo inadeguato, memorizzando un array dinamico separato per nodo foglia per gli elementi.

L'altro che trovo estremamente utile è che i tuoi algoritmi di rasterizzazione classici per disegnare forme possono essere utilizzati per effettuare ricerche nella griglia. Ad esempio, puoi usare la rasterizzazione della linea di Bresenham per cercare elementi che si intersecano con una linea, la rasterizzazione della linea di scansione per trovare quali celle intersecano un poligono, ecc. Dato che lavoro molto nell'elaborazione delle immagini, è davvero bello poter usare esattamente lo stesso codice ottimizzato che uso per tracciare pixel su un'immagine mentre uso per rilevare intersezioni contro oggetti in movimento in una griglia.

Detto questo, per rendere efficiente una griglia, non dovresti avere bisogno di più di 32 bit per cella di griglia. Dovresti essere in grado di memorizzare un milione di celle in meno di 4 megabyte. Ogni cella della griglia può semplicemente indicizzare il primo elemento nella cella e il primo elemento nella cella può quindi indicizzare il successivo elemento nella cella. Se stai conservando una sorta di contenitore completo con ogni singola cella, questo diventa esplosivo nell'uso della memoria e nelle allocazioni rapidamente. Invece puoi semplicemente fare:

struct Node
{
    int32_t next;
    ...
};

struct Grid
{
     vector<int32_t> cells;
     vector<Node> nodes;
};

Così:

inserisci qui la descrizione dell'immagine

Va bene, così ai contro. Devo ammetterlo, con una certa propensione e preferenza verso le reti, ma il loro principale svantaggio è che non sono scarne.

L'accesso a una cella della griglia specifica data una coordinata è a tempo costante e non richiede la discesa di un albero che è più economico, ma la griglia è densa, non sparsa, quindi potresti finire per controllare più celle del necessario. In situazioni in cui i tuoi dati sono distribuiti in modo molto scarso, la griglia potrebbe richiedere un controllo più approfondito per capire gli elementi che si intersecano, ad esempio una linea o un poligono pieno o un rettangolo o un cerchio di delimitazione. La griglia deve archiviare quella cella a 32 bit anche se è completamente vuota e quando si esegue una query di intersezione della forma, è necessario controllare quelle celle vuote se intersecano la forma.

Il vantaggio principale del quad-tree è naturalmente la sua capacità di archiviare dati sparsi e suddividere solo quanto necessario. Detto questo, è più difficile da implementare davvero bene, soprattutto se hai cose che si muovono in ogni frame. L'albero deve suddividere e liberare i nodi figlio al volo in modo molto efficiente, altrimenti si degrada in una griglia densa che spreca un sovraccarico per memorizzare i collegamenti padre- figlio. È molto fattibile implementare un quad-tree efficiente usando tecniche molto simili a quelle che ho descritto sopra per la griglia, ma in genere sarà più dispendioso in termini di tempo. E se lo fai nel modo in cui lo faccio io nella griglia, anche questo non è necessariamente ottimale, dal momento che porterebbe a una perdita nella capacità di garantire che tutti e 4 i figli di un nodo quad-tree siano archiviati in modo contiguo.

Inoltre, sia un albero a quattro alberi che una griglia non fanno un lavoro magnifico se hai un numero di elementi di grandi dimensioni che coprono gran parte dell'intera scena, ma almeno la griglia rimane piatta e non si suddivide all'ennesima potenza in quei casi . Il quad-albero dovrebbe immagazzinare elementi tra i rami e non solo foglie per gestire ragionevolmente tali casi, altrimenti vorrà suddividersi come un matto e degradare la qualità in modo estremamente rapido. Ci sono più casi patologici come questo che devi affrontare con un albero quad se vuoi che gestisca la più ampia gamma di contenuti. Ad esempio, un altro caso che può davvero inciampare in un albero quad è se si dispone di un carico di elementi coincidenti. A quel punto alcune persone ricorrono semplicemente alla definizione di un limite di profondità per il loro albero quad per impedirne la suddivisione all'infinito. La griglia ha un fascino che fa un lavoro decente,

La stabilità e la prevedibilità sono anche vantaggiose in un contesto di gioco, poiché a volte non si desidera necessariamente la soluzione più rapida possibile per il caso comune se a volte può portare a singhiozzi nei frame rate in scenari di casi rari rispetto a una soluzione che è ragionevolmente veloce tutto- ma non porta mai a tali inconvenienti e mantiene i frame rate fluidi e prevedibili. Una griglia ha quel tipo di quest'ultima qualità.

Detto questo, penso davvero che spetti al programmatore. Con cose come grid vs. quad-tree o octree vs. kd-tree vs. BVH, il mio voto è sullo sviluppatore più prolifico con un record per la creazione di soluzioni molto efficienti, indipendentemente dalla struttura dei dati che utilizza. C'è anche molto a livello micro, come multithreading, SIMD, layout di memoria compatibili con la cache e schemi di accesso. Alcune persone potrebbero considerare quei micro ma non hanno necessariamente un micro impatto. Queste cose potrebbero fare la differenza 100 volte da una soluzione all'altra. Nonostante ciò, se mi venissero dati personalmente alcuni giorni e mi fosse stato detto che dovevo implementare una struttura di dati per accelerare rapidamente il rilevamento delle collisioni di elementi che si muovevano in ogni frame, farei meglio in quel breve tempo implementando una griglia piuttosto che un quad -albero.

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.