Я пытаюсь реализовать 2D-версию статьи Фостера и Федькова «Практическая анимация жидкостей» здесь: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf
В основном все работает, за исключением раздела 8: «Сохранение массы». Там мы создали матрицу уравнений для расчета давления, необходимого для освобождения расходящейся жидкости.
Я полагаю, что мой код соответствует статье, однако я получаю неразрешимую матрицу во время сохранения массового шага.
Вот мои шаги для генерации матрицы A:
- Установите диагональные элементы равными отрицательному числу соседних жидких ячеек для ячейки i.
- Установите записи и 1, если в обеих ячейках i и j есть жидкость.
Обратите внимание, что в моей реализации ячейка , в жидкостной сетке соответствует строке gridWidth в матрице.
В документе упоминается: «Статический объект и пустые ячейки не нарушают эту структуру. В этом случае члены давления и скорости могут исчезнуть с обеих сторон», поэтому я удаляю столбцы и строки для ячеек, в которых нет жидкости.
Итак, мой вопрос: почему моя матрица в единственном числе? Я пропускаю какое-то граничное условие в каком-то другом месте в статье? Это факт, что моя реализация 2D?
Вот пример матрицы из моей реализации для сетки 2x2, где в ячейке 0,0 нет жидкости:
-1 0 1
0 -1 1
1 1 -2
редактировать
Мои исследования привели меня к мысли, что я не справляюсь с граничными условиями.
Прежде всего, на данный момент я могу сказать, что моя матрица представляет собой уравнение Пуассона с дискретным давлением. Это дискретный аналог применения оператора Лапласа, связывающего локальные изменения давления с расходимостью ячейки.
Насколько я понимаю, поскольку мы имеем дело с перепадами давления, необходимы граничные условия, чтобы "привязать" давления к абсолютному контрольному значению. В противном случае может быть бесконечное количество решений для системы уравнений.
В этих заметках даны 3 различных способа применения граничных условий, насколько я понимаю:
Дирихле - указывает абсолютные значения на границах.
Neummann - указывает производную на границах.
Робин - указывает какую-то линейную комбинацию абсолютного значения и производной на границах.
В работе Фостера и Федки ничего не упоминается, но я считаю, что они обеспечивают соблюдение граничных условий Дирихле, что примечательно из-за этого утверждения в конце 7.1.2, «Давление в поверхностной ячейке установлено на атмосферное давление».
Я прочитал заметки, которые связывал несколько раз, и до сих пор не совсем понимаю, что происходит. Как именно мы соблюдаем эти граничные условия? Глядя на другие реализации, кажется, что существует какое-то понятие «призрачных» ячеек, которые лежат на границе.
Здесь я ссылаюсь на несколько источников, которые могут быть полезны для других, читающих это.
Заметки о граничных условиях для матриц Пуассона
Пост вычислительной науки StackExchange о граничных условиях Неймана
Пост вычислительной науки StackExchange на Poisson Solver
Вот код, который я использую для генерации матрицы. Обратите внимание, что вместо явного удаления столбцов и строк я создаю и использую карту от индексов жидких ячеек до окончательных столбцов / строк матрицы.
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
}
}
}