Simulación de presión en una simulación líquida basada en rejilla

30

Tengo un sistema de agua basado en una cuadrícula 2D en mi juego XNA, tenemos un método que utiliza autómatas celulares para simular la caída y propagación del agua.

Ejemplo de agua que fluye por una pendiente:

Física del agua

Cada mosaico puede contener una masa de 0 a 255 valores de líquido, almacenados en un byte. No uso floatsel antiguo sistema de agua que tenía, sin embargo, agregó complicaciones y tuvo un impacto en el rendimiento.

Cada baldosa de agua se actualiza con un conjunto simple de reglas:

  1. Si el mosaico de abajo tiene espacio, muévase tanto como sea posible del mosaico actual al inferior (Flujo hacia abajo)
  2. Si los 2 lados no son iguales y no son cero y ambos son transitables, obtenemos la suma de las 3 fichas (izquierda + actual + derecha) y la dividimos entre 3 dejando el resto en la casilla central (actual)
  3. Si la regla anterior dio un número de 2 como suma, deberíamos dividir las fichas en los dos lados (1, 0, 1)
  4. Si la regla 2 dio 1 como la suma, elija un lado aleatorio para fluir
  5. Si la regla 2 falla, debemos verificar si un lado es aceptable y el otro no. Si eso es cierto, dividimos el mosaico actual por la mitad para los 2 mosaicos.

¿Cómo puedo ampliar esta lógica para incluir la presión? La presión hará que los líquidos suban sobre las "curvas en U" y llene las bolsas de aire.

Ejemplo sobre cómo esto falla actualmente:

Falla de presión

El agua debe fluir y ecualizarse a cada lado de la curva en U. Además, he creado métodos para averiguar qué tan lejos está un bloque de agua y, por lo tanto, cuánta presión está experimentando. Ahora necesito poder tomar estos números y aplicarlos a las otras áreas para igualar la presión.

Cyral
fuente
El problema es que es difícil mantenerlo como un autómata celular. Desde ahora, cada bloque necesita saber más que lo que está al lado. Creé un sistema similar al que estás buscando en 3D. Es un sistema bastante complejo, pero creo que sería más factible en 2D.
MichaelHouse
@ Byte56 Bueno, no necesitamos que sean autómatas celulares, siempre que podamos mantenerlo funcionando a una velocidad razonable.
Cyral
3
Crearé una respuesta completa si encuentro algo de tiempo esta noche. Sin embargo, en pocas palabras, esencialmente creé la búsqueda de caminos para el agua. Los bloques quieren encontrar un lugar con menos presión para ir. Se encuentran a través del otro agua buscando un lugar que tenga menos agua que ellos (aire al lado del agua incluido). Resuelve una gran mayoría de los casos de uso.
MichaelHouse
Gracias, eso sería apreciado. Leí algunas entrevistas con el creador de Dwarf Fortress y él hizo esto, creo, pero no estaba seguro de cómo superar algunos de los problemas con los que se topó, así que nunca lo intenté.
Cyral
1
Tenga en cuenta que, una vez que se agrega presión de aire, los dos ejemplos de bolsas de aire son potencialmente completamente válidos (cámaras de presión cerradas). Supongo que no está utilizando 255 bytes , sino valores 0-255; en cualquier caso, es probable que no quieras usar el rango completo de esa manera. Probablemente lo limitaría a, hmm, 0-15 para '1 atmósfera' de presión (no existe tal cosa como presión 'negativa', ¿verdad?), Permitiendo presiones más altas, de las que actualmente carece. Una vez que incluya los bloques de 'aire' en el simulador, el 'peso' naturalmente más alto de los bloques de agua debería hacer que fluya alrededor de las curvas.
Clockwork-Muse

Respuestas:

6

Tenga en cuenta que nunca he hecho esto; Estas son solo ideas que pueden ayudar. O podría ser totalmente falso. Desde que Terraria he querido abordar este problema, pero actualmente no estoy trabajando en ese juego.

Una forma en la que he considerado intentar es darle a cada bloque de agua superficial (cualquier bloque con agua y sin un bloque de agua encima) un valor de presión inicial igual (o una función de) su altura desde el fondo del mundo. El valor de presión implícito de cualquier ficha infranqueable es MAX_PRESSURE(digamos 255), y para una ficha al aire libre es MIN_PRESSURE(0).

