tcod-haskell-0.1.0.0: libtcod/samples/navier/main.cpp
/*
* Copyright (c) 2010 Jice
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The name of Jice may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY JICE ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JICE BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <math.h>
#include "main.hpp"
// gas simulation
// based on Jos Stam, "Real-Time Fluid Dynamics for Games". Proceedings of the Game Developer Conference, March 2003.
// http://www.dgp.toronto.edu/people/stam/reality/Research/pub.html
#define WIDTH 50
#define HEIGHT 50
#define WIDTHx2 (WIDTH*2)
#define HEIGHTx2 (HEIGHT*2)
// use square map
#define N MIN(WIDTHx2,HEIGHTx2)
// store a 2D map in a 1D array
#define SIZE (N+2)*(N+2)
// convert x,y to array index
#define IX(i,j) ((i)+(N+2)*(j))
#define SWAP(x0,x) {float *tmp=x0;x0=x;x=tmp;}
// 2D velocity maps (current and previous)
float u[SIZE],v[SIZE], u_prev[SIZE], v_prev[SIZE];
// density maps (current and previous)
float dens[SIZE], dens_prev[SIZE];
TCODImage img(WIDTHx2,HEIGHTx2);
float visc=1E-6f;
float diff=1E-5f;
float force=40.0f;
float source=5000.0f;
float stepDelay=0.0f;
int playerx=N/4,playery=N/4;
// set boundary conditions
void set_bnd ( int b, float * x ) {
for ( int i=1 ; i<=N ; i++ ) {
// west and east walls
x[IX(0,i)] = b == 1 ? -x[IX(1,i)] : x[IX(1,i)];
x[IX(N+1,i)] = b == 1 ? -x[IX(N,i)] : x[IX(N,i)];
// boundary doesn't work on north and south walls...
// dunno why...
x[IX(i,0)] = b == 1 ? -x[IX(i,1)] : x[IX(i,1)];
x[IX(i,N+1)] = b == 1 ? -x[IX(i,N)] : x[IX(i,N)];
}
// boundary conditions at corners
x[IX(0 ,0 )] = 0.5f*(x[IX(1,0 )]+x[IX(0 ,1)]);
x[IX(0 ,N+1)] = 0.5f*(x[IX(1,N+1)]+x[IX(0 ,N )]);
x[IX(N+1,0 )] = 0.5f*(x[IX(N,0 )]+x[IX(N+1,1)]);
x[IX(N+1,N+1)] = 0.5f*(x[IX(N,N+1)]+x[IX(N+1,N )]);
}
// update density map according to density sources
// x : density map
// s : density source map
// dt : elapsed time
void add_source(float *x, float *s, float dt) {
for (int i=0; i < SIZE; i++) {
x[i] += dt*s[i];
}
}
// update density or velocity map for diffusion
// b : boundary width
// x : current density map
// x0 : previous density map
// diff : diffusion coef
// dt : elapsed time
void diffuse( int b, float *x, float *x0, float diff, float dt) {
float a = diff*dt*N*N;
for (int k=0; k < 20; k++) {
for ( int i=1; i <= N; i++ ) {
for (int j=1; j<= N; j++) {
x[IX(i,j)] = (x0[IX(i,j)] + a*(x[IX(i-1,j)]+x[IX(i+1,j)]
+x[IX(i,j-1)]+x[IX(i,j+1)]))/(1+4*a);
}
}
set_bnd(b,x);
}
}
// update density map according to velocity map
// b : boundary width
// d : current density map
// d0 : previous density map
// u,v : current velocity map
// dt : elapsed time
void advect ( int b, float * d, float * d0, float * u, float * v, float dt ) {
int i0, j0, i1, j1;
float x, y, s0, t0, s1, t1, dt0;
dt0 = dt*N;
for ( int i=1 ; i<=N ; i++ ) {
for ( int j=1 ; j<=N ; j++ ) {
x = i-dt0*u[IX(i,j)];
y = j-dt0*v[IX(i,j)];
if (x<0.5) x=0.5;
if (x>N+0.5) x=N+ 0.5;
i0=(int)x;
i1=i0+1;
if (y<0.5) y=0.5;
if (y>N+0.5) y=N+ 0.5;
j0=(int)y;
j1=j0+1;
s1 = x-i0;
s0 = 1-s1;
t1 = y-j0;
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 );
}
void project ( float * u, float * v, float * p, float * div ) {
float h = 1.0f/N;
for ( int i=1 ; i<=N ; i++ ) {
for ( int j=1 ; j<=N ; j++ ) {
div[IX(i,j)] = -0.5f*h*(u[IX(i+1,j)]-u[IX(i-1,j)]+
v[IX(i,j+1)]-v[IX(i,j-1)]);
p[IX(i,j)] = 0;
}
}
set_bnd ( 0, div ); set_bnd ( 0, p );
for ( int k=0 ; k<20 ; k++ ) {
for ( int i=1 ; i<=N ; i++ ) {
for ( int j=1 ; j<=N ; j++ ) {
p[IX(i,j)] = (div[IX(i,j)]+p[IX(i-1,j)]+p[IX(i+1,j)]+
p[IX(i,j-1)]+p[IX(i,j+1)])/4;
}
}
set_bnd ( 0, p );
}
for ( int i=1 ; i<=N ; i++ ) {
for ( int j=1 ; j<=N ; j++ ) {
u[IX(i,j)] -= 0.5f*(p[IX(i+1,j)]-p[IX(i-1,j)])/h;
v[IX(i,j)] -= 0.5f*(p[IX(i,j+1)]-p[IX(i,j-1)])/h;
}
}
set_bnd ( 1, u ); set_bnd ( 2, v );
}
// do all three density steps
void update_density ( float * x, float * x0, float * u, float * v, float diff, float dt ) {
add_source ( x, x0, dt );
SWAP ( x0, x ); diffuse ( 0, x, x0, diff, dt );
SWAP ( x0, x ); advect ( 0, x, x0, u, v, dt );
}
void update_velocity( float * u, float * v, float * u0, float * v0, float visc, float dt ) {
add_source ( u, u0, dt );
add_source ( v, v0, dt );
SWAP ( u0, u ); diffuse ( 1, u, u0, visc, dt );
SWAP ( v0, v ); diffuse ( 2, v, v0, visc, dt );
project ( u, v, u0, v0 );
SWAP ( u0, u ); SWAP ( v0, v );
advect ( 1, u, u0, u0, v0, dt ); advect ( 2, v, v0, u0, v0, dt );
project ( u, v, u0, v0 );
}
void init() {
memset(u,0,sizeof(float)*SIZE);
memset(v,0,sizeof(float)*SIZE);
memset(u_prev,0,sizeof(float)*SIZE);
memset(v_prev,0,sizeof(float)*SIZE);
for (int i=0;i < SIZE; i++) dens[i]=0.0f;
memcpy(dens_prev,dens,sizeof(float)*SIZE);
}
void get_from_UI ( float * d, float * u, float * v, float elapsed, TCOD_key_t k,TCOD_mouse_t mouse ) {
int i,j;
float vx=0.0f,vy=0.0f;
stepDelay -= elapsed;
if ( stepDelay < 0.0f ) {
if ( TCODConsole::isKeyPressed(TCODK_UP) && playery > 0 ) {
playery--;
vy -= force;
}
if ( TCODConsole::isKeyPressed(TCODK_DOWN) && playery < N/2-1 ) {
playery++;
vx += force;
}
if ( TCODConsole::isKeyPressed(TCODK_LEFT) && playerx > 0 ) {
playerx--;
vx -= force;
}
if ( TCODConsole::isKeyPressed(TCODK_RIGHT) && playerx < N/2-1 ) {
playerx++;
vx += force;
}
stepDelay=0.2f; // move 5 cells per second
// try to move smoke when you walk inside it. doesn't seem to work...
u[IX(playerx*2,playery*2)] = 5*vx;
v[IX(playerx*2,playery*2)] = 5*vy;
}
for ( i=0 ; i<SIZE; i++ ) {
u[i] = v[i] = d[i] = 0.0f;
}
if ( !mouse.lbutton && !mouse.rbutton ) return;
i = mouse.cx*2;
j = mouse.cy*2;
if ( i<1 || i>N || j<1 || j>N ) return;
if ( mouse.lbutton ) {
float dx,dy,l;
dx=(float)(mouse.cx-playerx);
dy=(float)(mouse.cy-playery);
l=sqrtf(dx*dx+dy*dy);
if ( l > 0 ) {
l = 1.0f/l;
dx*=l;
dy*=l;
u[IX(playerx*2,playery*2)] = force * dx;
v[IX(playerx*2,playery*2)] = force * dy;
d[IX(playerx*2,playery*2)] = source;
}
}
}
void update(float elapsed, TCOD_key_t k, TCOD_mouse_t mouse) {
get_from_UI(dens_prev,u_prev,v_prev,elapsed,k,mouse);
update_velocity(u,v,u_prev,v_prev,visc,elapsed);
update_density(dens,dens_prev,u,v,diff,elapsed);
}
void render() {
static TCODColor deepBlue = TCODColor::darkestFlame;
static TCODColor highBlue = TCODColor::lightestYellow;
for (int x=0;x <= N; x++) {
for (int y=0; y <= N; y++ ) {
float coef=(float)(dens[IX(x,y)]/128.0f);
coef=CLAMP(0.0f,1.0f,coef);
img.putPixel(x,y,TCODColor::lerp(deepBlue,highBlue,coef ));
}
}
img.blit2x(TCODConsole::root,0,0);
TCODConsole::root->print(2,HEIGHT-2,"%4d fps", TCODSystem::getFps());
TCODConsole::root->setDefaultForeground(TCODColor::white);
TCODConsole::root->putChar(playerx,playery,'@');
}
int main (int argc, char *argv[]) {
// initialize the game window
TCODConsole::initRoot(WIDTH,HEIGHT,"pyromancer flame spell v" VERSION, false,TCOD_RENDERER_SDL);
TCODSystem::setFps(25);
TCODMouse::showCursor(true);
bool endCredits=false;
init();
while (! TCODConsole::isWindowClosed()) {
TCOD_key_t k;
TCOD_mouse_t mouse;
TCODSystem::checkForEvent(TCOD_EVENT_KEY|TCOD_EVENT_MOUSE, &k, &mouse);
/*
v_prev[IX(N/2,0)] = 1.0f;
u_prev[IX(N/3,N/3)]=1.0f;
dens_prev[IX(N/2,0)] = 128.0f;
*/
if ( k.vk == TCODK_PRINTSCREEN ) {
// screenshot
if (! k.pressed ) TCODSystem::saveScreenshot(NULL);
k.vk=TCODK_NONE;
} else if ( k.lalt && (k.vk == TCODK_ENTER || k.vk == TCODK_KPENTER) ) {
// switch fullscreen
if (! k.pressed ) TCODConsole::setFullscreen(!TCODConsole::isFullscreen());
k.vk=TCODK_NONE;
}
// update the game
update(TCODSystem::getLastFrameLength(),k,mouse);
// render the game screen
render();
TCODConsole::root->print(5,49,"Arrows to move, left mouse button to cast");
// render libtcod credits
if (! endCredits ) endCredits = TCODConsole::renderCredits(4,4,true);
// flush updates to screen
TCODConsole::root->flush();
}
return 0;
}