Fluid Flow Simulation with the Navier-Stokes Equation


Tap and drag to add turbulence to the simulation.

Wikipeida says that the Navier-Stokes equation is

ฯD๐ฎDt=โˆ’โˆ‡p+โˆ‡ยท๐›•+ฯ๐ 

where


ฯ is density
๐ฎ is the velocity vector
p is the pressure
๐›• is the stress tensor
g is any other acceleration

โˆ‡ is the gradient, equal to (โˆ‚โˆ‚x,โˆ‚โˆ‚y,โˆ‚โˆ‚z,...) for however many dimensions. This simulation is only 2 dimensional so in this case it is just (โˆ‚โˆ‚x,โˆ‚โˆ‚y).
โˆ‡ยท is the divergence and is the dot product of the gradient with the vector so it is equal to to(โˆ‚โˆ‚x,โˆ‚โˆ‚y,โˆ‚โˆ‚z...)ยท(Fx,Fy,Fz,...)=โˆ‚Fxโˆ‚x+โˆ‚Fyโˆ‚y+โˆ‚Fzโˆ‚z,... for however many dimensions. In 2 dimentions it'sโˆ‚Fxโˆ‚x+โˆ‚Fyโˆ‚y.
DDt is the material derivative which is โˆ‚โˆ‚t+๐ฎยทโˆ‡, which measures how much material will change measured at a point moving with the fluid.
Here it is taking the matieral derivative of the velocity, or saying how much a particles velocity will change as it flows through the fluid.
D๐ฎDt=โˆ‚๐ฎโˆ‚t+๐ฎยทโˆ‡๐ฎ

so the origional equation becomes

ฯ(โˆ‚๐ฎโˆ‚t+๐ฎยทโˆ‡๐ฎ)=โˆ’โˆ‡p+โˆ‡ยท๐›•+ฯ๐ 
โˆ‚๐ฎโˆ‚t+๐ฎยทโˆ‡๐ฎ=โˆ’1ฯโˆ‡p+1ฯโˆ‡ยท๐›•+๐ 
๐›• doesn't matter here since it is the stress tensor used for viscosity and when the shape of the box is changing size and volume, but both of those are ignored, and we can also don't have any external forces like gravity or electromagnetism so we can ignore ๐ .
โˆ‚๐ฎโˆ‚t+๐ฎยทโˆ‡๐ฎ=โˆ’1ฯโˆ‡p
โˆ‚๐ฎโˆ‚t=โˆ’๐ฎยทโˆ‡๐ฎโˆ’1ฯโˆ‡p
โˆ‚๐ฎโˆ‚t=โˆ’๐ฎยท(โˆ‚๐ฎโˆ‚x,โˆ‚๐ฎโˆ‚y)โˆ’1ฯโˆ‡p
โˆ‚๐ฎโˆ‚t=โˆ’๐ฎxโˆ‚๐ฎโˆ‚xโˆ’๐ฎyโˆ‚๐ฎโˆ‚yโˆ’1ฯโˆ‡p
Then expanding the vector
[โˆ‚๐ฎxโˆ‚tโˆ‚๐ฎyโˆ‚t]=โˆ’[๐ฎxโˆ‚๐ฎxโˆ‚x๐ฎxโˆ‚๐ฎyโˆ‚x]โˆ’[๐ฎyโˆ‚๐ฎxโˆ‚y๐ฎyโˆ‚๐ฎyโˆ‚y]โˆ’1ฯ[โˆ‚pโˆ‚xโˆ‚pโˆ‚y]
[โˆ‚๐ฎxโˆ‚tโˆ‚๐ฎyโˆ‚t]=โˆ’[๐ฎxโˆ‚๐ฎxโˆ‚x+๐ฎyโˆ‚๐ฎxโˆ‚y+1ฯโˆ‚pโˆ‚x๐ฎxโˆ‚๐ฎyโˆ‚x+๐ฎyโˆ‚๐ฎyโˆ‚y+1ฯโˆ‚pโˆ‚y]

Applying the finite distance method:

