Come trovare continuamente tutte le entità all'interno di un raggio in modo efficiente?


14

Ho un numero molto grande di entità (unità). Ad ogni passo, ogni unità deve conoscere le posizioni di tutte le unità vicine (la distanza è inferiore alla R costante ). Tutte le unità si muovono continuamente. Questo è in 3D.

In media, ci sarà l'1% del conteggio totale delle unità vicino a qualsiasi altra unità data con i vincoli dati.

Come posso farlo in modo efficiente, senza bruteforcing?


7
Avrai bisogno di un sistema di partizionamento spaziale: en.wikipedia.org/wiki/Space_partitioning
Tetrad

Risposte:


15

Usa uno dei più comuni algoritmi di partizionamento dello spazio, come Quadtree, Octree, albero BSP o anche un semplice sistema a griglia. Ognuno ha i propri pro e contro per ogni specifico scenario. Puoi leggere di più su di loro in questi libri .

Generalmente (o almeno così ho sentito, non ho troppa familiarità con il ragionamento alla base di questo) un Quadtree o Octree si adatta meglio agli ambienti esterni, mentre l'albero BSP si adatta meglio alle scene indoor. E la scelta tra l'uso di un Quadtree o un Octree dipende da quanto è piatto il tuo mondo. Se c'è una piccola variazione nell'asse Y usando un Octree sarebbe uno spreco. Un Octree è fondamentalmente un Quadtree con una dimensione aggiuntiva.

Infine, non trascurare la semplicità della soluzione Grid. Molte persone ignorano che una semplice griglia a volte può essere sufficiente (e persino più efficiente) per i loro problemi e invece passano direttamente a una soluzione più complessa.

L'uso di una griglia consiste semplicemente nel dividere il mondo in regioni equidistanti e nel conservare le entità nella regione appropriata del mondo. Quindi, data una posizione, trovare le entità vicine sarebbe una questione di iterare sulle regioni che intersecano il raggio di ricerca.

Diciamo che il tuo mondo variava da (-1000, -1000) a (1000, 1000) nel piano XZ. Ad esempio, potresti dividerlo in una griglia 10x10, in questo modo:

var grid = new List<Entity>[10, 10];

Quindi posizioneresti le entità nelle loro celle appropriate nella griglia. Ad esempio un'entità con XZ (-1000, -1000) cadrebbe sulla cella (0,0) mentre un'entità con XZ (1000, 1000) cadrebbe sulla cella (9, 9). Quindi, data una posizione e un raggio nel mondo, potresti determinare quali celle sono intersecate da questo "cerchio" e iterare solo su quelle, con un semplice doppio per.

In ogni caso, cerca tutte le alternative e scegli quella che sembra adattarsi meglio al tuo gioco. Ammetto di non essere ancora abbastanza informato sull'argomento per decidere quale degli algoritmi sarebbe meglio per te.

Modifica Trovato su un altro forum e potrebbe esserti di aiuto nella decisione:

Le griglie funzionano meglio quando gli oggetti della stragrande maggioranza si adattano all'interno di un quadrato della griglia e la distribuzione è abbastanza omogenea. Al contrario, i quadrifici funzionano quando gli oggetti hanno dimensioni variabili o sono raggruppati in piccole aree.

Data la tua vaga descrizione del problema, mi appoggio anche alla soluzione della griglia (ovvero, supponendo che le unità siano piccole e distribuite in modo abbastanza omogeneo).


Grazie per la risposta dettagliata. Sì, sembra semplice, la soluzione Grid è abbastanza buona per me.
OCyril,

0

Ho scritto questo qualche tempo fa. Ora è su un sito commerciale, ma puoi ottenere la fonte per uso personale gratuitamente. Potrebbe essere eccessivo ed è scritto in Java, ma è ben documentato, quindi non dovrebbe essere troppo difficile da tagliare e riscrivere in un'altra lingua. In pratica utilizza un Octree, con modifiche per gestire oggetti veramente grandi e multi-threading.

