Simulando pressão em uma simulação de líquido baseada em grade


30

Eu tenho um sistema de água baseado em grade 2D no meu jogo XNA, temos um método usando autômatos celulares para simular a queda e a propagação da água.

Exemplo de água que flui ladeira abaixo:

Física da Água

Cada bloco pode conter uma massa de 0 a 255 valores de líquido, armazenados em um byte. eu não usofloats , o antigo sistema de água que eu tinha, no entanto, acrescentou complicações e teve um impacto no desempenho.

Cada telha de água se atualiza com um conjunto simples de regras:

  1. Se o bloco abaixo tiver espaço, mova-se o máximo possível do bloco atual para o inferior (Flow Down)
  2. Se os dois lados não são iguais e não são zero e ambos são aceitáveis, obtemos a soma dos 3 blocos (esquerda + corrente + direita) e dividimos por 3 deixando o restante no bloco médio (atual)
  3. Se a regra acima deu um número 2 como soma, devemos dividir as peças nos dois lados (1, 0, 1)
  4. Se a regra 2 deu 1 como soma, escolha um lado aleatório para fluir para
  5. Se a regra 2 falhar, devemos verificar se um lado é aceitável e o outro não. Se isso for verdade, dividimos o bloco atual ao meio pelos 2 blocos

Como posso expandir essa lógica para incluir pressão? A pressão fará com que os líquidos subam sobre as "curvas em U" e preencha os bolsos de ar.

Exemplo de como isso atualmente falha:

Falha na pressão

A água deve fluir e equalizar em cada lado da curvatura em U. Além disso, criei métodos para descobrir a que distância está o bloco de água e, portanto, quanta pressão ele está sofrendo. Agora eu preciso ser capaz de pegar esses números e aplicá-los a outras áreas para equalizar a pressão.


O problema é que é difícil manter um autômato celular. Desde agora, cada bloco precisa saber mais do que apenas o próximo. Criei um sistema semelhante ao que você deseja em 3D. É um sistema bastante complexo, mas acho que seria mais viável em 2D.
Michaelhouse

@ Byte56 Bem, não precisamos que sejam autômatos celulares, desde que possamos mantê-lo funcionando a uma velocidade razoável.
187 Cyral

3
Vou criar uma resposta completa se encontrar algum tempo esta noite. No entanto, para simplificar, criei essencialmente o caminho para a água. Os blocos querem encontrar um lugar com menos pressão para ir. Eles encontram através da outra água procurando um lugar que tenha menos água do que eles (ar próximo à água incluído). Ele resolve uma grande maioria dos casos de uso.
Michaelhouse

Obrigado, isso seria apreciado. Li algumas entrevistas com o criador de Dwarf Fortress e ele fez isso, acredito, mas não sabia ao certo como superar alguns dos problemas que ele enfrentou, então nunca tentei de verdade.
Cyral 07/07

1
Observe que, depois de adicionar a pressão do ar, os dois exemplos de bolsa de ar são potencialmente completamente válidos (câmaras de pressão fechadas). Suponho que você não esteja usando 255 bytes , mas valores de 0 a 255 ; de qualquer forma, você provavelmente não vai querer usar toda a gama dessa maneira. Eu provavelmente o limitaria a, hmm, 0-15 para '1 atmosfera' de pressão (não existe pressão 'negativa', certo?), Permitindo pressões mais altas, das quais você atualmente não tem. Depois de incluir os blocos de 'ar' no sim, o 'peso' naturalmente mais alto dos blocos de água deve fazer com que flua pelas curvas.
Clockwork-Muse

Respostas:


6

Note que eu nunca fiz isso; estas são apenas idéias que podem ajudar. Ou pode ser totalmente falso. Eu estava querendo resolver esse problema desde Terraria, mas atualmente não estou trabalhando nesse jogo.

Uma maneira que eu considerei tentar é atribuir a cada bloco de água de superfície (qualquer bloco com água e sem bloco de água acima dele) um valor inicial de pressão igual a (ou em função de) sua altura do fundo do mundo. O valor implícito da pressão de qualquer ladrilho intransitável é MAX_PRESSURE(por exemplo, 255), e para um ladrilho a céu aberto é MIN_PRESSURE(0).

A pressão é então espalhada para cima / baixo / lateralmente de qualquer ladrilho com pressão mais alta para ladrilhos com pressão mais baixa durante cada tick, estilo autômato celular. Eu teria que ter uma simulação real para descobrir exatamente o que igualar. A pressão de um bloco deve ser igual à pressão implícita mais a pressão "excessiva" de aproximadamente equalizada (portanto, você só precisará armazenar essa pressão excessiva, não a pressão implícita).

Se um ladrilho de superfície tiver uma pressão maior que sua pressão implícita baseada em altura e se o ladrilho acima tiver espaço livre para a água, uma pequena porção de água será movida para cima. A água flui somente se o ladrilho tiver espaço e pressão mais baixa do que o esperado.