Luego, la presión se extiende hacia arriba / abajo / lateralmente desde cualquier mosaico con una presión más alta a mosaicos con una presión más baja durante cada tic, estilo de autómata celular. Tendría que obtener una simulación real para averiguar exactamente a qué ecualizar. La presión de un bloque debe ser igual a su presión implícita más la presión "excesiva" de alrededor de la ecualización (por lo que solo necesitaría almacenar este exceso de presión, no la presión implícita).

Si una loseta de superficie tiene una presión mayor que su presión implícita basada en la altura y si la loseta de arriba tiene espacio libre para el agua, una pequeña porción de agua se mueve hacia arriba. El agua solo fluye hacia abajo si la loseta tiene espacio, ya que tiene una presión más baja de lo esperado.

Esto simula más o menos la idea de que cuanto más profundo es el "punto" del agua, más presión tiene, aunque los valores de presión representan más la altura que la presión real (ya que se espera que las baldosas más altas tengan una "presión" más alta). Esto hace que la presión sea algo así como el htérmino en la ecuación (pero no realmente):

P' = P + qgh

El resultado es que si la presión del agua es más alta de lo que debería ser por su profundidad, será empujada hacia arriba. Debería significar que los niveles de agua en sistemas cerrados igualarán la presión en todos los niveles de altura con el tiempo.

No estoy seguro de cómo lidiar o si uno incluso necesita lidiar con las "burbujas de aire" que se crearían (donde una baldosa no superficial tendrá cantidades de agua no llenas cuando el agua se empuja hacia arriba). Tampoco estoy seguro de cómo evitaría que las presiones de agua de los bucles sean desiguales en un lado y luego, después de marcar, en el otro lado, de un lado a otro.

Sean Middleditch
fuente
20

Creé un sistema similar al que buscas en 3D. Tengo un video corto que demuestra la mecánica simple de esto aquí y una publicación de blog aquí .

Aquí hay un pequeño gif que hice de la mecánica de presión detrás de un muro invisible (jugado a alta velocidad):

ingrese la descripción de la imagen aquí

Permítanme explicar los datos involucrados, para dar una idea de algunas de las características del sistema. En el sistema actual, cada bloque de agua contiene lo siguiente en 2 bytes:

//Data2                          Data
//______________________________  _____________________________________
//|0    |0      |000   |000    |  |0        |0       |000      |000   |
//|Extra|FlowOut|Active|Largest|  |HasSource|IsSource|Direction|Height|
//------------------------------  -------------------------------------
  • Height es la cantidad de agua en el cubo, similar a su presión, pero mi sistema solo tiene 8 niveles.
  • Directiones la dirección en la que va el flujo. Al decidir dónde fluirá el agua a continuación, es más probable que continúe en su dirección actual. Esto también se utiliza para rastrear rápidamente un flujo hasta su cubo de origen cuando sea necesario.
  • IsSourceindica si este cubo es un cubo fuente, lo que significa que nunca se queda sin agua. Usado para la fuente de ríos, manantiales, etc. El cubo a la izquierda en el gif de arriba es un cubo fuente, por ejemplo.
  • HasSourceindica si este cubo está conectado a un cubo fuente. Cuando se conecta a una fuente, los cubos intentarán aprovechar la fuente para obtener más agua antes de buscar otros cubos sin fuente "más completos".
  • Largestle dice a este cubo cuál es el flujo más grande entre él y su cubo fuente. Esto significa que si el agua fluye a través de un espacio estrecho, limita el flujo a este cubo.
  • ActiveEs un mostrador. Cuando este cubo tiene un flujo activo que lo atraviesa, hacia él o desde él, el activo se incrementa. De lo contrario, el activo se disminuye aleatoriamente. Una vez que el activo llegue a cero (lo que significa que no está activo), la cantidad de agua comenzará a reducirse en este cubo. Este tipo de actos como evaporación o remojo en el suelo. ( Si tienes flujo, ¡deberías tener reflujo! )
  • FlowOutindica si este cubo está conectado a un cubo que está en el borde del mundo. Una vez que se hace un camino hacia el borde del mundo, el agua tiende a elegir ese camino sobre cualquier otro.
  • Extra es un bit extra para uso futuro.

