¿Cómo encontrar continuamente todas las entidades dentro de un radio de manera eficiente?

14

Tengo una gran cantidad de entidades (unidades). En cada paso, cada unidad necesita conocer las posiciones de todas las unidades cercanas (la distancia es menor que la constante R ). Todas las unidades se mueven continuamente. Esto es en 3D.

En promedio, habrá un 1% del recuento total de unidades cerca de cualquier otra unidad dada con las restricciones dadas.

¿Cómo puedo hacer esto de manera eficiente, sin fuerza bruta?

OCyril
fuente
77
Querrá algún tipo de sistema de partición espacial: en.wikipedia.org/wiki/Space_partuling
Tetrad

Respuestas:

15

Utilice uno de los algoritmos comunes de partición espacial, como un árbol Quadtree, Octree, BSP o incluso un simple sistema de cuadrícula. Cada uno tiene sus propios pros y contras para cada escenario específico. Puede leer más sobre ellos en estos libros .

En general (o eso he oído, no estoy muy familiarizado con el razonamiento detrás de esto), un Quadtree o Octree es más adecuado para entornos al aire libre, mientras que el árbol BSP se adapta mejor a las escenas interiores. Y la elección entre usar un Quadtree o un Octree depende de cuán plano sea su mundo. Si hay poca variación en el eje Y con un Octree sería un desperdicio. Un Octree es básicamente un Quadtree con una dimensión adicional.

Finalmente, no ignore la simplicidad de la solución Grid. Muchas personas ignoran que una cuadrícula simple a veces puede ser suficiente (e incluso más eficiente) para sus problemas, y en su lugar pasan directamente a una solución más compleja.

El uso de una cuadrícula consiste simplemente en dividir el mundo en regiones espaciadas de manera uniforme y almacenar las entidades en la región apropiada del mundo. Luego, dada una posición, encontrar las entidades vecinas sería una cuestión de iterar sobre las regiones que intersectan su radio de búsqueda.

Digamos que su mundo varió de (-1000, -1000) a (1000, 1000) en el plano XZ. Por ejemplo, podría dividirlo en una cuadrícula de 10x10, así:

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

Luego colocaría entidades en sus celdas apropiadas en la cuadrícula. Por ejemplo, una entidad con XZ (-1000, -1000) caería en la celda (0,0) mientras que una entidad con XZ (1000, 1000) caería en la celda (9, 9). Luego, dada una posición y un radio en el mundo, puede determinar qué celdas están intersectadas por este "círculo" e iterar solo sobre ellas, con un simple doble para.

De todos modos, investiga todas las alternativas y elige la que mejor se adapte a tu juego. Admito que todavía no tengo el conocimiento suficiente sobre el tema para decidir cuál de los algoritmos sería mejor para usted.

Editar Encontró esto en otro foro y podría ayudarlo con la decisión:

Las cuadrículas funcionan mejor cuando la gran mayoría de los objetos encajan dentro de un cuadrado de cuadrícula, y la distribución es bastante homogénea. Por el contrario, los quadtrees funcionan cuando los objetos tienen tamaños variables o están agrupados en áreas pequeñas.

Dada su vaga descripción del problema, también me apoyo en la solución de cuadrícula (es decir, suponiendo que las unidades son pequeñas y están distribuidas de manera bastante homogénea).

David Gouveia
fuente
Gracias por la respuesta detallada. Sí, parece que la solución Grid simple es lo suficientemente buena para mí.
OCyril
0

Escribí esto hace algún tiempo. Ahora está en un sitio comercial, pero puede obtener la fuente para uso personal de forma gratuita. Puede ser excesivo y está escrito en Java, pero está bien documentado, por lo que no debería ser demasiado difícil de recortar y reescribir en otro idioma. Básicamente utiliza un Octree, con ajustes para manejar objetos realmente grandes y subprocesos múltiples.

Encontré un Octree que ofrecía la mejor combinación de flexibilidad y eficiencia. Comencé con una cuadrícula, pero era imposible dimensionar los cuadrados correctamente y grandes parches de cuadrados vacíos consumieron espacio y poder de cómputo para nada. (Y eso fue solo en 2 dimensiones). Mi código maneja consultas de múltiples hilos, lo que agrega mucho a la complejidad, pero la documentación debería ayudarlo a solucionarlo si no lo necesita.

RalphChapin
fuente
0

Para aumentar su eficiencia, intente rechazar trivialmente el 99% de las "unidades" que no están cerca de la unidad objetivo utilizando una casilla de verificación de límites muy económica. Y espero que pueda hacer esto sin estructurar sus datos espacialmente. Por lo tanto, si todas sus unidades se almacenan en una estructura de datos plana, puede intentar recorrerla de principio a fin y, en primer lugar, verificar si la unidad actual está fuera del cuadro delimitador de la unidad de interés.

Defina un cuadro delimitador de gran tamaño para la unidad de interés de modo que pueda rechazar de forma segura los elementos que no tienen posibilidad de ser considerados "cercanos". El cheque de exclusión de un cuadro delimitador podría hacerse más barato que un cheque de radio. Sin embargo, en algunos sistemas donde se probó esto se descubrió que no era el caso. Los dos realizan casi por igual. Esto editado después de mucho debate a continuación.

Primero: clip de cuadro delimitador 2D.

