You are on page 1of 9

mikeash.com:justthisguy,youknow?

Home
Postedat2006031300:00|RSSfeed(Fulltextfeed)|BlogIndex
Nextarticle:CocoaSIMBLPlugins Book
Previousarticle:NSOpenGLContextandoneshot TheCompleteFridayQ&A,
Tags:fluidsimulation advancedtopicsinMacOS
XandiOSprogramming.
Blog
FluidSimulationforDummies GitHub
byMikeAsh MyGitHubpage,containing
variousopensource
librariesforMacandiOS
Inthespringandsummerof2005,IwrotemyMaster'sthesisonhigh development,andsome
performancerealtime3Dfluidsimulationandvolumetricrendering. miscellaneousprojects
ThebasicsofthefluidsimulationthatIusedarestraightforward,butI GliderFlying
hadaverydifficulttimeunderstandingit.Theavailablereference HDRidgeRunningVideo
ListofLandouts
materialswereallverygood,buttheywereabittoophysicsyand DayintheLife
mathyforme.Unabletofindsomethinggearedtowardssomebodyof SkylineSoaringClub
mymindset,I'dliketowritethepageIwishI'dhadayearago.Withthat SoaringSocietyof
America
goalinmind,I'mgoingtoshowyouhowtodosimple3Dfluid
GettingAnswers
simulation,stepbystep,withasmuchemphasisontheactual
Tensimplepointstofollow
programmingaspossible. togetgoodanswerson
IRC,mailinglists,and
ThesimulationcodeandideasthatI'mgoingtopresentarebasedonJosStam's otherplaces
paperRealTimeFluidDynamicsforGames.Ifyouwantamoreindepthlook MiscellaneousPages
atwhat'sgoingon,that'stheplacetogo.Youcanalsoreadallabouthowto Miscellaneousold,rarely
parallelizethesimulationandrendertheoutputin3DinmyMaster'sthesis. updatedcontent
mike@mikeash.com
Basics Emailme

FluidsimulationisbasedontheNavierStokesequations,whichare
fundamental,interesting,anddifficulttounderstandwithoutagoodbackgroundinphysicsanddifferential
equations.Tothatend,I'mgoingtoprettymuchignorethemexcepttoverybrieflyexplainwhattheysay.

Thinkofafluidasacollectionofboxesorcells.Eachboxhasvariousproperties,likevelocityand
density.Theseboxesinteractwiththeirneighbors,affectingtheirvelocityanddensity.Arealworld
fluidcanbeseenashavingahugenumberofabsolutelyminisculeboxes,allinteractingwiththeir
neighborsazilliontimesasecond.

Ofcourse,arealcomputercan'thandleazillioninteractionspersecond,norcanithandleazillion
littleboxes,sowehavetocompromise.We'llbreakthefluidupintoareasonablenumberofboxes
andtrytodoseveralinteractionspersecond.

Tomakethingssimpler,we'reonlygoingtoexamineincompressiblefluid.Airisanexampleofa
compressiblefluidyousquishitanditgetssmaller.Waterisanexampleofanincompressiblefluid
yousquishitanditpushesback,anddoesn'tgetanysmaller.Incompressiblefluidsaresimplerto
simulatebecausetheirdensityandpressureisalwaysconstant.

Ofcourse,movingwaterisreallyboringifthere'snothinginit,becauseyoucan'tseeitmoving!So
we'lladdsomedyesowecanactuallyseeitmovingaround.Thewaterisequallydenseeverywhere,
butsomeofithasmoredyethanothers,andthisvariationletsusseethingsmoving.Soremember,
wheneverI'mtalkingabout"density",I'mactuallytalkingaboutthedensityofthedye,notthedensity
ofthefluid.(Thistookmeaboutsixmonthstofigureout,that'swhyI'msoinsistent.)

DataStructures

Theseboxeswillberepresentedasseveral3Darrays.Ofcourse,sinceChatesmultidimensionalarrays,we'll
use1Darraysandfaketheextratwodimensions.Tothatend,wehaveourveryfirstcode:a#definewhichtakes
threecoordinates,anddumpsoutthecorrespondingarrayindexinaonedimensionalarray:
#defineIX(x,y,z)((x)+(y)*N+(z)*N*N)