โˆ‚f(x)โˆ‚xโ‰ˆf(x+ฮ”x)โˆ’f(x)ฮ”x

onto โˆ‚๐ฎxโˆ‚t makes:

๐ฎx(x,y,t+ฮ”t)โˆ’๐ฎx(x,y,t)ฮ”t=โˆ’๐ฎx(x,y,t)๐ฎx(x+ฯต,y,t)โˆ’๐ฎx(xโˆ’ฯต,y,t)2ฯตโˆ’๐ฎy(x,y,t)๐ฎx(x,y+ฯต,t)โˆ’๐ฎx(x,yโˆ’ฯต,t)2ฯตโˆ’1ฯp(x+ฯต,y,t)โˆ’p(xโˆ’ฯต,y,t)2ฯต

Then isolate ๐ฎx(x,y,t+ฮ”t):

๐ฎx(x,y,t+ฮ”t)=๐ฎx(x,y,t)โˆ’ฮ”t๐ฎx(x,y,t)๐ฎx(x+ฯต,y,t)โˆ’๐ฎx(xโˆ’ฯต,y,t)2ฯตโˆ’ฮ”t๐ฎy(x,y,t)๐ฎx(x,y+ฯต,t)โˆ’๐ฎx(x,yโˆ’ฯต,t)2ฯตโˆ’ฮ”t1ฯp(x+ฯต,y,t)โˆ’p(xโˆ’ฯต,y,t)2ฯต

The first 3 terms are just the advected velocity, because its equal to ๐ฎxโˆ’ฮ”t๐ฎยทโˆ‡๐ฎx. Make a new variable a(x,y,t) for the first 3 terms, the advected velocity.
๐ฎx(x,y,t+ฮ”t)=ax(x,y,t)โˆ’ฮ”t1ฯp(x+ฯต,y,t)โˆ’p(xโˆ’ฯต,y,t)2ฯต
In incompressable fluids, density is constant so ฯ is a constant. If ฯ is constant then dฯdt=0 so the conservation of mass equation:
dฯdt+โˆ‡ยท(ฯ๐ฎ)=0

becomes

โˆ‡ยท(ฯ๐ฎ)=0

and since ฯ is a constant it can be divided out

โˆ‡ยท๐ฎ=0

One point can't have 2 neighboring points both pulling from it or else that means it is going in 2 directions and creating new mass, or if a cell has 2 neighbors going into it mass is deleted since density can't change, so the divergence of the velocity at every point must equal zero.


So the solution must also have zero divergence
โˆ‚๐ฎxโˆ‚x+โˆ‚๐ฎyโˆ‚y=0

Then also apply the finite difference method

0=๐ฎx(x+ฯต,y,t+ฮ”t)โˆ’๐ฎx(xโˆ’ฯต,y,t+ฮ”t)2ฯต๐ฎy(x,y+ฯต,t+ฮ”t)โˆ’๐ฎy(x,yโˆ’ฯต,t+ฮ”t)2ฯต
We can substitute the previous equation for the 2 derivatives.
0=12ฯต((ax(x+ฯต,y,t)โˆ’ฮ”t1ฯp(x+2ฯต,y,t)โˆ’p(x,y,t)2ฯต)โˆ’(ax(xโˆ’ฯต,y,t)โˆ’ฮ”t1ฯp(x,y,t)โˆ’p(xโˆ’2ฯต,y,t)2ฯต)+(ay(x,y+ฯต,t)โˆ’ฮ”t1ฯp(x,y+2ฯต,t)โˆ’p(x,y,t)2ฯต)โˆ’(ay(x,yโˆ’ฯต,t)โˆ’ฮ”t1ฯp(x,y,t)โˆ’p(x,yโˆ’2ฯต,t)2ฯต))
Then move the knows and unknowns to the left and right sides.
ax(x+ฯต,y,t)โˆ’ax(xโˆ’ฯต,y,t)+ay(x,y+ฯต,t)โˆ’ay(x,yโˆ’ฯต,t)=โˆ’(โˆ’ฮ”t1ฯp(x+2ฯต,y,t)โˆ’p(x,y,t)2ฯต)+ฮ”t1ฯp(x,y,t)โˆ’p(xโˆ’2ฯต,y,t)2ฯต)โˆ’ฮ”t1ฯp(x,y+2ฯต,t)โˆ’p(x,y,t)2ฯต)+ฮ”t1ฯp(x,y,t)โˆ’p(x,yโˆ’2ฯต,t)2ฯต))
Then simplify
ax(x+ฯต,y,t)โˆ’ax(xโˆ’ฯต,y,t)+ay(x,y+ฯต,t)โˆ’ay(x,yโˆ’ฯต,t)=โˆ’ฮ”t2ฯตฯ(4p(x,y,t)โˆ’p(x+2ฯต,y,t)โˆ’p(xโˆ’2ฯต,y,t))โˆ’p(x,y+2ฯต,t)โˆ’p(x,yโˆ’2ฯต,t)))
In the code this systems of equations could be solved with a massive matrix with each voxel as a variable and have very perfect calculations, or just do it iteratevly and approximate with an iterative solver. This code uses the Gauss Seidel method. Set โˆ’ฮ”t2ฯตฯb(x,y,t) to the left hand side of the equation.
p(x,y)=p(x+ฯต,y)+p(xโˆ’ฯต,y)+p(x,y+ฯต)+p(x,yโˆ’ฯต)โˆ’b(x,y)4

