I have been trying to implement 2D tile based water into my game. I started out making the tiles appear on screen etc. I have a drawing function that draws each tile type. The problem I'm having is that when I call this function the Tiles that are water don't change position. Which makes me believe that this code isn't functioning properly. This code is called on every loop. This should update the masses of all the water tiles. For some reason nothing is happening. The water is staying in its original positions. My tiles are in a vector of the tile class is just
Tiles()
{
TileProp // the type of tile (GROUND,AIR,WATER)
Mass
NewMass
}
void App::SimulateCompression()
{
float Flow = 0;
float remainingmass = 0;
int ID = 0;
//Calculate and apply flow for each block
for (int X = 0; X < MAP_WIDTH; X++)
{
for(int Y = 0; Y < MAP_HEIGHT; Y++)
{
//Skip inert ground blocks
if(TileList[ID].TileProp == TILE_GROUND) continue;
//Custom push-only flow
Flow = 0;
remainingmass = TileList[ID].Mass;
if(remainingmass <= 0) continue;
//The block below this one
if(TileList[Rect2Lin(TILE_SIZE,X,(Y-1))].TileProp != TILE_GROUND)
{
Flow = GetStableWaterState(remainingmass + TileList[Rect2Lin(TILE_SIZE,X,(Y-1))].Mass /*mass[x][y-1]*/) - TileList[Rect2Lin(TILE_SIZE,X,(Y-1))].Mass;
if(Flow > MinFlow){Flow *= 0.5; /*leads to smoother flow*/}
int tempA = Min(MaxSpeed, remainingmass);
if(Flow > tempA){Flow = tempA;}
if(Flow < 0){Flow = 0;}
TileList[ID].NewMass -= Flow;
TileList[Rect2Lin(TILE_SIZE,X,(Y-1))].NewMass += Flow;
remainingmass -= Flow;
}
if(remainingmass <= 0) continue;
//Left
if(TileList[Rect2Lin(TILE_SIZE,(X - 1),Y)].TileProp != TILE_GROUND)
{
//Equalize the amount of water in this block and it's neighbour
Flow = (TileList[ID].Mass - TileList[Rect2Lin(TILE_SIZE,(X - 1),Y)].Mass)/4;
if(Flow > MinFlow){Flow *= 0.5;}
if(Flow > remainingmass){Flow = remainingmass;}
if(Flow < 0){Flow = 0;}
TileList[ID].NewMass -= Flow;
TileList[Rect2Lin(TILE_SIZE,(X - 1),Y)].NewMass += Flow;
remainingmass -= Flow;
}
if(remainingmass <= 0) continue;
//Right
if(TileList[Rect2Lin(TILE_SIZE,(X + 1),Y)].TileProp != TILE_GROUND)
{
//Equalize the amount of water in this block and it's neighbour
Flow = (TileList[ID].Mass - TileList[Rect2Lin(TILE_SIZE,(X + 1),Y)].Mass)/4;
if(Flow > MinFlow){Flow *= 0.5;}
if(Flow > remainingmass){Flow = remainingmass;}
if(Flow < 0){Flow = 0;}
TileList[ID].NewMass -= Flow;
TileList[Rect2Lin(TILE_SIZE,(X + 1),Y)].NewMass += Flow;
remainingmass -= Flow;
}
if(remainingmass <= 0) continue;
//Up. Only compressed water flows upwards
if(TileList[Rect2Lin(TILE_SIZE,X,(Y + 1))].TileProp != TILE_GROUND)
{
Flow = remainingmass - GetStableWaterState(remainingmass + TileList[Rect2Lin(TILE_SIZE,X,(Y + 1))].Mass);
if (Flow > MinFlow){Flow *= 0.5;}
int tempB = Min(MaxSpeed, remainingmass);
if(Flow > tempB){Flow = tempB;}
if(Flow < 0){Flow = 0;}
TileList[ID].NewMass -= Flow;
TileList[Rect2Lin(TILE_SIZE,X,(Y + 1))].NewMass += Flow;
remainingmass -= Flow;
}
ID++;
}
}
ID = 0;
//Copy the new mass values
for (int X = 0; X < MAP_WIDTH; X++)
{
for (int Y = 0; Y < MAP_HEIGHT; Y++)
{
TileList[ID].Mass = TileList[ID].NewMass;
ID++;
}
}
ID = 0;
for(int X = 0; X < MAP_WIDTH; X++)
{
for(int Y = 0; Y < MAP_HEIGHT; Y++)
{
//Skip ground blocks
if(TileList[ID].TileProp == TILE_GROUND) continue;
//Flag/unflag water blocks
if(TileList[ID].Mass > MinMass)
{
TileList[ID].TileProp = TILE_WATER;
}else
{
TileList[ID].TileProp = TILE_AIR;
}
ID++;
}
}
//Remove any water that has left the map
for(int X = 0; X < MAP_WIDTH; X++)
{
TileList[X].Mass = 0;
TileList[Rect2Lin(TILE_SIZE,X,MAP_HEIGHT - 1)].Mass = 0;
}
for(int Y = 0; Y < MAP_HEIGHT; Y++)
{
TileList[Rect2Lin(TILE_SIZE,0,Y)].Mass = 0;
TileList[Rect2Lin(TILE_SIZE,(MAP_WIDTH - 1),Y)].Mass = 0;
}
}
Ok, so ID is going to 0 because after ID hits 34 It just leaves the two nested for loops... Why would it do that?
//Calculate and apply flow for each block
for (int X = 0; X < MAP_WIDTH; X++)
{
for(int Y = 0; Y < MAP_HEIGHT; Y++)
{
//Skip inert ground blocks
if(TileList[ID].TileProp == TILE_GROUND) continue;
...
ID++;
}
}
TileList[34]
is probably a ground tile. At which point you hit that first if
over and over (since you never get to the ID++
at the very end of the loop) until you have exhausted the for
-loops.
Try this:
//Calculate and apply flow for each block
for (int X = 0; X < MAP_WIDTH; X++)
{
for(int Y = 0; Y < MAP_HEIGHT; Y++)
{
int ID = Rect2Lin(TILE_SIZE,X,Y));
//Skip inert ground blocks
if(TileList[ID].TileProp == TILE_GROUND) continue;
...
}
}
EDIT:
Ok, this works on my system:
#include <GL/glut.h>
#include <vector>
using namespace std;
// simple Eigen::Matrix work-alike
template< typename T >
class Matrix
{
public:
Matrix( const size_t rows, const size_t cols )
: mStride( cols )
, mHeight( rows )
, mStorage( rows * cols )
{}
T& operator()( const size_t row, const size_t col )
{
return mStorage[ row * mStride + col ];
}
const T& operator()( const size_t row, const size_t col ) const
{
return mStorage[ row * mStride + col ];
}
size_t rows() const { return mHeight; }
size_t cols() const { return mStride; }
private:
vector< T > mStorage;
size_t mStride;
size_t mHeight;
};
struct Cell
{
enum Type{ AIR, GROUND, WATER };
Cell()
: mType( AIR )
, mMass( 0 )
, mNewMass( 0 )
{}
Type mType;
float mMass;
float mNewMass;
};
const float MaxMass = 1.0f;
const float MinMass = 0.0001f;
const float MaxCompress = 0.02f;
const float MaxSpeed = 1.0f;
const float MinFlow = 0.01f;
//Take an amount of water and calculate how it should be split among two
//vertically adjacent cells. Returns the amount of water that should be in
//the bottom cell.
float get_stable_state_b( float total_mass )
{
if ( total_mass <= 1 )
{
return 1;
}
else if ( total_mass < 2*MaxMass + MaxCompress )
{
return (MaxMass*MaxMass + total_mass*MaxCompress)/(MaxMass + MaxCompress);
}
else
{
return (total_mass + MaxCompress)/2;
}
}
template< typename T >
T constrain( const T& val, const T& minVal, const T& maxVal )
{
return max( minVal, min( val, maxVal ) );
}
typedef Matrix< Cell > State;
void stepState( State& cur )
{
for( size_t y = 1; y < cur.rows()-1; ++y )
{
for( size_t x = 1; x < cur.cols()-1; ++x )
{
Cell& center = cur( y, x );
// Skip inert ground blocks
if( center.mType == Cell::GROUND )
continue;
// Custom push-only flow
float Flow = 0;
float remaining_mass = center.mMass;
if( remaining_mass <= 0 )
continue;
// The block below this one
Cell& below = cur( y-1, x );
if( below.mType != Cell::GROUND )
{
Flow = get_stable_state_b( remaining_mass + below.mMass ) - below.mMass;
if( Flow > MinFlow )
{
//leads to smoother flow
Flow *= 0.5;
}
Flow = constrain( Flow, 0.0f, min(MaxSpeed, remaining_mass) );
center.mNewMass -= Flow;
below.mNewMass += Flow;
remaining_mass -= Flow;
}
if ( remaining_mass <= 0 )
continue;
// Left
Cell& left = cur( y, x-1 );
if ( left.mType != Cell::GROUND )
{
// Equalize the amount of water in this block and it's neighbour
Flow = ( center.mMass - left.mMass ) / 4;
if ( Flow > MinFlow )
{
Flow *= 0.5;
}
Flow = constrain(Flow, 0.0f, remaining_mass);
center.mNewMass -= Flow;
left.mNewMass += Flow;
remaining_mass -= Flow;
}
if ( remaining_mass <= 0 )
continue;
// Right
Cell& right = cur( y, x+1 );
if ( right.mType != Cell::GROUND )
{
// Equalize the amount of water in this block and it's neighbour
Flow = ( center.mMass - right.mMass ) / 4;
if ( Flow > MinFlow )
{
Flow *= 0.5;
}
Flow = constrain(Flow, 0.0f, remaining_mass);
center.mNewMass -= Flow;
right.mNewMass += Flow;
remaining_mass -= Flow;
}
if ( remaining_mass <= 0 )
continue;
// The block above this one
Cell& above = cur( y+1, x );
if( above.mType != Cell::GROUND )
{
Flow = remaining_mass - get_stable_state_b( remaining_mass + above.mMass );
if( Flow > MinFlow )
{
//leads to smoother flow
Flow *= 0.5;
}
Flow = constrain( Flow, 0.0f, min(MaxSpeed, remaining_mass) );
center.mNewMass -= Flow;
above.mNewMass += Flow;
remaining_mass -= Flow;
}
}
}
for( size_t y = 0; y < cur.rows(); ++y )
{
for( size_t x = 0; x < cur.cols(); ++x )
{
cur( y, x ).mMass = cur( y, x ).mNewMass;
}
}
for( size_t y = 0; y < cur.rows(); ++y )
{
for( size_t x = 0; x < cur.cols(); ++x )
{
Cell& center = cur( y, x );
if( center.mType == Cell::GROUND )
{
center.mMass = center.mNewMass = 0.0f;
continue;
}
if( center.mMass > MinMass )
{
center.mType = Cell::WATER;
}
else
{
center.mType = Cell::AIR;
center.mMass = 0.0f;
}
}
}
// Remove any water that has left the map
for( size_t x = 0; x < cur.cols(); ++x )
{
cur( 0, x ).mMass = 0;
cur( cur.rows()-1, x ).mMass = 0;
}
for( size_t y = 0; y < cur.rows(); ++y )
{
cur( y, 0 ).mMass = 0;
cur( y, cur.cols()-1 ).mMass = 0;
}
}
void showState( const State& state )
{
glPolygonMode( GL_FRONT, GL_LINE );
glBegin( GL_QUADS );
glColor3ub( 0, 0, 0 );
for( size_t y = 0; y < state.rows(); ++y )
{
for( size_t x = 0; x < state.cols(); ++x )
{
glVertex2f( x+0, y+0 );
glVertex2f( x+1, y+0 );
glVertex2f( x+1, y+1 );
glVertex2f( x+0, y+1 );
}
}
glEnd();
glPolygonMode( GL_FRONT, GL_FILL );
glBegin( GL_QUADS );
for( size_t y = 0; y < state.rows(); ++y )
{
for( size_t x = 0; x < state.cols(); ++x )
{
if( state( y, x ).mType == Cell::AIR )
continue;
float height = 1.0f;
if( state( y, x ).mType == Cell::GROUND )
{
glColor3ub( 152, 118, 84 );
}
else
{
glColor3ub( 0, 135, 189 );
height = min( 1.0f, state( y, x ).mMass );
}
glVertex2f( x+0, y );
glVertex2f( x+1, y );
glVertex2f( x+1, y + height );
glVertex2f( x+0, y + height );
}
}
glEnd();
}
State state( 20, 20 );
void mouse( int button, int button_state, int x, int y )
{
float pctX = (float)x / glutGet( GLUT_WINDOW_WIDTH );
float pctY = 1.0f - ( (float)y / glutGet( GLUT_WINDOW_HEIGHT ) );
size_t cellX = pctX * state.cols();
size_t cellY = pctY * state.rows();
Cell& cur = state( cellY, cellX );
if( button_state == GLUT_UP )
return;
if( button == GLUT_LEFT_BUTTON )
{
cur.mType = ( cur.mType == Cell::GROUND ? Cell::AIR : Cell::GROUND );
cur.mMass = cur.mNewMass = 0.0f;
}
if( button == GLUT_RIGHT_BUTTON )
{
cur.mType = Cell::WATER;
cur.mMass = cur.mNewMass = 1.0f;
}
}
void display()
{
static bool firstTime = true;
if( firstTime )
{
firstTime = false;
for( size_t y = 0; y < state.rows(); ++y )
{
for( size_t x = 0; x < state.cols(); ++x )
{
state( y, x ).mType = (Cell::Type)( rand() % 3 );
state( y, x ).mMass = 1.0f;
state( y, x ).mNewMass = 1.0f;
}
}
}
glClearColor( 1, 1, 1, 1 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0, state.cols(), 0, state.rows(), -1, 1);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
stepState( state );
showState( state );
glutSwapBuffers();
}
void timer(int extra)
{
glutPostRedisplay();
glutTimerFunc(16, timer, 0);
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
glutInitWindowSize( 640, 480 );
glutCreateWindow( "Cells" );
glutDisplayFunc( display );
glutMouseFunc( mouse );
glutTimerFunc(0, timer, 0);
glutMainLoop();
return 0;
}