Next,let'sactuallylookathowwerepresentacubeoffluid.We'regoingtoneeditssize(thiscodeonlyhandles
perfectcubeswhichhavethesamelengthoneachsize),thelengthofthetimestep,theamountofdiffusion(how
faststuffspreadsoutinthefluid),andtheviscosity(howthickthefluidis).Wealsoneedadensityarrayand
threevelocityarrays,oneforx,y,andz.Wealsoneedscratchspaceforeacharraysothatwecankeepold
valuesaroundwhilewecomputethenewones.

Puttingallofthistogether,wegetourfluidcubestructure.
structFluidCube{
intsize;
floatdt;
floatdiff;
floatvisc;

float*s;
float*density;

float*Vx;
float*Vy;
float*Vz;

float*Vx0;
float*Vy0;
float*Vz0;
};
typedefstructFluidCubeFluidCube;

Tomakesureeverythinggetssetupproperly,weneedafunctionthatwillfilloutthiswholestructureforus:
FluidCube*FluidCubeCreate(intsize,intdiffusion,intviscosity,floatdt)
{
FluidCube*cube=malloc(sizeof(*cube));
intN=size;

cube>size=size;
cube>dt=dt;
cube>diff=diffusion;
cube>visc=viscosity;

cube>s=calloc(N*N*N,sizeof(float));
cube>density=calloc(N*N*N,sizeof(float));

cube>Vx=calloc(N*N*N,sizeof(float));
cube>Vy=calloc(N*N*N,sizeof(float));
cube>Vz=calloc(N*N*N,sizeof(float));

cube>Vx0=calloc(N*N*N,sizeof(float));
cube>Vy0=calloc(N*N*N,sizeof(float));
cube>Vz0=calloc(N*N*N,sizeof(float));

returncube;
}

Andfinallyweneedtobeabletodestroythisthingoncewe'redonewithit:
voidFluidCubeFree(FluidCube*cube)
{
free(cube>s);
free(cube>density);

free(cube>Vx);
free(cube>Vy);
free(cube>Vz);

free(cube>Vx0);
free(cube>Vy0);
free(cube>Vz0);

free(cube);
}

Sincethisjustinitializesamotionlesscubewithnodye,weneedawaybothtoaddsomedye:
voidFluidCubeAddDensity(FluidCube*cube,intx,inty,intz,floatamount)
{
intN=cube>size;
cube>density[IX(x,y,z)]+=amount;
}

Andtoaddsomevelocity:
voidFluidCubeAddVelocity(FluidCube*cube,intx,inty,intz,floatamountX,floatamountY,float
amountZ)
{
intN=cube>size;
intindex=IX(x,y,z);

cube>Vx[index]+=amountX;
cube>Vy[index]+=amountY;
cube>Vz[index]+=amountZ;
}

SimulationOutline
Therearethreemainoperationsthatwe'llusetoperformasimulationstep.

diffusePutadropofsoysauceinsomewater,andyou'llnoticethatitdoesn'tstaystill,butitspreads
out.Thishappensevenifthewaterandsaucearebothperfectlystill.Thisiscalleddiffusion.Weuse
diffusionbothintheobviouscaseofmakingthedyespreadout,andalsointhelessobviouscaseof
makingthevelocitiesofthefluidspreadout.
projectRememberwhenIsaidthatwe'reonlysimulatingincompressiblefluids?Thismeansthatthe
amountoffluidineachboxhastostayconstant.Thatmeansthattheamountoffluidgoinginhasto
beexactlyequaltotheamountoffluidgoingout.Theotheroperationstendtoscrewthingsupsothat
yougetsomeboxeswithanetoutflow,andsomewithanetinflow.Thisoperationrunsthroughallthe
cellsandfixesthemupsoeverythingisinequilibrium.

advectEverycellhasasetofvelocities,andthesevelocitiesmakethingsmove.Thisiscalled
advection.Aswithdiffusion,advectionappliesbothtothedyeandtothevelocitiesthemselves.

Therearealsotwosubroutinesthatareusedbytheseoperations.

set_bndThisisshortfor"setbounds",andit'sawaytokeepfluidfromleakingoutofyourbox.Not
thatitcouldreallyleak,sinceit'sjustasimulationinmemory,butnothavingwallsreallyscrewsup
thesimulationcode.Wallsareaddedbytreatingtheouterlayerofcellsasthewall.Basically,every
velocityinthelayernexttothisouterlayerismirrored.Sowhenyouhavesomevelocitytowardsthe
wallinthenexttoouterlayer,thewallgetsavelocitythatperfectlycountersit.

lin_solveIhonestlydon'tknowexactlywhatthisfunctiondoesorhowitworks.WhatIdoknowis
thatit'susedforbothdiffuseandproject.It'ssolvingalineardifferentialequationofsomesort,
althoughhowandwhatisnotentirelycleartome.

