Estou tentando implementar uma versão 2D do artigo de Foster e Fedkiw, "Animação prática de líquidos" aqui: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf
Principalmente tudo funciona, exceto a seção 8: "Conservação de massa". Lá, montamos uma matriz de equações para calcular as pressões necessárias para tornar o líquido livre de divergências.
Acredito que meu código coincide com o artigo, no entanto, estou obtendo uma matriz insolúvel durante a etapa de conservação da massa.
Aqui estão meus passos para gerar a matriz A:
- Defina as entradas diagonais para o número negativo de células líquidas adjacentes à célula i.
- Defina as entradas e para 1 se ambas as células iej tiverem líquido.
Observe que, na minha implementação, o cell , na grade líquida corresponde à linha gridWidth na matriz.
O artigo menciona: "Objeto estático e células vazias não perturbam essa estrutura. Nesse caso, os termos de pressão e velocidade podem desaparecer dos dois lados", então excluo as colunas e linhas de células que não têm líquido.
Então, minha pergunta é: Por que minha matriz é singular? Estou perdendo algum tipo de condição de contorno em algum outro lugar do jornal? É o fato de que minha implementação é 2D?
Aqui está um exemplo de matriz da minha implementação para uma grade 2x2 em que a célula em 0,0 não possui líquido:
-1 0 1
0 -1 1
1 1 -2
Editar
Minha pesquisa me levou a acreditar que não estou lidando adequadamente com as condições de contorno.
Antes de tudo, neste momento, posso dizer que minha matriz representa a equação de Poisson de pressão discreta. É o análogo discreto da aplicação do operador laplaciano que acopla as alterações de pressão local à divergência celular.
Pelo que entendi, como estamos lidando com diferenças de pressão, são necessárias condições de contorno para "ancorar" as pressões a um valor de referência absoluto. Caso contrário, pode haver um número infinito de soluções para o conjunto de equações.
Em essas notas , 3 maneiras diferentes são dadas para aplicar condições de contorno, para o melhor de meu entendimento:
Dirichlet - especifica valores absolutos nos limites.
Neummann - especifica a derivada nos limites.
Robin - especifica algum tipo de combinação linear do valor absoluto e a derivada nos limites.
O artigo de Foster e Fedki não menciona nada disso, mas acredito que eles impõem condições de contorno de Dirichlet, notáveis por causa dessa afirmação no final de 7.1.2, "A pressão em uma célula de superfície é definida como pressão atmosférica".
Eu li as anotações que vinculei algumas vezes e ainda não entendi direito a matemática. Como exatamente aplicamos essas condições de contorno? Olhando para outras implementações, parece haver algum tipo de noção de uma célula "fantasma" que se encontra na fronteira.
Aqui, vinculei a algumas fontes que podem ser úteis para outras pessoas que estão lendo isso.
Notas sobre condições de contorno para matrizes de Poisson
Computational Science StackExchange post sobre condições de contorno de Neumann
Ciência da Computação StackExchange post on Poisson Solver
Implementação de Physbam de Água
Aqui está o código que eu uso para gerar a matriz. Observe que, em vez de excluir explicitamente colunas e linhas, eu gero e uso um mapa dos índices de células líquidas para as colunas / linhas finais da matriz.
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
}
}
}