Ich versuche hier eine 2D-Version von Foster und Fedkiws Artikel "Praktische Animation von Flüssigkeiten" zu implementieren: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf
Fast alles funktioniert, außer Abschnitt 8: "Erhaltung der Masse". Dort haben wir eine Gleichungsmatrix erstellt, um die Drücke zu berechnen, die erforderlich sind, um die Flüssigkeit divergent frei zu machen.
Ich glaube, mein Code stimmt mit dem Papier überein, aber ich erhalte eine unlösbare Matrix während der Erhaltung des Massenschritts.
Hier sind meine Schritte zum Generieren der Matrix A:
- Setzen Sie die diagonalen Einträge auf das Negativ der Anzahl benachbarter Flüssigkeitszellen zu Zelle i.
- Setzen Sie die Einträge und auf 1, wenn beide Zellen i und j Flüssigkeit haben.
Beachten Sie, dass in meiner Implementierung die Zelle , im Flüssigkeitsgitter der Zeile gridWidth in der Matrix entspricht.
In dem Artikel wird erwähnt: "Statische Objekte und leere Zellen stören diese Struktur nicht. In diesem Fall können Druck- und Geschwindigkeitsterme von beiden Seiten verschwinden." Daher lösche ich die Spalten und Zeilen für Zellen, die keine Flüssigkeit enthalten.
Meine Frage lautet also: Warum ist meine Matrix singulär? Vermisse ich eine Randbedingung an einer anderen Stelle in der Zeitung? Ist es die Tatsache, dass meine Implementierung 2D ist?
Hier ist eine Beispielmatrix aus meiner Implementierung für ein 2x2-Gitter, in dem die Zelle bei 0,0 keine Flüssigkeit enthält:
-1 0 1
0 -1 1
1 1 -2
Bearbeiten
Meine Forschung hat mich zu der Annahme geführt, dass ich mit den Randbedingungen nicht richtig umgehe.
Zunächst kann ich an dieser Stelle sagen, dass meine Matrix die diskrete Druck-Poisson-Gleichung darstellt. Es ist das diskrete Analogon zur Anwendung des Laplace-Operators, der lokale Druckänderungen an die Zelldivergenz koppelt.
Soweit ich verstehen kann, sind Randbedingungen erforderlich, um die Drücke auf einem absoluten Referenzwert zu "verankern", da es sich um Druckunterschiede handelt. Andernfalls kann es unendlich viele Lösungen für den Satz von Gleichungen geben.
In diesen Anmerkungen werden nach meinem besten Verständnis drei verschiedene Möglichkeiten angegeben, um Randbedingungen anzuwenden:
Dirichlet - Gibt Absolutwerte an den Grenzen an.
Neummann - gibt die Ableitung an den Grenzen an.
Robin - gibt eine Art lineare Kombination des Absolutwerts und der Ableitung an den Grenzen an.
In Foster und Fedkis Artikel wird keines davon erwähnt, aber ich glaube, dass sie Dirichlet-Randbedingungen durchsetzen, was aufgrund dieser Aussage am Ende von 7.1.2, "Der Druck in einer Oberflächenzelle wird auf atmosphärischen Druck eingestellt", bemerkenswert ist.
Ich habe die Notizen, die ich ein paar Mal verlinkt habe, gelesen und verstehe die Mathematik immer noch nicht ganz. Wie genau setzen wir diese Randbedingungen durch? Bei anderen Implementierungen scheint es eine Art Vorstellung von "Ghost" -Zellen zu geben, die an der Grenze liegen.
Hier habe ich einige Quellen verlinkt, die für andere, die dies lesen, hilfreich sein können.
Hinweise zu Randbedingungen für Poisson-Matrizen
Computational Science StackExchange-Beitrag zu Neumann-Randbedingungen
Computational Science StackExchange-Beitrag auf Poisson Solver
Implementierung von Water Physbam
Hier ist der Code, mit dem ich die Matrix generiere. Beachten Sie, dass ich anstelle des expliziten Löschens von Spalten und Zeilen eine Zuordnung von Flüssigkeitszellenindizes zu den endgültigen Matrixspalten / -zeilen generiere und verwende.
for (int i = 0; i < cells.length; i++) {
for (int j = 0; j < cells[i].length; j++) {
FluidGridCell cell = cells[i][j];
if (!cell.hasLiquid)
continue;
// get indices for the grid and matrix
int gridIndex = i + cells.length * j;
int matrixIndex = gridIndexToMatrixIndex.get((Integer)gridIndex);
// count the number of adjacent liquid cells
int adjacentLiquidCellCount = 0;
if (i != 0) {
if (cells[i-1][j].hasLiquid)
adjacentLiquidCellCount++;
}
if (i != cells.length-1) {
if (cells[i+1][j].hasLiquid)
adjacentLiquidCellCount++;
}
if (j != 0) {
if (cells[i][j-1].hasLiquid)
adjacentLiquidCellCount++;
}
if (j != cells[0].length-1) {
if (cells[i][j+1].hasLiquid)
adjacentLiquidCellCount++;
}
// the diagonal entries are the negative count of liquid cells
liquidMatrix.setEntry(matrixIndex, // column
matrixIndex, // row
-adjacentLiquidCellCount); // value
// set off-diagonal values of the pressure matrix
if (cell.hasLiquid) {
if (i != 0) {
if (cells[i-1][j].hasLiquid) {
int adjacentGridIndex = (i-1) + j * cells.length;
int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
liquidMatrix.setEntry(matrixIndex, // column
adjacentMatrixIndex, // row
1.0); // value
liquidMatrix.setEntry(adjacentMatrixIndex, // column
matrixIndex, // row
1.0); // value
}
}
if (i != cells.length-1) {
if (cells[i+1][j].hasLiquid) {
int adjacentGridIndex = (i+1) + j * cells.length;
int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
liquidMatrix.setEntry(matrixIndex, // column
adjacentMatrixIndex, // row
1.0); // value
liquidMatrix.setEntry(adjacentMatrixIndex, // column
matrixIndex, // row
1.0); // value
}
}
if (j != 0) {
if (cells[i][j-1].hasLiquid) {
int adjacentGridIndex = i + (j-1) * cells.length;
int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
liquidMatrix.setEntry(matrixIndex, // column
adjacentMatrixIndex, // row
1.0); // value
liquidMatrix.setEntry(adjacentMatrixIndex, // column
matrixIndex, // row
1.0); // value
}
}
if (j != cells[0].length-1) {
if (cells[i][j+1].hasLiquid) {
int adjacentGridIndex = i + (j+1) * cells.length;
int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
liquidMatrix.setEntry(matrixIndex, // column
adjacentMatrixIndex, // row
1.0); // value
liquidMatrix.setEntry(adjacentMatrixIndex, // column
matrixIndex, // row
1.0); // value
}
}
}