Ahora que conocemos los datos, veamos una descripción general de alto nivel del algoritmo. La idea básica del sistema es priorizar el flujo hacia abajo y hacia afuera. Como explico en el video, trabajo de abajo hacia arriba. Cada capa de agua se procesa un nivel a la vez en el eje y. Los cubos para cada nivel se procesan al azar, cada cubo intentará extraer agua de su fuente en cada iteración.

Los cubos de flujo extraen agua de su fuente siguiendo su dirección de flujo hacia arriba hasta que alcanzan un cubo fuente o un cubo de flujo sin padre. Almacenar la dirección del flujo en cada cubo hace que seguir la ruta a la fuente sea tan fácil como atravesar una lista vinculada.

El pseudocódigo para el algoritmo es el siguiente:

for i = 0 to topOfWorld //from the bottom to the top
   while flowouts[i].hasitems() //while this layer has flow outs
       flowout = removeRandom(flowouts[i]) //select one randomly
       srcpath = getPathToParent(flowout) //get the path to its parent
       //set cubes as active and update their "largest" value
       //also removes flow from the source for this flow cycle
       srcpath.setActiveAndFlux() 

//now we deal with regular flow
for i = 0 to topOfWorld //from the bottom to the top
    while activeflows[i].hasitems() //while this layer has water
        flowcube = removeRandom(activeflows[i]) //select one randomly
        //if the current cube is already full, try to distribute to immediate neighbors
        flowamt = 0
        if flowcube.isfull 
           flowamt = flowcube.settleToSurrounding
        else
           srcpath = getPathToParent(flowcube) //get the path to its parent
           flowamt = srcpath.setActiveAndFlux()
           flowcube.addflow(flowamt)

        //if we didn't end up moving any flow this iteration, reduce the activity
        //if activity is 0 already, use a small random chance of removing flow
        if flowamt == 0
           flowcube.reduceActive()

 refillSourceCubes()

Las reglas básicas para expandir un flujo donde (ordenado por prioridad):

  1. Si el cubo de abajo tiene menos agua, fluya hacia abajo
  2. Si el cubo adyacente en el mismo nivel tiene menos agua, fluya lateralmente.
  3. Si el cubo de arriba tiene menos agua Y el cubo de fuente es más alto que el cubo de arriba, fluya hacia arriba.

Lo sé, eso es bastante alto nivel. Pero es difícil entrar en más detalle sin conseguir manera en detalles.

Este sistema funciona bastante bien. Puedo llenar fácilmente pozos de agua, que se desbordan para continuar hacia afuera. Puedo llenar túneles en forma de U como ves en el gif de arriba. Sin embargo, como dije, el sistema está incompleto y aún no he resuelto todo. No he trabajado en el sistema de flujo en mucho tiempo (decidí que no era necesario para alfa y lo puse en espera). Sin embargo, los problemas con los que estaba lidiando cuando lo puse en espera fueron:

  • Piscinas . Al obtener un gran charco de agua, los punteros de niño a padre son como un desorden loco de cualquier cubo aleatorio seleccionado para fluir en cualquier dirección. Como llenar una bañera con una cuerda tonta. Cuando desee drenar la bañera, ¿debe seguir el camino de la cuerda tonta de regreso a su fuente? ¿O deberías tomar lo que sea más cercano? Entonces, en situaciones en las que los cubos están en un gran grupo, es probable que simplemente ignoren sus flujos primarios y tomen todo lo que esté por encima de ellos. Se me ocurrió un código de trabajo básico para esto, pero nunca tuve una solución elegante con la que pudiera estar feliz.

  • Múltiples padres . Una secuencia secundaria podría ser alimentada fácilmente por más de una secuencia principal. Pero el niño que tiene un puntero a un padre soltero no lo permitiría. Esto se puede solucionar mediante el uso de suficientes bits para permitir un bit para cada posible dirección principal. Y es probable que cambie el algoritmo para seleccionar aleatoriamente una ruta en el caso de varios padres. Pero, nunca pude probarlo y ver qué otros problemas podrían exponer.

