Created
March 26, 2019 16:47
-
-
Save soscler/816708757b40fc10af03ee43b3284204 to your computer and use it in GitHub Desktop.
simple fluid simulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Usage: Drag with the mouse to add smoke to the fluid. This will also move a "rotor" that disturbs | |
// the velocity field at the mouse location. Press the indicated keys to change options | |
//-------------------------------------------------------------------------------------------------- | |
#include <rfftw.h> //the numerical simulation FFTW library | |
#include <stdio.h> //for printing the help text | |
#include <math.h> //for various math functions | |
#include <GL/glut.h> //the GLUT graphics library | |
//--- SIMULATION PARAMETERS ------------------------------------------------------------------------ | |
const int DIM = 50; //size of simulation grid | |
double dt = 0.4; //simulation time step | |
float visc = 0.001; //fluid viscosity | |
fftw_real *vx, *vy; //(vx,vy) = velocity field at the current moment | |
fftw_real *vx0, *vy0; //(vx0,vy0) = velocity field at the previous moment | |
fftw_real *fx, *fy; //(fx,fy) = user-controlled simulation forces, steered with the mouse | |
fftw_real *rho, *rho0; //smoke density at the current (rho) and previous (rho0) moment | |
rfftwnd_plan plan_rc, plan_cr; //simulation domain discretization | |
//--- VISUALIZATION PARAMETERS --------------------------------------------------------------------- | |
int winWidth, winHeight; //size of the graphics window, in pixels | |
int color_dir = 0; //use direction color-coding or not | |
float vec_scale = 1000; //scaling of hedgehogs | |
int draw_smoke = 0; //draw the smoke or not | |
int draw_vecs = 1; //draw the vector field or not | |
const int COLOR_BLACKWHITE=0; //different types of color mapping: black-and-white, rainbow, banded | |
const int COLOR_RAINBOW=1; | |
const int COLOR_BANDS=2; | |
const int DRAW_LINES=0 | |
const int DRAW_CONES=1 | |
int scalar_col = 0; //method for scalar coloring | |
int frozen = 0; //toggles on/off the animation | |
//------ SIMULATION CODE STARTS HERE ----------------------------------------------------------------- | |
//init_simulation: Initialize simulation data structures as a function of the grid size 'n'. | |
// Although the simulation takes place on a 2D grid, we allocate all data structures as 1D arrays, | |
// for compatibility with the FFTW numerical library. | |
void init_simulation(int n) | |
{ | |
int i; size_t dim; | |
dim = n * 2*(n/2+1)*sizeof(fftw_real); //Allocate data structures | |
vx = (fftw_real*) malloc(dim); | |
vy = (fftw_real*) malloc(dim); | |
vx0 = (fftw_real*) malloc(dim); | |
vy0 = (fftw_real*) malloc(dim); | |
dim = n * n * sizeof(fftw_real); | |
fx = (fftw_real*) malloc(dim); | |
fy = (fftw_real*) malloc(dim); | |
rho = (fftw_real*) malloc(dim); | |
rho0 = (fftw_real*) malloc(dim); | |
plan_rc = rfftw2d_create_plan(n, n, FFTW_REAL_TO_COMPLEX, FFTW_IN_PLACE); | |
plan_cr = rfftw2d_create_plan(n, n, FFTW_COMPLEX_TO_REAL, FFTW_IN_PLACE); | |
for (i = 0; i < n * n; i++) //Initialize data structures to 0 | |
{ vx[i] = vy[i] = vx0[i] = vy0[i] = fx[i] = fy[i] = rho[i] = rho0[i] = 0.0f; } | |
} | |
//FFT: Execute the Fast Fourier Transform on the dataset 'vx'. | |
// 'dirfection' indicates if we do the direct (1) or inverse (-1) Fourier Transform | |
void FFT(int direction,void* vx) | |
{ | |
if(direction==1) rfftwnd_one_real_to_complex(plan_rc,(fftw_real*)vx,(fftw_complex*)vx); | |
else rfftwnd_one_complex_to_real(plan_cr,(fftw_complex*)vx,(fftw_real*)vx); | |
} | |
int clamp(float x) | |
{ return ((x)>=0.0?((int)(x)):(-((int)(1-(x))))); } | |
float max(float x, float y) | |
{ return x < y ? x : y; } | |
//solve: Solve (compute) one step of the fluid flow simulation | |
void solve(int n, fftw_real* vx, fftw_real* vy, fftw_real* vx0, fftw_real* vy0, fftw_real visc, fftw_real dt) | |
{ | |
fftw_real x, y, x0, y0, f, r, U[2], V[2], s, t; | |
int i, j, i0, j0, i1, j1; | |
for (i=0;i<n*n;i++) | |
{ vx[i] += dt*vx0[i]; vx0[i] = vx[i]; vy[i] += dt*vy0[i]; vy0[i] = vy[i]; } | |
for ( x=0.5f/n,i=0 ; i<n ; i++,x+=1.0f/n ) | |
for ( y=0.5f/n,j=0 ; j<n ; j++,y+=1.0f/n ) | |
{ | |
x0 = n*(x-dt*vx0[i+n*j])-0.5f; | |
y0 = n*(y-dt*vy0[i+n*j])-0.5f; | |
i0 = clamp(x0); s = x0-i0; | |
i0 = (n+(i0%n))%n; | |
i1 = (i0+1)%n; | |
j0 = clamp(y0); t = y0-j0; | |
j0 = (n+(j0%n))%n; | |
j1 = (j0+1)%n; | |
vx[i+n*j] = (1-s)*((1-t)*vx0[i0+n*j0]+t*vx0[i0+n*j1])+s*((1-t)*vx0[i1+n*j0]+t*vx0[i1+n*j1]); | |
vy[i+n*j] = (1-s)*((1-t)*vy0[i0+n*j0]+t*vy0[i0+n*j1])+s*((1-t)*vy0[i1+n*j0]+t*vy0[i1+n*j1]); | |
} | |
for(i=0; i<n; i++) | |
for(j=0; j<n; j++) | |
{ vx0[i+(n+2)*j] = vx[i+n*j]; vy0[i+(n+2)*j] = vy[i+n*j]; } | |
FFT(1,vx0); | |
FFT(1,vy0); | |
for (i=0;i<=n;i+=2) | |
{ | |
x = 0.5f*i; | |
for (j=0;j<n;j++) | |
{ | |
y = j<=n/2 ? (fftw_real)j : (fftw_real)j-n; | |
r = x*x+y*y; | |
if ( r==0.0f ) continue; | |
f = (fftw_real)exp(-r*dt*visc); | |
U[0] = vx0[i +(n+2)*j]; V[0] = vy0[i +(n+2)*j]; | |
U[1] = vx0[i+1+(n+2)*j]; V[1] = vy0[i+1+(n+2)*j]; | |
vx0[i +(n+2)*j] = f*((1-x*x/r)*U[0] -x*y/r *V[0]); | |
vx0[i+1+(n+2)*j] = f*((1-x*x/r)*U[1] -x*y/r *V[1]); | |
vy0[i+ (n+2)*j] = f*( -y*x/r *U[0] + (1-y*y/r)*V[0]); | |
vy0[i+1+(n+2)*j] = f*( -y*x/r *U[1] + (1-y*y/r)*V[1]); | |
} | |
} | |
FFT(-1,vx0); | |
FFT(-1,vy0); | |
f = 1.0/(n*n); | |
for (i=0;i<n;i++) | |
for (j=0;j<n;j++) | |
{ vx[i+n*j] = f*vx0[i+(n+2)*j]; vy[i+n*j] = f*vy0[i+(n+2)*j]; } | |
} | |
// diffuse_matter: This function diffuses matter that has been placed in the velocity field. It's almost identical to the | |
// velocity diffusion step in the function above. The input matter densities are in rho0 and the result is written into rho. | |
void diffuse_matter(int n, fftw_real *vx, fftw_real *vy, fftw_real *rho, fftw_real *rho0, fftw_real dt) | |
{ | |
fftw_real x, y, x0, y0, s, t; | |
int i, j, i0, j0, i1, j1; | |
for ( x=0.5f/n,i=0 ; i<n ; i++,x+=1.0f/n ) | |
for ( y=0.5f/n,j=0 ; j<n ; j++,y+=1.0f/n ) | |
{ | |
x0 = n*(x-dt*vx[i+n*j])-0.5f; | |
y0 = n*(y-dt*vy[i+n*j])-0.5f; | |
i0 = clamp(x0); | |
s = x0-i0; | |
i0 = (n+(i0%n))%n; | |
i1 = (i0+1)%n; | |
j0 = clamp(y0); | |
t = y0-j0; | |
j0 = (n+(j0%n))%n; | |
j1 = (j0+1)%n; | |
rho[i+n*j] = (1-s)*((1-t)*rho0[i0+n*j0]+t*rho0[i0+n*j1])+s*((1-t)*rho0[i1+n*j0]+t*rho0[i1+n*j1]); | |
} | |
} | |
//set_forces: copy user-controlled forces to the force vectors that are sent to the solver. | |
// Also dampen forces and matter density to get a stable simulation. | |
void set_forces(void) | |
{ | |
int i; | |
for (i = 0; i < DIM * DIM; i++) | |
{ | |
rho0[i] = 0.995 * rho[i]; | |
fx[i] *= 0.85; | |
fy[i] *= 0.85; | |
vx0[i] = fx[i]; | |
vy0[i] = fy[i]; | |
} | |
} | |
//do_one_simulation_step: Do one complete cycle of the simulation: | |
// - set_forces: | |
// - solve: read forces from the user | |
// - diffuse_matter: compute a new set of velocities | |
// - gluPostRedisplay: draw a new visualization frame | |
void do_one_simulation_step(void) | |
{ | |
if (!frozen) | |
{ | |
set_forces(); | |
solve(DIM, vx, vy, vx0, vy0, visc, dt); | |
diffuse_matter(DIM, vx, vy, rho, rho0, dt); | |
glutPostRedisplay(); | |
} | |
} | |
//------ VISUALIZATION CODE STARTS HERE ----------------------------------------------------------------- | |
//rainbow: Implements a color palette, mapping the scalar 'value' to a rainbow color RGB | |
void rainbow(float value,float* R,float* G,float* B) | |
{ | |
const float dx=0.8; | |
if (value<0) value=0; if (value>1) value=1; | |
value = (6-2*dx)*value+dx; | |
*R = max(0.0,(3-fabs(value-4)-fabs(value-5))/2); | |
*G = max(0.0,(4-fabs(value-2)-fabs(value-4))/2); | |
*B = max(0.0,(3-fabs(value-1)-fabs(value-2))/2); | |
} | |
//set_colormap: Sets three different types of colormaps | |
void set_colormap(float vy) | |
{ | |
float R,G,B; | |
if (scalar_col==COLOR_BLACKWHITE) | |
R = G = B = vy; | |
else if (scalar_col==COLOR_RAINBOW) | |
rainbow(vy,&R,&G,&B); | |
else if (scalar_col==COLOR_BANDS) | |
{ | |
const int NLEVELS = 7; | |
vy *= NLEVELS; vy = (int)(vy); vy/= NLEVELS; | |
rainbow(vy,&R,&G,&B); | |
} | |
glColor3f(R,G,B); | |
} | |
//direction_to_color: Set the current color by mapping a direction vector (x,y), using | |
// the color mapping method 'method'. If method==1, map the vector direction | |
// using a rainbow colormap. If method==0, simply use the white color | |
void direction_to_color(float x, float y, int method) | |
{ | |
float r,g,b,f; | |
if (method) | |
{ | |
f = atan2(y,x) / 3.1415927 + 1; | |
r = f; | |
if(r > 1) r = 2 - r; | |
g = f + .66667; | |
if(g > 2) g -= 2; | |
if(g > 1) g = 2 - g; | |
b = f + 2 * .66667; | |
if(b > 2) b -= 2; | |
if(b > 1) b = 2 - b; | |
} | |
else | |
{ r = g = b = 1; } | |
glColor3f(r,g,b); | |
} | |
void draw_lines(){ | |
{ | |
glBegin(GL_LINES); //draw velocities | |
for (i = 0; i < DIM; i++) | |
for (j = 0; j < DIM; j++) | |
{ | |
idx = (j * DIM) + i; | |
direction_to_color(vx[idx],vy[idx],color_dir); | |
glVertex2f(wn + (fftw_real)i * wn, hn + (fftw_real)j * hn); | |
glVertex2f((wn + (fftw_real)i * wn) + vec_scale * vx[idx], (hn + (fftw_real)j * hn) + vec_scale * vy[idx]); | |
} | |
glEnd(); | |
} | |
} | |
void draw_cones(){ | |
} | |
void glyphChange(int scalar_glyph){ | |
if(scalar_glyph == DRAW_LINES) | |
draw_lines(); | |
else if(scalar_glyph == DRAW_CONES) | |
draw_cones(); | |
} | |
//visualize: This is the main visualization function | |
void visualize(void) | |
{ | |
int i, j, idx; double px,py; | |
fftw_real wn = (fftw_real)winWidth / (fftw_real)(DIM + 1); // Grid cell width | |
fftw_real hn = (fftw_real)winHeight / (fftw_real)(DIM + 1); // Grid cell heigh | |
if (draw_smoke) | |
{ | |
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); | |
for (j = 0; j < DIM - 1; j++) //draw smoke | |
{ | |
glBegin(GL_TRIANGLE_STRIP); | |
i = 0; | |
px = wn + (fftw_real)i * wn; | |
py = hn + (fftw_real)j * hn; | |
idx = (j * DIM) + i; | |
glColor3f(rho[idx],rho[idx],rho[idx]); | |
glVertex2f(px,py); | |
for (i = 0; i < DIM - 1; i++) | |
{ | |
px = wn + (fftw_real)i * wn; | |
py = hn + (fftw_real)(j + 1) * hn; | |
idx = ((j + 1) * DIM) + i; | |
set_colormap(rho[idx]); | |
glVertex2f(px, py); | |
px = wn + (fftw_real)(i + 1) * wn; | |
py = hn + (fftw_real)j * hn; | |
idx = (j * DIM) + (i + 1); | |
set_colormap(rho[idx]); | |
glVertex2f(px, py); | |
} | |
px = wn + (fftw_real)(DIM - 1) * wn; | |
py = hn + (fftw_real)(j + 1) * hn; | |
idx = ((j + 1) * DIM) + (DIM - 1); | |
set_colormap(rho[idx]); | |
glVertex2f(px, py); | |
glEnd(); | |
} | |
} | |
if (draw_vecs) | |
glyphChange() | |
} | |
//------ INTERACTION CODE STARTS HERE ----------------------------------------------------------------- | |
//display: Handle window redrawing events. Simply delegates to visualize(). | |
void display(void) | |
{ | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
glMatrixMode(GL_MODELVIEW); | |
glLoadIdentity(); | |
visualize(); | |
glFlush(); | |
glutSwapBuffers(); | |
} | |
//reshape: Handle window resizing (reshaping) events | |
void reshape(int w, int h) | |
{ | |
glViewport(0.0f, 0.0f, (GLfloat)w, (GLfloat)h); | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h); | |
winWidth = w; winHeight = h; | |
} | |
//keyboard: Handle key presses | |
void keyboard(unsigned char key, int x, int y) | |
{ | |
switch (key) | |
{ | |
case 't': dt -= 0.001; break; | |
case 'T': dt += 0.001; break; | |
case 'c': color_dir = 1 - color_dir; break; | |
case 'S': vec_scale *= 1.2; break; | |
case 's': vec_scale *= 0.8; break; | |
case 'V': visc *= 5; break; | |
case 'v': visc *= 0.2; break; | |
case 'x': draw_smoke = 1 - draw_smoke; | |
if (draw_smoke==0) draw_vecs = 1; break; | |
case 'y': draw_vecs = 1 - draw_vecs; | |
if (draw_vecs==0) draw_smoke = 1; break; | |
case 'm': scalar_col++; if (scalar_col>COLOR_BANDS) scalar_col=COLOR_BLACKWHITE; break; | |
case 'a': frozen = 1-frozen; break; | |
case 'q': exit(0); | |
} | |
} | |
// drag: When the user drags with the mouse, add a force that corresponds to the direction of the mouse | |
// cursor movement. Also inject some new matter into the field at the mouse location. | |
void drag(int mx, int my) | |
{ | |
int xi,yi,X,Y; double dx, dy, len; | |
static int lmx=0,lmy=0; //remembers last mouse location | |
// Compute the array index that corresponds to the cursor location | |
xi = (int)clamp((double)(DIM + 1) * ((double)mx / (double)winWidth)); | |
yi = (int)clamp((double)(DIM + 1) * ((double)(winHeight - my) / (double)winHeight)); | |
X = xi; Y = yi; | |
if (X > (DIM - 1)) X = DIM - 1; if (Y > (DIM - 1)) Y = DIM - 1; | |
if (X < 0) X = 0; if (Y < 0) Y = 0; | |
// Add force at the cursor location | |
my = winHeight - my; | |
dx = mx - lmx; dy = my - lmy; | |
len = sqrt(dx * dx + dy * dy); | |
if (len != 0.0) { dx *= 0.1 / len; dy *= 0.1 / len; } | |
fx[Y * DIM + X] += dx; | |
fy[Y * DIM + X] += dy; | |
rho[Y * DIM + X] = 10.0f; | |
lmx = mx; lmy = my; | |
} | |
//main: The main program | |
int main(int argc, char **argv) | |
{ | |
printf("Fluid Flow Simulation and Visualization\n"); | |
printf("=======================================\n"); | |
printf("Click and drag the mouse to steer the flow!\n"); | |
printf("T/t: increase/decrease simulation timestep\n"); | |
printf("S/s: increase/decrease hedgehog scaling\n"); | |
printf("c: toggle direction coloring on/off\n"); | |
printf("V/v: increase decrease fluid viscosity\n"); | |
printf("x: toggle drawing matter on/off\n"); | |
printf("y: toggle drawing hedgehogs on/off\n"); | |
printf("m: toggle thru scalar coloring\n"); | |
printf("a: toggle the animation on/off\n"); | |
printf("q: quit\n\n"); | |
glutInit(&argc, argv); | |
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); | |
glutInitWindowSize(500,500); | |
glutCreateWindow("Real-time smoke simulation and visualization"); | |
glutDisplayFunc(display); | |
glutReshapeFunc(reshape); | |
glutIdleFunc(do_one_simulation_step); | |
glutKeyboardFunc(keyboard); | |
glutMotionFunc(drag); | |
init_simulation(DIM); //initialize the simulation data structures | |
glutMainLoop(); //calls do_one_simulation_step, keyboard, display, drag, reshape | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment