Symulacja ciśnienia w siatkowej symulacji cieczy

30

W mojej grze XNA mam system wodny oparty na siatce 2D, mamy metodę wykorzystującą automaty komórkowe do symulacji spadania i rozprzestrzeniania się wody.

Przykład wody spływającej ze zbocza:

Fizyka Wody

Każda płytka może zawierać masę od 0 do 255 wartości cieczy, zapisanych w bajcie. Nie używam floats, starego systemu wodnego, który miałem, ale to komplikowało i miało hit wydajności.

Każdy kafelek wody aktualizuje się za pomocą prostego zestawu zasad:

  1. Jeśli na kafelku poniżej jest miejsce, przenieś jak najwięcej z bieżącego kafelka na dolny (Flow Down)
  2. Jeśli 2 strony nie są takie same i nie są zerowe, a obie są przejezdne, otrzymujemy sumę 3 płytek (lewa + prąd + prawa) i dzielimy ją przez 3, pozostawiając resztę na środkowej (bieżącej) płytce
  3. Jeśli powyższa reguła podała liczbę 2 jako sumę, powinniśmy podzielić płytki na dwie strony (1, 0, 1)
  4. Jeśli reguła 2 podała 1 jako sumę, wybierz losową stronę, do której wpłynie
  5. Jeśli reguła 2 zawiodła, powinniśmy sprawdzić, czy jedna strona jest przejezdna, a druga nie. Jeśli to prawda, dzielimy bieżącą płytkę na pół dla 2 płytek

Jak mogę rozwinąć tę logikę, aby uwzględnić presję? Ciśnienie spowoduje wzrost cieczy powyżej „zakrętów U” i wypełnienie kieszeni powietrznych.

Przykład, jak to się obecnie nie powiedzie:

Awaria ciśnienia

Woda powinna płynąć i wyrównywać się po obu stronach U-Bend. Dodatkowo stworzyłem metody, aby dowiedzieć się, jak daleko jest blok wodny, a tym samym, jak duży nacisk on odczuwa. Teraz muszę móc wziąć te liczby i zastosować je w innych obszarach, aby wyrównać presję.

Cyral
źródło
Problem polega na tym, że trudno jest zachować automaty komórkowe. Od teraz każdy blok musi wiedzieć więcej niż tylko to, co jest obok. Stworzyłem system podobny do tego, który chcesz w 3D. Jest to dość złożony system, ale myślę, że bardziej wykonalny byłby w 2D.
MichaelHouse
@ Byte56 Cóż, nie musimy być automatami komórkowymi, o ile możemy utrzymać je przy rozsądnej prędkości.
Cyral
3
Stworzę pełną odpowiedź, jeśli znajdę trochę czasu tego wieczoru. Mówiąc najprościej, zasadniczo stworzyłem szukanie ścieżki dla wody. Bloki chcą znaleźć miejsce z mniejszą presją. Ścieżki znajdują przez drugą wodę, szukając miejsca, w którym jest mniej wody niż one (w tym powietrze obok wody). Rozwiązuje znaczną większość przypadków użycia.
MichaelHouse
Dzięki, byłoby to mile widziane. Przeczytałem kilka wywiadów z twórcą Dwarf Fortress i zrobił to, wierzę, ale nie byłem pewien, jak rozwiązać niektóre problemy, na które natrafił, więc nigdy tak naprawdę nie próbowałem.
Cyral
1
Zauważ, że po dodaniu ciśnienia powietrza dwa przykłady kieszeni powietrznych są potencjalnie całkowicie poprawne (zamknięte komory ciśnieniowe). Zakładam, że nie używasz 255 bajtów , ale raczej wartości 0-255; w każdym razie prawdopodobnie nie będziesz chciał korzystać z pełnego zakresu w ten sposób. Prawdopodobnie ograniczyłbym to do, hmm, 0-15 dla „1 atmosfery” ciśnienia (nie ma czegoś takiego jak „ujemne” ciśnienie, prawda?), Dopuszczając wyższe ciśnienia, których obecnie brakuje. Po dołączeniu do karty bloków „powietrznych”, naturalnie wyższy „ciężar” bloków wodnych powinien spowodować, że opłynie ona zakręty.
Clockwork-Muse