MichaelHouse
fuente
¡Gracias! ¡Muy informativo! Comenzaré a trabajar en esto pronto y lo aceptaré si todo sale bien.
Cyral
Cosa segura. Me imagino un híbrido de su sistema y este sería muy efectivo para un mundo 2D. Hazme ping en el chat (con @ byte56) si quieres discutir los detalles.
MichaelHouse
Bien, podría pasar un día más o menos antes de que tenga la oportunidad de probar esto.
Cyral
3
Comprensiblemente. Probablemente pasé meses resolviéndolo (y volviéndolo a trabajar). Sin embargo, estaré por aquí por un tiempo :)
MichaelHouse
2

Estoy de acuerdo con Sean, pero lo haría un poco diferente:

Un bloque genera una presión igual a su propio peso (la cantidad de agua que contiene) y lo aplica a los bloques debajo y al lado. No veo ninguna razón por la cual su posición en el mundo sea relevante.

En cada marca mueva el agua de alta a baja presión, pero mueva solo una fracción del agua necesaria para igualar. El agua también puede ser empujada hacia arriba si la presión en el bloque es demasiado grande para la presión que se aplica sobre el cuadrado.

Obtendrá bucles donde la presión del agua fluye demasiado en un sentido y luego tiene que corregir, pero como no mueve toda la cantidad de agua por tic, estos se amortiguarán. Creo que en realidad es algo bueno, ya que obtendrás efectos de sobretensión a medida que el agua se inunda en un área como lo harías en realidad.

Loren Pechtel
fuente
Si el agua se moviera hacia arriba cuando la presión aplicada desde arriba fuera demasiado grande, no se movería hacia un bloque de presión más baja. Para que la presión de arriba sea demasiado grande, tendría que ser mayor que el bloque de abajo. Además, la presión tiene que moverse tanto hacia arriba como hacia abajo y hacia la izquierda / derecha.
MichaelHouse
@ Byte56 Estás malinterpretando lo que dije. Estoy diciendo que el agua sube cuando la presión en el bloque que está analizando es demasiado alta para la presión que se aplica desde arriba, ¡no es que la presión desde arriba sea demasiado grande!
Loren Pechtel
Bien, permítanme reformular lo que dijo para que entienda: "el agua sube cuando la presión en el bloque que está analizando es mayor que la presión aplicada desde arriba". ¿Es eso correcto?
MichaelHouse
@ Byte56 Sí. La presión en el bloque debe ser el peso del agua sobre él o aplicarse lateralmente cuando tenemos una superficie sólida en algún lugar arriba. Demasiada poca presión significa que no hay suficiente agua arriba, mueva el agua hacia arriba.
Loren Pechtel
Solo me gustaría agregar que si se trata de agua que fluye, esto no será suficiente y también debe considerar la inercia o el agua se moverá demasiado lento.
cubo
1

Puede agregar una regla que intente ir hacia la izquierda o hacia la derecha (a través de las paredes) con los mosaicos hasta que encuentre un lugar libre, comenzando con las capas en la parte inferior. Si no puede encontrar, el mosaico permanece en la posición actual. Si lo encuentra, las otras reglas garantizarán el reemplazo de la ficha movida (si es necesario).

almanegra
fuente
Esta también es una buena idea, no estoy seguro si funcionaría en todos los casos, pero lo consideraré.
Cyral
¡Okay! Déjame saber si funcionó o no. saludos
almanegra
Lo haré, solo he estado un poco ocupado últimamente.
Cyral
-2

¿por qué no puedes definir otro tipo de bloque que actúe como una cantidad inamovible de presión? Por lo tanto, cuando usa su forma de mover normalmente los bloques de agua y verificar si puede moverse hacia arriba, no puede.

Aún mejor sería agregar otra definición a esos bloques que permita al usuario ingresar la cantidad de presión por bloque, aumentando la presión de acuerdo con la cantidad de bloques de agua que se le agreguen.

SD1990
fuente
1
"Por lo tanto, cuando usa su forma de mover normalmente los bloques de agua y verifica si puede moverse hacia arriba, no puede". Sí ... ya no puede. Ese es el problema, no estoy buscando una manera de hacer que permanezca igual.
Cyral