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):
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.
IsSource
indica 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.
HasSource
indica 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".
Largest
diz 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! )
FlowOut
indica 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):
- Se o cubo abaixo tiver menos água, flua para baixo
- Se o cubo adjacente no mesmo nível tiver menos água, flua lateralmente.
- 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.