Ho trovato un Octree che offriva la migliore combinazione di flessibilità ed efficienza. Ho iniziato con una griglia, ma era impossibile dimensionare correttamente i quadrati e grandi patch di quadrati vuoti hanno esaurito lo spazio e la potenza di calcolo per niente. (E questo era solo in 2 dimensioni.) Il mio codice gestisce le query da più thread, il che aggiunge molto alla complessità, ma la documentazione dovrebbe aiutarti a ovviare a questo se non ne hai bisogno.


0

Per aumentare la tua efficienza, prova a rifiutare banalmente il 99% di "unità" che non sono vicine all'unità bersaglio usando un check box delimitato molto economico. E spero che tu possa farlo senza strutturare i tuoi dati spazialmente. Pertanto, se tutte le unità sono archiviate in una struttura dati piatta, è possibile provare a attraversarla dall'inizio alla fine e in primo luogo verificare che l'unità corrente sia al di fuori del riquadro di delimitazione dell'unità di interesse.

Definire un riquadro di delimitazione sovradimensionato per l'unità di interesse in modo tale da poter respingere in modo sicuro oggetti che non hanno alcuna possibilità di essere considerati "vicini". Il controllo per esclusione da un rettangolo di selezione potrebbe essere più economico di un controllo del raggio. Tuttavia su alcuni sistemi in cui questo è stato testato è stato riscontrato che non era il caso. I due si esibiscono quasi allo stesso modo. Questo è stato modificato dopo molti dibattiti di seguito.

Primo: clip 2D delimitatore.

// returns true if the circle supplied is completely OUTSIDE the bounding box, rectClip
bool canTrivialRejectCircle(Vertex2D& vCentre, WorldUnit radius, Rect& rectClip) {
  if (vCentre.x + radius < rectClip.l ||
    vCentre.x - radius > rectClip.r ||
    vCentre.y + radius < rectClip.b ||
    vCentre.y - radius > rectClip.t)
    return true;
  else
    return false;
}

Rispetto a qualcosa del genere (in 3D):

BOOL bSphereTest(CObject3D* obj1, CObject3D* obj2 )
{
  D3DVECTOR relPos = obj1->prPosition - obj2->prPosition;
  float dist = relPos.x * relPos.x + relPos.y * relPos.y + relPos.z * relPos.z;
  float minDist = obj1->fRadius + obj2->fRadius;
  return dist <= minDist * minDist;
}.

Se l'oggetto non viene banalmente rifiutato, si esegue un test di collisione più costoso e accurato. Ma stai solo cercando la vicinanza, quindi il test della sfera è adatto a questo, ma solo per l'1% degli oggetti che sopravvivono al banale rifiuto.

Questo articolo supporta la scatola per il rifiuto banale. http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

Se questo approccio lineare non ti offre le prestazioni di cui hai bisogno, potrebbe essere richiesta una struttura gerarchica di dati come quella degli altri poster. Vale la pena considerare gli alberi a R. Supportano i cambiamenti dinamici. Sono i BTree del mondo spaziale.

Non volevo che tu avessi tutta quella fatica di introdurre una tale complessità se potessi evitarlo. Inoltre, che dire del costo per mantenere aggiornati questi complessi dati mentre gli oggetti si spostano più volte al secondo?

Tenere presente che una griglia è una struttura di dati spaziali profondi a un livello. Questo limite significa che non è veramente scalabile. Man mano che il mondo cresce di dimensioni, aumenta anche il numero di cellule che devi coprire. Alla fine quel numero di celle stesso diventa un problema di prestazioni. Tuttavia, per un mondo di determinate dimensioni, ti darà un enorme aumento delle prestazioni senza partizionamento spaziale.


1
L'OP ha affermato espressamente di voler evitare un approccio a forza bruta, che è esattamente ciò che descrivi nel tuo primo paragrafo. Inoltre, come pensi che un controllo del riquadro di delimitazione sia più economico di un controllo della sfera di delimitazione ?! È solo sbagliato.
notlesh