OK,nowwe'rereadytoseethemainsimulationfunction:
voidFluidCubeStep(FluidCube*cube)
{
intN=cube>size;
floatvisc=cube>visc;
floatdiff=cube>diff;
floatdt=cube>dt;
float*Vx=cube>Vx;
float*Vy=cube>Vy;
float*Vz=cube>Vz;
float*Vx0=cube>Vx0;
float*Vy0=cube>Vy0;
float*Vz0=cube>Vz0;
float*s=cube>s;
float*density=cube>density;

diffuse(1,Vx0,Vx,visc,dt,4,N);
diffuse(2,Vy0,Vy,visc,dt,4,N);
diffuse(3,Vz0,Vz,visc,dt,4,N);

project(Vx0,Vy0,Vz0,Vx,Vy,4,N);

advect(1,Vx,Vx0,Vx0,Vy0,Vz0,dt,N);
advect(2,Vy,Vy0,Vx0,Vy0,Vz0,dt,N);
advect(3,Vz,Vz0,Vx0,Vy0,Vz0,dt,N);

project(Vx,Vy,Vz,Vx0,Vy0,4,N);

diffuse(0,s,density,diff,dt,4,N);
advect(0,density,s,Vx,Vy,Vz,dt,N);
}

Tosummarize,thisdoesthefollowingsteps:
Diffuseallthreevelocitycomponents.
Fixupvelocitiessotheykeepthingsincompressible
Movethevelocitiesaroundaccordingtothevelocitiesofthefluid(confusedyet?).
Fixupthevelocitiesagain
Diffusethedye.
Movethedyearoundaccordingtothevelocities.
Sothat'sthebasicsofit.Nowwe'llgetintotheimplementationofeachfunction.

set_bnd
Asnotedabove,thisfunctionsetstheboundarycellsattheouteredgesofthecubesotheyperfectlycounteract
theirneighbors.

There'sabitofoddnessherewhichis,whatisthisbpramaeter?Itcanbe0,1,2,or3,andeachvalue
hasaspecialmeaningwhichisnotatallobvious.Theanswerliesiswhatkindofdatacanbepassed
intothisfunction.

Wehavefourdifferentkindsofdata(x,y,andzvelocities,plusdensity)floatingaround,andanyof
thosefourcanbepassedintoset_bnd.Youmaynoticethatthisisexactlythenumberofvaluesthis
parametercanhave,andthisisnotacoincidence.

Let'slookataboundarycell(horribleASCIIartwarning):
+++
||^|
|||
|||
+++

Herewe'reattheleftedgeofthecube.Theleftcellistheboundarycellthatneedstocounteractitsneighbor,the
rightcell.Therightcellhasavelocitythat'supandtotheleft.

Theboundarycell'sxvelocityneedstobeoppositeitsneighbor,butitsyvelocityneedstobeequalto
itsneighbor.Thiswillproducearesultlikeso:
+++
|^|^|
|/||
|/||
+++

Thiscounteractsthemotionofthefluidwhichwouldtakeitthroughthewall,andpreservestherestofthe
motion.Soyoucanseethatwhatactionistakendependsonwhicharraywe'redealingwithifwe'redealing
withxvelocities,thenwehavetosettheboundarycell'svaluetotheoppositeofitsneighbor,butforeverything
elsewesetittobethesame.

Thatistheanswertothemysteriousb.Ittellsthefunctionwhicharrayit'sdealingwith,andso
whethereachboundarycellshouldbesetequaloroppositeitsneighbor'svalue.
Thisfunctionalsosetscorners.Thisisdoneverysimply,bysettingeachcornercellequaltothe
averageofitsthreeneighbors.

