//An attempt to speed up the simulation by keeping count of how many active tiles //each part of the world has, and skipping those that have none. It didn't work so well. //Not very optimized. I still left it in as it might be of iterest to someone. class SimulationWorldWithGrid extends SimulationWorld { final int defaultGridSize = 24; ActivitySquare[][] activityGrid = null; int activityGridWidth; int activityGridHeight; int gridSquareSize; //Note : world dimensions must be divisible by grid size. The class //will automatically expand the world if necessary. //Initializes the world and the activity grid. public SimulationWorldWithGrid( int the_width, int the_height, int squareSize ){ super(); this.initMap( the_width, the_height, squareSize ); } public SimulationWorldWithGrid( int the_width, int the_height ){ super(); this.initMap( the_width, the_height, defaultGridSize ); } //Advance the simulation by one step using the activity grid void stepSimulation(){ simulationFrameCount++; activeTiles = 0; update_mask = byte ( ( simulationFrameCount & (long)1 ) << 7 ); byte updated_water = byte(water | update_mask); byte unupdated_water = byte( updated_water ^ update_mask ); byte updated_stillwater = byte(water | update_mask | staticTile); int horizontal_dir = (update_mask != 0) ? 1 : -1; byte thisTile; ActivitySquare thisSquare; //Look over the activity grid, bottom-up for ( int gridY = 1; gridY <= activityGridWidth; gridY++ ){ for ( int gridX = 1; gridX <= activityGridWidth; gridX++ ){ thisSquare = activityGrid[gridX][gridY]; //Skip squares with no active tiles/cells if ( thisSquare.getActiveCells() <= 0 ) continue; //Loop over all the tiles in this square for ( int y = thisSquare.y1; y <= thisSquare.y2; y++ ){ int y_minus_1 = y - 1; //'tis optimization for ( int x = thisSquare.x1; x <= thisSquare.x2; x++ ){ //Skip static blocks if ( isStatic(x, y) ) continue; //Skip water tiles that have already been udpated if ( tiles[x][y] == updated_water ) continue; activeTiles++; thisTile = byte(tiles[x][y] & clearUpdateMask); switch ( thisTile ){ case water: //Water always flows down, if it can. Otherwise, it may flow diagonally down if //both the direct and diagonal neighbours are empty. if ( tiles[x][y_minus_1] == air ){ tiles[x][y_minus_1] = updated_water; getSquare(x, y_minus_1).incActiveCells(); tiles[x][y] = air; thisSquare.decActiveCells(); this.activateNeighbours( x, y ); } else if ( (tiles[x - horizontal_dir][y_minus_1] == air) && ( (tiles[x - horizontal_dir][y] & clearAllMask) != water) ){ tiles[x - horizontal_dir][y_minus_1] = updated_water; getSquare(x - horizontal_dir, y_minus_1).incActiveCells(); tiles[x][y] = air; thisSquare.decActiveCells(); this.activateNeighbours( x, y ); } else if ( (tiles[x + horizontal_dir][y_minus_1] == air) && ( (tiles[x + horizontal_dir][y] & clearAllMask) != water) ){ tiles[x + horizontal_dir][y_minus_1] = updated_water; getSquare(x + horizontal_dir, y_minus_1).incActiveCells(); tiles[x][y] = air; thisSquare.decActiveCells(); this.activateNeighbours( x, y ); } else { //Deactivate this water tile tiles[x][y] = updated_stillwater; thisSquare.decActiveCells(); } break; case faucet: //Faucets generate water below them if ( tiles[x][y_minus_1] == air ) { tiles[x][y_minus_1] = updated_water; getSquare(x, y_minus_1).incActiveCells(); } break; default: break; } //switch }//inner loop over thisSquare-x }//inner loop over thisSquare.y }//loop over grid y }//loop over grid y //Remove anything that might have left the world clearBorders(); } void initMap( int the_width, int the_height ){ this.initMap( the_width, the_height, defaultGridSize ); } void initMap( int the_width, int the_height, int gridSize ){ //Map dimensions must be divisible by grid dimensions. if ( the_width % gridSize != 0 ){ the_width = the_width + (gridSize - (the_width % gridSize)); } if ( the_height % gridSize != 0 ){ the_height = the_height + (gridSize - (the_height % gridSize)); } //Set up the map super.initMap(the_height, the_width); //Set up the activity grid initActivityGrid(gridSize); } //Initialize the activity grid. squareSize is the length of one grid square's border. void initActivityGrid( int squareSize ){ gridSquareSize = squareSize; activityGridWidth = ceil( float(world_width) / gridSquareSize); activityGridHeight = ceil(float(world_height) / gridSquareSize); //println("Grid width : " + activityGridWidth + ", grid height : " + activityGridHeight); activityGrid = new ActivitySquare[activityGridWidth + 2][activityGridHeight + 2]; //Create grid squares for( int x = 1; x <= activityGridWidth; x++ ){ for( int y = 1; y <= activityGridHeight; y++ ){ activityGrid[x][y] = new ActivitySquare( constrain((x-1)*gridSquareSize+1, 1, world_width), //X coordinate of the bottom-left corner constrain((y-1)*gridSquareSize+1, 1, world_height), //Y coordinate of the bottom-left corner constrain(x*gridSquareSize, 1, world_width), //Top-right X constrain(y*gridSquareSize, 1, world_height) //Top-right Y ); } } //Create "null" squares around the map so that we don't have to //special-case everything. for( int x = 0; x <= activityGridWidth + 1; x++ ){ activityGrid[x][0] = new NullActivitySquare( constrain( (x-1)*gridSquareSize + 1, 0, world_width + 1), 0, constrain( x*gridSquareSize, 0, world_width + 1), 0 ); activityGrid[x][activityGridHeight+1] = new NullActivitySquare( constrain( (x-1)*gridSquareSize + 1, 0, world_width + 1), world_height + 1, constrain( x*gridSquareSize, 0, world_width + 1), world_height + 1 ); } for( int y = 1; y <= activityGridHeight; y++ ){ activityGrid[0][y] = new NullActivitySquare( 0, constrain( (y-1)*gridSquareSize+1, 1, world_height), 0, constrain( y*gridSquareSize, 1, world_height) ); activityGrid[activityGridWidth+1][y] = new NullActivitySquare( world_width + 1, constrain( (y-1)*gridSquareSize+1, 1, world_height), world_width + 1, constrain( y*gridSquareSize, 1, world_height) ); } recalcActivityGrid(); } //Recalculate the number of active tiles in each grid square void recalcActivityGrid(){ if ( this.activityGrid == null ) return; for( int x = 1; x <= activityGridWidth; x++ ){ for( int y = 1; y <= activityGridHeight; y++ ){ ActivitySquare thisSquare = activityGrid[x][y]; thisSquare.activeCells = 0; for ( int wx = thisSquare.x1; wx <= thisSquare.x2; wx++ ){ for ( int wy = thisSquare.y1; wy <= thisSquare.y2; wy++ ){ if ( !isStatic(wx, wy) ) thisSquare.incActiveCells(); } } } } } //Reset the world map by filling it with air (empty squares) public void clear(){ super.clear(); recalcActivityGrid(); } //Fill the world map with random squares public void randomize(){ super.randomize(); recalcActivityGrid(); } //Create the worst case scenario - fill the entire map with active tiles. public void worstCaseWorld(){ super.worstCaseWorld(); recalcActivityGrid(); } public final ActivitySquare getSquare( int x, int y ){ //Map the coordinates to the activity grid x = ceil(float(x) / gridSquareSize); y = ceil(float(y) / gridSquareSize); //Constrain them to the actual grid dimensions x = constrain(x, 0, activityGridWidth + 1); y = constrain(y, 0, activityGridHeight + 1); //Return the matching square return activityGrid[x][y]; } //Activate the neighbouring water tiles of the specified tile void activateNeighbours( int x, int y ){ if ( ( tiles[x][y + 1] & clearUpdateMask ) == stillwater ){ tiles[x][y + 1] = byte( tiles[x][y + 1] & (~staticTile) ); getSquare(x, y + 1).incActiveCells(); } if ( ( tiles[x - 1][y + 1] & clearUpdateMask ) == stillwater ){ tiles[x - 1][y + 1] = byte( tiles[x - 1][y + 1] & (~staticTile) ); getSquare(x - 1, y + 1).incActiveCells(); } if ( ( tiles[x + 1][y + 1] & clearUpdateMask ) == stillwater ){ tiles[x + 1][y + 1] = byte( tiles[x + 1][y + 1] & (~staticTile) ); getSquare(x + 1, y + 1).incActiveCells(); } } //Set a tile to a given value and update the activity grid public void setTile( int x, int y, byte newTile ){ byte oldTile = tiles[x][y]; tiles[x][y] = newTile; //Update the activity grid ActivitySquare thisSquare = getSquare(x, y); if ( isStatic(newTile) != isStatic(oldTile) ){ if ( isStatic(newTile) ) { thisSquare.decActiveCells(); } else { thisSquare.incActiveCells(); } } //Possibly activate the neighbours if this is an air tile if ( (newTile == air) && (oldTile != air) ){ activateNeighbours(x, y); } } public void display( int minx, int miny, int maxx, int maxy ){ super.display( minx, miny, maxx, maxy ); int minGridX = floor(minx / gridSquareSize); int minGridY = floor(miny / gridSquareSize); int maxGridX = ceil(maxx / gridSquareSize) + 1; int maxGridY = ceil(maxy / gridSquareSize) + 1; drawActivityGrid( minGridX, minGridY, maxGridX, maxGridY ); } public void drawActivityGrid( int minx, int miny, int maxx, int maxy ){ minx = constrain(minx, 0, activityGridWidth + 1); maxx = constrain(maxx, minx, activityGridWidth + 1); miny = constrain(miny, 0, activityGridHeight + 1); maxy = constrain(maxy, miny, activityGridHeight + 1); textAlign(CENTER, CENTER); //Draw all visible squares stroke(100, 100, 100); for( int x = minx; x <= maxx; x++ ){ for( int y = miny; y <= maxy; y++ ){ ActivitySquare thisSquare = activityGrid[x][y]; fill( 180, 255, 100, 130); rect( (thisSquare.x1 - 1)*TILE_SIZE, (world_height - thisSquare.y2)*TILE_SIZE, (thisSquare.x2 - thisSquare.x1 + 1)*TILE_SIZE, (thisSquare.y2 - thisSquare.y1 + 1)*TILE_SIZE ); fill( 0, 0, 0); text( str(thisSquare.getActiveCells()) + " [" + str(thisSquare.x1) + ", " + str(thisSquare.y1) + " - " + str(thisSquare.x2) + ", " + str(thisSquare.y2) + "]", (thisSquare.x1 + (thisSquare.x2 - thisSquare.x1)/2)*TILE_SIZE, (world_height - (thisSquare.y1 + (thisSquare.y2 - thisSquare.y1)/2) )*TILE_SIZE ); } } } }