Particionamiento del espacio cuando todo se mueve


8

Antecedentes

Junto con un amigo estoy trabajando en un juego 2D que se desarrolla en el espacio. Para hacerlo lo más inmersivo e interactivo posible, queremos que haya miles de objetos flotando libremente, algunos agrupados, otros a la deriva en el espacio vacío.

Desafío

Para descargar el motor de renderización y física, necesitamos implementar algún tipo de partición espacial. Hay dos desafíos que tenemos que superar. El primer desafío es que todo se está moviendo, por lo que la reconstrucción / actualización de la estructura de datos debe ser extremadamente económica, ya que tendrá que hacerse en cada cuadro. El segundo desafío es la distribución de objetos, como se dijo antes, podría haber grupos de objetos juntos y vastos trozos de espacio vacío y, para empeorar aún más, no hay límite para el espacio.

Tecnologías existentes

He analizado las técnicas existentes como BSP-Trees, QuadTrees, kd-Trees e incluso R-Trees, pero por lo que puedo decir, estas estructuras de datos no se ajustan perfectamente desde la actualización de muchos objetos que se han movido a otras celdas Es relativamente caro.

Lo que he intentado

Tomé la decisión de que necesito una estructura de datos que esté más orientada a la inserción / actualización rápida que a devolver la menor cantidad de visitas posibles dada una consulta. Para ese propósito, hice las celdas implícitas para que cada objeto, dada su posición, pueda calcular en qué celda (s) debería estar. Luego uso un HashMapque asigna coordenadas de celda a un ArrayList(el contenido de la celda). Esto funciona bastante bien ya que no se pierde memoria en las celdas 'vacías' y es fácil calcular qué celdas inspeccionar. Sin embargo, crear todos esos ArrayLists (el peor de los casos, N) es costoso y, por lo tanto, está creciendo HashMapmuchas veces (aunque eso se mitiga ligeramente al darle una gran capacidad inicial).

Problema

Bien, esto funciona pero aún no es muy rápido. Ahora puedo intentar micro-optimizar el código JAVA. Sin embargo, no espero demasiado de eso, ya que el generador de perfiles me dice que paso la mayor parte del tiempo creando todos los objetos que uso para almacenar las celdas. Espero que haya otros trucos / algoritmos que lo hagan mucho más rápido, así que aquí está mi estructura de datos ideal:

  • La prioridad número uno es la rápida actualización / reconstrucción de toda la estructura de datos.
  • Es menos importante dividir finamente los objetos en contenedores de igual tamaño, podemos dibujar algunos objetos adicionales y hacer algunas comprobaciones adicionales de colisión si eso significa que la actualización es un poco más rápida
  • La memoria no es realmente importante (juego de PC)

"[...] tendrá que hacerse cada fotograma". ¿Por qué? ¿No puedes predecir si un objeto dejará su celda en un futuro cercano?
API-Beast el

¿No puedes saltarte la actualización de objetos que están muy lejos del reproductor? ¿O al menos actualizarlos significativamente con menos frecuencia?
Liosan el

@ Mr.Beast y Liosan, esas dos ideas combinadas podrían funcionar, pero los objetos tendrán que ser capaces de descubrirse a sí mismos si sucedió algo significativo (como un aumento rápido de la velocidad), etc. ¿Tiene algún ejemplo de esta idea utilizada?
Roy T.

El generador de perfiles le dice que se pasa la mayor parte del tiempo creando ArrayList o inicializando los objetos contenidos. ¿No podría preasignar y agrupar estos objetos?
Fabien

@Fabien, de hecho, la asignación y el crecimiento de ArrayList es el mayor problema, la agrupación podría ser una solución. Me pregunto si puedo averiguar por prueba y error qué tan grande debería ser el grupo y qué tan grandes deberían ser los arrays en el grupo.
Roy T.

Respuestas:


6

