Il partizionamento dello spazio quando tutto si muove


8

sfondo

Insieme a un amico sto lavorando a un gioco 2D ambientato nello spazio. Per renderlo il più coinvolgente e interattivo possibile, vogliamo che ci siano migliaia di oggetti che fluttuano liberamente intorno, alcuni raggruppati insieme, altri alla deriva nello spazio vuoto.

Sfida

Per liberare il motore di rendering e fisica dobbiamo implementare una sorta di partizionamento spaziale. Ci sono due sfide che dobbiamo superare. La prima sfida è che tutto si sta muovendo, quindi la ricostruzione / aggiornamento della struttura dei dati deve essere estremamente economica poiché dovrà essere eseguita in ogni frame. La seconda sfida è la distribuzione degli oggetti, come detto prima potrebbero esserci gruppi di oggetti insieme e vasti frammenti di spazio vuoto e, a peggiorare le cose, non c'è limite allo spazio.

Tecnologie esistenti

Ho esaminato tecniche esistenti come alberi BSP, QuadTree, alberi kd e persino alberi R, ma per quanto ne so, queste strutture di dati non si adattano perfettamente dall'aggiornamento di molti oggetti che si sono spostati su altre celle è relativamente costoso.

Quello che ho provato

Ho preso la decisione che ho bisogno di una struttura di dati più orientata al rapido inserimento / aggiornamento piuttosto che alla restituzione del minor numero possibile di hit a seguito di una query. A tale scopo ho reso implicite le celle in modo che ogni oggetto, data la sua posizione, possa calcolare in quale cella dovrebbe essere. Quindi uso un HashMapche mappa le coordinate della cella su un ArrayList(il contenuto della cella). Funziona abbastanza bene poiché non c'è memoria persa su celle "vuote" ed è facile calcolare quali celle ispezionare. Tuttavia, la creazione di tutte quelle ArrayLists (nel peggiore dei casi N) è costosa e quindi sta crescendo HashMapmolte volte (sebbene ciò sia leggermente mitigato dandogli una grande capacità iniziale).

Problema

OK, quindi funziona ma non è ancora molto veloce. Ora posso provare a micro-ottimizzare il codice JAVA. Tuttavia, non mi aspetto troppo dal momento che il profiler mi dice che la maggior parte del tempo viene impiegato nella creazione di tutti quegli oggetti che utilizzo per memorizzare le celle. Spero che ci siano altri trucchi / algoritmi là fuori che lo rendono molto più veloce, quindi ecco come appare la mia struttura dati ideale:

  • La priorità numero uno è l'aggiornamento / ricostruzione rapida dell'intera struttura dei dati
  • È meno importante dividere gli oggetti in bidoni di dimensioni uguali, possiamo disegnare alcuni oggetti extra e fare qualche controllo extra di collisione se ciò significa che l'aggiornamento è un po 'più veloce
  • La memoria non è molto importante (gioco per PC)

"[...] dovrà essere fatto ogni fotogramma." Perché? Non puoi prevedere se un oggetto lascerà la sua cella nel prossimo futuro?
API-Beast,

Non riesci a saltare l'aggiornamento di oggetti molto lontani dal lettore? O almeno aggiornarli significativamente meno spesso?
Liosan,

@ Mr.Beast e Liosan, quelle due idee combinate potrebbero funzionare ma gli oggetti dovranno essere in grado di capire se stessi accadesse qualcosa di significativo (come un rapido aumento della velocità) ecc ... Hai qualche esempio di questa idea utilizzata?
Roy T.

Il profiler ti dice che la maggior parte del tempo è dedicato alla creazione di ArrayList o all'inizializzazione degli oggetti contenuti? Non potresti preallocare e raggruppare questi oggetti?
Fabien,

@Fabien, infatti, l'allocazione e la crescita dell'ArrayList è il problema più grande, il pooling potrebbe essere una soluzione. Mi chiedo se riesco a capire, per tentativi ed errori, quanto dovrebbe essere grande il pool e quanto grandi dovrebbero essere gli arraylist nel pool.
Roy T.

Risposte:


6

