Я использую Electron с TypeScript для создания прототипа некоторого кода симуляции жидкости, используя алгоритм Lattice Boltzmann, который в конечном итоге войдет в игру. До сих пор я использовал статические граничные условия (при симуляционных расчетах, происходящих только внутри сетки, а значения для граничных ячеек оставались фиксированными), в этом режиме все работает нормально. В частности, я могу наложить внутренние граничные условия (например, обеспечить, чтобы определенная плотность жидкости всегда выходила из определенного узла решетки на каждом кадре, чтобы имитировать шланг / сопло ракеты / что угодно), просто вручную установив значения ячеек между каждымшаг моделирования.
Однако, если я переключусь на использование периодических граничных условий (т. е. круговая топология астероиды мир), все моделирование станет статичным. Я просто получаю постоянную плотность жидкости везде, на все времена, и все мои граничные условия стираются, независимо от того, где в цикле моделирования (до потоковой передачи или до столкновения) я выбираю их утверждение. Я не уверен, что периодические граничные условия окажутся релевантными для игры, но эта ошибка заставляет меня думать, что где-то в симуляции должна быть какая-то небольшая ошибка.
Полный код доступен на https://github.com/gliese1337/balloon-prototype/tree/deopt, но я ожидаю, что соответствующие части выглядят следующим образом:
class LatticeBoltzmann {
private streamed: Float32Array; // microscopic densities along each lattice direction
private collided: Float32Array;
public rho: Float32Array; // macroscopic density; cached for rendering
...
public stream(barriers: boolean[]) {
const { xdim, ydim, collided, streamed } = this;
const index = (x: number, y: number) => (x%xdim)+(y%ydim)*xdim;
const cIndex = (x: number, y: number, s: -1|1, j: number) =>
9*(((x+s*cxs[j])%xdim)+((y+s*cys[j])%ydim)*xdim)+j;
// Move particles along their directions of motion:
for (let y=1; y<ydim-1; y++) {
for (let x=1; x<xdim-1; x++) {
const i = index(x, y);
const i9 = i*9;
for (let j=0;j<9;j++) {
streamed[i9 + j] = collided[cIndex(x, y, -1, j)];
}
}
}
// Handle bounce-back from barriers
for (let y=0; y<ydim; y++) {
for (let x=0; x<xdim; x++) {
const i = index(x, y);
const i9 = i*9;
if (barriers[i]) {
for (let j=1;j<9;j++) {
streamed[cIndex(x, y, 1, j)] = collided[i9 + opp[j]];
}
}
}
}
}
// Set all densities in a cell to their equilibrium values for a given velocity and density:
public setEquilibrium(x: number, y: number, ux: number, uy: number, rho: number) {
const { xdim, streamed } = this;
const i = x + y*xdim;
this.rho[i] = rho;
const i9 = i*9;
const u2 = 1 - 1.5 * (ux * ux + uy * uy);
for (let j = 0; j < 9; j++) {
const dir = cxs[j]*ux + cys[j]*uy;
streamed[i9+j] = weights[j] * rho * (u2 + 3 * dir + 4.5 * dir * dir);
}
}
}
Данные решетки хранятся в двух плоских массивах, collided
, который содержит конечные состояния после шага столкновения и служит входом дляэтап потоковой передачи и streamed
, который содержит конечные состояния после этапа потоковой передачи и служит в качестве входных данных для следующего этапа столкновения. 9 векторных компонентов для решетки D2Q9 хранятся в смежных блоках, которые затем группируются в строки. Обратите внимание, что я уже использую операции мода для вычисления индексов массива по координатам решетки;это совершенно не имеет значения, если расчеты моделирования распространяются только на внутреннюю часть решетки, но при этом периодические границы должны быть готовы к работе, как только циклы for (let y=1; y<ydim-1; y++)
и for (let x=1; x<xdim-1; x++)
изменят свои границы на for (let y=0; y<ydim; y++)
и for (let x=0; x<xdim; x++)
соответственно. И действительно, именно с этим конкретным 6-символьным изменением у меня возникают проблемы.
Метод setEquilibrium
используется для наложения граничных условий. В коде драйвера он в настоящее время вызывается так, один раз за кадр:
// Make fluid flow in from the left edge and out through the right edge
function setBoundaries(LB: LatticeBoltzmann, ux: number) {
for (let y=0; y<ydim; y++) {
LB.setEquilibrium(0, y, ux, 0, 1);
LB.setEquilibrium(xdim-1, y, ux, 0, 1);
}
}
При статических граничных условиях вызывать его один раз за кадр оказывается излишним, поскольку он изменяет только узлы граничной решетки. Однако смещение жестко запрограммированных значений x во внутреннюю часть решетки (когда на самом деле необходимо переустанавливать граничные условия один раз за кадр) делает именно то, что вы ожидаете - это заставляет жидкость появляться или исчезать в определенных местах. Однако переключение на периодические граничные условия приводит к тому, что этот код перестает иметь какой-либо видимый эффект.
Итак ... кто-нибудь знает, что я могу делать неправильно?