Simuler la pression dans une simulation de liquide basée sur une grille

30

J'ai un système d'eau basé sur une grille 2D dans mon jeu XNA, nous avons une méthode utilisant des automates cellulaires pour simuler la chute et la propagation de l'eau.

Exemple d'eau coulant sur une pente:

Physique de l'eau

Chaque tuile peut contenir une masse de 0 à 255 valeurs de liquide, stockées dans un octet. Je n'utilise pas floats, l'ancien système d'eau que j'avais fait, mais cela a ajouté des complications et a eu un impact sur les performances.

Chaque tuile eau se met à jour avec un ensemble simple de règles:

  1. Si la tuile ci-dessous contient de l'espace, déplacez-vous autant que possible de la tuile actuelle vers la dernière (Flow Down)
  2. Si les 2 côtés ne sont pas identiques et ne sont pas nuls et que les deux sont passables, nous obtenons la somme des 3 tuiles (gauche + courant + droite) et la divisons par 3 en laissant le reste sur la tuile centrale (actuelle)
  3. Si la règle ci-dessus a donné un nombre de 2 comme somme, nous devons diviser les tuiles en deux côtés (1, 0, 1)
  4. Si la règle 2 donne 1 comme somme, choisissez un côté aléatoire dans lequel
  5. Si la règle 2 a échoué, nous devons vérifier si un côté est passable et l'autre non. Si c'est vrai, nous divisons la tuile actuelle en deux pour les 2 tuiles

Comment puis-je étendre cette logique pour inclure la pression? La pression fera remonter les liquides au-dessus des «coudes en U» et remplira les poches d'air.

Exemple sur la façon dont cela échoue actuellement:

Échec de la pression

L'eau doit s'écouler et s'égaliser de chaque côté du coude en U. De plus, j'ai créé des méthodes pour savoir à quelle distance se trouve un bloc d'eau et, par conséquent, la pression qu'il subit. Maintenant, je dois pouvoir prendre ces chiffres et les appliquer aux autres zones pour égaliser la pression.

