This fluid simulation is based off of a paper by Stam. On page 7, he describes the basic idea behind advection:
Start with two grids: one that contains the density values from the previous time step and one that will contain the new values. For each grid cell of the latter we trace the cell’s center position backwards through the velocity field. We then linearly interpolate from the grid of previous density values and assign this value to the current grid cell.
Advect code. The two density grids are d
and d0
, u
and v
are velocity components, dt
is the time step, N
(global) is grid size, b
can be ignored:
void advect(int b, vfloat &d, const vfloat &d0, const vfloat &u, const vfloat &v, float dt, std::vector<bool> &bound)
{
float dt0 = dt*N;
for (int i=1; i<=N; i++)
{
for (int j=1; j<=N; j++)
{
float x = i - dt0*u[IX(i,j)];
float y = j - dt0*v[IX(i,j)];
if (x<0.5) x=0.5; if (x>N+0.5) x=N+0.5;
int i0=(int)x; int i1=i0+1;
if (y<0.5) y=0.5; if (y>N+0.5) y=N+0.5;
int j0=(int)y; int j1=j0+1;
float s1 = x-i0; float s0 = 1-s1; float t1 = y-j0; float t0 = 1-t1;
d[IX(i,j)] = s0*(t0*d0[IX(i0,j0)] + t1*d0[IX(i0,j1)]) +
s1*(t0*d0[IX(i1,j0)] + t1*d0[IX(i1,j1)]);
}
}
set_bnd(b, d, bound);
}
This method is concise and works well enough, but implementing object boundaries is tricky for me to figure out because values are traced backwards and interpolated. My current solution is to simply push density out of boundaries if there is an empty space (or spaces) next to it, but that is inaccurate and causes density to build up, especially on corners and areas with diagonal velocity. only visually accurate. I'm looking for "correctness" now.
Relevant parts of my boundary code:
void set_bnd(const int b, vfloat &x, std::vector<bool> &bound)
{
//...
for (int i=1; i<=N; i++)
{
for (int j=1; j<=N; j++)
{
if (bound[IX(i,j)])
{
//...
else if (b==0)
{
// Distribute density from bound to surrounding cells
int nearby_count = !bound[IX(i+1,j)] + !bound[IX(i-1,j)] + !bound[IX(i,j+1)] + !bound[IX(i,j-1)];
if (!nearby_count) x[IX(i,j)] = 0;
else
x[IX(i,j)] = ((bound[IX(i+1,j)] ? 0 : x[IX(i+1,j)]) +
(bound[IX(i-1,j)] ? 0 : x[IX(i-1,j)]) +
(bound[IX(i,j+1)] ? 0 : x[IX(i,j+1)]) +
(bound[IX(i,j-1)] ? 0 : x[IX(i,j-1)])) / surround;
}
}
}
}
}
bound
is a vector of bools with rows and columns 0
to N+1
. Boundary objects are set up before the main loop by setting cell coordinates in bound
to 1
.
The paper vaguely states "Then we simply have to add
some code to the set_bnd()
routine to fill in values for the occupied cells from the values of
their direct neighbors", which is sort of what I'm doing. I am looking for a way to implement boundaries more accurately, that is having non-fluid solid boundaries and maybe eventually supporting boundaries for multiple fluids. Visual quality is much more important than physics correctness.
bound
? My guess is that boundary cells are row 0, row N+1, column 0, and column N+1 [from diagram in paper]? Also,set_bnd
haselse if (b==0)
and theif
for thatelse
[above] is missing. Can you add a bit more? I do see some problems [problem similar to video frame boundaries and motion vectors], but before I attempt an answer I'd like to have a bit more info. Also, when isb
zero/non-zero? – Caseaseset_bnd
deal with edge conditions that aren't needed for the question. A full version of my code is here – Creatinineforall bpt in bound: bpt.i,bpt.j,bpt.xxx
(e.g. precalc more stuff, so fewer if's in loop). To see better, I did the grid at bottom of render loop:SDL_SetRenderDrawColor(renderer,100,100,0,0); SDL_RenderDrawRect(renderer,&r);
but some lines are double wide due to rounding – Caseaseadvect
is done – Creatinineadvect
to work with collisions instead of what I'm doing now. The current code looks visually acceptable with stationary boundaries, but an extension like moving boundaries such as two fluids (fire and air, water and air) would need a "real" advect solution. – Creatinine