Isso simula aproximadamente a ideia de que quanto mais profunda a água "aponte", mais pressão ela terá, embora os valores da pressão representem mais a altura do que a pressão real (já que se espera que ladrilhos mais altos tenham "pressão" mais alta). Isso faz com que a pressão seja mais ou menos como o htermo na equação (mas não realmente):

P' = P + qgh

O resultado é que, se a pressão da água for maior do que deveria para sua profundidade, ela será empurrada para cima. Isso significa que os níveis de água em sistemas fechados igualarão a pressão em todos os níveis de altura ao longo do tempo.

Não tenho certeza de como lidar ou se é necessário lidar com as "bolhas de ar" que seriam criadas (onde um ladrilho que não seja de superfície terá quantidades de água não cheias à medida que a água é empurrada para cima). Também não tenho certeza de como você evitaria que as pressões da água fossem desiguais de um lado e depois, depois de assinalar, desiguais do outro lado, para frente e para trás.


20

Eu criei um sistema semelhante ao que você procura em 3D. Eu tenho um pequeno vídeo demonstrando a mecânica simples aqui e uma postagem no blog aqui .

Aqui está um pequeno gif que eu fiz da mecânica da pressão atrás de uma parede invisível (tocada em alta velocidade):

insira a descrição da imagem aqui

Deixe-me explicar os dados envolvidos, para dar uma idéia de alguns dos recursos do sistema. No sistema atual, cada bloco de água contém o seguinte em 2 bytes:

//Data2                          Data
//______________________________  _____________________________________
//|0    |0      |000   |000    |  |0        |0       |000      |000   |
//|Extra|FlowOut|Active|Largest|  |HasSource|IsSource|Direction|Height|
//------------------------------  -------------------------------------
  • Height é a quantidade de água no cubo, semelhante à sua pressão, mas meu sistema tem apenas 8 níveis.
  • Directioné a direção em que o fluxo está indo. Ao decidir para onde a água fluirá a seguir, é mais provável que continue na direção atual. Isso também é usado para rastrear rapidamente um fluxo de volta ao seu cubo de origem, quando necessário.
  • IsSourceindica se este cubo é um cubo de origem, o que significa que nunca fica sem água. Usado para a fonte de rios, nascentes, etc. O cubo à esquerda no gif acima é um cubo de origem, por exemplo.
  • HasSourceindica se este cubo está conectado a um cubo de origem. Quando conectado a uma fonte, os cubos tentam extrair mais água da fonte antes de procurar outros cubos não-fonte "mais completos".
  • Largestdiz a este cubo qual é o maior fluxo entre ele e seu cubo de origem. Isso significa que se a água estiver fluindo através de um espaço estreito, isso limitará o fluxo a este cubo.
  • Activeé um contador. Quando esse cubo tem um fluxo ativo passando por ele, para ele ou a partir dele, o ativo é incrementado. Caso contrário, o ativo é aleatoriamente decrementado. Uma vez que o ativo atinja zero (o que significa não ativo), a quantidade de água começará a ser reduzida neste cubo. Esse tipo de ação atua como evaporação ou imersão no solo. ( Se você tem fluxo, deve ter refluxo! )
  • FlowOutindica se este cubo está conectado a um cubo que está na extremidade do mundo. Uma vez que um caminho para a extremidade do mundo é feito, a água tende a escolher esse caminho em detrimento de qualquer outro.
  • Extra é um pouco mais para uso futuro.

Agora que conhecemos os dados, vamos analisar uma visão geral de alto nível do algoritmo. A idéia básica do sistema é priorizar o fluxo para baixo e para fora. Como explico no vídeo, trabalho de baixo para cima. Cada camada de água é processada um nível de cada vez no eixo y. Os cubos de cada nível são processados ​​aleatoriamente, cada cubo tentará extrair água de sua fonte em cada iteração.

Os cubos de fluxo retiram a água de sua fonte seguindo a direção do fluxo de volta até atingirem um cubo de origem ou um cubo de fluxo sem pai. Armazenar a direção do fluxo em cada cubo facilita o acompanhamento do caminho para a origem como percorrer uma lista vinculada.

O pseudo-código para o algoritmo é o seguinte:

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()

As regras básicas para expandir um fluxo em que (ordenadas por prioridade):

  1. Se o cubo abaixo tiver menos água, flua para baixo
  2. Se o cubo adjacente no mesmo nível tiver menos água, flua lateralmente.
  3. Se o cubo acima tiver menos água E o cubo de origem for maior que o cubo acima, flua para cima.

Eu sei, isso é um nível bastante alto. Mas é difícil entrar em mais detalhes sem sair do caminho em detalhes.