Cyral
la source
Le problème est qu'il est difficile de garder un automate cellulaire. Depuis, chaque bloc doit en savoir plus que ce qui est à côté. J'ai créé un système similaire à celui que vous souhaitez en 3D. C'est un système assez complexe, mais je pense que ce serait plus faisable en 2D.
MichaelHouse
@ Byte56 Eh bien, nous n'avons pas besoin qu'il s'agisse d'automates cellulaires, tant que nous pouvons le faire fonctionner à une vitesse raisonnable.
Cyral
3
Je vais créer une réponse complète si je trouve un peu de temps ce soir. Cependant, pour faire simple, j'ai essentiellement créé des repères pour l'eau. Les blocs veulent trouver un endroit où la pression est moindre. Ils trouvent le chemin à travers l'autre eau à la recherche d'un endroit qui a moins d'eau qu'eux (air à côté de l'eau inclus). Il résout une grande majorité des cas d'utilisation.
MichaelHouse
Merci, ce serait apprécié. J'ai lu quelques interviews avec le créateur de Dwarf Fortress et il l'a fait je le crois, mais je ne savais pas comment surmonter certains des problèmes qu'il a rencontrés, donc je n'ai jamais vraiment essayé.
Cyral
1
Notez qu'une fois la pression d'air ajoutée, les deux exemples de poches d'air sont potentiellement complètement valides (chambres de pression fermées). Je suppose que vous n'utilisez pas 255 octets , mais plutôt des valeurs de 0 à 255; en tout cas, vous n'allez probablement pas vouloir utiliser la gamme complète de cette façon. Je la limiterais probablement à, hmm, 0-15 pour une pression de «1 atmosphère» (il n'y a pas de pression «négative», n'est-ce pas?), Permettant des pressions plus élevées, ce qui vous manque actuellement. Une fois que vous avez inclus les blocs «air» dans la simulation, le «poids» naturellement plus élevé des blocs d'eau devrait le faire circuler dans les virages.
Clockwork-Muse

Réponses:

6

Notez que je n'ai jamais fait cela; ce ne sont que des idées qui peuvent aider. Ou pourrait être totalement faux. Je voulais résoudre ce problème depuis Terraria, mais je ne travaille pas actuellement sur un tel jeu.

J'ai envisagé d'essayer de donner à chaque bloc d'eau de surface (tout bloc contenant de l'eau et sans bloc d'eau au-dessus) une valeur de pression initiale égale à (ou une fonction de) sa hauteur par rapport au bas du monde. La valeur de pression implicite de toute tuile infranchissable est MAX_PRESSURE(disons 255), et pour une tuile en plein air est MIN_PRESSURE(0).

La pression est ensuite répartie vers le haut / bas / latéralement à partir de n'importe quelle tuile avec une pression plus élevée vers des tuiles avec une pression plus faible pendant chaque tick, style d'automates cellulaires. Je devrais obtenir une simulation réelle pour savoir exactement à quoi égaliser. La pression d'un bloc doit être égale à sa pression implicite plus la "surpression" de la pression égalisée (il vous suffit donc de stocker cette surpression, pas la pression implicite).

Si une tuile de surface a une pression supérieure à sa pression implicite basée sur la hauteur et si la tuile ci-dessus a un espace libre pour l'eau, une petite partie de l'eau est déplacée vers le haut. L'eau ne coule que si les carreaux ont tous deux de la place et une pression plus faible que prévu.

Cela simule à peu près l'idée que plus le "point" de l'eau est profond, plus il a de pression, bien que les valeurs de pression représentent plus la hauteur que la pression réelle (car les tuiles plus élevées devraient avoir une "pression" plus élevée). Cela rend la pression un peu comme le hterme de l'équation (mais pas vraiment):

P' = P + qgh

Le résultat est que si la pression de l'eau est plus élevée qu'elle ne devrait l'être pour sa profondeur, elle sera poussée vers le haut. Cela devrait signifier que les niveaux d'eau dans les systèmes fermés égaliseront la pression à tous les niveaux de hauteur au fil du temps.

Je ne sais pas comment faire face ou si l'on doit même faire face aux "bulles d'air" qui seraient créées (où une tuile non superficielle aura des quantités d'eau non pleines lorsque l'eau sera poussée vers le haut). Je ne sais pas non plus comment vous pourriez éviter que les boucles de pressions d'eau soient inégales d'un côté, puis après avoir coché être inégales de l'autre côté, d'avant en arrière.

Sean Middleditch
la source
20

J'ai créé un système similaire à celui que vous recherchez en 3D. J'ai une courte vidéo démontrant les mécanismes simples de celui-ci ici et un blog ici .

Voici un petit gif que j'ai fait des mécanismes de pression derrière un mur invisible (joué à grande vitesse):

entrez la description de l'image ici

Permettez-moi d'expliquer les données impliquées, pour donner une idée de certaines des caractéristiques du système. Dans le système actuel, chaque bloc d'eau contient les éléments suivants sur 2 octets:

//Data2                          Data
//______________________________  _____________________________________
//|0    |0      |000   |000    |  |0        |0       |000      |000   |
//|Extra|FlowOut|Active|Largest|  |HasSource|IsSource|Direction|Height|
//------------------------------  -------------------------------------
  • Height est la quantité d'eau dans le cube, similaire à votre pression, mais mon système n'a que 8 niveaux.
  • Directionest la direction du flux. Lorsque vous décidez où l'eau coulera ensuite, il est plus probable qu'elle continue dans sa direction actuelle. Il est également utilisé pour remonter rapidement un flux remontant jusqu'à son cube source en cas de besoin.
  • IsSourceindique si ce cube est un cube source, ce qui signifie qu'il ne manque jamais d'eau. Utilisé pour la source des rivières, sources, etc. Le cube à gauche dans le gif ci-dessus est un cube source, par exemple.
  • HasSourceindique si ce cube est connecté à un cube source. Lorsqu'ils sont connectés à une source, les cubes essaieront de puiser dans la source pour plus d'eau avant de rechercher d'autres cubes non sources "plus complets".
  • Largestindique à ce cube quel est le plus grand flux entre lui et son cube source. Cela signifie que si l'eau s'écoule à travers un espace étroit, cela limite le débit vers ce cube.
  • Activeest un compteur. Lorsque ce cube a un flux actif qui le traverse, vers lui ou depuis celui-ci, l'actif est incrémenté. Sinon, actif est décrémenté de façon aléatoire. Une fois actif atteint zéro (ce qui signifie non actif), la quantité d'eau commencera à être réduite dans ce cube. Ce genre d'actes comme l'évaporation ou le trempage dans le sol. ( Si vous avez un flux, vous devriez avoir un reflux! )
  • FlowOutindique si ce cube est connecté à un cube qui est au bord du monde. Une fois qu'un chemin vers le bord du monde est fait, l'eau a tendance à choisir ce chemin plutôt qu'un autre.
  • Extra est un peu supplémentaire pour une utilisation future.

Maintenant que nous connaissons les données, regardons un aperçu de haut niveau de l'algorithme. L'idée de base du système est de prioriser les flux descendants et sortants. Comme je l'explique dans la vidéo, je travaille de bas en haut. Chaque couche d'eau est traitée un niveau à la fois sur l'axe y. Les cubes pour chaque niveau sont traités au hasard, chaque cube tentera de tirer de l'eau de sa source à chaque itération.

Les cubes d'écoulement tirent l'eau de leur source en suivant leur direction d'écoulement jusqu'à ce qu'ils atteignent un cube source ou un cube d'écoulement sans parent. Le stockage de la direction du flux dans chaque cube permet de suivre le chemin vers la source aussi facilement que de parcourir une liste liée.

Le pseudo-code de l'algorithme est le suivant:

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

Les règles de base pour développer un flux où (classés par priorité):

  1. Si le cube ci-dessous contient moins d'eau, descendez
  2. Si le cube adjacent au même niveau a moins d'eau, s'écouler latéralement.
  3. Si le cube ci-dessus contient moins d'eau ET que le cube source est supérieur au cube ci-dessus, remontez.

Je sais, c'est un niveau assez élevé. Mais il est difficile d'entrer dans plus de détails sans se voie dans les détails.

Ce système fonctionne plutôt bien. Je peux facilement remplir des fosses d'eau qui débordent pour continuer vers l'extérieur. Je peux remplir des tunnels en U comme vous le voyez dans le gif ci-dessus. Cependant, comme je l'ai dit, le système est incomplet et je n'ai pas encore tout réglé. Je n'ai pas travaillé sur le système de flux depuis longtemps (j'ai décidé qu'il n'était pas nécessaire pour alpha et je le mettrais en attente). Cependant, les problèmes que je traitais lorsque je l'ai mis en attente où:

  • Piscines . Lorsque vous obtenez une grande piscine d'eau, les pointeurs de l'enfant au parent sont comme un désordre fou de n'importe quel cube aléatoire sélectionné pour couler dans n'importe quelle direction. Comme remplir une baignoire de ficelle idiote. Lorsque vous voulez vider la baignoire, devez-vous suivre le chemin de la ficelle idiote jusqu'à sa source? Ou devriez-vous simplement prendre ce qui est le plus proche? Donc, dans les situations où les cubes sont dans un grand pool, ils devraient probablement ignorer leurs flux parents et tirer de tout ce qui est au-dessus d'eux. J'ai trouvé un code de travail de base pour cela, mais je n'ai jamais eu de solution élégante dont je pourrais être satisfait.

  • Plusieurs parents . Un flux enfant peut facilement être alimenté par plusieurs flux parent. Mais l'enfant ayant un pointeur vers un parent seul ne le permettrait pas. Cela peut être résolu en utilisant suffisamment de bits pour permettre un bit pour chaque direction parent possible. Et probablement changer l'algorithme pour sélectionner au hasard un chemin dans le cas de plusieurs parents. Mais, je ne l'ai jamais fait pour tester et voir quels autres problèmes pourraient exposer.

