Sto cercando di implementare una versione 2D del documento di Foster e Fedkiw, "Animazione pratica dei liquidi" qui: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf
Quasi tutto funziona, tranne per la sezione 8: "Conservazione della messa". Lì, abbiamo creato una matrice di equazioni per calcolare le pressioni necessarie per rendere il liquido divergente libero.
Credo che il mio codice corrisponda al documento, tuttavia sto ottenendo una matrice irrisolvibile durante la conservazione del passo di massa.
Ecco i miei passaggi per generare la matrice A:
- Imposta le voci diagonali al negativo del numero di celle liquide adiacenti alla cella i.
- Imposta le voci e a 1 se entrambe le celle i e j hanno liquido.
Si noti che, nella mia implementazione, cella , nella griglia del liquido corrisponde alla riga gridWidth nella matrice.
L'articolo menziona "L'oggetto statico e le celle vuote non disturbano questa struttura. In tal caso i termini di pressione e velocità possono scomparire da entrambi i lati", quindi elimino le colonne e le righe per le celle che non hanno liquido.
Quindi la mia domanda è: perché la mia matrice è singolare? Mi sto perdendo una sorta di condizione al contorno in qualche altro punto del documento? È il fatto che la mia implementazione sia in 2D?
Ecco una matrice di esempio dalla mia implementazione per una griglia 2x2 in cui la cella a 0,0 non ha liquido:
-1 0 1
0 -1 1
1 1 -2
modificare
La mia ricerca mi ha portato a credere che non sto gestendo correttamente le condizioni al contorno.
Prima di tutto, a questo punto posso dire che la mia matrice rappresenta l'equazione di Poisson a pressione discreta. È l'analogo discreto dell'applicazione dell'operatore laplaciano che accoppia le variazioni di pressione locale alla divergenza cellulare.
Per quanto posso capire, poiché abbiamo a che fare con differenze di pressione, sono necessarie condizioni al contorno per "ancorare" le pressioni a un valore di riferimento assoluto. Altrimenti potrebbe esserci un numero infinito di soluzioni all'insieme di equazioni.
In queste note , vengono forniti 3 modi diversi per applicare le condizioni al contorno, per quanto ne so:
Dirichlet: specifica i valori assoluti ai confini.
Neummann: specifica il derivato ai confini.
Robin: specifica una sorta di combinazione lineare del valore assoluto e della derivata ai confini.
Il documento di Foster e Fedki non menziona nessuno di questi, ma credo che facciano rispettare le condizioni al contorno di Dirichlet, in particolare a causa di questa affermazione alla fine di 7.1.2, "La pressione in una cellula superficiale è impostata sulla pressione atmosferica".
Ho letto le note che ho collegato alcune volte e ancora non capisco bene la matematica in corso. Come imponiamo esattamente queste condizioni al contorno? Guardando altre implementazioni, sembra che ci sia una sorta di nozione di cellule "fantasma" che giacciono al limite.
Qui ho collegato ad alcune fonti che potrebbero essere utili ad altri leggendo questo.
Note sulle condizioni al contorno per le matrici di Poisson
Stack di calcolo computazionale Scambio post sulle condizioni al contorno di Neumann
Stack di calcolo computazionale Scambio post su Risolutore di Poisson
Implementazione di Water Physbam
Ecco il codice che uso per generare la matrice. Si noti che, invece di eliminare esplicitamente colonne e righe, generi e utilizzo una mappa dagli indici delle celle liquide alle colonne / righe della matrice finale.
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
}
}
}