Tengo un montón de objetos de diferentes tamaños y velocidades que gravitan uno hacia el otro. En cada actualización, tengo que revisar cada objeto y sumar las fuerzas debido a la gravedad de cada otro objeto. No escala muy bien, es uno de los dos grandes cuellos de botella que he encontrado en mi juego, y no estoy seguro de qué hacer para mejorar el rendimiento.
Se siente como que debería ser capaz de mejorar el rendimiento. En un momento dado, probablemente el 99% de los objetos en el sistema solo tendrán una influencia insignificante en un objeto. Por supuesto, no puedo ordenar los objetos por masa y solo considero los 10 objetos más grandes o algo así, porque la fuerza varía más con la distancia que con la masa (la ecuación está en la línea de force = mass1 * mass2 / distance^2
). Creo que una buena aproximación sería considerar los objetos más grandes y los objetos más cercanos, ignorando los cientos de pequeños fragmentos de roca en el otro lado del mundo que no pueden afectar nada, pero para descubrir qué objetos son más cercano tengo que iterar sobre todos los objetos, y sus posiciones cambian constantemente, así que no es que pueda hacerlo solo una vez.
Actualmente estoy haciendo algo como esto:
private void UpdateBodies(List<GravitatingObject> bodies, GameTime gameTime)
{
for (int i = 0; i < bodies.Count; i++)
{
bodies[i].Update(i);
}
}
//...
public virtual void Update(int systemIndex)
{
for (int i = systemIndex + 1; i < system.MassiveBodies.Count; i++)
{
GravitatingObject body = system.MassiveBodies[i];
Vector2 force = Gravity.ForceUnderGravity(body, this);
ForceOfGravity += force;
body.ForceOfGravity += -force;
}
Vector2 acceleration = Motion.Acceleration(ForceOfGravity, Mass);
ForceOfGravity = Vector2.Zero;
Velocity += Motion.Velocity(acceleration, elapsedTime);
Position += Motion.Position(Velocity, elapsedTime);
}
(tenga en cuenta que he eliminado mucho código; por ejemplo, las pruebas de colisión, no repito los objetos por segunda vez para detectar colisiones).
Por lo tanto, no siempre estoy iterando sobre toda la lista: solo hago eso para el primer objeto, y cada vez que el objeto encuentra la fuerza que siente hacia otro objeto, ese otro objeto siente la misma fuerza, por lo que solo actualiza ambos ellos, y luego ese primer objeto no tiene que ser considerado nuevamente para el resto de la actualización.
Las funciones Gravity.ForceUnderGravity(...)
y Motion.Velocity(...)
, etc., solo usan un poco de matemática vectorial incorporada de XNA.
Cuando dos objetos chocan, crean escombros sin masa. Se mantiene en una lista separada y los objetos masivos no iteran sobre los escombros como parte de su cálculo de velocidad, pero cada pieza de escombros debe iterar sobre las partículas masivas.
Esto no tiene que escalar a límites increíbles. El mundo no es ilimitado, contiene un borde que destruye los objetos que lo cruzan; me gustaría poder manejar unos mil objetos, actualmente el juego comienza a ahogarse alrededor de 200.
¿Alguna idea sobre cómo podría mejorar esto? ¿Alguna heurística que pueda usar para reducir la longitud del bucle de cientos a solo unos pocos? ¿Algún código que puedo ejecutar con menos frecuencia que cada actualización? ¿Debería simplemente multiprocesarlo hasta que sea lo suficientemente rápido como para permitir un mundo de tamaño decente? ¿Debería intentar descargar los cálculos de velocidad a la GPU? Si es así, ¿cómo diseñaría eso? ¿Puedo mantener datos estáticos compartidos en la GPU? ¿Puedo crear funciones HLSL en la GPU y llamarlas arbitrariamente (usando XNA) o tienen que ser parte del proceso de extracción?
G * m1 * m2 / r^2
, donde G es solo para modificar el comportamiento. (aunque no puedo simplemente hacer que sigan un camino, porque el usuario puede alterar el sistema)