MichaelHouse
la source
Merci! Très instructif! Je vais bientôt commencer à travailler dessus et l'accepter si tout se passe bien.
Cyral
Chose sûre. J'imagine un hybride de votre système et celui-ci serait très efficace pour un monde 2D. Envoyez-moi un ping dans le chat (avec @ byte56) si vous souhaitez discuter des détails.
MichaelHouse
D'accord, ça pourrait prendre un jour ou deux avant que j'aie la chance de l'essayer.
Cyral
3
Naturellement. J'ai probablement passé des mois à le travailler (et à le retravailler). Je serai là pendant un certain temps cependant :)
MichaelHouse
2

Je suis en quelque sorte d'accord avec Sean mais je le ferais un peu différemment:

Un bloc génère une pression égale à son propre poids (la quantité d'eau qu'il contient) et l'applique aux blocs situés en dessous et à côté. Je ne vois aucune raison pour laquelle sa position dans le monde est pertinente.

Sur chaque tique, déplacez l'eau de la haute pression à la basse pression, mais ne déplacez qu'une fraction de l'eau nécessaire pour l'égalisation. L'eau peut également être poussée vers le haut si la pression dans le bloc est trop élevée pour la pression appliquée sur le carré.

Vous obtiendrez des boucles où la pression de l'eau s'écoule trop loin dans un sens et doit ensuite être corrigée, mais comme vous ne déplacez pas toute la quantité d'eau par tick, elles seront amorties. Je pense que c'est en fait une bonne chose, car vous obtiendrez des effets de surtension lorsque les inondations dans une zone comme vous le feriez en réalité.