Odpowiedzi:

6

Zauważ, że nigdy tego nie zrobiłem; są to tylko pomysły, które mogą pomóc. Lub może być całkowicie fałszywy. Chciałem rozwiązać ten problem od czasu Terrarii, ale obecnie nie pracuję nad taką grą.

Sposobem, który rozważałem, jest nadanie każdemu blokowi wody powierzchniowej (każdemu blokowi z wodą i bez bloku wodnego) początkowej wartości ciśnienia równej (lub funkcji) jego wysokości z dna świata. Implikowana wartość ciśnienia dowolnej nieprzejezdnej płytki wynosi MAX_PRESSURE(powiedzmy 255), a dla płytki na wolnym powietrzu wynosi MIN_PRESSURE(0).

Nacisk jest następnie rozkładany w górę / w dół / na boki z dowolnej płytki o wyższym ciśnieniu na płytki o niższym ciśnieniu podczas każdego tyknięcia, w stylu automatów komórkowych. Musiałbym wykonać rzeczywistą symulację, aby dowiedzieć się dokładnie, do czego się wyrównać. Ciśnienie bloku powinno być równe jego ciśnieniu niejawnemu powiększonemu o „nadwyżkowe” ciśnienie z otoczenia wyrównywane (więc wystarczy przechowywać to nadciśnienie, a nie ciśnienie ukryte).

Jeśli płytka powierzchniowa ma ciśnienie większe niż jej domyślny nacisk oparty na wysokości, a jeśli płytka powyżej ma wolną przestrzeń dla wody, niewielka część wody jest przesuwana w górę. Woda spływa tylko wtedy, gdy na obu płytkach jest miejsce, ponieważ ciśnienie jest niższe niż oczekiwano.

To z grubsza symuluje ideę, że im głębszy „punkt” wody, tym większy jest nacisk, chociaż wartości ciśnienia reprezentują większą wysokość niż ciśnienie rzeczywiste (ponieważ oczekuje się, że wyższe płytki będą miały wyższe „ciśnienie”). To sprawia, że ​​ciśnienie jest trochę takie jak hw równaniu (ale nie bardzo):

P' = P + qgh

Powoduje to, że jeśli ciśnienie wody jest wyższe niż powinno być dla jego głębokości, zostanie ono wypchnięte. Powinno to oznaczać, że poziomy wody w zamkniętych systemach wyrównają ciśnienie na wszystkich poziomach wysokości w czasie.

Nie jestem pewien, jak sobie z tym poradzić ani czy w ogóle trzeba poradzić sobie z „pęcherzykami powietrza”, które powstałyby (gdzie płytka niepowierzchniowa będzie miała niepełne ilości wody, gdy woda zostanie wypchnięta w górę). Nadal nie jestem pewien, jak można uniknąć nierówności ciśnienia wody po jednej stronie, a następnie po zaznaczeniu nierówności po drugiej stronie, tam iz powrotem.

Sean Middleditch
źródło
20

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ą):

wprowadź opis zdjęcia tutaj

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.
  • Directionjest 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.
  • IsSourcewskazuje, 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.
  • HasSourcewskazuje, 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.
  • Largestmó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.
  • Activejest 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ąć! )
  • FlowOutwskazuje, 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):

  1. Jeśli sześcian poniżej ma mniej wody, spływaj
  2. Jeśli sąsiadujący sześcian na tym samym poziomie ma mniej wody, przepłyń bocznie.
  3. 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ć.

MichaelHouse
źródło
Dzięki! Bardzo informujące! Wkrótce zacznę nad tym pracować i zaakceptuję to, jeśli wszystko pójdzie dobrze.
Cyral
Jasne. Wyobrażam sobie hybrydę twojego systemu i ten byłby bardzo skuteczny w świecie 2D. Wyślij mi ping na czacie (z @ byte56), jeśli chcesz omówić szczegóły.
MichaelHouse
W porządku, może minie dzień, zanim zdążę to wypróbować.
Cyral
3
Zrozumiały. Prawdopodobnie spędziłem miesiące na opracowywaniu go (i ponownym opracowywaniu). Będę jeszcze jakiś czas :)
MichaelHouse
2