Withoutfurtherado,thecode:
staticvoidset_bnd(intb,float*x,intN)
{
for(intj=1;j<N1;j++){
for(inti=1;i<N1;i++){
x[IX(i,j,0)]=b==3?x[IX(i,j,1)]:x[IX(i,j,1)];
x[IX(i,j,N1)]=b==3?x[IX(i,j,N2)]:x[IX(i,j,N2)];
}
}
for(intk=1;k<N1;k++){
for(inti=1;i<N1;i++){
x[IX(i,0,k)]=b==2?x[IX(i,1,k)]:x[IX(i,1,k)];
x[IX(i,N1,k)]=b==2?x[IX(i,N2,k)]:x[IX(i,N2,k)];
}
}
for(intk=1;k<N1;k++){
for(intj=1;j<N1;j++){
x[IX(0,j,k)]=b==1?x[IX(1,j,k)]:x[IX(1,j,k)];
x[IX(N1,j,k)]=b==1?x[IX(N2,j,k)]:x[IX(N2,j,k)];
}
}

x[IX(0,0,0)]=0.33f*(x[IX(1,0,0)]
+x[IX(0,1,0)]
+x[IX(0,0,1)]);
x[IX(0,N1,0)]=0.33f*(x[IX(1,N1,0)]
+x[IX(0,N2,0)]
+x[IX(0,N1,1)]);
x[IX(0,0,N1)]=0.33f*(x[IX(1,0,N1)]
+x[IX(0,1,N1)]
+x[IX(0,0,N)]);
x[IX(0,N1,N1)]=0.33f*(x[IX(1,N1,N1)]
+x[IX(0,N2,N1)]
+x[IX(0,N1,N2)]);
x[IX(N1,0,0)]=0.33f*(x[IX(N2,0,0)]
+x[IX(N1,1,0)]
+x[IX(N1,0,1)]);
x[IX(N1,N1,0)]=0.33f*(x[IX(N2,N1,0)]
+x[IX(N1,N2,0)]
+x[IX(N1,N1,1)]);
x[IX(N1,0,N1)]=0.33f*(x[IX(N2,0,N1)]
+x[IX(N1,1,N1)]
+x[IX(N1,0,N2)]);
x[IX(N1,N1,N1)]=0.33f*(x[IX(N2,N1,N1)]
+x[IX(N1,N2,N1)]
+x[IX(N1,N1,N2)]);
}

lin_solve

Asstatedbefore,thisfunctionismysterious,butitdoessomekindofsolving.thisisdonebyrunningthrough
thewholearrayandsettingeachcelltoacombinationofitsneighbors.Itdoesthisseveraltimesthemore
iterationsitdoes,themoreaccuratetheresults,buttheslowerthingsrun.Inthestepfunctionabove,four
iterationsareused.Aftereachiteration,itresetstheboundariessothecalculationsdon'texplode.

Here'sthecode:
staticvoidlin_solve(intb,float*x,float*x0,floata,floatc,intiter,intN)
{
floatcRecip=1.0/c;
for(intk=0;k<iter;k++){
for(intm=1;m<N1;m++){
for(intj=1;j<N1;j++){
for(inti=1;i<N1;i++){
x[IX(i,j,m)]=
(x0[IX(i,j,m)]
+a*(x[IX(i+1,j,m)]
+x[IX(i1,j,m)]
+x[IX(i,j+1,m)]
+x[IX(i,j1,m)]
+x[IX(i,j,m+1)]
+x[IX(i,j,m1)]
))*cRecip;
}
}
}
set_bnd(b,x,N);
}
}

diffuse
Diffuseisreallysimpleitjustprecalculatesavalueandpasseseverythingofftolin_solve.Sothatmeans,
whileIknowwhatitdoes,Idon'treallyknowhow,sincealltheworkisinthatmysteriousfunction.Code:
staticvoiddiffuse(intb,float*x,float*x0,floatdiff,floatdt,intiter,intN)
{
floata=dt*diff*(N2)*(N2);
lin_solve(b,x,x0,a,1+6*a,iter,N);
}

project

Thisfunctionisalsosomewhatmysteriousastoexactlyhowitworks,butitdoessomemorerunningthrough
thedataandsettingvalues,withsomecallstolin_solvethrowninforfun.Code:
staticvoidproject(float*velocX,float*velocY,float*velocZ,float*p,float*div,intiter,int
N)
{
for(intk=1;k<N1;k++){
for(intj=1;j<N1;j++){
for(inti=1;i<N1;i++){
div[IX(i,j,k)]=0.5f*(
velocX[IX(i+1,j,k)]
velocX[IX(i1,j,k)]
+velocY[IX(i,j+1,k)]
velocY[IX(i,j1,k)]
+velocZ[IX(i,j,k+1)]
velocZ[IX(i,j,k1)]
)/N;
p[IX(i,j,k)]=0;
}
}
}
set_bnd(0,div,N);
set_bnd(0,p,N);
lin_solve(0,p,div,1,6,iter,N);

for(intk=1;k<N1;k++){
for(intj=1;j<N1;j++){
for(inti=1;i<N1;i++){
velocX[IX(i,j,k)]=0.5f*(p[IX(i+1,j,k)]
p[IX(i1,j,k)])*N;
velocY[IX(i,j,k)]=0.5f*(p[IX(i,j+1,k)]
p[IX(i,j1,k)])*N;
velocZ[IX(i,j,k)]=0.5f*(p[IX(i,j,k+1)]
p[IX(i,j,k1)])*N;
}
}
}
set_bnd(1,velocX,N);
set_bnd(2,velocY,N);
set_bnd(3,velocZ,N);
}