Este sistema funciona muito bem. Posso encher facilmente poços de água, que transbordam para continuar para fora. Posso encher túneis em forma de U, como você pode ver no gif acima. No entanto, como eu disse, o sistema está incompleto e ainda não resolvi tudo. Não trabalho no sistema de fluxo há muito tempo (decidi que não era necessário para o alfa e o colocava em espera). No entanto, os problemas com os quais eu estava lidando quando o coloquei em espera onde:

  • Piscinas . Ao obter uma grande piscina de água, os indicadores de filho para pai são como uma bagunça louca de qualquer cubo aleatório selecionado para fluir em qualquer direção. Como encher uma banheira com barbante bobo. Quando você deseja drenar a banheira, você deve seguir o caminho da corda boba até sua origem? Ou você deve apenas pegar o que estiver mais próximo? Portanto, em situações em que os cubos estão em uma grande piscina, eles provavelmente devem ignorar os fluxos pai e extrair o que estiver acima deles. Eu vim com algum código básico de trabalho para isso, mas nunca tive uma solução elegante com a qual eu pudesse ser feliz.

  • Pais múltiplos . Um fluxo filho pode ser facilmente alimentado por mais de um fluxo pai. Mas a criança com um ponteiro para um pai solteiro não permitiria isso. Isso pode ser corrigido usando bits suficientes para permitir um pouco para cada direção pai possível. E provavelmente alterando o algoritmo para selecionar aleatoriamente um caminho no caso de vários pais. Mas nunca cheguei a isso para testar e ver quais outros problemas podem expor.


Obrigado! Muito informativo! Vou começar a trabalhar nisso em breve e aceitá-lo se tudo correr bem.
Cyral 8/07/2013

Coisa certa. Eu imagino um híbrido do seu sistema e este seria muito eficaz para um mundo 2D. Ligue-me no chat (com @ byte56) se quiser discutir detalhes.
MichaelHouse

Tudo bem, pode demorar um dia ou mais até que eu tenha a chance de tentar fazer isso.
Cyral

3
Compreensível. Eu provavelmente passei meses trabalhando nisso (e trabalhando novamente). Eu vou ser em torno de um tempo embora :)
Michaelhouse

2

Eu meio que concordo com Sean, mas faria um pouco diferente:

Um bloco gera uma pressão igual ao seu próprio peso (quanta água está nele) e a aplica nos blocos abaixo e ao lado dele. Não vejo razão para que sua posição no mundo seja relevante.

Em cada marca, mova a água de alta pressão para baixa pressão, mas mova apenas uma fração da água necessária para equalizar. A água também pode ser empurrada para cima se a pressão no bloco for muito alta para a pressão aplicada no quadrado.

Você obterá loops onde a pressão da água flui muito de uma maneira e depois precisa ser corrigida, mas como você não move toda a quantidade de água por carrapato, esses serão amortecidos. Eu acho que é realmente uma coisa boa, pois você terá efeitos de picos de água à medida que a água inunda uma área como faria na realidade.


Se a água subisse quando a pressão aplicada de cima fosse muito alta, ela não se moveria para um bloco de pressão mais baixa. Para que a pressão acima seja muito grande, teria que ser maior que o bloco abaixo. Além disso, a pressão precisa subir e descer e esquerda / direita.
MichaelHouse

@ Byte56 Você está interpretando mal o que eu disse. Estou dizendo que a água sobe quando a pressão no bloco que você está analisando é muito alta para a pressão aplicada de cima, não que a pressão de cima seja muito alta!
Loren Pechtel

Tudo bem, deixe-me reformular o que você disse para que eu entenda: "a água sobe quando a pressão no bloco que você está analisando é maior que a pressão aplicada de cima". Isso está correto?
MichaelHouse

@ Byte56 Sim. A pressão no bloco deve ser o peso da água acima dele ou ser aplicada de lado quando tivermos uma superfície sólida em algum lugar acima. Pouca pressão significa que não há água suficiente acima, mova a água para cima.
Loren Pechtel

Gostaria apenas de acrescentar que, se você estiver lidando com água corrente, isso não será suficiente e você também precisará considerar a inércia ou a água se moverá muito devagar.
Cube8 /

1

Você pode adicionar uma regra que tenta ir para a esquerda ou direita (através das paredes) com os ladrilhos até encontrar um ponto livre, começando pelas camadas na parte inferior. Se você não conseguir encontrar, o bloco permanecerá na posição atual. Se você encontrar, as outras regras garantirão a substituição do bloco movido (se necessário).


Essa também é uma boa idéia, não tenho certeza se funcionaria em todos os casos, mas vou considerá-la.
21413 Cyral

Está bem! Deixe-me saber se funcionou ou não. relação
almanegra

Eu vou, só tenho estado um pouco ocupado ultimamente.
Cyral

-2

por que você não pode definir outro tipo de bloco que atua como uma quantidade imóvel de pressão? Portanto, quando você usa o seu modo de mover normalmente os blocos de água e verificar se ele pode subir, ele não pode.

Melhor ainda seria adicionar outra definição a esses blocos que permita ao usuário inserir a quantidade de pressão por bloco, aumentando a pressão de acordo com a quantidade de blocos de água a adicionar.


1
"Portanto, quando você usa o seu modo de mover normalmente os blocos de água e verificar se ele pode subir, ele não pode." Sim ... já não pode. Esse é o problema, não estou procurando uma maneira de fazê-lo permanecer o mesmo.
21413 Cyral
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.