La tecnica che stai usando è molto simile a una tecnica di fisica computazionale chiamata dinamica molecolare, dove le traiettorie degli atomi (di solito ora nella gamma di particelle da 100k a 10M) sono seguite con passi temporali molto piccoli. Il problema principale è che per calcolare la forza su una particella, devi confrontare la sua posizione con la posizione di ogni altra particella, che si scala molto male (n al quadrato).

Vi posso suggerire un trucco, che richiede di scegliere una distanza massima che le cose possano interagire. Come punto di partenza, inizierei con qualcosa come 1/10 della dimensione lunga del tuo spazio e mi adeguerei al gusto (taglio più lungo significa più preciso, ma più calcoli).

Il metodo consiste nel passare attraverso ogni particella (i). (I) ottiene un array in cui tutte le particelle nel range di i vengono aggiunte all'array. Quello che ottieni alla fine è un array 2d, in cui la sua voce è un array della particella nel range di i. Per calcolare le forze per i, devi solo controllare le voci nella matrice di i.

L'arte di questo metodo è scegliere la distanza di taglio e l'imbottitura extra (ad es. 20%). Il guadagno di velocità è che devi solo controllare alcune interazioni per ogni particella e ricalcolare i vicini solo ogni diversi passaggi. Suggerirei di scegliere una velocità piuttosto veloce, e capire quanti passi ci vorranno per attraversare la regione di "imbottitura". L'ampliamento dell'imbottitura (50% o addirittura il 100% del cutoff) ti dà più passaggi tra il ricalcolo del vicino, ma rende ogni passo un po 'più lento. Il bilanciamento di questo è un compromesso.

Un altro trucco nel calcolare le distanze è lavorare con d ^ 2 invece di d, rimuovendo un sacco di chiamate a pow () e sqrt ().

Modifica: difficile trovare un link di riferimento che non sia super tecnico. Questo è l'unico che sono riuscito a trovare.


Sembra un'idea promettente, ci penserò sicuramente!
Roy T.

2

La tua soluzione suona piuttosto bene se riesci a realizzare la struttura dei dati in o (n), allora direi che l'ottimizzazione deve essere fatta sulla scelta della struttura dei dati piuttosto che sull'algoritmo.

Ho un'implementazione simile con alcune differenze: la struttura principale dei dati è un array di dimensioni fisse (come ArrayList) che è il migliore per l'accesso diretto a un elemento. Ogni cella dell'array contiene un elenco collegato, che è il migliore per inserimenti e buono come l'elenco di array in cui eseguire il loop. In seguito avremo bisogno di eliminare elementi dall'elenco collegato, quindi per rendere questa operazione molto veloce l'idea è quella di memorizzare in ogni elemento dell'elenco un iteratore che punti a se stesso (hai detto che la memoria non è un problema, giusto?)

Per l'inizializzazione, ogni "particella" viene inserita alla fine dell'elenco collegato che corrisponde alla cella dell'array che corrisponde alla sua posizione nello spazio, supponendo che lo spazio sia partizionato in riquadri di dimensioni fisse. Quindi siamo ancora con o (n) complessità, ma ottimizziamo il tutto usando contenitori più adatti all'uso.

Ogni "particella" ha un riferimento al suo elenco di collegamenti contenenti per fornire un rapido accesso ai suoi vicini.

Ad ogni fotogramma possiamo fare l'interazione tra ogni particella con il suo elenco di vicini, e direi anche con le 8 tessere circostanti per evitare effetti treshold vicino ai bordi delle tessere.

Non è necessario ricalcolare l'intero partizionamento in ciascun frame; dobbiamo solo rimuovere e rimettere un oggetto quando si sposta di oltre una determinata distanza o, per sicurezza, ogni X frame. Un'idea potrebbe essere quella di memorizzare la posizione di ciascun elemento nel momento in cui è stato inserito in un elenco collegato e in ciascun fotogramma confrontare la posizione corrente con quella precedente.



Ho usato qualcosa di simile a questo nel calcolo dei dati basato su posizioni atomizzate simulate. Accelerò un calcolo che impiegava ore / giorni e lo trasformò in minuti. È un po 'complicato da configurare.
Brian Broom,
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.