Estoy tratando de implementar una versión 2D del documento de Foster y Fedkiw, "Animación práctica de líquidos" aquí: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf
Casi todo funciona, excepto la sección 8: "Conservación de la misa". Allí, configuramos una matriz de ecuaciones para calcular las presiones necesarias para liberar el líquido divergente.
Creo que mi código coincide con el documento, sin embargo, obtengo una matriz insoluble durante la conservación del paso de masa.
Aquí están mis pasos para generar la matriz A:
- Establezca las entradas diagonales en el negativo del número de celdas líquidas adyacentes a la celda i.
- Establecer las entradas y a 1 si ambas celdas i y j tienen líquido.
Tenga en cuenta que, en mi implementación, la celda , en la rejilla líquida corresponde a la fila gridWidth en la matriz
El documento menciona: "El objeto estático y las celdas vacías no interrumpen esta estructura. En ese caso, los términos de presión y velocidad pueden desaparecer de ambos lados", por lo que elimino las columnas y filas de las celdas que no tienen líquido.
Entonces mi pregunta es: ¿Por qué mi matriz es singular? ¿Me estoy perdiendo algún tipo de condición límite en algún otro lugar del periódico? ¿Es el hecho de que mi implementación es 2D?
Aquí hay una matriz de ejemplo de mi implementación para una cuadrícula de 2x2 donde la celda a 0,0 no tiene líquido:
-1 0 1
0 -1 1
1 1 -2
Editar
Mi investigación me ha llevado a creer que no estoy manejando adecuadamente las condiciones de contorno.
En primer lugar, en este punto puedo decir que mi matriz representa la ecuación de Poisson de presión discreta. Es el análogo discreto de aplicar el operador laplaciano acoplando los cambios de presión locales a la divergencia celular.
Hasta donde puedo entender, dado que estamos lidiando con diferencias de presión, se necesitan condiciones de frontera para "anclar" las presiones a un valor de referencia absoluto. De lo contrario, puede haber un número infinito de soluciones para el conjunto de ecuaciones.
En estas notas , se dan 3 formas diferentes de aplicar condiciones de contorno, a mi entender:
Dirichlet: especifica valores absolutos en los límites.
Neummann: especifica la derivada en los límites.
Robin: especifica algún tipo de combinación lineal del valor absoluto y la derivada en los límites.
El artículo de Foster y Fedki no menciona ninguno de estos, pero creo que imponen las condiciones de frontera de Dirichlet, notables debido a esta declaración al final de 7.1.2, "La presión en una celda de superficie se ajusta a la presión atmosférica".
He leído las notas que vinculé varias veces y todavía no entiendo las matemáticas. ¿Cómo aplicamos exactamente estas condiciones límite? Mirando otras implementaciones, parece haber algún tipo de noción de celdas "fantasmas" que se encuentran en el límite.
Aquí he vinculado a algunas fuentes que pueden ser útiles para otros que leen esto.
Notas sobre condiciones de contorno para matrices de Poisson
Computational Science StackExchange post en condiciones de contorno de Neumann
Computational Science StackExchange post en Poisson Solver
Implementación de agua Physbam
Aquí está el código que uso para generar la matriz. Tenga en cuenta que, en lugar de eliminar explícitamente columnas y filas, genero y uso un mapa de índices de celdas líquidas a las columnas / filas de la matriz final.
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
}
}
}