Sì, lo so che vuole evitare la forza bruta che sarebbe evitata andando allo sforzo di introdurre una struttura gerarchica di dati nella sua applicazione. Ma quello potrebbe essere un grande sforzo. Se non vuole ancora farlo, può provare l'approccio lineare che è forza bruta ma potrebbe non funzionare così male se la sua lista non è molto grande. Proverò a modificare il codice sopra per inserire nella mia banale casella di delimitazione la funzione di rifiuto banale. Non penso di aver sbagliato.
Ciaran,

Il collegamento a GDnet è interrotto, ma il test della sfera canonica è molto semplice, molto economico e non si ramifica:inside = (dot(p-p0, p-p0) <= r*r)
Lars Viklund

Invece ho incollato il codice sopra. Sembra tutt'altro che economico rispetto al riquadro di delimitazione.
Ciaran,

1
@Ciaran Sinceramente, l'articolo sembra davvero male. Prima di tutto non esegue i test con dati realistici, ma utilizza sempre gli stessi valori più e più volte. Non qualcosa che incontrerai in uno scenario reale. E no, secondo l'articolo il BB è più veloce solo quando non c'è collisione (es. Il controllo fallisce alla prima ifaffermazione). Inoltre non molto realistico. Ma onestamente, se stai iniziando a ottimizzare cose come questa, allora stai sicuramente iniziando dal posto sbagliato.
Bummzack,

0

Devo rendere questa una risposta perché non ho i punti per commentare o votare. Per il 99% delle persone che pongono questa domanda, la soluzione è un rettangolo di selezione, come descritto da Ciaran. In un linguaggio compilato, rifiuterà 100.000 unità irrilevanti in un batter d'occhio. Ci sono molte spese generali legate alle soluzioni non a forza bruta; con numeri più piccoli (diciamo meno di 1000) saranno più costosi in termini di tempo di elaborazione rispetto al controllo della forza bruta. E impiegheranno molto più tempo a programmare.

Non sono sicuro di cosa significhi "un numero molto elevato" nella domanda, o cosa significhino altre persone che cercano qui risposte. Sospetto che i miei numeri sopra siano conservativi e potrebbero essere moltiplicati per 10; Personalmente sono piuttosto prevenuto dalle tecniche di forza bruta e sono seriamente infastidito dal loro funzionamento. Ma non vorrei che qualcuno con, diciamo, 10.000 unità perdesse tempo con una soluzione stravagante quando poche linee di codice veloci avrebbero funzionato. Possono sempre avere fantasia in seguito, se necessario.

Inoltre, vorrei notare che un controllo della sfera di delimitazione richiede una moltiplicazione in cui il riquadro di delimitazione non lo fa. La moltiplicazione, per sua natura, richiede più volte che l'addizione e i confronti. Ci sarà sicuramente una combinazione di lingua, sistema operativo e hardware in cui il controllo della sfera sarà più veloce di un controllo casella, ma nella maggior parte dei luoghi e delle volte il controllo casella deve essere più veloce, anche se la sfera rifiuta alcune unità irrilevanti la scatola accetta. (E dove la sfera è più veloce, è molto probabile che una nuova versione del compilatore / interprete / ottimizzatore lo cambi.)


Mentre non c'è nulla di sbagliato nella tua risposta, non stai rispondendo alla domanda. È stato specificamente richiesto un approccio "non bruteforce". Inoltre, sembra che tu abbia ripetuto ciò che Ciaran ha già scritto e abbiamo avuto una lunga discussione-commento sui test AABB vs. Circle. La differenza di prestazioni è semplicemente irrilevante. Meglio scegliere un volume limite che si adatti alla maggior parte dei candidati alla collisione, poiché ridurrà la quantità di test in fase stretta effettivi .. che avranno un impatto maggiore sulle prestazioni complessive.
Bummzack,
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.