La técnica que está utilizando es muy similar a una técnica de física computacional llamada dinámica molecular, donde las trayectorias de los átomos (generalmente ahora en el rango de partículas de 100k a 10M) se siguen con pasos de tiempo muy pequeños. El principal problema es que para calcular la fuerza en una partícula, debe comparar su posición con la posición de cada otra partícula, que se escala muy mal (n al cuadrado).

Puedo sugerir un truco que requiere que elijas una distancia máxima para que las cosas puedan interactuar. Como punto de partida, comenzaría con algo así como 1/10 de la dimensión larga de su espacio y me ajustaría al gusto (un corte más largo significa más precisión, pero más cálculos).

El método consiste en recorrer cada partícula (i). (I) obtiene una matriz donde todas las partículas en el rango de i se agregan a la matriz. Lo que obtienes al final es una matriz 2d, donde la entrada i-ésima es una matriz de la partícula en el rango de i. Para calcular las fuerzas para i, solo tiene que verificar las entradas en la matriz de i.

El arte de este método es elegir la distancia de corte y el relleno adicional (por ejemplo, 20%). La ganancia de velocidad es que solo tiene que verificar algunas interacciones para cada partícula, y recalcula los vecinos solo cada varios pasos. Sugeriría elegir una velocidad algo rápida y calcular cuántos pasos de tiempo se necesitarían para cruzar la región de "relleno". Ampliar el relleno (50% o incluso 100% del límite) le brinda más pasos entre el recálculo vecino, pero hace que cada paso sea un poco más lento. El equilibrio de esto es una compensación.

Otro truco para calcular las distancias es trabajar con d ^ 2 en lugar de d, eliminando un montón de llamadas a pow () y sqrt ().

Editar: Difícil de encontrar un enlace de referencia que no sea súper técnico. Este es el único que pude encontrar.


Eso suena como una idea prometedora, ¡seguro lo veré!
Roy T.

2

Su propia solución suena bastante bien si puede lograr construir la estructura de datos en o (n), entonces diría que la optimización debe hacerse en la elección de la estructura de datos en lugar de en el algoritmo.

Tengo una implementación similar con algunas diferencias: la estructura de datos principal es una matriz de tamaño fijo (como ArrayList) que es la mejor para el acceso directo a un elemento. Cada celda de la matriz contiene una lista vinculada, que es la mejor para las inserciones y tan buena como la lista de la matriz para hacer un bucle. Más tarde necesitaremos eliminar elementos de la lista vinculada, por lo que para hacer esta operación muy rápida, una idea es almacenar en cada elemento de la lista un iterador que se señale a sí mismo (usted dijo que la memoria no es un problema, ¿verdad?)

Para la inicialización, cada "partícula" se inserta al final de la lista vinculada que corresponde a la celda en la matriz que coincide con su posición en el espacio, suponiendo que el espacio esté dividido en mosaicos de tamaño fijo. Así que todavía estamos con complejidad o (n), pero optimizamos todo usando contenedores más adecuados para el uso.

Cada "partícula" tiene una referencia a su lista vinculada que contiene para proporcionar un acceso rápido a sus vecinos.

En cada cuadro podemos hacer la interacción entre cada partícula con su lista de vecinos, y yo diría que también con las 8 fichas circundantes para evitar efectos de umbral cerca de los bordes de las fichas.

No es necesario volver a calcular toda la partición en cada cuadro; solo necesitamos eliminar y volver a colocar un elemento cuando se mueve más de una distancia determinada o, por seguridad, cada X cuadros. Una idea podría ser almacenar la posición de cada elemento en el momento en que se insertó en una lista vinculada y, en cada cuadro, comparar la posición actual con la anterior.



Usé algo similar a esto al calcular datos basados ​​en posiciones de átomos simuladas. Aceleró un cálculo que tomó horas / días y lo convirtió en minutos. Es un poco complicado de configurar.
Brian Broom
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.