And with that system of equations iterate by setting the initial guess for every point p0(x,y)=0. Then iterate with

pn+1(x,y)=pn(x+ฯต,y)+pn(xโˆ’ฯต,y)+pn(x,y+ฯต)+pn(x,yโˆ’ฯต)โˆ’b(x,y)4

After the divergence is cleared, the velocities just need to move both the color, themselves, and any other values in the direction they are pointing.


In code this is optimised by pulling instead from behind instead of pushing out in front because the end point of the velicity will be inbetween 4 squares and writing values is almost always more expensive than reading values, so instead it will read the 4 values of the squares if the velocity vector was rotated 180 degrees or muliplied by -1, linear interpolate the expected value at the sub-voxel position, then write it into only one square saving time and making the simulation faster at about the same accuracy as the intuitive method.

Now the hardest part is done, but there is still diffusion where quares take the average values of their neighbors over time this is modeled by
cn+1(x,y)=cn(x,y)+kโˆ—(an(xโˆ’ฯต,y)+cn+1(x+ฯต,y)+an+1(x,yโˆ’ฯต)+an+1(x,y+ฯต)4โˆ’cn(x,y))
between the new and the current attrubute,a and the percent it goes towards it in that time tick,k
this is again a system of equations that can easily be solved with the Gauss-Seidel method

For boundaries and walls, the velocity in that voxel must be both 0 in the y and x direction, so when divergence clearing is calculated it will make the velocity near only be able to flow parrallel to obsticles and the boundary. Since the divergence clearing isn't perfect it doesn't make velocity exacly parralel so some fluid leaks off the edge or into walls, so a script should iterate through every voxel and make the x velocity 0 if there are obstacles or boundaries to the left or right, and y velocity 0 if there are up or down, exept a more computationally efficient way is to just set the voxels which are considered boundaries to have a x and y velocity of 0, then the divergence clearing will automatically make flow parralel to them

For the numerical implementation of derivatives it is just
df(x)dx=limhโ†’0f(x+h)โˆ’f(x)h

which can also be

df(x)dx=limhโ†’0f(x+h)โˆ’f(xโˆ’0)hโˆ’(โˆ’0)

then to

df(x)dx=limhโ†’0f(x+h)โˆ’f(xโˆ’h)hโˆ’(โˆ’h)

df(x)dx=limhโ†’0f(x+h)โˆ’f(xโˆ’h)2h

and in code form with the voxels where the position must be integers the most accurate would be would be
df(x)dx=f(x+ฯต)โˆ’f(xโˆ’ฯต)2


used sources:
https://jamie-wong.com/2016/08/05/webgl-fluid-simulation/