// 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;
}

En comparación con algo como esto (en 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;
}.

Si el objeto no se rechaza trivialmente, se realiza una prueba de colisión más costosa y precisa. Pero solo está buscando cercanías, por lo que la prueba de esfera es adecuada para eso, pero solo para el 1% de los objetos que sobreviven al rechazo trivial.

Este artículo admite la caja para el rechazo trivial. http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

Si este enfoque lineal no le brinda el rendimiento que necesita, entonces se puede requerir una estructura de datos jerárquica, como los otros carteles han estado hablando. Vale la pena considerar los R-Trees. Apoyan cambios dinámicos. Son los BTrees del mundo espacial.

Simplemente no quería que te tomaras la molestia de introducir tanta complejidad si pudieras evitarlo. Además, ¿qué pasa con el costo de mantener esta estructura de datos compleja actualizada a medida que los objetos se mueven varias veces por segundo?

Tenga en cuenta que una cuadrícula es una estructura de datos espaciales profundos de un nivel. Este límite significa que no es realmente escalable. A medida que el mundo crece en tamaño, también lo hace la cantidad de células que necesita cubrir. Finalmente, ese número de celdas se convierte en un problema de rendimiento. Sin embargo, para un mundo de cierto tamaño, le dará un aumento de rendimiento masivo sin particiones espaciales.

Ciaran
fuente
1
El OP dijo específicamente que quiere evitar un enfoque de fuerza bruta, que es exactamente lo que usted describe en su primer párrafo. Además, ¿cómo crees que un cheque de cuadro delimitador es más barato que un cheque de esfera delimitador? Eso está mal.
notlesh
Sí, sé que quiere evitar la fuerza bruta que se evitaría si se esfuerza por introducir una estructura de datos jerárquica en su aplicación. Pero eso podría ser mucho esfuerzo. Si todavía no quiere hacer eso, puede probar el enfoque lineal, que es la fuerza bruta, pero puede no funcionar tan mal si su lista no es muy grande. Intentaré editar el código anterior para poner en mi cuadro de límite 2D la función de rechazo trivial. No creo que me haya equivocado.
Ciaran
El enlace a GDnet está roto, pero la prueba de esfera canónica es muy simple, muy barata y no se ramifica:inside = (dot(p-p0, p-p0) <= r*r)
Lars Viklund el
En su lugar, he pegado el código arriba. Parece todo menos barato en comparación con el cuadro delimitador.
Ciaran
1
@Ciaran Honestamente, ese artículo parece realmente malo. En primer lugar, no realiza las pruebas con datos realistas, sino que utiliza los mismos valores una y otra vez. No es algo que encontrará en un escenario real. Y no, según el artículo, el BB solo es más rápido cuando no hay colisión (por ejemplo, el cheque falla en la primera ifdeclaración). Además no es muy realista. Pero, sinceramente, si está comenzando a optimizar cosas como esta, entonces definitivamente está comenzando en el lugar equivocado.
bummzack
0

Tengo que responder a esto porque no tengo los puntos para comentar o votar. Para el 99% de las personas que hacen esta pregunta, un cuadro delimitador es la solución, según lo descrito por Ciaran. En un lenguaje compilado, rechazará 100,000 unidades irrelevantes en un abrir y cerrar de ojos. Hay una gran cantidad de gastos generales involucrados con las soluciones de fuerza no bruta; con números más pequeños (digamos menos de 1000) serán más caros en términos de tiempo de procesamiento que el control de fuerza bruta. Y tomarán mucho más tiempo de programación.

No estoy seguro de qué significa "un número muy grande" en la pregunta, o qué querrán decir otras personas que buscan respuestas aquí. Sospecho que mis números anteriores son conservadores y podrían multiplicarse por 10; Personalmente, tengo bastante prejuicio contra las técnicas de fuerza bruta y estoy muy molesto por lo bien que funcionan. Pero no quisiera que alguien con, digamos, 10,000 unidades pierda tiempo con una solución elegante cuando unas pocas líneas rápidas de código harán el truco. Siempre pueden volverse elegantes más tarde si es necesario.

Además, me gustaría señalar que una verificación de la esfera delimitadora requiere multiplicación donde el cuadro delimitador no. La multiplicación, por su naturaleza, lleva varias veces más tiempo que la suma y las comparaciones. Es probable que haya una combinación de lenguaje, sistema operativo y hardware donde la verificación de la esfera será más rápida que una casilla de verificación, pero en la mayoría de los lugares y momentos la casilla de verificación debe ser más rápida, incluso si la esfera rechaza algunas unidades irrelevantes. La caja acepta. (Y donde la esfera es más rápida, es muy probable que una nueva versión del compilador / intérprete / optimizador cambie eso).

RalphChapin
fuente
Si bien no hay nada malo con su respuesta, no está respondiendo la pregunta. Se solicitó específicamente un enfoque "sin fuerza bruta". También parece que repite lo que Ciaran ya escribió y tuvimos una larga discusión de comentarios sobre AABB vs. pruebas de círculo. La diferencia de rendimiento es simplemente irrelevante. Elija mejor un volumen delimitador que se ajuste a la mayoría de sus candidatos de colisión, ya que reducirá la cantidad de pruebas reales de fase estrecha ... lo que tendrá un mayor impacto en el rendimiento general.
bummzack