advect

Thisfunctionisresponsibleforactuallymovingthingsaround.Tothatend,itlooksateachcellin
turn.Inthatcell,itgrabsthevelocity,followsthatvelocitybackintime,andseeswhereitlands.It
thentakesaweightedaverageofthecellsaroundthespotwhereitlands,thenappliesthatvaluetothe
currentcell.

Here'sthecode:
staticvoidadvect(intb,float*d,float*d0,float*velocX,float*velocY,float*velocZ,float
dt,intN)
{
floati0,i1,j0,j1,k0,k1;

floatdtx=dt*(N2);
floatdty=dt*(N2);
floatdtz=dt*(N2);

floats0,s1,t0,t1,u0,u1;
floattmp1,tmp2,tmp3,x,y,z;

floatNfloat=N;
floatifloat,jfloat,kfloat;
inti,j,k;

for(k=1,kfloat=1;k<N1;k++,kfloat++){
for(j=1,jfloat=1;j<N1;j++,jfloat++){
for(i=1,ifloat=1;i<N1;i++,ifloat++){
tmp1=dtx*velocX[IX(i,j,k)];
tmp2=dty*velocY[IX(i,j,k)];
tmp3=dtz*velocZ[IX(i,j,k)];
x=ifloattmp1;
y=jfloattmp2;
z=kfloattmp3;

if(x<0.5f)x=0.5f;
if(x>Nfloat+0.5f)x=Nfloat+0.5f;
i0=floorf(x);
i1=i0+1.0f;
if(y<0.5f)y=0.5f;
if(y>Nfloat+0.5f)y=Nfloat+0.5f;
j0=floorf(y);
j1=j0+1.0f;
if(z<0.5f)z=0.5f;
if(z>Nfloat+0.5f)z=Nfloat+0.5f;
k0=floorf(z);
k1=k0+1.0f;

s1=xi0;
s0=1.0fs1;
t1=yj0;
t0=1.0ft1;
u1=zk0;
u0=1.0fu1;

inti0i=i0;
inti1i=i1;
intj0i=j0;
intj1i=j1;
intk0i=k0;
intk1i=k1;

d[IX(i,j,k)]=

s0*(t0*(u0*d0[IX(i0i,j0i,k0i)]
+u1*d0[IX(i0i,j0i,k1i)])
+(t1*(u0*d0[IX(i0i,j1i,k0i)]
+u1*d0[IX(i0i,j1i,k1i)])))
+s1*(t0*(u0*d0[IX(i1i,j0i,k0i)]
+u1*d0[IX(i1i,j0i,k1i)])
+(t1*(u0*d0[IX(i1i,j1i,k0i)]
+u1*d0[IX(i1i,j1i,k1i)])));
}
}
}
set_bnd(b,d,N);
}

WrappingUp
Andthereyouhaveit,everythingyouneedtomakeyourown3Dfluidsimulation.

Viewthefullsource

Notethatactuallydisplayingthisanimationisabitmoredifficult.Thiswasactuallytheprimarytopic
ofmythesis,asthefluidsimulationwasprettymuchjustborrowedfromStamandpulledinto3D.
ThiskindofthreedimensionalgriddataisnotsomethingthatmostgraphicshardwareandAPIsknow
howtorender,andsoit'safairlychallengingtopic.

Ifyouwanttoshowtheresultsofthesimulation,youhaveseveralchoices.Onewouldbetorewrite
thesimulatorintwodimensionsinsteadofthree,andyoucanthenjustdisplaythedensitypointer
onscreenasaregularimage.Anotherchoicewouldbetorunthefull3Dsimulationbutonlydisplay
2Dsliceofit.Andfinally,anotherchoicewouldbetorunasimulationsmallenoughthatyoucanjust
drawonepolygonpercellwithouttakingtoomuchofaspeedhit.

Ofcourse,ifyoulikeachallenge,feelfreetocheckoutthevarioustechniquesforvolumetric
rendering.

Ihopeyouenjoyedthiswhirlwindtourthroughfluidsimulation.Havefun!