Stworzyłem system podobny do tego, którego szukasz w 3D. Mam krótki film wykazujący proste mechanikę nim tutaj i na blogu tutaj .
Oto mały gif, który zrobiłem z mechaniki nacisku za niewidzialną ścianą (graną z dużą prędkością):
Pozwól mi wyjaśnić związane z tym dane, aby dać wyobrażenie o niektórych funkcjach systemu. W obecnym systemie każdy blok wody zawiera następujące elementy w 2 bajtach:
//Data2 Data
//______________________________ _____________________________________
//|0 |0 |000 |000 | |0 |0 |000 |000 |
//|Extra|FlowOut|Active|Largest| |HasSource|IsSource|Direction|Height|
//------------------------------ -------------------------------------
Height
to ilość wody w kostce, podobna do twojego ciśnienia, ale mój system ma tylko 8 poziomów.
Direction
jest kierunkiem przepływu. Przy podejmowaniu decyzji o tym, gdzie woda przepłynie w następnej kolejności, bardziej prawdopodobne jest kontynuowanie jej w bieżącym kierunku. Służy to również do szybkiego śledzenia przepływu z powrotem do kostki źródłowej, gdy jest to potrzebne.
IsSource
wskazuje, czy ta kostka jest kostką źródłową, co oznacza, że nigdy nie zabraknie jej wody. Używany do źródła rzek, źródeł itp. Sześcian po lewej w powyższym gifie to na przykład sześcian źródłowy.
HasSource
wskazuje, czy ta kostka jest podłączona do kostki źródłowej. Po podłączeniu do źródła kostki będą próbowały stukać źródło w celu uzyskania większej ilości wody, zanim zaczną szukać innych „pełniejszych” kostek niebędących źródłami.
Largest
mówi tej kostce, jaki jest największy przepływ między nią a jej kostką źródłową. Oznacza to, że jeśli woda przepływa przez wąską szczelinę, ogranicza przepływ do tego sześcianu.
Active
jest licznikiem. Kiedy ten sześcian ma aktywny przepływ przechodzący przez niego, do niego lub z niego, aktywny zostaje zwiększony. W przeciwnym razie aktywny jest losowo zmniejszany. Gdy aktywna osiągnie zero (czyli nieaktywna), ilość wody zacznie się zmniejszać w tej kostce. Ten rodzaj działa jak parowanie lub wnikanie w ziemię. ( Jeśli masz przepływ, powinieneś odpłynąć! )
FlowOut
wskazuje, czy ta kostka jest połączona z kostką na skraju świata. Po utworzeniu ścieżki na skraj świata woda zwykle wybiera tę ścieżkę.
Extra
to dodatkowy kawałek do wykorzystania w przyszłości.
Teraz, gdy znamy dane, spójrzmy na ogólny przegląd algorytmu. Podstawową ideą systemu jest ustalenie priorytetów w przepływie i odpływie. Jak wyjaśniam na filmie, pracuję od podstaw. Każda warstwa wody jest przetwarzana jeden poziom na raz na osi y. Kostki dla każdego poziomu są przetwarzane losowo, każda kostka będzie próbowała wyciągać wodę ze źródła podczas każdej iteracji.
Kostki przepływowe wyciągają wodę ze źródła, podążając z powrotem ich kierunkiem przepływu, aż dotrą do kostki źródłowej lub kostki przepływowej bez elementu nadrzędnego. Zapamiętanie kierunku przepływu w każdej kostce sprawia, że podążanie ścieżką do źródła jest tak proste, jak przejście przez połączoną listę.
Pseudo-kod dla algorytmu jest następujący:
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()
Podstawowe zasady rozszerzania przepływu gdzie (uporządkowane według priorytetów):
- Jeśli sześcian poniżej ma mniej wody, spływaj
- Jeśli sąsiadujący sześcian na tym samym poziomie ma mniej wody, przepłyń bocznie.
- Jeśli sześcian powyżej ma mniej wody ORAZ sześcian źródła jest wyższy niż sześcian powyżej, przepłyń w górę.
Wiem, to całkiem wysoki poziom. Ale trudno dostać się do bardziej szczegółowo, bez uzyskiwania drogę w szczegółach.
Ten system działa całkiem dobrze. Mogę łatwo napełnić doły wody, które przelewają się, aby kontynuować na zewnątrz. Mogę wypełnić tunele w kształcie litery U, jak widać na powyższym gifie. Jednak, jak powiedziałem, system jest niekompletny i jeszcze nie wszystko dopracowałem. Dawno nie pracowałem nad systemem przepływu (zdecydowałem, że nie jest on potrzebny w wersji alfa i zawiesiłem go). Jednak problemy, z którymi miałem do czynienia, gdy je odłożyłem, gdzie:
Baseny . Gdy zdobywamy dużą kałużę wody, wskaźniki od dziecka do rodzica są jak szalony bałagan z dowolnej losowej kostki wybranej do płynięcia w dowolnym kierunku. Jak wypełnienie wanny głupim sznurkiem. Kiedy chcesz opróżnić wannę, powinieneś podążać ścieżką głupiego sznurka z powrotem do źródła? A może powinieneś wziąć to, co jest najbliższe? Tak więc w sytuacjach, w których kostki znajdują się w dużej sadzawce, prawdopodobnie powinny po prostu zignorować przepływy rodziców i wyciągnąć wszystko, co jest nad nimi. Wymyśliłem do tego podstawowy działający kod, ale nigdy nie miałem eleganckiego rozwiązania, z którego mógłbym być zadowolony.
Wielu rodziców . Strumień potomny może być łatwo zasilany przez więcej niż jeden strumień macierzysty. Ale dziecko mające wskaźnik do samotnego rodzica nie pozwoli na to. Można to naprawić za pomocą wystarczającej liczby bitów, aby pozwolić na bit dla każdego możliwego kierunku rodzica. I prawdopodobnie zmiana algorytmu, aby losowo wybrać ścieżkę w przypadku wielu rodziców. Ale nigdy nie zabrałem się za to, aby przetestować i zobaczyć, jakie inne problemy mogą ujawnić.