Loren Pechtel
la source
Si l'eau devait remonter alors que la pression appliquée d'en haut était trop élevée, elle ne se déplacerait pas dans un bloc de pression inférieure. Pour que la pression ci-dessus soit trop élevée, elle doit être supérieure au bloc ci-dessous. De plus, la pression doit monter, descendre et gauche / droite.
MichaelHouse
@ Byte56 Vous avez mal interprété ce que j'ai dit. Je dis que l'eau monte lorsque la pression dans le bloc que vous analysez est trop élevée pour la pression appliquée d'en haut, pas que la pression d'en haut soit trop grande!
Loren Pechtel
D'accord, laissez-moi reformuler ce que vous avez dit pour que je comprenne: "l'eau monte lorsque la pression dans le bloc que vous analysez est supérieure à la pression appliquée par le haut". Est-ce exact?
MichaelHouse
@ Byte56 Oui. La pression dans le bloc devrait être le poids de l'eau au-dessus ou être appliqué latéralement lorsque nous avons une surface solide quelque part au-dessus. Trop peu de pression dessus signifie qu'il n'y a pas assez d'eau au-dessus, faites monter l'eau.
Loren Pechtel
Je voudrais simplement ajouter que si vous avez affaire à de l'eau qui coule, cela ne sera pas suffisant et vous devrez également tenir compte de l'inertie ou l'eau se déplacera trop lentement.
cube
1

Vous pouvez ajouter une règle qui essaie d'aller à gauche ou à droite (à travers les murs) avec les tuiles jusqu'à ce que vous trouviez un endroit libre, en commençant par les couches en bas. Si vous ne trouvez pas, la tuile reste à la position actuelle. Si vous trouvez, alors les autres règles garantiront le remplacement de la tuile déplacée (si nécessaire).

almanegra
la source
C'est aussi une bonne idée, je ne sais pas si cela fonctionnerait dans tous les cas, mais je vais y réfléchir.
Cyral
D'accord! Faites-moi savoir si cela a fonctionné ou non. regards
almanegra
Je vais juste être un peu occupé ces derniers temps.
Cyral
-2

pourquoi ne pouvez-vous pas définir un autre type de bloc qui agit comme une quantité de pression inamovible? Par conséquent, lorsque vous utilisez votre façon de déplacer normalement les blocs d'eau et de vérifier si elle peut monter, cela ne peut pas.

Encore mieux serait d'ajouter une autre définition à ces blocs qui permet à l'utilisateur d'entrer la quantité de pression par bloc, en augmentant la pression en fonction de la quantité de blocs d'eau qui y s'ajoutent.

SD1990
la source
1
"Par conséquent, lorsque vous utilisez votre façon de déplacer normalement les blocs d'eau et de vérifier si elle peut monter, cela ne peut pas." Oui ... Ça ne peut déjà pas. C'est le problème, je ne cherche pas un moyen de le faire rester le même.
Cyral