package electrosphere.server.terrain.generation; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * Core continent phase terrain generator */ class TectonicSimulation { //size of a parallelized chunk static final int PARALLEL_CHUNK_SIZE = 32; //number of threads for threadpool static final int THREAD_POOL_COUNT = 16; //the dimensions of the map int DIMENSION = 200; int[][] asthenosphereHeat; int[][] rockHardness; int[][] elevation; int[][] smoothedElevation; int currentElev[][]; int newElevation[][]; //currents used for pushing terrain elevation around the map Vector[][] currents; //hotspots that thrust rock up from the ocean floor List spots = new ArrayList(); int time = 0; int lifespan = 75000; Random rand; //thread pool for parallelized force calculation ThreadPoolExecutor threadPool; /** * Constructor * @param seed Seed for random */ protected TectonicSimulation(long seed){ this.rand = new Random(seed); threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(THREAD_POOL_COUNT); } /** * Sets the data width * @param newDim The dimension of the data */ protected void setDimension(int newDim){ DIMENSION = newDim; if(DIMENSION % PARALLEL_CHUNK_SIZE != 0){ //this requirement is for parallelization purposes throw new Error("DIMENSION MUST BE A MULTIPLE OF 16!"); } } /** * Sets the simulation lifespan for the Continent Phase * @param newLifespan The lifespan in units of simulation frames */ protected void setLifespan(int newLifespan){ lifespan = newLifespan; } /** * Runs the continent phase simulation. Blocks until completed */ protected void run(){ allocateData(); long lastTime = System.currentTimeMillis(); //construct convection cells prior to simulation constructConvectionCells(); //main simulation while(true){ time++; simulateHotspots(); heatToElevation(); applyVectorsToElevationParallel(); calculateSmoothedElevations(); // try { // TimeUnit.MILLISECONDS.sleep(1); // } catch (InterruptedException ex) { // } if(time % 500 == 0) { long new_Time = System.currentTimeMillis(); long time_Delta = new_Time - lastTime; lastTime = new_Time; System.out.println("Progress: " + time + "/" + lifespan + " ETA: " + (time_Delta * (lifespan - time) / 1000 / 500) + "S"); } if(time > lifespan){ break; } } //TODO: //next subphase is to find large areas without continents and place ones there //the terrain added in this next phase will be made with a more quick and dirty implementation //shutdown threadpool threadPool.shutdown(); } /** * Gets the raw terrain * @return The raw terrain */ protected int[][] getTerrain(){ return elevation; } /** * Gets the terrain smoothed * @return The terrain smoothed */ protected int[][] getTerrainSmoothed(){ return smoothedElevation; } /** * Allocates all arrays generated based on the dimension provided */ private void allocateData(){ asthenosphereHeat = new int[DIMENSION][DIMENSION]; elevation = new int[DIMENSION][DIMENSION]; smoothedElevation = new int[DIMENSION][DIMENSION]; newElevation = new int[DIMENSION][DIMENSION]; currents = new Vector[DIMENSION][DIMENSION]; currentElev = new int[DIMENSION][DIMENSION]; for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ currents[x][y] = new Vector(); } } } /** * If the asthenosphere is sufficiently hot, increases elevation of position */ private void heatToElevation(){ for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ if(asthenosphereHeat[x][y] > 25){ if(electrosphere.server.terrain.generation.Utilities.random_Integer(1, 10, rand) == 10){ elevation[x][y] = elevation[x][y] + 1; if(elevation[x][y] > 100){ elevation[x][y] = 100; } } } } } } /** * Constructs convection cells in the force vector field */ private void constructConvectionCells(){ //controls whether the cell rotates clockwise or couterclockwise boolean isCellType1 = false; //one fourth of the width of the data set int fourth = DIMENSION / 4; for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ //the current position RELATIVE to the center point of the current convection cell center int normalizedX = x; int normalizedY = y; //determine relative position and whether convection cell type one or two if(y < fourth || (y < fourth * 3 && y > (fourth * 2) - 1)){ isCellType1 = true; if(normalizedY > fourth){ normalizedY = normalizedY - fourth * 2; } } else { isCellType1 = false; if(normalizedY > fourth * 2 + 1){ normalizedY = normalizedY - fourth * 3; } else { normalizedY = normalizedY - fourth; } } while(normalizedX > fourth){ normalizedX = normalizedX - fourth; } if(normalizedX < 0){ normalizedX = 0; } if(normalizedY < 0){ normalizedY = 0; } //one eighth of the width of the data set int eigth = fourth / 2; //Moves the relative position to be in its correct eighth normalizedY = normalizedY - eigth; normalizedX = normalizedX - eigth; //calculates the distance from convection cell center to the relative position float magnitude = (float)Math.sqrt(Math.pow(normalizedY, 2) + Math.pow(normalizedX, 2)); //If the distance is small enough we stretch it along the X axis ... ? if(magnitude < fourth / 10){ normalizedX = normalizedX + fourth / 10; magnitude = (float)Math.sqrt(Math.pow(normalizedY, 2) + Math.pow(normalizedX, 2)); } //calculates the angle of the point relative to convection cell center double offsetAngle = Math.atan2(normalizedY / magnitude, normalizedX / magnitude); if(offsetAngle < 0){ offsetAngle = offsetAngle + Math.PI * 2; } //rotate based on cell type if(isCellType1){ offsetAngle = offsetAngle + Math.PI / 2; } else { offsetAngle = offsetAngle - Math.PI / 2; } //normalize while(offsetAngle > Math.PI * 2){ offsetAngle = offsetAngle - Math.PI * 2; } while(offsetAngle < 0){ offsetAngle = offsetAngle + Math.PI * 2; } //Lastly, actually set the force vector currents[x][y].x = (int)(99 * Math.cos(offsetAngle)); currents[x][y].y = (int)(99 * Math.sin(offsetAngle)); } } } /** * Moves the terrain around based on the vector field */ private void applyVectorsToElevation(){ //allocate new elevation array for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ newElevation[x][y] = 0; currentElev[x][y] = elevation[x][y]; } } //transfer terrain for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ boolean transfer = false; if (Utilities.random_Integer(1, 50, rand) == 1) { transfer = true; } int transfer_goal; if(Utilities.random_Integer(1, 2, rand)==1){ transfer_goal = Utilities.random_Integer(20, 60, rand); } else { transfer_goal = Utilities.random_Integer(0, 60, rand); } if(Utilities.random_Integer(1, 2, rand)==1){ if (currents[x][y].x >= 0) { if (transfer) { if (x + 1 < DIMENSION) { while(newElevation[x + 1][y] + currentElev[x + 1][y] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x + 1][y]++; currentElev[x][y]--; } } else { } } } else { if (transfer) { if (x - 1 >= 0) { while(newElevation[x - 1][y] + currentElev[x - 1][y] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x - 1][y]++; currentElev[x][y]--; } } else { } } } } else { if (currents[x][y].y >= 0) { if (transfer) { if (y + 1 < DIMENSION) { // V REPLACE THIS WITH GOAL while(newElevation[x][y + 1] + currentElev[x][y + 1] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x][y + 1]++; currentElev[x][y]--; } } else { } } } else { if (transfer) { if (y - 1 >= 0) { while(newElevation[x][y - 1] + currentElev[x][y - 1] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x][y - 1]++; currentElev[x][y]--; } } else { } } } } } } //move data from temporary array to main array for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ newElevation[x][y] = newElevation[x][y] + currentElev[x][y]; while(newElevation[x][y] > 99){ newElevation[x][y] = 99; } elevation[x][y] = newElevation[x][y]; } } } /** * Applies a smooth kernel to the terrain data */ private void calculateSmoothedElevations(){ int[][] buffer = new int[DIMENSION][DIMENSION]; for(int x = 1; x < DIMENSION - 2; x++){ for(int y = 1; y < DIMENSION - 2; y++){ buffer[x][y] = elevation[x][y] * 4 * elevation[x+1][y] * 2 + elevation[x-1][y] * 2 + elevation[x][y+1] * 2 + elevation[x][y-1] * 2 + elevation[x+1][y+1] + elevation[x+1][y-1] + elevation[x-1][y+1] + elevation[x-1][y-1]; buffer[x][y] = (int)(buffer[x][y] / 16.0); while(buffer[x][y] > 100){ buffer[x][y] = buffer[x][y]/2; } smoothedElevation[x][y] = buffer[x][y]; } } for(int x = 1; x < DIMENSION - 2; x++){ for(int y = 1; y < DIMENSION - 2; y++){ buffer[x][y] = smoothedElevation[x][y] * 4 * smoothedElevation[x+1][y] * 2 + smoothedElevation[x-1][y] * 2 + smoothedElevation[x][y+1] * 2 + smoothedElevation[x][y-1] * 2 + smoothedElevation[x+1][y+1] + smoothedElevation[x+1][y-1] + smoothedElevation[x-1][y+1] + smoothedElevation[x-1][y-1]; buffer[x][y] = (int)(buffer[x][y] / 16.0); while(buffer[x][y] > 100){ buffer[x][y] = buffer[x][y]/2; } smoothedElevation[x][y] = buffer[x][y]; } } } /** * simulates the hotspot logic */ private void simulateHotspots(){ if(spots.size() >= 1){ List to_Remove = new ArrayList(); Iterator spot_Iterator = spots.iterator(); while(spot_Iterator.hasNext()){ Hotspot current_Spot = spot_Iterator.next(); if(current_Spot.life_current >= current_Spot.life_max){ to_Remove.add(current_Spot); } } spot_Iterator = to_Remove.iterator(); while(spot_Iterator.hasNext()){ Hotspot current_Spot = spot_Iterator.next(); spots.remove(current_Spot); } } if(spots.size() < 5){ spots.add(new Hotspot( Utilities.random_Integer(0, DIMENSION - 1, rand), Utilities.random_Integer(0, DIMENSION - 1, rand), Utilities.random_Integer(6000, 10000, rand), Utilities.random_Integer(3, 5, rand), this)); } for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ asthenosphereHeat[x][y] = 0; } } if(spots.size() >= 1){ Iterator spot_Iterator = spots.iterator(); while(spot_Iterator.hasNext()){ Hotspot current_Spot = spot_Iterator.next(); current_Spot.simulate(); } } } /** * Fills in the gaps not covered by the main chunks */ private void applyVectorToElevationGaps(){ for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ if(x % 16 == 0 || x % 16 == 1 || y % 16 == 0 || y % 16 == 1){ boolean transfer = false; if (Utilities.random_Integer(1, 50, rand) == 1) { transfer = true; } int transfer_goal; if(Utilities.random_Integer(1, 2, rand)==1){ transfer_goal = Utilities.random_Integer(20, 60, rand); } else { transfer_goal = Utilities.random_Integer(0, 60, rand); } if(Utilities.random_Integer(1, 2, rand)==1){ if (currents[x][y].x >= 0) { if (transfer) { if (x + 1 < DIMENSION) { while(newElevation[x + 1][y] + currentElev[x + 1][y] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x + 1][y]++; currentElev[x][y]--; } } else { } } } else { if (transfer) { if (x - 1 >= 0) { while(newElevation[x - 1][y] + currentElev[x - 1][y] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x - 1][y]++; currentElev[x][y]--; } } else { } } } } else { if (currents[x][y].y >= 0) { if (transfer) { if (y + 1 < DIMENSION) { // V REPLACE THIS WITH GOAL while(newElevation[x][y + 1] + currentElev[x][y + 1] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x][y + 1]++; currentElev[x][y]--; } } else { } } } else { if (transfer) { if (y - 1 >= 0) { while(newElevation[x][y - 1] + currentElev[x][y - 1] < 99 && currentElev[x][y] > transfer_goal){ newElevation[x][y - 1]++; currentElev[x][y]--; } } else { } } } } } } } } //latch for synchronizing parallel force vector computation CountDownLatch latch; /** * Moves the terrain around based on the vector field, parallelized */ private void applyVectorsToElevationParallel(){ //allocate new elevation array for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ newElevation[x][y] = 0; currentElev[x][y] = elevation[x][y]; } } latch = new CountDownLatch(DIMENSION / PARALLEL_CHUNK_SIZE * DIMENSION / PARALLEL_CHUNK_SIZE); //transfer terrain in main chunks for(int x = 0; x < DIMENSION / PARALLEL_CHUNK_SIZE; x++){ for(int y = 0; y < DIMENSION / PARALLEL_CHUNK_SIZE; y++){ threadPool.execute(new TerrainMovementWorker( DIMENSION, x, y, new Random(rand.nextLong()), currents, newElevation, currentElev, latch )); } } //await main chunks try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } //fill in gaps applyVectorToElevationGaps(); //move data from temporary array to main array for(int x = 0; x < DIMENSION; x++){ for(int y = 0; y < DIMENSION; y++){ newElevation[x][y] = newElevation[x][y] + currentElev[x][y]; while(newElevation[x][y] > 99){ newElevation[x][y] = 99; } elevation[x][y] = newElevation[x][y]; } } } /** * A worker thread for simulating terrain moving due to force vector field */ static class TerrainMovementWorker implements Runnable { //size of data map int continentPhaseDimension; //The offsets into the data array int offsetX; int offsetY; //random Random rand; //force vector field Vector[][] currents; //new elevation map to fill in int[][] newElevation; //reference elevation map to pull from int[][] referenceElevation; //latch to resynchronize threads CountDownLatch latch; protected TerrainMovementWorker( int continentPhaseDimension, int offsetX, int offsetY, Random rand, Vector[][] currents, int[][] newElevation, int[][] referenceElevation, CountDownLatch latch ){ this.continentPhaseDimension = continentPhaseDimension; this.offsetX = offsetX; this.offsetY = offsetY; this.rand = rand; this.currents = currents; this.newElevation = newElevation; this.referenceElevation = referenceElevation; this.latch = latch; } /** * Runs the terrain movement simulation for this worker */ @Override public void run() { for(int x = 0; x < PARALLEL_CHUNK_SIZE; x++){ for(int y = 0; y < PARALLEL_CHUNK_SIZE; y++){ if(x % PARALLEL_CHUNK_SIZE != 0 && x % PARALLEL_CHUNK_SIZE != 1 && y % PARALLEL_CHUNK_SIZE != 0 && y % PARALLEL_CHUNK_SIZE != 1){ //current absolute position in data arrays int currentX = x + offsetX * PARALLEL_CHUNK_SIZE; int currentY = y + offsetY * PARALLEL_CHUNK_SIZE; //roll whether should transfer terrain or not boolean transfer = false; if (Utilities.random_Integer(1, 50, rand) == 1) { transfer = true; } //sets the goal of how much elevation to transfer to neighbors int transferGoal; if(Utilities.random_Integer(1, 2, rand)==1){ transferGoal = Utilities.random_Integer(20, 60, rand); } else { transferGoal = Utilities.random_Integer(0, 60, rand); } //roll whether to transfer horizontally or vertically if(Utilities.random_Integer(1, 2, rand)==1){ //transfers horizontally if (currents[currentX][currentY].x >= 0) { if (transfer) { if (currentX + 1 < continentPhaseDimension) { while( newElevation[currentX + 1][currentY] + referenceElevation[currentX + 1][currentY] < 99 && referenceElevation[currentX][currentY] > transferGoal){ newElevation[currentX + 1][currentY]++; referenceElevation[currentX][currentY]--; } } } } else { if (transfer) { if (currentX - 1 >= 0) { while( newElevation[currentX - 1][currentY] + referenceElevation[currentX - 1][currentY] < 99 && referenceElevation[currentX][currentY] > transferGoal){ newElevation[currentX - 1][currentY]++; referenceElevation[currentX][currentY]--; } } } } } else { //transfer vertically if (currents[currentX][currentY].y >= 0) { if (transfer) { if (currentY + 1 < continentPhaseDimension) { // V REPLACE THIS WITH GOAL while( newElevation[currentX][currentY + 1] + referenceElevation[currentX][currentY + 1] < 99 && referenceElevation[currentX][currentY] > transferGoal){ newElevation[currentX][currentY + 1]++; referenceElevation[currentX][currentY]--; } } } } else { if (transfer) { if (currentY - 1 >= 0) { while( newElevation[currentX][currentY - 1] + referenceElevation[currentX][currentY - 1] < 99 && referenceElevation[currentX][currentY] > transferGoal){ newElevation[currentX][currentY - 1]++; referenceElevation[currentX][currentY]--; } } } } } } } } latch.countDown(); } } }