我正在使用 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
,而 保存流步骤之后的最终状态并用作下一个碰撞步骤的输入。D2Q9 晶格的 9 个向量分量存储在连续的块中,然后分组为行。请注意,我已经在使用 mod 操作从晶格坐标计算数组索引;只要模拟计算仅在晶格内部范围内,这完全无关紧要,但是一旦for (let y=1; y<ydim-1; y++)
andfor (let x=1; x<xdim-1; x++)
循环将其边界更改为for (let y=0; y<ydim; y++)
and ,它应该使周期性边界准备就绪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 值移动到晶格的内部(实际上每帧重新设置一次边界条件是必要的)完全符合您的预期——它使流体在特定位置出现或消失。然而,切换到周期性边界条件会导致该代码不再具有任何可见的效果。
所以...有人知道我可能做错了什么吗?