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):
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.
Direction
est 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.
IsSource
indique 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.
HasSource
indique 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".
Largest
indique à 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.
Active
est 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! )
FlowOut
indique 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é):
- Si le cube ci-dessous contient moins d'eau, descendez
- Si le cube adjacent au même niveau a moins d'eau, s'écouler latéralement.
- 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.