W pewnym sensie zgadzam się z Seanem, ale zrobiłbym to trochę inaczej:

Blok generuje ciśnienie równe jego własnej masie (ile wody w nim jest) i przykłada go do bloków poniżej i obok niego. Nie widzę powodu, dla którego jego pozycja na świecie jest istotna.

Na każdym kleszczu przenieś wodę z wysokiego ciśnienia na niskie ciśnienie, ale przenieś tylko ułamek wody potrzebnej do wyrównania. Wodę można również wypchnąć, jeśli ciśnienie w bloku jest zbyt duże, aby wywierać nacisk na kwadrat.

Dostaniesz pętle, w których ciśnienie wody przepływa zbyt daleko w jedną stronę, a następnie trzeba je poprawić, ale ponieważ nie przesuniesz całej ilości wody na kleszcza, zostaną one wytłumione. Myślę, że to naprawdę dobra rzecz, ponieważ dostaniesz efekt przypływu, gdy woda zaleje obszar, tak jak w rzeczywistości.

Loren Pechtel
źródło
Gdyby woda podniosła się, gdy przykładane z góry ciśnienie było zbyt duże, nie przenosiłoby się do bloku o niższym ciśnieniu. Aby ciśnienie powyżej było zbyt duże, musiałoby być większe niż blok poniżej. Dodatkowo ciśnienie musi rosnąć w górę, a także w dół i w lewo / w prawo.
MichaelHouse
@ Byte56 Źle interpretujesz to, co powiedziałem. Mówię, że woda podnosi się, gdy ciśnienie w bloku, który analizujesz, jest zbyt wysokie, aby można było zastosować ciśnienie z góry, nie że ciśnienie z góry jest zbyt duże!
Loren Pechtel,
OK, więc pozwól mi sformułować to, co powiedziałeś, więc rozumiem: „woda podnosi się, gdy ciśnienie w bloku, który analizujesz, jest większe niż ciśnienie przykładane z góry”. Czy to jest poprawne?
MichaelHouse
@ Byte56 Tak. Ciśnienie w bloku powinno być ciężarem wody nad nim lub być stosowane na boki, gdy mamy gdzieś twardą powierzchnię. Zbyt mały nacisk oznacza, że ​​na górze nie ma wystarczającej ilości wody, przenieś wodę do góry.
Loren Pechtel,
Chciałbym tylko dodać, że jeśli masz do czynienia z płynącą wodą, to nie wystarczy i musisz również wziąć pod uwagę bezwładność lub woda będzie poruszać się zbyt wolno.
sześcian
1

Możesz dodać regułę, która próbuje przejść w lewo lub w prawo (przez ściany) z kafelkami, aż znajdziesz wolne miejsce, zaczynając od warstw na dole. Jeśli nie możesz znaleźć, płytka pozostaje na bieżącej pozycji. Jeśli znajdziesz, inne zasady zagwarantują wymianę przeniesionego kafelka (jeśli to konieczne).

almanegra
źródło
To także dobry pomysł, nie jestem pewien, czy zadziała we wszystkich przypadkach, ale rozważę to.
Cyral
Dobrze! Daj mi znać, czy to zadziałało, czy nie. pozdrowienia
almanegra,
Będę ostatnio trochę zajęty.
Cyral,
-2

dlaczego nie możesz zdefiniować innego rodzaju bloku, który działa jak nieznośny nacisk? Dlatego kiedy używasz swojego sposobu normalnego przemieszczania bloków wodnych i sprawdzania, czy może się przesunąć w górę, nie może.

Jeszcze lepiej byłoby dodać kolejną definicję do tych bloków, która pozwala użytkownikowi wprowadzić ciśnienie na blok, zwiększając ciśnienie zgodnie z ilością dodawanych bloków wodnych.

SD1990
źródło
1
„dlatego, gdy używasz swojego sposobu normalnego przemieszczania bloków wodnych i sprawdzania, czy może się poruszać w górę, nie może”. Tak ... Już nie może. To jest problem, nie szukam sposobu, aby pozostać niezmienionym.
Cyral