diff --git a/assets/Data/foliage.json b/assets/Data/foliage.json index 402d4c9b..fb0129fc 100644 --- a/assets/Data/foliage.json +++ b/assets/Data/foliage.json @@ -17,6 +17,13 @@ } ], "modelPath" : "Models/falloak1.fbx" + }, + { + "name" : "Green Grass", + "tokens" : [ + "AMBIENT" + ], + "modelPath" : "Models/falloak1.fbx" } ] diff --git a/assets/Data/voxelTypes.json b/assets/Data/voxelTypes.json new file mode 100644 index 00000000..04c10a1e --- /dev/null +++ b/assets/Data/voxelTypes.json @@ -0,0 +1,19 @@ +{ + "types" : [ + { + "id" : 0, + "name" : "air" + }, + { + "id" : 1, + "name" : "dirt" + }, + { + "id" : 2, + "name" : "grass", + "ambientFoliage" : [ + "Grass" + ] + } + ] +} \ No newline at end of file diff --git a/docs/TerrainEditing.txt b/docs/TerrainEditing.txt new file mode 100644 index 00000000..71b5a98e --- /dev/null +++ b/docs/TerrainEditing.txt @@ -0,0 +1,20 @@ +electrosphere.client.terrain.editing.TerrainEditing + - Client static interface for editing terrain + - The idea is that this provides functions you can call anywhere from client side to trigger a request to perform a terrain edit + +Which leads to + +electrosphere.server.terrain.editing.TerrainEditing + - Server utility functions for actually editing terrain + - Does the calculations of a real coordinate + radius to determine which cells to edit and how much + - This then updates the server terrain manager with edits via the VoxelCellManager interface + +VoxelCellManager interface + - Provides an interface on top of DataCellManager to update terrain functions + - Makes functions that must be implemented on data cell manager so implementation specific to cell manager + - For GriddedDataCellManager, this uses a lock and updates values + - As values are updated, they should be send 1-by-1 over the network via individual update packets to the client + +When client receives voxel update packet in ClientTerrainManager, it triggers the cell to update that specific drawcell +This should also update all ambient foliage + diff --git a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java index a9fa819a..dca760ba 100644 --- a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java +++ b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java @@ -13,7 +13,9 @@ import org.joml.Matrix4f; import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3f; +import org.joml.Vector3i; +import electrosphere.client.terrain.cache.ChunkData; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; @@ -26,7 +28,7 @@ import electrosphere.renderer.buffer.ShaderAttribute; import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes; /** - * Manages foliage (grass, small plants, etc) that should be shown, typically instanced + * Manages ambient foliage (grass, small plants, etc) that should be shown, typically instanced */ public class ClientFoliageManager { @@ -50,6 +52,8 @@ public class ClientFoliageManager { //FoliageCells that are active and have foliage that is being drawn Set activeCells = new HashSet(); + //map of position-based key to foliage cell at the position + Map locationCellMap = new HashMap(); //The maximum distance a cell can be away from the player before being destroyed static final float CELL_DISTANCE_MAX = 25f; //The maximum number of foliage cells @@ -172,6 +176,16 @@ public class ClientFoliageManager { return new Quaterniond().rotationX(-Math.PI / 2.0f).rotateLocalY(Math.PI * placementRandomizer.nextFloat()); } + /** + * Gets a key for a foliage cell in the localCellMap + * @param worldPosition The world position of the cell + * @param voxelPosition The voxel position of the cell + * @return The key for the cell + */ + private String getFoliageCellKey(Vector3i worldPosition, Vector3i voxelPosition){ + return worldPosition.x + "_" + worldPosition.y + "_" + worldPosition.z + "_" + voxelPosition.x + "_" + voxelPosition.y + "_" + voxelPosition.z; + } + /** * Makes an already created entity a drawable, instanced entity (client only) by backing it with an InstancedActor @@ -188,6 +202,62 @@ public class ClientFoliageManager { Globals.clientScene.registerEntityToTag(entity, EntityTags.DRAW_INSTANCED); } + /** + * Evaluates a chunk to see where foliage cells should be created or updated + * @param worldPos The world position of the chunk + */ + public void evaluateChunk(Vector3i worldPos){ + ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos); + for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){ + //can't go to very top 'cause otherwise there would be no room to put grass + for(int y = 0; y < ChunkData.CHUNK_SIZE - 1; y++){ + for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){ + String key = getFoliageCellKey(worldPos, new Vector3i(x,y,z)); + if(locationCellMap.get(key) != null){ + //destroy if there's no longer ground or + //if the cell above is now occupied or + //if the lower cell is no longer supporting foliage + if(data.getWeight(new Vector3i(x,y,z)) <= 0 || + data.getWeight(new Vector3i(x,y + 1,z)) > 0 || + !typeSupportsFoliage(data.getType(new Vector3i(x,y,z)))){ + //TODO: destroy + } else { + //TODO: evaluate if foliage is placed well + } + } else { + //create if current is ground and above is air + if( + data.getWeight(new Vector3i(x,y,z)) > 0 && + data.getWeight(new Vector3i(x,y + 1,z)) < 0 && + typeSupportsFoliage(data.getType(new Vector3i(x,y,z))) + ){ + //create foliage cell + } + } + } + } + } + //evaluate top cells if chunk above this one exists + ChunkData aboveData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0)); + if(aboveData != null){ + for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){ + for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){ + if(data.getWeight(new Vector3i(x,ChunkData.CHUNK_SIZE - 1,z)) > 0 && aboveData.getWeight(new Vector3i(x,0,z)) < 0){ + + } + } + } + } + } + + /** + * Gets whether the voxel type supports foliage or not + * @param type + * @return + */ + private boolean typeSupportsFoliage(int type){ + return Globals.gameConfigCurrent.getVoxelData().getTypeFromId(type).getAmbientFoliage() != null; + } /** * Evaluate all foliage cells to see if any should be deconstructed and any new ones should be created @@ -196,7 +266,7 @@ public class ClientFoliageManager { Vector3d playerPosition = EntityUtils.getPosition(Globals.playerEntity); for(FoliageCell activeCell : activeCells){ //if cell is outside of range of player, disable cell - if(activeCell.position.distance(playerPosition) > CELL_DISTANCE_MAX){ + if(Globals.clientWorldData.convertWorldToRealSpace(activeCell.worldPosition).distance(playerPosition) > CELL_DISTANCE_MAX){ //TODO: destroy cell } } diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java index 12e3366c..20e2068e 100644 --- a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java +++ b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java @@ -3,6 +3,7 @@ package electrosphere.client.foliagemanager; import java.util.Set; import org.joml.Vector3d; +import org.joml.Vector3i; import electrosphere.entity.Entity; @@ -10,9 +11,12 @@ import electrosphere.entity.Entity; * Contains a set of foliage entities and groups them together. */ public class FoliageCell { - //position of the foliage cell - Vector3d position; + //position of the foliage cell in world coordinates + protected Vector3i worldPosition; + //position of the foliage cell in local coordinates + protected Vector3i localPosition; //constituent entities - Set containedEntities; + protected Set containedEntities; + } diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java index 64dd5444..e15e4fb9 100644 --- a/src/main/java/electrosphere/client/scene/ClientWorldData.java +++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java @@ -102,6 +102,19 @@ public class ClientWorldData { ); } + /** + * Converts a world space vector to a real space vector + * @param position The world space vector + * @return The real space vector + */ + public Vector3d convertWorldToRealSpace(Vector3i position){ + return new Vector3d( + convertWorldToReal(position.x), + convertWorldToReal(position.y), + convertWorldToReal(position.z) + ); + } + public Vector3i convertRealToVoxelSpace(Vector3d position){ return new Vector3i( (int)Math.floor(position.x - convertChunkToRealSpace(convertRealToChunkSpace(position.x))), diff --git a/src/main/java/electrosphere/client/sim/ClientSimulation.java b/src/main/java/electrosphere/client/sim/ClientSimulation.java index a5108ec8..18c10927 100644 --- a/src/main/java/electrosphere/client/sim/ClientSimulation.java +++ b/src/main/java/electrosphere/client/sim/ClientSimulation.java @@ -57,9 +57,7 @@ public class ClientSimulation { } } //update foliage - if(Globals.clientFoliageManager != null){ - Globals.clientFoliageManager.update(); - } + Globals.clientFoliageManager.update(); //tally collidables and offset position accordingly // for(Entity currentCollidable : Globals.entityManager.getEntitiesWithTag(EntityTags.COLLIDABLE)){ // CollidableTree tree = CollidableTree.getCollidableTree(currentCollidable); diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java index 449a5d6f..fe63818a 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java @@ -173,6 +173,8 @@ public class DrawCellManager { drawable.add(targetKey); //make drawable entity keyCellMap.get(targetKey).generateDrawableEntity(); + //evaluate for foliage + Globals.clientFoliageManager.evaluateChunk(worldPos); } } } @@ -204,6 +206,8 @@ public class DrawCellManager { // } keyCellMap.get(targetKey).destroy(); keyCellMap.get(targetKey).generateDrawableEntity(); + //evaluate for foliage + Globals.clientFoliageManager.evaluateChunk(worldPos); } drawable.add(targetKey); } diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index a6694317..ce1854db 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -8,6 +8,8 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; +import org.joml.Vector3i; + import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.cache.ClientTerrainCache; @@ -127,9 +129,25 @@ public class ClientTerrainManager { Globals.drawCellManager.markUpdateable(worldX, worldY, worldZ); } + /** + * Gets the chunk data at a given world position + * @param worldX The x component of the world coordinate + * @param worldY The y component of the world coordinate + * @param worldZ The z component of the world coordinate + * @return The chunk data if it exists, otherwise null + */ public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ); } + + /** + * Gets the chunk data at a given world position + * @param worldPos The world position as a joml vector + * @return The chunk data if it exists, otherwise null + */ + public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos){ + return terrainCache.getSubChunkDataAtPoint(worldPos.x, worldPos.y, worldPos.z); + } diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 545ef92d..877cd16d 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -378,12 +378,9 @@ public class Globals { clientHitboxManager = new HitboxManager(); //ai manager aiManager = new AIManager(); - //data cell manager + //realm & data cell manager realmManager = new RealmManager(); - // dataCellManager = new DataCellManager(); - // griddedDataCellManager = new GriddedDataCellManager(); entityDataCellMapper = new EntityDataCellMapper(); - // dataCellLocationResolver = new DataCellLocationResolver(); //nav mesh manager navMeshManager = new NavMeshManager(); //terrain diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index 5da5e629..2961d586 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -136,7 +136,7 @@ public class Main { //debug: create terrain/world viewer - // TerrainViewer.runViewer(); + TerrainViewer.runViewer(); //create the drawing context if(Globals.RUN_CLIENT && !Globals.HEADLESS){ diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index 4c69959c..1e82a361 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -80,6 +80,8 @@ public class ClientLoading { Globals.controlHandler.setHandlerState(ControlHandler.ControlsState.NO_INPUT); //initialize the "real" objects simulation initClientSimulation(); + //init foliage manager + initFoliageManager(); //initialize the cell manager (client) initDrawCellManager(); //initialize the basic graphical entities of the world (skybox, camera) @@ -90,8 +92,6 @@ public class ClientLoading { setSimulationsToReady(); //init culling manager and other graphics-focused non-simulation items initEntityCullingManager(); - //init foliage manager - initFoliageManager(); //hide cursor Globals.controlHandler.hideMouse(); //make loading window disappear diff --git a/src/main/java/electrosphere/game/data/Config.java b/src/main/java/electrosphere/game/data/Config.java index 18ac5272..04d16b6b 100644 --- a/src/main/java/electrosphere/game/data/Config.java +++ b/src/main/java/electrosphere/game/data/Config.java @@ -14,6 +14,7 @@ import electrosphere.game.data.object.type.model.ObjectTypeLoader; import electrosphere.game.data.object.type.model.ObjectTypeMap; import electrosphere.game.data.projectile.ProjectileTypeHolder; import electrosphere.game.data.structure.type.model.StructureTypeMap; +import electrosphere.game.data.voxel.VoxelData; import electrosphere.game.server.race.model.RaceMap; import electrosphere.game.server.symbolism.model.SymbolMap; import electrosphere.util.FileUtils; @@ -32,6 +33,8 @@ public class Config { SymbolMap symbolMap; RaceMap raceMap; ProjectileTypeHolder projectileTypeHolder; + //data about every voxel type + VoxelData voxelData; public static Config loadDefaultConfig(){ Config config = new Config(); @@ -42,6 +45,7 @@ public class Config { config.objectTypeLoader = loadObjectTypes("Data/objects.json"); config.symbolMap = FileUtils.loadObjectFromAssetPath("Data/symbolism.json", SymbolMap.class); config.raceMap = FileUtils.loadObjectFromAssetPath("Data/races.json", RaceMap.class); + config.voxelData = FileUtils.loadObjectFromAssetPath("Data/voxelTypes.json", VoxelData.class); config.projectileTypeHolder = FileUtils.loadObjectFromAssetPath("Data/projectile.json", ProjectileTypeHolder.class); config.projectileTypeHolder.init(); return config; @@ -139,5 +143,9 @@ public class Config { public ProjectileTypeHolder getProjectileMap(){ return projectileTypeHolder; } + + public VoxelData getVoxelData(){ + return voxelData; + } } diff --git a/src/main/java/electrosphere/game/data/foliage/type/model/FoliageTypeMap.java b/src/main/java/electrosphere/game/data/foliage/type/model/FoliageTypeMap.java index 481b2b13..e75839c0 100644 --- a/src/main/java/electrosphere/game/data/foliage/type/model/FoliageTypeMap.java +++ b/src/main/java/electrosphere/game/data/foliage/type/model/FoliageTypeMap.java @@ -4,17 +4,26 @@ import electrosphere.game.data.foliage.type.FoliageType; import java.util.List; /** - * - * @author amaterasu + * The map of all types of foliage in the game */ public class FoliageTypeMap { + //The list of all foliage types List foliageList; + /** + * Gets the list of all foliage types + * @return The list of all foliage types + */ public List getFoliageList() { return foliageList; } + /** + * Gets a foliage type by its name + * @param name The name of the foliage type + * @return The type object + */ public FoliageType getFoliage(String name){ for(FoliageType foliage : foliageList){ if(foliage.getName().matches(name)){ diff --git a/src/main/java/electrosphere/game/data/voxel/VoxelData.java b/src/main/java/electrosphere/game/data/voxel/VoxelData.java new file mode 100644 index 00000000..d3fe8af2 --- /dev/null +++ b/src/main/java/electrosphere/game/data/voxel/VoxelData.java @@ -0,0 +1,47 @@ +package electrosphere.game.data.voxel; + +import java.util.Set; + +/** + * A list of all voxel types in game + */ +public class VoxelData { + //The set of all voxel types + Set types; + + /** + * Gets all voxel types + * @return The set of all voxel types + */ + public Set getTypes(){ + return types; + } + + /** + * Gets the voxel type by its name, or null if that type does not exist + * @param name The name of the voxel type + * @return The voxel type or null + */ + public VoxelType getTypeFromName(String name){ + for(VoxelType type : types){ + if(type.name.contains(name)){ + return type; + } + } + return null; + } + + /** + * Gets the voxel type by its id, or null if that type does not exist + * @param id The id of the voxel type + * @return The voxel type or null + */ + public VoxelType getTypeFromId(int id){ + for(VoxelType type : types){ + if(type.id == id){ + return type; + } + } + return null; + } +} diff --git a/src/main/java/electrosphere/game/data/voxel/VoxelType.java b/src/main/java/electrosphere/game/data/voxel/VoxelType.java new file mode 100644 index 00000000..422ff939 --- /dev/null +++ b/src/main/java/electrosphere/game/data/voxel/VoxelType.java @@ -0,0 +1,39 @@ +package electrosphere.game.data.voxel; + +import java.util.Set; + +/** + * Data about a particular type of voxel + */ +public class VoxelType { + //the id of this voxel type + int id; + //the name of the type + String name; + //any ambient foliage that can be placed on this voxel type + Set ambientFoliage; + + /** + * Gets the id of the voxel type + * @return The id + */ + public int getId(){ + return id; + } + + /** + * Gets the name of the voxel type + * @return The name + */ + public String getName(){ + return name; + } + + /** + * Gets the names of all ambient foliage that can be placed on this voxel type + * @return The set of names + */ + public Set getAmbientFoliage(){ + return ambientFoliage; + } +} diff --git a/src/main/java/electrosphere/game/terrain/processing/TerrainInterpolator.java b/src/main/java/electrosphere/game/terrain/processing/TerrainInterpolator.java index 8ee0b945..1e686b91 100644 --- a/src/main/java/electrosphere/game/terrain/processing/TerrainInterpolator.java +++ b/src/main/java/electrosphere/game/terrain/processing/TerrainInterpolator.java @@ -874,8 +874,13 @@ public class TerrainInterpolator { // // } - - public static float[][] getBicubicInterpolatedChunk(float[][] macroValues, long[][] randomizerValues, int dynamicInterpolationRatio, float randomDampener){ + /** + * Gets the bicubic interpolation of an array of 5 x 5 values to an array of (dynamicInterpolationRatio + 1) x (dynamicInterpolationRatio + 1) + * @param macroValues The array of values to sample from + * @param dynamicInterpolationRatio The interpolation ratio + * @return The interpolated array + */ + public static float[][] getBicubicInterpolatedChunk(float[][] macroValues, int dynamicInterpolationRatio){ float[][] rVal = new float[dynamicInterpolationRatio + 1][dynamicInterpolationRatio + 1]; float[][] subValues = new float[4][4]; @@ -894,14 +899,6 @@ public class TerrainInterpolator { // //Inbetween phase 1 // - long phase1Randomizer = - randomizerValues[0][0] + - randomizerValues[0][1] + - randomizerValues[0][2] + - randomizerValues[0][3]; - - Random randomizer = new Random(phase1Randomizer); - float a0 = subValues[0][3] - subValues[0][2] - subValues[0][0] + subValues[0][1]; float a1 = subValues[0][0] - subValues[0][1] - a0; float a2 = subValues[0][2] - subValues[0][0]; @@ -909,7 +906,7 @@ public class TerrainInterpolator { for(int i = 0; i < dynamicInterpolationRatio + 1; i++){ float x = (float)i/(float)dynamicInterpolationRatio; float x2 = x * x; - inbetweenStage[0][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener; + inbetweenStage[0][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3; } @@ -917,14 +914,6 @@ public class TerrainInterpolator { //Inbetween phase 2 // - phase1Randomizer = - randomizerValues[1][0] + - randomizerValues[1][1] + - randomizerValues[1][2] + - randomizerValues[1][3]; - - randomizer = new Random(phase1Randomizer); - a0 = subValues[1][3] - subValues[1][2] - subValues[1][0] + subValues[1][1]; a1 = subValues[1][0] - subValues[1][1] - a0; a2 = subValues[1][2] - subValues[1][0]; @@ -932,7 +921,7 @@ public class TerrainInterpolator { for(int i = 0; i < dynamicInterpolationRatio + 1; i++){ float x = (float)i/(float)dynamicInterpolationRatio; float x2 = x * x; - inbetweenStage[1][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener; + inbetweenStage[1][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3; } @@ -940,14 +929,6 @@ public class TerrainInterpolator { //Inbetween phase 3 // - phase1Randomizer = - randomizerValues[2][0] + - randomizerValues[2][1] + - randomizerValues[2][2] + - randomizerValues[2][3]; - - randomizer = new Random(phase1Randomizer); - a0 = subValues[2][3] - subValues[2][2] - subValues[2][0] + subValues[2][1]; a1 = subValues[2][0] - subValues[2][1] - a0; a2 = subValues[2][2] - subValues[2][0]; @@ -955,7 +936,7 @@ public class TerrainInterpolator { for(int i = 0; i < dynamicInterpolationRatio + 1; i++){ float x = (float)i/(float)dynamicInterpolationRatio; float x2 = x * x; - inbetweenStage[2][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener; + inbetweenStage[2][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3; } @@ -964,14 +945,6 @@ public class TerrainInterpolator { //Inbetween phase 4 // - phase1Randomizer = - randomizerValues[3][0] + - randomizerValues[3][1] + - randomizerValues[3][2] + - randomizerValues[3][3]; - - randomizer = new Random(phase1Randomizer); - a0 = subValues[3][3] - subValues[3][2] - subValues[3][0] + subValues[3][1]; a1 = subValues[3][0] - subValues[3][1] - a0; a2 = subValues[3][2] - subValues[3][0]; @@ -979,7 +952,7 @@ public class TerrainInterpolator { for(int i = 0; i < dynamicInterpolationRatio + 1; i++){ float x = (float)i/(float)dynamicInterpolationRatio; float x2 = x * x; - inbetweenStage[3][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener; + inbetweenStage[3][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3; } @@ -989,14 +962,6 @@ public class TerrainInterpolator { for(int x = 0; x < dynamicInterpolationRatio + 1; x++){ -// long phase2Randomizer = -// randomizerValues[3][0] + -// randomizerValues[3][1] + -// randomizerValues[3][2] + -// randomizerValues[3][3]; - -// randomizer = new Random(phase2Randomizer); - a0 = inbetweenStage[3][x] - inbetweenStage[2][x] - inbetweenStage[0][x] + inbetweenStage[1][x]; a1 = inbetweenStage[0][x] - inbetweenStage[1][x] - a0; a2 = inbetweenStage[2][x] - inbetweenStage[0][x]; @@ -1004,8 +969,7 @@ public class TerrainInterpolator { for(int y = 0; y < dynamicInterpolationRatio + 1; y++){ float i = (float)y/(float)dynamicInterpolationRatio; float i2 = i * i; - rVal[y][x] = a0 * i * i2 + a1 * i2 + a2 * i + a3 + randomizer.nextFloat() * randomDampener; -// rVal[x][y] = i * inbetweenStage[1][x] + (1.0f - i) * inbetweenStage[2][x]; + rVal[y][x] = a0 * i * i2 + a1 * i2 + a2 * i + a3; } } diff --git a/src/main/java/electrosphere/server/content/ServerContentManager.java b/src/main/java/electrosphere/server/content/ServerContentManager.java index d0eec60d..97d5e5e5 100644 --- a/src/main/java/electrosphere/server/content/ServerContentManager.java +++ b/src/main/java/electrosphere/server/content/ServerContentManager.java @@ -13,7 +13,7 @@ public class ServerContentManager { if(!Globals.serverWorldData.isArena()){ //in other words, if not arena mode //if on disk (has already been generated) //else create from scratch - EnvironmentGenerator.generatePlains(cell, worldPos, Globals.serverTerrainManager.getRandomizerAtPoint(worldPos.x, worldPos.z)); + EnvironmentGenerator.generatePlains(cell, worldPos, 0); } cell.setNavMesh( NavMeshUtils.createMeshFromChunk(Globals.serverTerrainManager.getChunk( diff --git a/src/main/java/electrosphere/server/terrain/generation/ErosionSimulation.java b/src/main/java/electrosphere/server/terrain/generation/ErosionSimulation.java new file mode 100644 index 00000000..b1e1b59a --- /dev/null +++ b/src/main/java/electrosphere/server/terrain/generation/ErosionSimulation.java @@ -0,0 +1,425 @@ +package electrosphere.server.terrain.generation; + +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; + +import electrosphere.game.terrain.processing.TerrainInterpolator; + +/** + * Performs an erosion simulation that expands the heightmap and simulates drainage across the world + */ +public class ErosionSimulation { + + //The number of threads to farm simulation chunks out to + private static final int NUMBER_THREADS = 16; + + + //The initial heightmap passed into the simulation + private float[][] startHeightmap; + + //The actually valid data is flipped between primaryHeightmap and alternateHeightmap as the simulation runs + //so that no more than one allocation has to happen. isStartHeightmap tracks which one contains the + //valid data. + + //The primary data heightmap + private float[][] primaryHeightmap; + //The second array to hold values + private float[][] alternateHeightmap; + //Controls which heightmap contains the hot data + private boolean isPrimaryHeightmap = true; + + //Keeps track of how much water is in a given location + private float[][] primaryHydrationMap; + private float[][] alternateHydrationMap; + + //The height at which the ocean begins. No erosion simulation will happen below this point. + private float oceanLevel; + + //The size of the chunks of simulation that are created + private int interpolationRatio; + + //Random for seeding worker threads + Random rand; + + //threadpool for the step phase + ThreadPoolExecutor threadPool; + + protected ErosionSimulation(float[][] heightmap, float oceanLevel, int interpolationRatio, long randomSeed){ + this.interpolationRatio = interpolationRatio; + this.startHeightmap = heightmap; + this.primaryHeightmap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio]; + this.alternateHeightmap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio]; + this.primaryHydrationMap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio]; + this.alternateHydrationMap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio]; + this.oceanLevel = oceanLevel; + this.rand = new Random(randomSeed); + } + + /** + * Runs the erosion simulation + */ + protected void simulate(){ + setup(); + float totalHydration = getTotalHydration(); + float waterLevelRatio = totalHydration / (this.primaryHydrationMap.length * this.primaryHydrationMap[0].length); + while(totalHydration > 0){ + CountDownLatch latch = new CountDownLatch(this.startHeightmap.length * this.startHeightmap[0].length); + for(int x = 0; x < this.startHeightmap.length; x++){ + for(int y = 0; y < this.startHeightmap[0].length; y++){ + //queue location + ErosionJob job = new ErosionJob( + primaryHeightmap, + alternateHeightmap, + primaryHydrationMap, + alternateHydrationMap, + isPrimaryHeightmap, + new Vector(x,y), + interpolationRatio, + oceanLevel, + waterLevelRatio, + rand.nextLong(), + latch + ); + threadPool.submit(job); + } + } + //await all jobs + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + totalHydration = getTotalHydration(); + waterLevelRatio = totalHydration / (this.primaryHydrationMap.length * this.primaryHydrationMap[0].length); + //flip primary map + isPrimaryHeightmap = !isPrimaryHeightmap; + System.out.println(totalHydration + " - " + waterLevelRatio); + } + } + + /** + * Sets up the simulation + */ + private void setup(){ + threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(NUMBER_THREADS); + //interpolate start heightmap into full heightmap + int sampleCount = 25; + int[] xSampleOffset = new int[]{ + -2,-1,0,1,2, + -2,-1,0,1,2, + -2,-1,0,1,2, + -2,-1,0,1,2, + -2,-1,0,1,2, + }; + int[] ySampleOffset = new int[]{ + -2,-2,-2,-2,-2, + -1,-1,-1,-1,-1, + 0,0,0,0,0, + 1,1,1,1,1, + 2,2,2,2,2, + }; + for(int x = 0; x < startHeightmap.length; x++){ + for(int y = 0; y < startHeightmap[0].length; y++){ + //get the array of samples + float[][] sample = new float[5][5]; + for(int i = 0; i < sampleCount; i++){ + if(x + xSampleOffset[i] >= 0 && x + xSampleOffset[i] < startHeightmap.length && + y + ySampleOffset[i] >= 0 && y + ySampleOffset[i] < startHeightmap[0].length){ + //have to add 2 to xSampleOffset and ySampleOffset to get accurate position in sample array + sample[xSampleOffset[i] + 2][ySampleOffset[i] + 2] = startHeightmap[x + xSampleOffset[i]][y + ySampleOffset[i]]; + } else { + sample[xSampleOffset[i] + 2][ySampleOffset[i] + 2] = 0; + } + } + float[][] interpolatedValues = TerrainInterpolator.getBicubicInterpolatedChunk(sample, interpolationRatio); + for(int m = 0; m < interpolationRatio; m++){ + for(int n = 0; n < interpolationRatio; n++){ + primaryHeightmap[x * interpolationRatio + m][y * interpolationRatio + n] = interpolatedValues[m][n]; + alternateHeightmap[x * interpolationRatio + m][y * interpolationRatio + n] = interpolatedValues[m][n]; + //seed initial hydration map + primaryHydrationMap[x * interpolationRatio + m][y * interpolationRatio + n] = 1; + alternateHydrationMap[x * interpolationRatio + m][y * interpolationRatio + n] = 1; + } + } + } + } + } + + /** + * Gets the data resulting from the erosion simulation + * @return The data + */ + protected float[][] getData(){ + if(isPrimaryHeightmap){ + return primaryHeightmap; + } else { + return alternateHeightmap; + } + } + + /** + * Returns the total hydration of the currently active map + * @return The total hydration + */ + private float getTotalHydration(){ + float sum = 0; + float highestElevation = 0; + float highestHydration = 0; + if(isPrimaryHeightmap){ + for(int x = 0; x < primaryHydrationMap.length; x++){ + for(int y = 0; y < primaryHydrationMap[0].length; y++){ + sum = sum + primaryHydrationMap[x][y]; + if(primaryHeightmap[x][y] > highestElevation){ + highestElevation = primaryHeightmap[x][y]; + } + if(primaryHydrationMap[x][y] > highestHydration){ + highestHydration = primaryHydrationMap[x][y]; + } + } + } + } else { + for(int x = 0; x < alternateHydrationMap.length; x++){ + for(int y = 0; y < alternateHydrationMap[0].length; y++){ + sum = sum + alternateHydrationMap[x][y]; + if(alternateHeightmap[x][y] > highestElevation){ + highestElevation = alternateHeightmap[x][y]; + } + if(alternateHydrationMap[x][y] > highestHydration){ + highestHydration = alternateHydrationMap[x][y]; + } + } + } + } + System.out.println("Highest elev: " + highestElevation); + System.out.println("Highest hydra: " + highestHydration); + return sum; + } + + /** + * A runnable job of simulation erosion on a single chunk + */ + static class ErosionJob implements Runnable { + + static final int MAX_HYDRATION = 25; + + float primaryHeightmap[][]; + float alternateHeightmap[][]; + float[][] primaryHydrationMap; + float[][] alternateHydrationMap; + boolean usePrimaryMaps; + Vector targetLocation; + int interpolationRatio; + float oceanLevel; + float waterLevelRatio; + Random rand; + CountDownLatch latch; + + protected ErosionJob( + float[][] primaryHeightmap, + float[][] alternateHeightmap, + float[][] primaryHydrationMap, + float[][] alternateHydrationMap, + boolean usePrimaryMaps, + Vector targetLocation, + int interpolationRatio, + float oceanLevel, + float waterLevelRatio, + long randomSeed, + CountDownLatch latch + ){ + this.primaryHeightmap = primaryHeightmap; + this.alternateHeightmap = alternateHeightmap; + this.primaryHydrationMap = primaryHydrationMap; + this.alternateHydrationMap = alternateHydrationMap; + this.usePrimaryMaps = usePrimaryMaps; + this.targetLocation = targetLocation; + this.interpolationRatio = interpolationRatio; + this.oceanLevel = oceanLevel; + this.waterLevelRatio = waterLevelRatio; + this.rand = new Random(randomSeed); + this.latch = latch; + } + + static final int[] offsetX = new int[]{ + -1,1,0,0 + }; + static final int[] offsetY = new int[]{ + 0,0,-1,1 + }; + @Override + public void run() { + for(int x = 0; x < interpolationRatio; x++){ + for(int y = 0; y < interpolationRatio; y++){ + float currentHeight = 0; + int targetX = targetLocation.x * interpolationRatio + x; + int targetY = targetLocation.y * interpolationRatio + y; + if(usePrimaryMaps){ + currentHeight = primaryHeightmap[targetX][targetY]; + float oldHydration = primaryHydrationMap[targetX][targetY]; + float newHydration = 0; + float highestEncounteredElevation = 0; + int numberHydrationHits = 0; + // if(targetX == 728 && targetY == 732){ + // System.out.println("asdf"); + // } + //calculate total hydration + for(int i = 0; i < 4; i++){ + if(targetX + offsetX[i] >= 0 && targetX + offsetX[i] < primaryHeightmap.length && + targetY + offsetY[i] >= 0 && targetY + offsetY[i] < primaryHeightmap[0].length + ){ + if(currentHeight < primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]]){ + numberHydrationHits++; + float sourceHydration = primaryHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]]; + float percentageFromSource = calculatePercentageRunoff( + targetX + offsetX[i], + targetY + offsetY[i], + targetX, + targetY, + primaryHeightmap + ); + newHydration = newHydration + sourceHydration * percentageFromSource; + } else if(currentHeight == primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] && rand.nextInt() % alternateHydrationMap[targetX][targetY] > 5) { + // numberHydrationHits++; + // alternateHydrationMap[targetX][targetY] = alternateHydrationMap[targetX][targetY] + primaryHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]]; + } else { + if(primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] > highestEncounteredElevation){ + highestEncounteredElevation = primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]]; + } + } + } + } + //calculate shear due to hydration + float shear = Math.abs(newHydration - oldHydration); + if(waterLevelRatio > 1.0f){ + shear = alternateHydrationMap[targetX][targetY] / waterLevelRatio; + } + //clamp hydration value + alternateHydrationMap[targetX][targetY] = Math.min(newHydration,MAX_HYDRATION); + if(numberHydrationHits == 4){ + alternateHydrationMap[targetX][targetY] = 0; + } + if(currentHeight > oceanLevel && numberHydrationHits < 4){ + alternateHeightmap[targetX][targetY] = Math.max(highestEncounteredElevation,currentHeight - 0.1f / shear); + } else { + //if below sea level, delete hydration + alternateHeightmap[targetX][targetY] = currentHeight; + alternateHydrationMap[targetX][targetY] = 0; + } + } else { + // if(targetX == 728 && targetY == 732){ + // System.out.println("asdf"); + // } + currentHeight = alternateHeightmap[targetX][targetY]; + float oldHydration = primaryHydrationMap[targetX][targetY]; + float newHydration = 0; + float highestEncounteredElevation = 0; + int numberHydrationHits = 0; + //check each neighbor to see who we can interact with + for(int i = 0; i < 4; i++){ + if(targetX + offsetX[i] >= 0 && targetX + offsetX[i] < primaryHeightmap.length && + targetY + offsetY[i] >= 0 && targetY + offsetY[i] < primaryHeightmap[0].length + ){ + //if the neighbor is taller, pull hydration from it + if(currentHeight < alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]]){ + numberHydrationHits++; + float sourceHydration = primaryHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]]; + float percentageFromSource = calculatePercentageRunoff( + targetX + offsetX[i], + targetY + offsetY[i], + targetX, + targetY, + alternateHeightmap + ); + newHydration = newHydration + sourceHydration * percentageFromSource; + } else if(currentHeight == alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] && rand.nextInt() % primaryHydrationMap[targetX][targetY] > 5) { + //if the neighbor is the same height, have a chance to pull hydration from it + // numberHydrationHits++; + // primaryHydrationMap[targetX][targetY] = primaryHydrationMap[targetX][targetY] + alternateHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]]; + } else { + //if the neighbor is smaller, but taller than the tallest neighbor currently encountered, record its height + if(alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] > highestEncounteredElevation){ + highestEncounteredElevation = alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]]; + } + } + } + } + //calculate shear due to hydration + float shear = Math.abs(newHydration - oldHydration); + //bound the hydration by the total hydration of the map + //This keeps the hydration from explosively increasing unbounded + if(waterLevelRatio > 1.0f){ + shear = alternateHydrationMap[targetX][targetY] / waterLevelRatio; + } + //clamp hydration value + primaryHydrationMap[targetX][targetY] = Math.min(newHydration,MAX_HYDRATION); + //If every neighbor is taller, this is a local minimum and should be treated as a lake (removes all hydration) + if(numberHydrationHits == 4){ + primaryHydrationMap[targetX][targetY] = 0; + } + if(currentHeight > oceanLevel && numberHydrationHits < 4){ + primaryHeightmap[targetX][targetY] = Math.max(highestEncounteredElevation,currentHeight - 0.1f / shear); + } else { + //if below sea level, delete hydration + primaryHeightmap[targetX][targetY] = currentHeight; + primaryHydrationMap[targetX][targetY] = 0; + } + } + } + } + latch.countDown(); + } + + /** + * Basically calculates how much runoff should go from source to destination. The percentage is based on how much of the total drop to all neighbors destination would be. + * @param sourceX The x coordinate of the source of the water + * @param sourceY The y coordinate of the source of the water + * @param destinationX The x coordinate of the destination of the water + * @param destinationY The y coordinate of the destination of the water + * @param elevationMapToCheck The elevation map to use as reference + * @return The percentage of water to pull from source to destination + */ + private float calculatePercentageRunoff(int sourceX, int sourceY, int destinationX, int destinationY, float[][] elevationMapToCheck){ + //the difference between the source and destination points in elevation + float heightDifferencToDestination = elevationMapToCheck[sourceX][sourceY] - elevationMapToCheck[destinationX][destinationY]; + //the sum difference between source and all its smaller neighbors + float totalHeightDifference = 0; + for(int i = 0; i < 4; i++){ + if(sourceX + offsetX[i] >= 0 && sourceX + offsetX[i] < primaryHeightmap.length && + sourceY + offsetY[i] >= 0 && sourceY + offsetY[i] < primaryHeightmap[0].length + ){ + if(elevationMapToCheck[sourceX][sourceY] > elevationMapToCheck[sourceX + offsetX[i]][sourceY + offsetY[i]]){ + totalHeightDifference = totalHeightDifference + elevationMapToCheck[sourceX][sourceY] - elevationMapToCheck[sourceX + offsetX[i]][sourceY + offsetY[i]]; + } + } + } + return heightDifferencToDestination / totalHeightDifference; + } + + /** + * Checks if a given location has all neighbors that are the same height + * @param sourceX The location to check x coordinate + * @param sourceY The location to check y coordinate + * @param elevationMapToCheck The elevation map to reference + * @return True if all neighbors are flat, false otherwise + */ + private boolean neighborIsFlat(int sourceX, int sourceY, float[][] elevationMapToCheck){ + for(int i = 0; i < 4; i++){ + if(sourceX + offsetX[i] >= 0 && sourceX + offsetX[i] < primaryHeightmap.length && + sourceY + offsetY[i] >= 0 && sourceY + offsetY[i] < primaryHeightmap[0].length + ){ + if(elevationMapToCheck[sourceX][sourceY] != elevationMapToCheck[sourceX + offsetX[i]][sourceY + offsetY[i]]){ + return false; + } + } + } + return true; + } + } + + void createVisualization(){ + + } + +} diff --git a/src/main/java/electrosphere/server/terrain/generation/Hotspot.java b/src/main/java/electrosphere/server/terrain/generation/Hotspot.java index 66cd15af..7cf6873f 100644 --- a/src/main/java/electrosphere/server/terrain/generation/Hotspot.java +++ b/src/main/java/electrosphere/server/terrain/generation/Hotspot.java @@ -12,10 +12,10 @@ class Hotspot { int life_max; int magnitude_current; int magnitude_max; - TerrainGenerator parent; + TectonicSimulation parent; - protected Hotspot(int x, int y, int life_max, int magnitude, TerrainGenerator parent) { + protected Hotspot(int x, int y, int life_max, int magnitude, TectonicSimulation parent) { this.x = x; this.y = y; this.life_current = 0; diff --git a/src/main/java/electrosphere/server/terrain/generation/InterpolationDisplay.java b/src/main/java/electrosphere/server/terrain/generation/InterpolationDisplay.java index b4f1775a..9b0e456b 100644 --- a/src/main/java/electrosphere/server/terrain/generation/InterpolationDisplay.java +++ b/src/main/java/electrosphere/server/terrain/generation/InterpolationDisplay.java @@ -10,9 +10,9 @@ import javax.swing.JPanel; */ class InterpolationDisplay extends JPanel{ - TerrainGen parent; + TerrainGenerator parent; - protected InterpolationDisplay(TerrainGen parent){ + protected InterpolationDisplay(TerrainGenerator parent){ this.parent = parent; } @@ -22,9 +22,9 @@ class InterpolationDisplay extends JPanel{ if(parent.displayToggle == 0) { for (int x = 0; x < width; x++) { for (int y = 0; y < width; y++) { - if (parent.mountainParsed[x][y] > TerrainGen.MOUNTAIN_THRESHOLD - 1) { + if (parent.mountainParsed[x][y] > TerrainGenerator.MOUNTAIN_THRESHOLD - 1) { g.setColor(new Color((int) (parent.elevation[x][y] / 100.0 * 254 * (parent.brightness / 100.0)), 1, 1)); - } else if (parent.oceanParsed[x][y] > TerrainGen.OCEAN_THRESHOLD - 1) { + } else if (parent.oceanParsed[x][y] > TerrainGenerator.OCEAN_THRESHOLD - 1) { g.setColor( new Color( 1, @@ -38,7 +38,29 @@ class InterpolationDisplay extends JPanel{ g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2); } } - } else if(parent.displayToggle == 1){ + } else if(parent.displayToggle == 1) { + for (int x = 0; x < parent.continentPhaseDimension * parent.EROSION_INTERPOLATION_RATIO; x++) { + for (int y = 0; y < parent.continentPhaseDimension * parent.EROSION_INTERPOLATION_RATIO; y++) { + if (parent.erosionHeightmap[x][y] > TerrainGenerator.MOUNTAIN_THRESHOLD - 1) { + float color = Math.max(0,Math.min(parent.erosionHeightmap[x][y],100)); + g.setColor(new Color((int) (color / 100.0 * 254 * (parent.brightness / 100.0)), 1, 1)); + } else if (parent.erosionHeightmap[x][y] < TerrainGenerator.OCEAN_THRESHOLD - 1) { + float color = Math.max(0,Math.min(parent.erosionHeightmap[x][y],100)); + g.setColor( + new Color( + 1, + (int) (color / 100.0 * 254 * (parent.brightness / 100.0)), + (int) (color / 100.0 * 254 * (parent.brightness / 100.0)) + ) + ); + } else { + float color = Math.max(0,Math.min(parent.erosionHeightmap[x][y],100)); + g.setColor(new Color(1, (int) (color / 100.0 * 254 * (parent.brightness / 100.0)), 1)); + } + g.fillRect(x + 25, y + 25, 1, 1); + } + } + } else if(parent.displayToggle == 2){ for (int x = 0; x < width; x++) { for (int y = 0; y < width; y++) { if (parent.precipitationChart[x][y] > 0) { @@ -55,7 +77,7 @@ class InterpolationDisplay extends JPanel{ g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2); } } - } else if(parent.displayToggle == 2){ + } else if(parent.displayToggle == 3){ for (int x = 0; x < width; x++) { for (int y = 0; y < width; y++) { // if (TerrainInterpolator.precipitation_Chart[x][y] > 0) { @@ -72,7 +94,7 @@ class InterpolationDisplay extends JPanel{ g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2); } } - } else if(parent.displayToggle == 3){ + } else if(parent.displayToggle == 4){ for (int x = 0; x < width; x++) { for (int y = 0; y < width; y++) { if (parent.climateCategory[x][y] == 0) { @@ -143,7 +165,7 @@ class InterpolationDisplay extends JPanel{ g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2); } } - } else if(parent.displayToggle == 4){ + } else if(parent.displayToggle == 5){ for (int x = 0; x < width; x++) { for (int y = 0; y < width; y++) { if (parent.continentIdField[x][y] > 8) { @@ -170,7 +192,7 @@ class InterpolationDisplay extends JPanel{ g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2); } } - } else if (parent.displayToggle == 5) { + } else if (parent.displayToggle == 6) { Continent current = parent.continents.get(parent.current_Continent); g.drawString("dim_x: " + current.dim_x, 20, 20); g.drawString("dim_y: " + current.dim_y, 20, 30); @@ -182,7 +204,7 @@ class InterpolationDisplay extends JPanel{ } } } - } else if (parent.displayToggle == 6){ + } else if (parent.displayToggle == 7){ // Continent current = parent.continents.get(parent.current_Continent); // for(int x = 0; x < Region.REGION_DIMENSION; x++){ // for(int y = 0; y < Region.REGION_DIMENSION; y++){ diff --git a/src/main/java/electrosphere/server/terrain/generation/TectonicSimulation.java b/src/main/java/electrosphere/server/terrain/generation/TectonicSimulation.java new file mode 100644 index 00000000..321be0bc --- /dev/null +++ b/src/main/java/electrosphere/server/terrain/generation/TectonicSimulation.java @@ -0,0 +1,645 @@ +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(); + } + + } +} diff --git a/src/main/java/electrosphere/server/terrain/generation/TerrainGen.java b/src/main/java/electrosphere/server/terrain/generation/TerrainGen.java deleted file mode 100644 index 68083932..00000000 --- a/src/main/java/electrosphere/server/terrain/generation/TerrainGen.java +++ /dev/null @@ -1,1477 +0,0 @@ -package electrosphere.server.terrain.generation; - -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Random; - -import javax.swing.JFrame; - -import electrosphere.server.terrain.models.TerrainModel; - -/** - * - * @author satellite - */ -public class TerrainGen { - int elevation[][]; - int continentPhaseDimension = 128; - //the interpolation ratio applied to the statically generated terrain - int interpolationRatio = 10; - //the interpolated phase dimension - int interpolationPhaseDimension = continentPhaseDimension * interpolationRatio; - //vertical static interpolation ratio - int verticalInterpolationRatio = 10; - //the interpolation ratio applied to the dynamically generated terrain per chunk - int dynamicInterpRatio = 1000; - JFrame frame; - InterpolationDisplay graphics; - int brightness = 0; - int brightnessHoldIncrementer = 0; - boolean brightnessIncreasing = true; - int displayToggle = 0; - int max_Display_Toggle = 6; - - static final int MAX_HEIGHT = 100; - - int[][] mountainParsed; - static final int MOUNTAIN_THRESHOLD = 75; - static final int MOUNTAIN_RANGE_SIZE_MINIMUM = 10; - - int[][] oceanParsed; - static final int OCEAN_THRESHOLD = 25; - - Vector wind_field[][]; - - int[][] precipitationChart; - static final int RAIN_SHADOW_BLOCKER_HEIGHT = 80; - - int[][] temperatureChart; - - long[][] chunkRandomizer; - - /* - 0 - ocean - 1 - tropical - 2 - subtropical - 3 - temperate - 4 - dry - 5 - polar - 6 - mountainous - */ - public int[][] climateCategory; - - public int numberContinents = 0; - public int[][] continentIdField; - - public List continents = new ArrayList(); - - public int current_Continent = 1; - public int current_Region_X = 0; - public int current_Region_Y = 0; - - Random rand = new Random(); - - // public static void main(String args[]){ - - // TerrainGenerator tergen = new TerrainGenerator(); - // tergen.set_Dimension(DIMENSION); - // tergen.set_Lifespan(15000); - // tergen.run(); - // elevation = tergen.get_Terrain(); - - - // elevation = interpolate_Elevation_Raws(elevation); - // DIMENSION = DIMENSION * 2; - - // elevation = compression_filter(elevation); - // elevation = three_halves_filter(elevation); - // elevation = smooth_Terrain_Further(elevation); - // elevation = small_Kernel_Sharpen(elevation); - // elevation = smooth_Terrain_Further(elevation); - // elevation = three_halves_filter(elevation); - // elevation = small_Kernel_Smooth(elevation); - // elevation = small_Kernel_Sharpen(elevation); - // elevation = smooth_Terrain_Further(elevation); - // elevation = smooth_Terrain_Further(elevation); - // elevation = land_exponential_filter(elevation); - // elevation = land_exponential_filter(elevation); - // elevation = land_exponential_filter(elevation); - // elevation = land_exponential_filter(elevation); - - // mountainParsed = new int[DIMENSION][DIMENSION]; - // oceanParsed = new int[DIMENSION][DIMENSION]; - // continentIdField = new int[DIMENSION][DIMENSION]; - - // mountainParsed = parse_Mountainscapes(elevation); - - // oceanParsed = parse_Oceans(elevation); - - // wind_field = map_Wind_Field(); - - // precipitationChart = calculate_Rain_Shadows(elevation,oceanParsed,wind_field); - - // temperatureChart = generate_Temperature_Chart(); - - // climateCategory = infer_Climate_Category(); - - // determine_Continents(); - - // fill_Continents(); - - // //for display tbh - // anchor_To_Real_Region(); - - - // display_toggle = 0; - - // create_Frame(); - - // while(true){ - // if(brightness_increasing){ - // if(brightness < 100){ - // brightness++; - // } else { - // } - // } else { - // if(brightness > 0){ - // brightness--; - // } else { - // brightness_increasing = true; - // display_toggle++; - // if(display_toggle > 1){ - // display_toggle = 0; - // } - // } - // } - // frame.repaint(); - // Utilities.sleep(10); - // } - // } - - /** - * Generates a new TerrainModel - * @return The TerrainModel - */ - public TerrainModel generateModel(){ - TerrainModel rVal; - - //generate continent-phase terrain - TerrainGenerator tergen = new TerrainGenerator(1); - tergen.setDimension(continentPhaseDimension); - tergen.setLifespan(15000); - tergen.run(); - elevation = tergen.getTerrain(); - - //interpolate terrain to smooth what has been received from TerrainGenerator - elevation = interpolateElevationRaws(elevation); - - //While the TerrainGenerator does a great job of outlining a rough sketch of how the terrain should look, - //a lot of filters are applied to give the terrain a much cleaner and realistic look. This is arguably - //where the real magic of the terrain generation happens - - //flatten the terrain and raise it above sealevel - elevation = compressionFilter(elevation); - elevation = threeHalvesFilter(elevation); - - //knead the terrain - elevation = smoothTerrainFurther(elevation); - elevation = smallKernelSharpen(elevation); - elevation = smoothTerrainFurther(elevation); - - //raise terrain - elevation = threeHalvesFilter(elevation); - - //knead terrain mode - elevation = smallKernelSmooth(elevation); - elevation = smallKernelSharpen(elevation); - elevation = smoothTerrainFurther(elevation); - elevation = smoothTerrainFurther(elevation); - - //Make the land part of the heightmap scale exponentially instead of linearly - //Basically makes the terrain sharper - elevation = landExponentialFilter(elevation); - elevation = landExponentialFilter(elevation); - elevation = landExponentialFilter(elevation); - elevation = landExponentialFilter(elevation); - elevation = landExponentialFilter(elevation); - elevation = landExponentialFilter(elevation); - - mountainParsed = new int[continentPhaseDimension][continentPhaseDimension]; - oceanParsed = new int[continentPhaseDimension][continentPhaseDimension]; - continentIdField = new int[continentPhaseDimension][continentPhaseDimension]; - - //generate designation arrays for features about the terrain - mountainParsed = parseMountainscapes(elevation); - oceanParsed = parseOceans(elevation); - wind_field = mapWindField(); - precipitationChart = calculateRainShadows(elevation,oceanParsed,wind_field); - temperatureChart = generateTemperatureChart(); - climateCategory = inferClimateCategory(); - determineContinents(); - - //generate continent objects - fillContinents(); - - float[][] modelInput = interpolateIntegerElevations(elevation); - - modelInput = applyRandomizationFilter(modelInput); - - chunkRandomizer = getChunkRandomizer(modelInput, interpolationPhaseDimension); - - rVal = new TerrainModel( - interpolationPhaseDimension, - modelInput, - chunkRandomizer, - OCEAN_THRESHOLD * verticalInterpolationRatio, - MOUNTAIN_THRESHOLD * verticalInterpolationRatio, - dynamicInterpRatio - ); - - //create internal renderer - createRenderer(); - - boolean test = true; - while(test){ - if(brightnessIncreasing){ - if(brightness < 100){ - brightness++; - } else { - } - } else { - if(brightness > 0){ - brightness--; - } else { - brightnessIncreasing = true; - displayToggle++; - if(displayToggle > 1){ - displayToggle = 0; - } - } - } - frame.repaint(); - Utilities.sleep(10); - } - - return rVal; - } - - public long[][] getChunkRandomizer(float[][] elevationMap, int DIMENSION){ - long[][] rVal = new long[DIMENSION][DIMENSION]; - for(int x = 0; x < DIMENSION; x++){ - for(int y = 0; y < DIMENSION; y++){ - long seed = (long)elevationMap[x][y]; - if(x - 1 > 0){ - seed = seed + (long)elevationMap[x-1][y]; - if(y - 1 > 0){ - seed = seed + (long)elevationMap[x-1][y-1]; - } - if(y + 1 < DIMENSION){ - seed = seed + (long)elevationMap[x-1][y+1]; - } - } - if(x + 1 < DIMENSION){ - seed = seed + (long)elevationMap[x+1][y]; - if(y - 1 > 0){ - seed = seed + (long)elevationMap[x+1][y-1]; - } - if(y + 1 < DIMENSION){ - seed = seed + (long)elevationMap[x+1][y+1]; - } - } - if(y - 1 > 0){ - seed = seed + (long)elevationMap[x][y-1]; - } - if(y + 1 < DIMENSION){ - seed = seed + (long)elevationMap[x][y+1]; - } - rVal[x][y] = seed; - } - } - return rVal; - } - - float[][] applyRandomizationFilter(float[][] elevation){ - float[][] rVal = new float[elevation.length][elevation[0].length]; - for(int x = 0; x < continentPhaseDimension-1; x++){ - for(int y = 0; y < continentPhaseDimension-1; y++){ - rVal[x][y] = elevation[x][y] + Utilities.random_Integer(0, (int)(elevation[x][y] / verticalInterpolationRatio), rand); - } - } - return rVal; - } - - float[][] interpolateIntegerElevations(int[][] elevation){ - int interpRatio = interpolationRatio; - float[][] rVal = new float[continentPhaseDimension * interpRatio][continentPhaseDimension * interpRatio]; - for(int x = 0; x < continentPhaseDimension-1; x++){ - for(int y = 0; y < continentPhaseDimension-1; y++){ - for(int i = 0; i < interpRatio; i++){ - for(int j = 0; j < interpRatio; j++){ - float ratio1 = (i*j) * 1.0f / (interpRatio*interpRatio); - float ratio2 = ((interpRatio-i)*j) * 1.0f / (interpRatio*interpRatio); - float ratio3 = (i*(interpRatio-j)) * 1.0f / (interpRatio*interpRatio); - float ratio4 = ((interpRatio-i)*(interpRatio-j)) * 1.0f / (interpRatio*interpRatio); - rVal[x*interpRatio+interpRatio-i][y*interpRatio+interpRatio-j] = - (elevation[x ][y ] * ratio1 + - elevation[x+1][y ] * ratio2 + - elevation[x ][y+1] * ratio3 + - elevation[x+1][y+1] * ratio4) * - verticalInterpolationRatio - ; - } - } - } - } - interpolationPhaseDimension = continentPhaseDimension * interpRatio; - return rVal; - } - - public void anchor_To_Real_Region(){ - //current_Region_X - //continents - //current_Continent - Continent target_continent = continents.get(current_Continent); - for(int x = 0; x < target_continent.dim_x; x++){ - for(int y = 0; y < target_continent.dim_y; y++){ - if(target_continent.regions[x][y]!=null){ - current_Region_X = x; - current_Region_Y = y; - x = target_continent.dim_x; - y = target_continent.dim_y; - } else { - - } - } - } - } - - int[][] simulate_Drainage(int[][] water_level, int[][] elevation, int dim_x, int dim_y){ - int[][] rVal = new int[dim_x][dim_y]; - for(int x = 0; x < dim_x; x++){ - for(int y = 0; y < dim_y; y++){ - rVal[x][y] = 0; - } - } - int kernel_offset_x[] = { - -1,0,0,1 - }; - int kernel_offset_y[] = { - 0,-1,1,0 - }; - int offset_kernel_size = 4; - for(int x = 0; x < dim_x; x++){ - for(int y = 0; y < dim_y; y++){ - int num_Water_Particles = water_level[x][y]; - int elevation_Center = elevation[x][y]; - int attractor_Values[] = new int[offset_kernel_size]; - for(int j = 0; j < offset_kernel_size; j++) { - if(x + kernel_offset_x[j] >= 0 && x + kernel_offset_x[j] < dim_x && y + kernel_offset_y[j] >= 0 && y + kernel_offset_y[j] < dim_y) { - if(elevation[x+kernel_offset_x[j]][y+kernel_offset_y[j]] < elevation[x][y]){ - attractor_Values[j] = (int)((elevation[x][y]-elevation[x+kernel_offset_x[j]][y+kernel_offset_y[j]])*1.0*water_level[x+kernel_offset_x[j]][y+kernel_offset_y[j]]); - } else { - attractor_Values[j] = 0; - } - } - } - int rand_num = 0; - int rand_Space = 0; - for(int j = 0; j < offset_kernel_size; j++){ - rand_Space = rand_Space + attractor_Values[j]; - } - rand_Space = rand_Space + water_level[x][y]; // account for water not moving downwards - int rand_Incrementer = 0; - boolean done = false; - for(int i = 0; i < num_Water_Particles; i++){ - done = false; - rand_Incrementer = 0; - rand_num = Utilities.random_Integer(0, rand_Space, rand); - for(int j = 0; j < offset_kernel_size; j++){ - rand_Incrementer = rand_Incrementer + attractor_Values[j]; - if(rand_num < rand_Incrementer){ - if(attractor_Values[j] > 0){ - if(rVal[x+kernel_offset_x[j]][y+kernel_offset_y[j]] < 400){ - rVal[x+kernel_offset_x[j]][y+kernel_offset_y[j]]++; - } - done = true; - } - } - } - if(!done){ - rVal[x][y]++; - } - } - } - } - return rVal; - } - - int sumIntegerArray(int[][] arr, int dim_x, int dim_y){ - int sum = 0; - for(int x = 0; x < dim_x; x++){ - for(int y = 0; y < dim_y; y++){ - sum = sum + arr[x][y]; - } - } - return sum; - } - - /** - * Generates an elevation map of a given continent - * @param c The continent to generate off of - */ - void generateRegionElevationMaps(Continent c){ - int neighborOffsetX[] = { - 1,0,-1,1,-1,1,0,-1 - }; - int neighborOffsetY[] = { - 1,1,1,0,0,-1,-1,-1 - }; - //generate neighbor links - for(int x = 0; x < c.dim_x; x++){ - for(int y = 0; y < c.dim_y; y++){ - if(c.regions[x][y]!=null){ - for(int i = 0; i < 8; i++){ - if(x + neighborOffsetX[i] >= 0 && x + neighborOffsetX[i] < c.dim_x && y + neighborOffsetY[i] >= 0 && y + neighborOffsetY[i] < c.dim_y){ - //+1 comes from getting the center of the kernel - //ie kernel is 3x3 therefore the center position is at 1,1 array-wise - c.regions[x][y].neighbors[neighborOffsetX[i]+1][neighborOffsetY[i]+1] = c.regions[x + neighborOffsetX[i]][y + neighborOffsetY[i]]; - } else { - c.regions[x][y].neighbors[neighborOffsetX[i]+1][neighborOffsetY[i]+1] = null; - } - } - } - } - } - int regionDim = Region.REGION_DIMENSION; - //set elevation goals - for(int x = 0; x < c.dim_x; x++){ - for(int y = 0; y < c.dim_y; y++){ - if(c.regions[x][y]!=null){ - c.regions[x][y].elevation_Goal = c.elevation[x][y]; - } - } - } - //interpolate base elevation values - int interpolation_kernel_offset_x[] = {0,1,0,1}; - int interpolation_kernel_offset_y[] = {0,0,1,1}; - int interpolation_kernel_size = 4; - int corner_Vals[][] = new int[2][2]; - for(int x = 0; x < c.dim_x; x++){ - for(int y = 0; y < c.dim_y; y++){ - if (c.regions[x][y] != null) { - c.regions[x][y].chart_Elevation = new int[Region.REGION_DIMENSION][Region.REGION_DIMENSION]; - //first the north-west corner - for(int i = 0; i < interpolation_kernel_size; i++){ - if(x-1+interpolation_kernel_offset_x[i] >= 0 && x-1+interpolation_kernel_offset_x[i] < c.dim_x && - y-1+interpolation_kernel_offset_y[i] >= 0 && y-1+interpolation_kernel_offset_y[i] < c.dim_y){ - if(c.regions[x-1+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]]!=null){ - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = - c.regions[x-1+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]].elevation_Goal; - } else { - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; - } - } - } - int midpoint = (int)(Region.REGION_DIMENSION/2.0); - int sum = 0; - for(int i = 0; i < midpoint; i++){ - for(int j = 0; j < midpoint; j++){ - sum = 0; - sum = sum + (midpoint-i)*(midpoint-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; - sum = sum + (i)*(midpoint-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; - sum = sum + (midpoint-i)*(j)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; - sum = sum + (i)*(j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; - c.regions[x][y].chart_Elevation[i][j] = sum; - } - } - //now the north-east corner - for(int i = 0; i < interpolation_kernel_size; i++){ - if(x+interpolation_kernel_offset_x[i] >= 0 && x+interpolation_kernel_offset_x[i] < c.dim_x && - y-1+interpolation_kernel_offset_y[i] >= 0 && y-1+interpolation_kernel_offset_y[i] < c.dim_y){ - if(c.regions[x+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]]!=null){ - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = - c.regions[x+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]].elevation_Goal; - } else { - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; - } - } - } - for(int i = midpoint; i < Region.REGION_DIMENSION; i++){ - for(int j = 0; j < midpoint; j++){ - sum = 0; - sum = sum + (Region.REGION_DIMENSION-i)*(midpoint-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; - sum = sum + (i-midpoint)*(midpoint-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; - sum = sum + (Region.REGION_DIMENSION-i)*(j)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; - sum = sum + (i-midpoint)*(j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; - c.regions[x][y].chart_Elevation[i][j] = sum; - } - } - //now the south-west corner - for(int i = 0; i < interpolation_kernel_size; i++){ - if(x-1+interpolation_kernel_offset_x[i] >= 0 && x-1+interpolation_kernel_offset_x[i] < c.dim_x && - y+interpolation_kernel_offset_y[i] >= 0 && y+interpolation_kernel_offset_y[i] < c.dim_y){ - if(c.regions[x-1+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]]!=null){ - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = - c.regions[x-1+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]].elevation_Goal; - } else { - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; - } - } - } - for(int i = 0; i < midpoint; i++){ - for(int j = midpoint; j < Region.REGION_DIMENSION; j++){ - sum = 0; - sum = sum + (midpoint-i)*(Region.REGION_DIMENSION-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; - sum = sum + (i)*(Region.REGION_DIMENSION-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; - sum = sum + (midpoint-i)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; - sum = sum + (i)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; - c.regions[x][y].chart_Elevation[i][j] = sum; - } - } - //now the south-east corner - for(int i = 0; i < interpolation_kernel_size; i++){ - if(x+interpolation_kernel_offset_x[i] >= 0 && x+interpolation_kernel_offset_x[i] < c.dim_x && - y+interpolation_kernel_offset_y[i] >= 0 && y+interpolation_kernel_offset_y[i] < c.dim_y){ - if(c.regions[x+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]]!=null){ - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = - c.regions[x+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]].elevation_Goal; - } else { - corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; - } - } - } - for(int i = midpoint; i < Region.REGION_DIMENSION; i++){ - for(int j = midpoint; j < Region.REGION_DIMENSION; j++){ - sum = 0; - sum = sum + (Region.REGION_DIMENSION-i)*(Region.REGION_DIMENSION-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; - sum = sum + (i-midpoint)*(Region.REGION_DIMENSION-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; - sum = sum + (Region.REGION_DIMENSION-i)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; - sum = sum + (i-midpoint)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; - c.regions[x][y].chart_Elevation[i][j] = sum; - - } - } - } - } - } - Region region_Current = null; - for(int x = 0; x < c.dim_x; x++){ - for(int y = 0; y < c.dim_y; y++){ - if(c.regions[x][y]!=null){ - if(region_Current == null){ - region_Current = c.regions[x][y]; - } else if(c.regions[x][y].elevation_Goal > region_Current.elevation_Goal){ - region_Current = c.regions[x][y]; - } - } - } - } - //drainage simulation - //operates on an open set - int adjacency_array_x[] = { - 1,0,1,2 - }; - int adjacency_array_y[] = { - 0,1,2,1 - }; - boolean simulate_Drainage = true; - List open_Set = new ArrayList(); - open_Set.add(region_Current); - if(simulate_Drainage){ - while(!open_Set.isEmpty()){ - region_Current.chart_Drainage = new int[regionDim][regionDim]; - for(int x = 0; x < regionDim; x++){ - for(int y = 0; y < regionDim; y++){ - region_Current.chart_Drainage[x][y] = 1; - } - } - if (region_Current.neighbors[1][0] != null) { - if (region_Current.neighbors[1][0].finished_Drainage_Simulation == true) { - for(int x = 0; x < regionDim; x++){ - region_Current.chart_Drainage[x][0] = 1 + region_Current.neighbors[1][0].chart_Drainage[x][regionDim-1]; - } - } - } - if (region_Current.neighbors[0][1] != null) { - if (region_Current.neighbors[0][1].finished_Drainage_Simulation == true) { - for(int x = 0; x < regionDim; x++){ - region_Current.chart_Drainage[0][x] = 1 + region_Current.neighbors[0][1].chart_Drainage[regionDim-1][x]; - } - } - } - if (region_Current.neighbors[1][2] != null) { - if (region_Current.neighbors[1][2].finished_Drainage_Simulation == true) { - for(int x = 0; x < regionDim; x++){ - region_Current.chart_Drainage[x][regionDim-1] = 1 + region_Current.neighbors[1][2].chart_Drainage[x][0]; - } - } - } - if (region_Current.neighbors[2][1] != null) { - if (region_Current.neighbors[2][1].finished_Drainage_Simulation == true) { - for(int x = 0; x < regionDim; x++){ - region_Current.chart_Drainage[regionDim-1][x] = 1 + region_Current.neighbors[2][1].chart_Drainage[0][x]; - } - } - } - region_Current.chart_Max_Water_Flow = new int[regionDim][regionDim]; -// while(sum_Array(region_Current.chart_Drainage,region_Dim,region_Dim)>0){ -// region_Current.chart_Drainage = simulate_Drainage(region_Current.chart_Drainage,region_Current.chart_Elevation,Region.REGION_DIMENSION,Region.REGION_DIMENSION); -// for(int x = 0; x < region_Dim; x++){ -// for(int y = 0; y < region_Dim; y++){ -// if(region_Current.chart_Drainage[x][y] > region_Current.chart_Max_Water_Flow[x][y]){ -// region_Current.chart_Max_Water_Flow[x][y] = region_Current.chart_Drainage[x][y]; -// } -// } -// } -// } - region_Current.chart_Drainage = region_Current.chart_Max_Water_Flow; - open_Set.remove(region_Current); - //TODO: When adding regions to the set, check if _their_ neighbors are higher elevation recursively (probably going to need its own function) - for (int i = 0; i < 4; i++) { - if (region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]] != null) { - if (region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]].finished_Drainage_Simulation == false) { - int next_height = region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]].elevation_Goal; - int index_to_insert_at = 0; - Iterator region_Iterator = open_Set.iterator(); - while (region_Iterator.hasNext()) { - Region next = region_Iterator.next(); - if (next.elevation_Goal > next_height) { - index_to_insert_at++; - } else { - break; - } - } - if(!open_Set.contains(region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]])){ - open_Set.add(index_to_insert_at, region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]]); - } - } - } - } - region_Current.finished_Drainage_Simulation = true; - if(open_Set.size() > 0){ - region_Current = open_Set.get(0); - } - } - } - } - - /** - * Creates the internal renderer for displaying data - */ - void createRenderer(){ - frame = new JFrame(); - graphics = new InterpolationDisplay(this); - frame.setBounds(25, 25, 300 + continentPhaseDimension, 300 + continentPhaseDimension); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH); - frame.add(graphics); - frame.setVisible(true); - frame.addKeyListener(new KeyListener(){ - public void keyTyped(KeyEvent ke) { - } - - @Override - public void keyPressed(KeyEvent ke) { - if(ke.getKeyCode() == KeyEvent.VK_C){ - increment_Current_Continent(); - } else if(ke.getKeyCode() == KeyEvent.VK_V){ - incrementDisplayToggle(); - } - } - - @Override - public void keyReleased(KeyEvent ke) { - } - } - ); - } - - /** - * Generates continent objects from the raw designation arrays - */ - void fillContinents(){ - for(int i = 1; i < numberContinents + 1; i++){ - Continent current = new Continent(); - continents.add(current); - //find dimensions - int minX = -1; - int minY = -1; - int maxX = -1; - int maxY = -1; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(continentIdField[x][y] == i){ - if(minX == -1){ - minX = x; - minY = y; - maxX = x; - maxY = y; - } else { - if (x < minX) { - minX = x; - } - if (x > maxX) { - maxX = x; - } - if (y < minY) { - minY = y; - } - if (y > maxY) { - maxY = y; - } - } - } - } - } - //The dimensions of the continent - int dimX = maxX - minX + 1; - int dimY = maxY - minY + 1; - current.dim_x = dimX; - current.dim_y = dimY; - current.chart_Climate = new int[dimX][dimY]; - current.chart_Precipitation = new int[dimX][dimY]; - current.chart_Temperature = new int[dimX][dimY]; - current.chart_Wind_Macro = new int[dimX][dimY]; - current.elevation = new int[dimX][dimY]; - //zero out arrays - for(int x = 0; x < dimX; x++){ - for(int y = 0; y < dimY; y++){ - current.elevation[x][y] = 0; - current.chart_Climate[x][y] = 0; - current.chart_Precipitation[x][y] = 0; - current.chart_Temperature[x][y] = 50; - current.chart_Wind_Macro[x][y] = 0; - } - } - //fill continent level arrays - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(continentIdField[x][y] == i){ - current.size++; - current.elevation[x-minX][y-minY] = elevation[x][y]; - current.chart_Climate[x-minX][y-minY] = climateCategory[x][y]; - current.chart_Precipitation[x-minX][y-minY] = precipitationChart[x][y]; - current.chart_Temperature[x-minX][y-minY] = temperatureChart[x][y]; - current.chart_Wind_Macro[x-minX][y-minY] = wind_field[x][y].x; - } - } - } - //create regions - current.regions = new Region[dimX][dimY]; - for(int x = 0; x < dimX; x++){ - for(int y = 0; y < dimY; y++){ - if(continentIdField[x+minX][y+minY] == i){ - current.regions[x][y] = new Region(); - current.regions[x][y].elevation_Goal = current.elevation[x][y]; - } else { - current.regions[x][y] = null; - } - } - } - generateRegionElevationMaps(current); - } - } - - /** - * Recursive function to floodfill designations of a continent - * @param x The x position to check - * @param y The y position to check - * @param number The continent id of the continent currently being floodfilled - */ - void floodfillContinentNumber(int x, int y, int number){ - continentIdField[x][y] = number; - int offset_X[] = {0,-1,0,1}; - int offset_Y[] = {1,0,-1,0}; - for(int i = 0; i < 4; i++){ - if(x + offset_X[i] >= 0 && x + offset_Y[i] < continentPhaseDimension){ - if(y + offset_Y[i] >= 0 && y + offset_Y[i] < continentPhaseDimension){ - if(oceanParsed[x + offset_X[i]][y + offset_Y[i]] < 25){ - if(continentIdField[x + offset_X[i]][y + offset_Y[i]] == 0){ - floodfillContinentNumber(x + offset_X[i], y + offset_Y[i], number); - } - } - } - } - } - } - - /** - * Uses flood fill to determine continents - */ - void determineContinents(){ - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(oceanParsed[x][y] < 25){ - if(continentIdField[x][y] == 0){ - numberContinents++; - floodfillContinentNumber(x,y,numberContinents); - } - } - } - } - } - - /** - * Generates a map tagging each position into a climate classification - * - * 0 - ocean - * 1 - tropical - * 2 - subtropical - * 3 - temperate - * 4 - dry - * 5 - polar - * 6 - mountainous - * - * @return The classification map - */ - int[][] inferClimateCategory(){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++) { - if (elevation[x][y] > OCEAN_THRESHOLD) { - if (elevation[x][y] > MOUNTAIN_THRESHOLD) { - rVal[x][y] = 6; - } else { - if (precipitationChart[x][y] > 0) { - if (temperatureChart[x][y] > 94) { - rVal[x][y] = 1; - } else if (temperatureChart[x][y] > 80) { - rVal[x][y] = 2; - } else if (temperatureChart[x][y] > 40) { - rVal[x][y] = 3; - } else { - rVal[x][y] = 5; - } - } else { - if (temperatureChart[x][y] > 75) { - rVal[x][y] = 4; - } else if (temperatureChart[x][y] > 40) { - rVal[x][y] = 4; - } else if (temperatureChart[x][y] > 25) { - rVal[x][y] = 5; - } else { - rVal[x][y] = 5; - } - } - } - } else { - if (temperatureChart[x][y] > 75) { - rVal[x][y] = 0; - } else if (temperatureChart[x][y] > 40) { - rVal[x][y] = 0; - } else if (temperatureChart[x][y] > 25) { - rVal[x][y] = 0; - } else { - rVal[x][y] = 5; - } - } - } - } - return rVal; - } - - /** - * Generates a map representing average temperature - * @return The map - */ - int[][] generateTemperatureChart(){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - int temp = 0; - for(int x = 0; x < continentPhaseDimension; x++){ - temp = (int)(100 - (Math.abs(x - continentPhaseDimension/2.0) / (continentPhaseDimension/2.0) * 100.0)); - temp = (int)(Math.sin(temp/100.0 * Math.PI / 2.0) * 100.0); - for(int y = 0; y < continentPhaseDimension; y++){ - rVal[y][x] = temp; - } - } - return rVal; - } - - /** - * Calculates rain shadowing across the heightmap - * @param elevation_raws The elevation data - * @param ocean_parsed The ocean marking map - * @param wind_field The field of average wind flow map - * @return The map marking rain shadows - */ - int[][] calculateRainShadows(int[][] elevation_raws, int[][] ocean_parsed, Vector[][] wind_field){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - int rainParticles[][] = new int[continentPhaseDimension][continentPhaseDimension]; - int numRainParticles = 0; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(ocean_parsed[x][y] > 1){ - rainParticles[x][y] = 1; - rVal[x][y] = 1; - numRainParticles++; - } - } - } - while(numRainParticles > continentPhaseDimension * 2) { - for (int x = 0; x < continentPhaseDimension; x++) { - for (int y = 0; y < continentPhaseDimension; y++) { - if (rainParticles[x][y] >= 1) { - if (wind_field[x][y].x == -1) { - if (x > 0) { - if (elevation_raws[x - 1][y] < RAIN_SHADOW_BLOCKER_HEIGHT) { - rainParticles[x - 1][y] = 1; - rVal[x - 1][y] = 1; - } else { - } - if (wind_field[x][y].y == -1) { - if (y > 0) { - if (elevation_raws[x - 1][y - 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { - rainParticles[x - 1][y - 1] = 1; - rVal[x - 1][y - 1] = 1; - } - } - } else if (wind_field[x][y].y == 1) { - if (y < continentPhaseDimension - 1) { - if (elevation_raws[x - 1][y + 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { - rainParticles[x - 1][y + 1] = 1; - rVal[x - 1][y + 1] = 1; - } - } - } - } else { - } - } else { - if (x < continentPhaseDimension - 1) { - if (elevation_raws[x + 1][y] < RAIN_SHADOW_BLOCKER_HEIGHT) { - rainParticles[x + 1][y] = 1; - rVal[x + 1][y] = 1; - } else { - } - if (wind_field[x][y].y == -1) { - if (y > 0) { - if (elevation_raws[x + 1][y - 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { - rainParticles[x + 1][y - 1] = 1; - rVal[x + 1][y - 1] = 1; - } - } - } else if (wind_field[x][y].y == 1) { - if (y < continentPhaseDimension - 1) { - if (elevation_raws[x + 1][y + 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { - rainParticles[x + 1][y + 1] = 1; - rVal[x + 1][y + 1] = 1; - } - } - } - } else { - } - } - rainParticles[x][y] = 0; - } - } - } - numRainParticles = 0; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(rainParticles[x][y] == 1){ - numRainParticles++; - } - } - } - } - return rVal; - } - - /** - * Calculates a vector field representing average wind flow across the heightmap - * @return The vector field representing average wind flow - */ - Vector[][] mapWindField(){ - Vector rVal[][] = new Vector[continentPhaseDimension][continentPhaseDimension]; - //One sixth of the dimension of the heightmap - int sixth = (int)(continentPhaseDimension / 6.0); - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(x < sixth){ - rVal[x][y] = new Vector(); - rVal[x][y].x = -1; - rVal[x][y].y = -1; - } else if(x < sixth * 2){ - rVal[x][y] = new Vector(); - rVal[x][y].x = 1; - rVal[x][y].y = 1; - } else if(x < sixth * 3){ - rVal[x][y] = new Vector(); - rVal[x][y].x = -1; - rVal[x][y].y = -1; - } else if(x < sixth * 4){ - rVal[x][y] = new Vector(); - rVal[x][y].x = -1; - rVal[x][y].y = 1; - } else if(x < sixth * 5){ - rVal[x][y] = new Vector(); - rVal[x][y].x = 1; - rVal[x][y].y = -1; - } else { - rVal[x][y] = new Vector(); - rVal[x][y].x = -1; - rVal[x][y].y = 1; - } - } - } - return rVal; - } - - /** - * Fills out an array that marks positions in the heightmap as oceans or not oceans - * @param data The heightmap - * @return A new array that contains the designations - */ - int[][] parseOceans(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(data[x][y] < OCEAN_THRESHOLD){ - rVal[x][y] = OCEAN_THRESHOLD; - } else { - rVal[x][y] = 0; - } - } - } - return rVal; - } - - /** - * Fills out an array that marks positions in the heightmap as mountains or not mountains - * @param data The heightmap - * @return A new array that contains the designations - */ - int[][] parseMountainscapes(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(data[x][y] > MOUNTAIN_THRESHOLD){ - rVal[x][y] = MOUNTAIN_THRESHOLD; - } else { - rVal[x][y] = 0; - } - } - } - return rVal; - } - - int[][] closed_Set; - - int floodfill_Reach_Threshold(int[][] data, int[][] closed_Set, int x, int y, int min_val, int max_val){ - int rVal = 0; - int num_hits; - int offset_X[] = {-1,-1,-1,0,0,0,1,1,1}; - int offset_Y[] = {-1,0,1,-1,0,1,-1,0,1}; - if(data[x][y] > min_val && data[x][y] < max_val){ - closed_Set[x][y] = 1; - for(int i = 0; i < 9; i++){ - if(x + offset_X[i] >= 0 && - x + offset_X[i] < continentPhaseDimension && - y + offset_Y[i] >= 0 && - y + offset_Y[i] < continentPhaseDimension){ - if(closed_Set[x + offset_X[i]][y+offset_Y[i]] == 0){ - rVal = rVal + floodfill_Reach_Threshold(data,closed_Set,x + offset_X[i],y + offset_Y[i], min_val, max_val); - } - } - } - } - return rVal; - } - - int[][] remove_Small_Mountain_Ranged(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - boolean removed_Something = false; -// for (int x = 0; x < DIMENSION; x++) { -// for (int y = 0; y < DIMENSION; y++) { -// rVal[x][y] = data[x][y]; -// } -// } -// while (true) { - for (int x = 0; x < continentPhaseDimension; x++) { - for (int y = 0; y < continentPhaseDimension; y++) { - rVal[x][y] = data[x][y]; - if (data[x][y] > MOUNTAIN_THRESHOLD) { - rVal[x][y] = rVal[x][y] - 5; -// closed_Set = new int[DIMENSION][DIMENSION]; -// if (floodfill_Reach_Threshold(rVal, closed_Set, x, y, mountain_Threshold, 101) < mountain_Range_Size_Minimum) { -// rVal[x][y] = rVal[x][y] - 25; -//// removed_Something = true; -// } - } - } - } -// if(!removed_Something){ -// break; -//// } else { -// removed_Something = false; -// } -// } - return rVal; - } - - /** - * Compresses the extremes of the map towards the center half. - * IE: - * values less than 25 -> Center 50 <- values greater than 75 - * @param data The heightmap to apply the compression filter to - * @return A new heightmap containing the compressed data - */ - int[][] compressionFilter(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(data[x][y] < 25){ - rVal[x][y] = (int)(data[x][y] * 3.0 / 2.0); - } else if(data[x][y] > 75){ - rVal[x][y] = (int)(data[x][y] / 3.0 * 2.0); - } else { - rVal[x][y] = data[x][y]; - } - } - } - return rVal; - } - - /** - * Applies a filter that pushes the terrain upwards by making all values "Three halves" of their original values. - * @param data The heightmap to apply the filter to - * @return A new array containing the data with filter applied - */ - int[][] threeHalvesFilter(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - rVal[x][y] = (int)(data[x][y] * 3.0 / 2.0); - if(rVal[x][y] > 100){ - rVal[x][y] = 100; - } - } - } - return rVal; - } - - int[][] two_third_filter(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - rVal[x][y] = (int)(data[x][y] * 2.0 / 3.0); - if(rVal[x][y] < 0){ - rVal[x][y] = 0; - } - } - } - return rVal; - } - - int[][] high_end_two_third_filter(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++) { - if (data[x][y] > 50) { - rVal[x][y] = (int) (data[x][y] * 2.0 / 3.0); - if (rVal[x][y] < 0) { - rVal[x][y] = 0; - } - } else { - rVal[x][y] = data[x][y]; - } - } - } - return rVal; - } - - int[][] reduce_mid_high_end_filter(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++) { - if (data[x][y] > 50 && data[x][y] < 75) { - rVal[x][y] = (int) (data[x][y] * 2.0 / 3.0); - if (rVal[x][y] < 0) { - rVal[x][y] = 0; - } - } else { - rVal[x][y] = data[x][y]; - } - } - } - return rVal; - } - - /** - * Applies a small radius vertical, horizontal, and radial smoothing kernel to the data - * @param elevationMap The heightmap to apply the filter to - * @return A new array containing the data with filter applied - */ - int[][] smallKernelSmooth(int elevationMap[][]){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - int kernelOffsetX[] = { - -1,0,1, - -1,0,1, - -1,0,1 - }; - int kernelOffsetY[] = { - -1,-1,-1, - 0,0,0, - 1,1,1 - }; - int kernelModifier[] = { - 1,2,1, - 2,4,2, - 1,2,1 - }; - for(int x = 1; x < continentPhaseDimension - 1; x++){ - for(int y = 1; y < continentPhaseDimension - 1; y++){ - int sum = 0; - for(int i = 0; i < 9; i++){ - sum = sum + elevationMap[x+kernelOffsetX[i]][y+kernelOffsetY[i]] * kernelModifier[i]; - } - sum = (int)(sum/13.0); - if(sum > 100){ - sum = 100; - } - rVal[x][y] = sum; - } - } - return rVal; - } - - /** - * Applies a small horizontal, vertical, and radial sharpen filter to the data - * @param elevationMap The heightmap to apply the filter to - * @return A new array containing the data with filter applied - */ - int[][] smallKernelSharpen(int elevationMap[][]){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - int kernelOffsetX[] = { - -1,0,1, - -1,0,1, - -1,0,1 - }; - int kernelOffsetY[] = { - -1,-1,-1, - 0,0,0, - 1,1,1 - }; - int kernelModifier[] = { - 0,-1,0, - -1,5,-1, - 0,-1,0 - }; - for(int x = 1; x < continentPhaseDimension - 1; x++){ - for(int y = 1; y < continentPhaseDimension - 1; y++){ - int sum = 0; - for(int i = 0; i < 9; i++){ - sum = sum + elevationMap[x+kernelOffsetX[i]][y+kernelOffsetY[i]] * kernelModifier[i]; - } -// sum = (int)(sum/13.0); - if(sum > 100){ - sum = 100; - } - if(sum < 0){ - sum = 0; - } - rVal[x][y] = sum; - } - } - return rVal; - } - - - /** - * Applies a vertical, horizontal, and radial smoothing kernel to the heightmap data. - * @param elevationMap The heightmap to apply the filter to - * @return A new array containing the data with filter applied - */ - int[][] smoothTerrainFurther(int elevationMap[][]){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - int kernelOffsetX[] = {-2,-1,0,1,2, - -2,-1,0,1,2, - -2,-1,0,1,2, - -2,-1,0,1,2, - -2,-1,0,1,2, - }; - int kernelOffsetY[] = {-2,-2,-2,-2,-2, - -1,-1,-1,-1,-1, - 0,0,0,0,0, - 1,1,1,1,1, - 2,2,2,2,2}; - int kernelModifier[] = { - 1,4,6,4,1, - 4,16,24,16,4, - 6,24,36,24,6, - 4,16,24,16,4, - 1,4,6,4,1 - }; - for(int x = 2; x < continentPhaseDimension - 2; x++){ - for(int y = 2; y < continentPhaseDimension - 2; y++){ - int sum = 0; - for(int i = 0; i < 25; i++){ - sum = sum + elevationMap[x+kernelOffsetX[i]][y+kernelOffsetY[i]] * kernelModifier[i]; - } - sum = (int)(sum/256.0); - if(sum > 100){ - sum = 100; - } - rVal[x][y] = sum; - } - } - return rVal; - } - - /** - * Applies a filter that makes the land height scale exponentially - * IE Terrain that looks like: - * ^ - * / \ - * / \ - * Becomes - * ^ - * | | - * __ __ - * @param data The data to apply the filter to - * @return A new array containing the data with the filter applied - */ - int[][] landExponentialFilter(int[][] data){ - int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; - for(int x = 0; x < continentPhaseDimension; x++){ - for(int y = 0; y < continentPhaseDimension; y++){ - if(data[x][y] > TerrainGen.OCEAN_THRESHOLD && data[x][y] < 90){ - //This is the total height that can be above land - //eg if ocean level is 25, and the maximum possible height is 100, the maximum height relative to sea level would be 75 - int maxHeightAboveLand = MAX_HEIGHT - OCEAN_THRESHOLD; - //This is the current height above sea level - int currentHeightAboveLand = data[x][y] - OCEAN_THRESHOLD; - //This is the percentage of the total height above sea level that the current height is - //In other words if max is 75 and current is 25, it would be 33% of the total height that it COULD be, relative to sea level - float percentageAboveLand = (float)currentHeightAboveLand / (float)currentHeightAboveLand; - //The weight for the exponential component of the calculation - float exponentialComponentWeight = 0.2f; - //The weight for the existing data - float existingComponentWeight = 0.8f; - //calculate rVal and make sure stays above sealevel as it's terrain after all - rVal[x][y] = (int)(data[x][y] * Math.exp(-(1.0f - percentageAboveLand)) * exponentialComponentWeight + data[x][y] * existingComponentWeight); - if(rVal[x][y] < TerrainGen.OCEAN_THRESHOLD){ - rVal[x][y] = TerrainGen.OCEAN_THRESHOLD + 1; - } - //Because of how exponentials work, this check should never pass - if(Math.exp(-(1.0 - percentageAboveLand)) > 1.0){ - System.out.println("WARNING!!"); - } - } else { - rVal[x][y] = data[x][y]; - } - } - } - return rVal; - } - - int[][] generate_Interpolation(int[][] data){ - /* - Looks at 3x3 kernel - */ - int kernel_offset_x[] = {-1,0,1,-1,0,1,-1,0,1}; - int kernel_offset_y[] = {1,1,1,0,0,0,-1,-1,-1}; - int rVal[][] = new int[100][100]; - return rVal; - } - - /** - * Doubles the dimensions of the map so that the new space can be interpolated linearly using existing values - * @param raw The original input map - * @return The new, interpolated output map - */ - int[][] interpolateElevationRaws(int[][] raw){ - int rVal[][] = new int[continentPhaseDimension * 2][continentPhaseDimension * 2]; - //perform interpolation - for(int x = 0; x < continentPhaseDimension - 1; x++){ - for(int y = 0; y < continentPhaseDimension - 1; y++){ - rVal[x*2][y*2] = raw[x][y]; - rVal[x*2+1][y*2] = (raw[x][y] + raw[x+1][y])/2; - rVal[x*2][y*2+1] = (raw[x][y] + raw[x][y+1])/2; - rVal[x*2+1][y*2+1] = (raw[x][y] + raw[x+1][y+1])/2; - } - } - //double dimension to account for new array size - continentPhaseDimension = continentPhaseDimension * 2; - return rVal; - } - - int[][] load_Data(String path){ - int rVal[][] = null; - try (BufferedReader br = new BufferedReader(new FileReader(path));){ - String line; - line = br.readLine(); - int dim_x = Integer.parseInt(line); - interpolationPhaseDimension = dim_x; - line = br.readLine(); - int dim_y = Integer.parseInt(line); - rVal = new int[dim_x][dim_y]; - int incrementer_x = 0; - int incrementer_y = 0; - while ((line = br.readLine()) != null) { - incrementer_y = 0; - while(line != ""){ - rVal[incrementer_x][incrementer_y] = Integer.parseInt(Utilities.string_To_First_Space(line)); - if(line.contains(" ")){ - line = line.substring(Utilities.get_Position_Of_Next_Instance_Of_Char_In_String(line, ' ') + 1); - } else { - line = ""; - } - incrementer_y++; - } - incrementer_x++; - } - } catch (FileNotFoundException ex) { - } catch (IOException ex) { - } - return rVal; - } - - public void increment_Current_Continent(){ - current_Continent++; - if (current_Continent > numberContinents - 1) { - current_Continent = 0; - } - while(continents.get(current_Continent).size < 50){ - current_Continent++; - if(current_Continent > numberContinents - 1){ - current_Continent = 0; - } - } - } - - /** - * Increments the display mode of the built in data renderer - */ - public void incrementDisplayToggle(){ - displayToggle++; - if(displayToggle > max_Display_Toggle){ - displayToggle = 0; - } - } - - //the interpolation ratio applied to the statically generated terrain - public void setInterpolationRatio(int interpRatio){ - interpolationRatio = interpRatio; - } - - //the interpolation ratio applied to the dynamically generated terrain per chunk - public void setDynamicInterpolationRatio(int dynInterpRatio){ - dynamicInterpRatio = dynInterpRatio; - } - - //Sets the vertical interpolation ratio - public void setVerticalInterpolationRatio(int vertInterpRatio){ - verticalInterpolationRatio = vertInterpRatio; - } - - public void setRandomSeed(long seed){ - Utilities.seed_Random_Functions(seed); - } -} diff --git a/src/main/java/electrosphere/server/terrain/generation/TerrainGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TerrainGenerator.java index 12e13754..e3227f45 100644 --- a/src/main/java/electrosphere/server/terrain/generation/TerrainGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/TerrainGenerator.java @@ -1,645 +1,1382 @@ package electrosphere.server.terrain.generation; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; 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; + +import javax.swing.JFrame; + +import electrosphere.server.terrain.models.TerrainModel; /** - * Core continent phase terrain generator + * + * @author satellite */ -class TerrainGenerator { +public class TerrainGenerator { + int elevation[][]; + int continentPhaseDimension = 128; + //the interpolation ratio applied to the statically generated terrain + int interpolationRatio = 1; + //the interpolated phase dimension + int interpolationPhaseDimension = continentPhaseDimension * interpolationRatio; + //vertical static interpolation ratio + int verticalInterpolationRatio = 10; + //the interpolation ratio applied to the dynamically generated terrain per chunk + int dynamicInterpRatio = 1000; + JFrame frame; + InterpolationDisplay graphics; + int brightness = 0; + int brightnessHoldIncrementer = 0; + boolean brightnessIncreasing = true; + int displayToggle = 0; + int max_Display_Toggle = 6; - //size of a parallelized chunk - static final int PARALLEL_CHUNK_SIZE = 32; - //number of threads for threadpool - static final int THREAD_POOL_COUNT = 16; + static final int MAX_HEIGHT = 100; + + int[][] mountainParsed; + static final int MOUNTAIN_THRESHOLD = 75; + static final int MOUNTAIN_RANGE_SIZE_MINIMUM = 10; + + int[][] oceanParsed; + static final int OCEAN_THRESHOLD = 25; + + Vector wind_field[][]; + + int[][] precipitationChart; + static final int RAIN_SHADOW_BLOCKER_HEIGHT = 80; + + int[][] temperatureChart; - //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; + static final int EROSION_INTERPOLATION_RATIO = 4; + + /* + 0 - ocean + 1 - tropical + 2 - subtropical + 3 - temperate + 4 - dry + 5 - polar + 6 - mountainous + */ + public int[][] climateCategory; + + public int numberContinents = 0; + public int[][] continentIdField; + + public List continents = new ArrayList(); + + + public int current_Continent = 1; + public int current_Region_X = 0; + public int current_Region_Y = 0; - Random rand; + Random rand = new Random(); - //thread pool for parallelized force calculation - ThreadPoolExecutor threadPool; + float[][] erosionHeightmap; + /** - * Constructor - * @param seed Seed for random + * Generates a new TerrainModel + * @return The TerrainModel */ - protected TerrainGenerator(long seed){ - this.rand = new Random(seed); - threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(THREAD_POOL_COUNT); + public TerrainModel generateModel(){ + TerrainModel rVal; + + //generate continent-phase terrain + TectonicSimulation tergen = new TectonicSimulation(1); + tergen.setDimension(continentPhaseDimension); + tergen.setLifespan(15000); + tergen.run(); + elevation = tergen.getTerrain(); + + //interpolate terrain to smooth what has been received from TerrainGenerator + elevation = interpolateElevationRaws(elevation); + + //While the TerrainGenerator does a great job of outlining a rough sketch of how the terrain should look, + //a lot of filters are applied to give the terrain a much cleaner and realistic look. This is arguably + //where the real magic of the terrain generation happens + + //flatten the terrain and raise it above sealevel + elevation = compressionFilter(elevation); + elevation = threeHalvesFilter(elevation); + + //knead the terrain + elevation = smoothTerrainFurther(elevation); + elevation = smallKernelSharpen(elevation); + elevation = smoothTerrainFurther(elevation); + + //raise terrain + elevation = threeHalvesFilter(elevation); + + //knead terrain mode + elevation = smallKernelSmooth(elevation); + elevation = smallKernelSharpen(elevation); + elevation = smoothTerrainFurther(elevation); + elevation = smoothTerrainFurther(elevation); + + //Make the land part of the heightmap scale exponentially instead of linearly + //Basically makes the terrain sharper + elevation = landExponentialFilter(elevation); + elevation = landExponentialFilter(elevation); + elevation = landExponentialFilter(elevation); + elevation = landExponentialFilter(elevation); + elevation = landExponentialFilter(elevation); + elevation = landExponentialFilter(elevation); + + mountainParsed = new int[continentPhaseDimension][continentPhaseDimension]; + oceanParsed = new int[continentPhaseDimension][continentPhaseDimension]; + continentIdField = new int[continentPhaseDimension][continentPhaseDimension]; + + //generate designation arrays for features about the terrain + mountainParsed = parseMountainscapes(elevation); + oceanParsed = parseOceans(elevation); + wind_field = mapWindField(); + precipitationChart = calculateRainShadows(elevation,oceanParsed,wind_field); + temperatureChart = generateTemperatureChart(); + climateCategory = inferClimateCategory(); + determineContinents(); + + //generate continent objects + fillContinents(); + + float[][] modelInput = interpolateIntegerElevations(elevation); + + modelInput = applyRandomizationFilter(modelInput); + + + float[][] erosionInput = new float[elevation.length][elevation[0].length]; + for(int x = 0; x < elevation.length; x++){ + for(int y = 0; y < elevation[0].length; y++){ + erosionInput[x][y] = elevation[x][y]; + } + } + ErosionSimulation erosionSimulation = new ErosionSimulation(erosionInput, OCEAN_THRESHOLD, EROSION_INTERPOLATION_RATIO, rand.nextLong()); + erosionSimulation.simulate(); + erosionHeightmap = erosionSimulation.getData(); + + rVal = new TerrainModel( + interpolationPhaseDimension, + modelInput, + OCEAN_THRESHOLD * verticalInterpolationRatio, + MOUNTAIN_THRESHOLD * verticalInterpolationRatio, + dynamicInterpRatio + ); + + + //create internal renderer + createRenderer(); + + boolean test = true; + while(test){ + if(brightnessIncreasing){ + if(brightness < 100){ + brightness++; + } else { + } + } else { + if(brightness > 0){ + brightness--; + } else { + brightnessIncreasing = true; + displayToggle++; + if(displayToggle > 1){ + displayToggle = 0; + } + } + } + frame.repaint(); + Utilities.sleep(10); + } + + return rVal; } - /** - * 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!"); + float[][] applyRandomizationFilter(float[][] elevation){ + float[][] rVal = new float[elevation.length][elevation[0].length]; + for(int x = 0; x < continentPhaseDimension-1; x++){ + for(int y = 0; y < continentPhaseDimension-1; y++){ + rVal[x][y] = elevation[x][y] + Utilities.random_Integer(0, (int)(elevation[x][y] / verticalInterpolationRatio), rand); + } + } + return rVal; + } + + float[][] interpolateIntegerElevations(int[][] elevation){ + int interpRatio = interpolationRatio; + float[][] rVal = new float[continentPhaseDimension * interpRatio][continentPhaseDimension * interpRatio]; + for(int x = 0; x < continentPhaseDimension-1; x++){ + for(int y = 0; y < continentPhaseDimension-1; y++){ + for(int i = 0; i < interpRatio; i++){ + for(int j = 0; j < interpRatio; j++){ + float ratio1 = (i*j) * 1.0f / (interpRatio*interpRatio); + float ratio2 = ((interpRatio-i)*j) * 1.0f / (interpRatio*interpRatio); + float ratio3 = (i*(interpRatio-j)) * 1.0f / (interpRatio*interpRatio); + float ratio4 = ((interpRatio-i)*(interpRatio-j)) * 1.0f / (interpRatio*interpRatio); + rVal[x*interpRatio+interpRatio-i][y*interpRatio+interpRatio-j] = + (elevation[x ][y ] * ratio1 + + elevation[x+1][y ] * ratio2 + + elevation[x ][y+1] * ratio3 + + elevation[x+1][y+1] * ratio4) * + verticalInterpolationRatio + ; + } + } + } + } + interpolationPhaseDimension = continentPhaseDimension * interpRatio; + return rVal; + } + + public void anchor_To_Real_Region(){ + //current_Region_X + //continents + //current_Continent + Continent target_continent = continents.get(current_Continent); + for(int x = 0; x < target_continent.dim_x; x++){ + for(int y = 0; y < target_continent.dim_y; y++){ + if(target_continent.regions[x][y]!=null){ + current_Region_X = x; + current_Region_Y = y; + x = target_continent.dim_x; + y = target_continent.dim_y; + } else { + + } + } } } - /** - * Sets the simulation lifespan for the Continent Phase - * @param newLifespan The lifespan in units of simulation frames - */ - protected void setLifespan(int newLifespan){ - lifespan = newLifespan; + int[][] simulate_Drainage(int[][] water_level, int[][] elevation, int dim_x, int dim_y){ + int[][] rVal = new int[dim_x][dim_y]; + for(int x = 0; x < dim_x; x++){ + for(int y = 0; y < dim_y; y++){ + rVal[x][y] = 0; + } + } + int kernel_offset_x[] = { + -1,0,0,1 + }; + int kernel_offset_y[] = { + 0,-1,1,0 + }; + int offset_kernel_size = 4; + for(int x = 0; x < dim_x; x++){ + for(int y = 0; y < dim_y; y++){ + int num_Water_Particles = water_level[x][y]; + int elevation_Center = elevation[x][y]; + int attractor_Values[] = new int[offset_kernel_size]; + for(int j = 0; j < offset_kernel_size; j++) { + if(x + kernel_offset_x[j] >= 0 && x + kernel_offset_x[j] < dim_x && y + kernel_offset_y[j] >= 0 && y + kernel_offset_y[j] < dim_y) { + if(elevation[x+kernel_offset_x[j]][y+kernel_offset_y[j]] < elevation[x][y]){ + attractor_Values[j] = (int)((elevation[x][y]-elevation[x+kernel_offset_x[j]][y+kernel_offset_y[j]])*1.0*water_level[x+kernel_offset_x[j]][y+kernel_offset_y[j]]); + } else { + attractor_Values[j] = 0; + } + } + } + int rand_num = 0; + int rand_Space = 0; + for(int j = 0; j < offset_kernel_size; j++){ + rand_Space = rand_Space + attractor_Values[j]; + } + rand_Space = rand_Space + water_level[x][y]; // account for water not moving downwards + int rand_Incrementer = 0; + boolean done = false; + for(int i = 0; i < num_Water_Particles; i++){ + done = false; + rand_Incrementer = 0; + rand_num = Utilities.random_Integer(0, rand_Space, rand); + for(int j = 0; j < offset_kernel_size; j++){ + rand_Incrementer = rand_Incrementer + attractor_Values[j]; + if(rand_num < rand_Incrementer){ + if(attractor_Values[j] > 0){ + if(rVal[x+kernel_offset_x[j]][y+kernel_offset_y[j]] < 400){ + rVal[x+kernel_offset_x[j]][y+kernel_offset_y[j]]++; + } + done = true; + } + } + } + if(!done){ + rVal[x][y]++; + } + } + } + } + return rVal; + } + + int sumIntegerArray(int[][] arr, int dim_x, int dim_y){ + int sum = 0; + for(int x = 0; x < dim_x; x++){ + for(int y = 0; y < dim_y; y++){ + sum = sum + arr[x][y]; + } + } + return sum; } /** - * Runs the continent phase simulation. Blocks until completed + * Generates an elevation map of a given continent + * @param c The continent to generate off of */ - 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) { + void generateRegionElevationMaps(Continent c){ + int neighborOffsetX[] = { + 1,0,-1,1,-1,1,0,-1 + }; + int neighborOffsetY[] = { + 1,1,1,0,0,-1,-1,-1 + }; + //generate neighbor links + for(int x = 0; x < c.dim_x; x++){ + for(int y = 0; y < c.dim_y; y++){ + if(c.regions[x][y]!=null){ + for(int i = 0; i < 8; i++){ + if(x + neighborOffsetX[i] >= 0 && x + neighborOffsetX[i] < c.dim_x && y + neighborOffsetY[i] >= 0 && y + neighborOffsetY[i] < c.dim_y){ + //+1 comes from getting the center of the kernel + //ie kernel is 3x3 therefore the center position is at 1,1 array-wise + c.regions[x][y].neighbors[neighborOffsetX[i]+1][neighborOffsetY[i]+1] = c.regions[x + neighborOffsetX[i]][y + neighborOffsetY[i]]; + } else { + c.regions[x][y].neighbors[neighborOffsetX[i]+1][neighborOffsetY[i]+1] = null; + } + } + } + } + } + int regionDim = Region.REGION_DIMENSION; + //set elevation goals + for(int x = 0; x < c.dim_x; x++){ + for(int y = 0; y < c.dim_y; y++){ + if(c.regions[x][y]!=null){ + c.regions[x][y].elevation_Goal = c.elevation[x][y]; + } + } + } + //interpolate base elevation values + int interpolation_kernel_offset_x[] = {0,1,0,1}; + int interpolation_kernel_offset_y[] = {0,0,1,1}; + int interpolation_kernel_size = 4; + int corner_Vals[][] = new int[2][2]; + for(int x = 0; x < c.dim_x; x++){ + for(int y = 0; y < c.dim_y; y++){ + if (c.regions[x][y] != null) { + c.regions[x][y].chart_Elevation = new int[Region.REGION_DIMENSION][Region.REGION_DIMENSION]; + //first the north-west corner + for(int i = 0; i < interpolation_kernel_size; i++){ + if(x-1+interpolation_kernel_offset_x[i] >= 0 && x-1+interpolation_kernel_offset_x[i] < c.dim_x && + y-1+interpolation_kernel_offset_y[i] >= 0 && y-1+interpolation_kernel_offset_y[i] < c.dim_y){ + if(c.regions[x-1+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]]!=null){ + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = + c.regions[x-1+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]].elevation_Goal; + } else { + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; + } + } + } + int midpoint = (int)(Region.REGION_DIMENSION/2.0); + int sum = 0; + for(int i = 0; i < midpoint; i++){ + for(int j = 0; j < midpoint; j++){ + sum = 0; + sum = sum + (midpoint-i)*(midpoint-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; + sum = sum + (i)*(midpoint-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; + sum = sum + (midpoint-i)*(j)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; + sum = sum + (i)*(j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; + c.regions[x][y].chart_Elevation[i][j] = sum; + } + } + //now the north-east corner + for(int i = 0; i < interpolation_kernel_size; i++){ + if(x+interpolation_kernel_offset_x[i] >= 0 && x+interpolation_kernel_offset_x[i] < c.dim_x && + y-1+interpolation_kernel_offset_y[i] >= 0 && y-1+interpolation_kernel_offset_y[i] < c.dim_y){ + if(c.regions[x+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]]!=null){ + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = + c.regions[x+interpolation_kernel_offset_x[i]][y-1+interpolation_kernel_offset_y[i]].elevation_Goal; + } else { + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; + } + } + } + for(int i = midpoint; i < Region.REGION_DIMENSION; i++){ + for(int j = 0; j < midpoint; j++){ + sum = 0; + sum = sum + (Region.REGION_DIMENSION-i)*(midpoint-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; + sum = sum + (i-midpoint)*(midpoint-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; + sum = sum + (Region.REGION_DIMENSION-i)*(j)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; + sum = sum + (i-midpoint)*(j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; + c.regions[x][y].chart_Elevation[i][j] = sum; + } + } + //now the south-west corner + for(int i = 0; i < interpolation_kernel_size; i++){ + if(x-1+interpolation_kernel_offset_x[i] >= 0 && x-1+interpolation_kernel_offset_x[i] < c.dim_x && + y+interpolation_kernel_offset_y[i] >= 0 && y+interpolation_kernel_offset_y[i] < c.dim_y){ + if(c.regions[x-1+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]]!=null){ + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = + c.regions[x-1+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]].elevation_Goal; + } else { + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; + } + } + } + for(int i = 0; i < midpoint; i++){ + for(int j = midpoint; j < Region.REGION_DIMENSION; j++){ + sum = 0; + sum = sum + (midpoint-i)*(Region.REGION_DIMENSION-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; + sum = sum + (i)*(Region.REGION_DIMENSION-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; + sum = sum + (midpoint-i)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; + sum = sum + (i)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; + c.regions[x][y].chart_Elevation[i][j] = sum; + } + } + //now the south-east corner + for(int i = 0; i < interpolation_kernel_size; i++){ + if(x+interpolation_kernel_offset_x[i] >= 0 && x+interpolation_kernel_offset_x[i] < c.dim_x && + y+interpolation_kernel_offset_y[i] >= 0 && y+interpolation_kernel_offset_y[i] < c.dim_y){ + if(c.regions[x+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]]!=null){ + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = + c.regions[x+interpolation_kernel_offset_x[i]][y+interpolation_kernel_offset_y[i]].elevation_Goal; + } else { + corner_Vals[interpolation_kernel_offset_x[i]][interpolation_kernel_offset_y[i]] = 0; + } + } + } + for(int i = midpoint; i < Region.REGION_DIMENSION; i++){ + for(int j = midpoint; j < Region.REGION_DIMENSION; j++){ + sum = 0; + sum = sum + (Region.REGION_DIMENSION-i)*(Region.REGION_DIMENSION-j)*corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[0]]; + sum = sum + (i-midpoint)*(Region.REGION_DIMENSION-j)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[0]]; + sum = sum + (Region.REGION_DIMENSION-i)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[0]][interpolation_kernel_offset_y[1]]; + sum = sum + (i-midpoint)*(j-midpoint)* corner_Vals[interpolation_kernel_offset_x[1]][interpolation_kernel_offset_y[1]]; + c.regions[x][y].chart_Elevation[i][j] = sum; + + } + } + } + } + } + Region region_Current = null; + for(int x = 0; x < c.dim_x; x++){ + for(int y = 0; y < c.dim_y; y++){ + if(c.regions[x][y]!=null){ + if(region_Current == null){ + region_Current = c.regions[x][y]; + } else if(c.regions[x][y].elevation_Goal > region_Current.elevation_Goal){ + region_Current = c.regions[x][y]; + } + } + } + } + //drainage simulation + //operates on an open set + int adjacency_array_x[] = { + 1,0,1,2 + }; + int adjacency_array_y[] = { + 0,1,2,1 + }; + boolean simulate_Drainage = true; + List open_Set = new ArrayList(); + open_Set.add(region_Current); + if(simulate_Drainage){ + while(!open_Set.isEmpty()){ + region_Current.chart_Drainage = new int[regionDim][regionDim]; + for(int x = 0; x < regionDim; x++){ + for(int y = 0; y < regionDim; y++){ + region_Current.chart_Drainage[x][y] = 1; + } + } + if (region_Current.neighbors[1][0] != null) { + if (region_Current.neighbors[1][0].finished_Drainage_Simulation == true) { + for(int x = 0; x < regionDim; x++){ + region_Current.chart_Drainage[x][0] = 1 + region_Current.neighbors[1][0].chart_Drainage[x][regionDim-1]; + } + } + } + if (region_Current.neighbors[0][1] != null) { + if (region_Current.neighbors[0][1].finished_Drainage_Simulation == true) { + for(int x = 0; x < regionDim; x++){ + region_Current.chart_Drainage[0][x] = 1 + region_Current.neighbors[0][1].chart_Drainage[regionDim-1][x]; + } + } + } + if (region_Current.neighbors[1][2] != null) { + if (region_Current.neighbors[1][2].finished_Drainage_Simulation == true) { + for(int x = 0; x < regionDim; x++){ + region_Current.chart_Drainage[x][regionDim-1] = 1 + region_Current.neighbors[1][2].chart_Drainage[x][0]; + } + } + } + if (region_Current.neighbors[2][1] != null) { + if (region_Current.neighbors[2][1].finished_Drainage_Simulation == true) { + for(int x = 0; x < regionDim; x++){ + region_Current.chart_Drainage[regionDim-1][x] = 1 + region_Current.neighbors[2][1].chart_Drainage[0][x]; + } + } + } + region_Current.chart_Max_Water_Flow = new int[regionDim][regionDim]; +// while(sum_Array(region_Current.chart_Drainage,region_Dim,region_Dim)>0){ +// region_Current.chart_Drainage = simulate_Drainage(region_Current.chart_Drainage,region_Current.chart_Elevation,Region.REGION_DIMENSION,Region.REGION_DIMENSION); +// for(int x = 0; x < region_Dim; x++){ +// for(int y = 0; y < region_Dim; y++){ +// if(region_Current.chart_Drainage[x][y] > region_Current.chart_Max_Water_Flow[x][y]){ +// region_Current.chart_Max_Water_Flow[x][y] = region_Current.chart_Drainage[x][y]; +// } +// } +// } // } - 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]--; - } + region_Current.chart_Drainage = region_Current.chart_Max_Water_Flow; + open_Set.remove(region_Current); + //TODO: When adding regions to the set, check if _their_ neighbors are higher elevation recursively (probably going to need its own function) + for (int i = 0; i < 4; i++) { + if (region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]] != null) { + if (region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]].finished_Drainage_Simulation == false) { + int next_height = region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]].elevation_Goal; + int index_to_insert_at = 0; + Iterator region_Iterator = open_Set.iterator(); + while (region_Iterator.hasNext()) { + Region next = region_Iterator.next(); + if (next.elevation_Goal > next_height) { + index_to_insert_at++; } else { + break; } } + if(!open_Set.contains(region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]])){ + open_Set.add(index_to_insert_at, region_Current.neighbors[adjacency_array_x[i]][adjacency_array_y[i]]); + } + } + } + } + region_Current.finished_Drainage_Simulation = true; + if(open_Set.size() > 0){ + region_Current = open_Set.get(0); + } + } + } + } + + /** + * Creates the internal renderer for displaying data + */ + void createRenderer(){ + frame = new JFrame(); + graphics = new InterpolationDisplay(this); + frame.setBounds(25, 25, 300 + continentPhaseDimension, 300 + continentPhaseDimension); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH); + frame.add(graphics); + frame.setVisible(true); + frame.addKeyListener(new KeyListener(){ + public void keyTyped(KeyEvent ke) { + } + + @Override + public void keyPressed(KeyEvent ke) { + if(ke.getKeyCode() == KeyEvent.VK_C){ + increment_Current_Continent(); + } else if(ke.getKeyCode() == KeyEvent.VK_V){ + incrementDisplayToggle(); + } + } + + @Override + public void keyReleased(KeyEvent ke) { + } + } + ); + } + + /** + * Generates continent objects from the raw designation arrays + */ + void fillContinents(){ + for(int i = 1; i < numberContinents + 1; i++){ + Continent current = new Continent(); + continents.add(current); + //find dimensions + int minX = -1; + int minY = -1; + int maxX = -1; + int maxY = -1; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(continentIdField[x][y] == i){ + if(minX == -1){ + minX = x; + minY = y; + maxX = x; + maxY = y; + } else { + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } + if (y > maxY) { + maxY = y; + } + } + } + } + } + //The dimensions of the continent + int dimX = maxX - minX + 1; + int dimY = maxY - minY + 1; + current.dim_x = dimX; + current.dim_y = dimY; + current.chart_Climate = new int[dimX][dimY]; + current.chart_Precipitation = new int[dimX][dimY]; + current.chart_Temperature = new int[dimX][dimY]; + current.chart_Wind_Macro = new int[dimX][dimY]; + current.elevation = new int[dimX][dimY]; + //zero out arrays + for(int x = 0; x < dimX; x++){ + for(int y = 0; y < dimY; y++){ + current.elevation[x][y] = 0; + current.chart_Climate[x][y] = 0; + current.chart_Precipitation[x][y] = 0; + current.chart_Temperature[x][y] = 50; + current.chart_Wind_Macro[x][y] = 0; + } + } + //fill continent level arrays + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(continentIdField[x][y] == i){ + current.size++; + current.elevation[x-minX][y-minY] = elevation[x][y]; + current.chart_Climate[x-minX][y-minY] = climateCategory[x][y]; + current.chart_Precipitation[x-minX][y-minY] = precipitationChart[x][y]; + current.chart_Temperature[x-minX][y-minY] = temperatureChart[x][y]; + current.chart_Wind_Macro[x-minX][y-minY] = wind_field[x][y].x; + } + } + } + //create regions + current.regions = new Region[dimX][dimY]; + for(int x = 0; x < dimX; x++){ + for(int y = 0; y < dimY; y++){ + if(continentIdField[x+minX][y+minY] == i){ + current.regions[x][y] = new Region(); + current.regions[x][y].elevation_Goal = current.elevation[x][y]; } 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]--; - } + current.regions[x][y] = null; + } + } + } + generateRegionElevationMaps(current); + } + } + + /** + * Recursive function to floodfill designations of a continent + * @param x The x position to check + * @param y The y position to check + * @param number The continent id of the continent currently being floodfilled + */ + void floodfillContinentNumber(int x, int y, int number){ + continentIdField[x][y] = number; + int offset_X[] = {0,-1,0,1}; + int offset_Y[] = {1,0,-1,0}; + for(int i = 0; i < 4; i++){ + if(x + offset_X[i] >= 0 && x + offset_Y[i] < continentPhaseDimension){ + if(y + offset_Y[i] >= 0 && y + offset_Y[i] < continentPhaseDimension){ + if(oceanParsed[x + offset_X[i]][y + offset_Y[i]] < 25){ + if(continentIdField[x + offset_X[i]][y + offset_Y[i]] == 0){ + floodfillContinentNumber(x + offset_X[i], y + offset_Y[i], number); + } + } + } + } + } + } + + /** + * Uses flood fill to determine continents + */ + void determineContinents(){ + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(oceanParsed[x][y] < 25){ + if(continentIdField[x][y] == 0){ + numberContinents++; + floodfillContinentNumber(x,y,numberContinents); + } + } + } + } + } + + /** + * Generates a map tagging each position into a climate classification + * + * 0 - ocean + * 1 - tropical + * 2 - subtropical + * 3 - temperate + * 4 - dry + * 5 - polar + * 6 - mountainous + * + * @return The classification map + */ + int[][] inferClimateCategory(){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++) { + if (elevation[x][y] > OCEAN_THRESHOLD) { + if (elevation[x][y] > MOUNTAIN_THRESHOLD) { + rVal[x][y] = 6; + } else { + if (precipitationChart[x][y] > 0) { + if (temperatureChart[x][y] > 94) { + rVal[x][y] = 1; + } else if (temperatureChart[x][y] > 80) { + rVal[x][y] = 2; + } else if (temperatureChart[x][y] > 40) { + rVal[x][y] = 3; } else { + rVal[x][y] = 5; + } + } else { + if (temperatureChart[x][y] > 75) { + rVal[x][y] = 4; + } else if (temperatureChart[x][y] > 40) { + rVal[x][y] = 4; + } else if (temperatureChart[x][y] > 25) { + rVal[x][y] = 5; + } else { + rVal[x][y] = 5; } } } } 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]--; + if (temperatureChart[x][y] > 75) { + rVal[x][y] = 0; + } else if (temperatureChart[x][y] > 40) { + rVal[x][y] = 0; + } else if (temperatureChart[x][y] > 25) { + rVal[x][y] = 0; + } else { + rVal[x][y] = 5; + } + } + } + } + return rVal; + } + + /** + * Generates a map representing average temperature + * @return The map + */ + int[][] generateTemperatureChart(){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + int temp = 0; + for(int x = 0; x < continentPhaseDimension; x++){ + temp = (int)(100 - (Math.abs(x - continentPhaseDimension/2.0) / (continentPhaseDimension/2.0) * 100.0)); + temp = (int)(Math.sin(temp/100.0 * Math.PI / 2.0) * 100.0); + for(int y = 0; y < continentPhaseDimension; y++){ + rVal[y][x] = temp; + } + } + return rVal; + } + + /** + * Calculates rain shadowing across the heightmap + * @param elevation_raws The elevation data + * @param ocean_parsed The ocean marking map + * @param wind_field The field of average wind flow map + * @return The map marking rain shadows + */ + int[][] calculateRainShadows(int[][] elevation_raws, int[][] ocean_parsed, Vector[][] wind_field){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + int rainParticles[][] = new int[continentPhaseDimension][continentPhaseDimension]; + int numRainParticles = 0; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(ocean_parsed[x][y] > 1){ + rainParticles[x][y] = 1; + rVal[x][y] = 1; + numRainParticles++; + } + } + } + while(numRainParticles > continentPhaseDimension * 2) { + for (int x = 0; x < continentPhaseDimension; x++) { + for (int y = 0; y < continentPhaseDimension; y++) { + if (rainParticles[x][y] >= 1) { + if (wind_field[x][y].x == -1) { + if (x > 0) { + if (elevation_raws[x - 1][y] < RAIN_SHADOW_BLOCKER_HEIGHT) { + rainParticles[x - 1][y] = 1; + rVal[x - 1][y] = 1; + } else { } + if (wind_field[x][y].y == -1) { + if (y > 0) { + if (elevation_raws[x - 1][y - 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { + rainParticles[x - 1][y - 1] = 1; + rVal[x - 1][y - 1] = 1; + } + } + } else if (wind_field[x][y].y == 1) { + if (y < continentPhaseDimension - 1) { + if (elevation_raws[x - 1][y + 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { + rainParticles[x - 1][y + 1] = 1; + rVal[x - 1][y + 1] = 1; + } + } + } + } else { + } + } else { + if (x < continentPhaseDimension - 1) { + if (elevation_raws[x + 1][y] < RAIN_SHADOW_BLOCKER_HEIGHT) { + rainParticles[x + 1][y] = 1; + rVal[x + 1][y] = 1; + } else { + } + if (wind_field[x][y].y == -1) { + if (y > 0) { + if (elevation_raws[x + 1][y - 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { + rainParticles[x + 1][y - 1] = 1; + rVal[x + 1][y - 1] = 1; + } + } + } else if (wind_field[x][y].y == 1) { + if (y < continentPhaseDimension - 1) { + if (elevation_raws[x + 1][y + 1] < RAIN_SHADOW_BLOCKER_HEIGHT) { + rainParticles[x + 1][y + 1] = 1; + rVal[x + 1][y + 1] = 1; + } + } + } } else { } } + rainParticles[x][y] = 0; + } + } + } + numRainParticles = 0; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(rainParticles[x][y] == 1){ + numRainParticles++; } } } } - //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; + return rVal; + } + + /** + * Calculates a vector field representing average wind flow across the heightmap + * @return The vector field representing average wind flow + */ + Vector[][] mapWindField(){ + Vector rVal[][] = new Vector[continentPhaseDimension][continentPhaseDimension]; + //One sixth of the dimension of the heightmap + int sixth = (int)(continentPhaseDimension / 6.0); + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(x < sixth){ + rVal[x][y] = new Vector(); + rVal[x][y].x = -1; + rVal[x][y].y = -1; + } else if(x < sixth * 2){ + rVal[x][y] = new Vector(); + rVal[x][y].x = 1; + rVal[x][y].y = 1; + } else if(x < sixth * 3){ + rVal[x][y] = new Vector(); + rVal[x][y].x = -1; + rVal[x][y].y = -1; + } else if(x < sixth * 4){ + rVal[x][y] = new Vector(); + rVal[x][y].x = -1; + rVal[x][y].y = 1; + } else if(x < sixth * 5){ + rVal[x][y] = new Vector(); + rVal[x][y].x = 1; + rVal[x][y].y = -1; + } else { + rVal[x][y] = new Vector(); + rVal[x][y].x = -1; + rVal[x][y].y = 1; } - elevation[x][y] = newElevation[x][y]; + } + } + return rVal; + } + + /** + * Fills out an array that marks positions in the heightmap as oceans or not oceans + * @param data The heightmap + * @return A new array that contains the designations + */ + int[][] parseOceans(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(data[x][y] < OCEAN_THRESHOLD){ + rVal[x][y] = OCEAN_THRESHOLD; + } else { + rVal[x][y] = 0; + } + } + } + return rVal; + } + + /** + * Fills out an array that marks positions in the heightmap as mountains or not mountains + * @param data The heightmap + * @return A new array that contains the designations + */ + int[][] parseMountainscapes(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(data[x][y] > MOUNTAIN_THRESHOLD){ + rVal[x][y] = MOUNTAIN_THRESHOLD; + } else { + rVal[x][y] = 0; + } + } + } + return rVal; + } + + int[][] closed_Set; + + int floodfill_Reach_Threshold(int[][] data, int[][] closed_Set, int x, int y, int min_val, int max_val){ + int rVal = 0; + int num_hits; + int offset_X[] = {-1,-1,-1,0,0,0,1,1,1}; + int offset_Y[] = {-1,0,1,-1,0,1,-1,0,1}; + if(data[x][y] > min_val && data[x][y] < max_val){ + closed_Set[x][y] = 1; + for(int i = 0; i < 9; i++){ + if(x + offset_X[i] >= 0 && + x + offset_X[i] < continentPhaseDimension && + y + offset_Y[i] >= 0 && + y + offset_Y[i] < continentPhaseDimension){ + if(closed_Set[x + offset_X[i]][y+offset_Y[i]] == 0){ + rVal = rVal + floodfill_Reach_Threshold(data,closed_Set,x + offset_X[i],y + offset_Y[i], min_val, max_val); + } + } + } + } + return rVal; + } + + int[][] remove_Small_Mountain_Ranged(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + boolean removed_Something = false; +// for (int x = 0; x < DIMENSION; x++) { +// for (int y = 0; y < DIMENSION; y++) { +// rVal[x][y] = data[x][y]; +// } +// } +// while (true) { + for (int x = 0; x < continentPhaseDimension; x++) { + for (int y = 0; y < continentPhaseDimension; y++) { + rVal[x][y] = data[x][y]; + if (data[x][y] > MOUNTAIN_THRESHOLD) { + rVal[x][y] = rVal[x][y] - 5; +// closed_Set = new int[DIMENSION][DIMENSION]; +// if (floodfill_Reach_Threshold(rVal, closed_Set, x, y, mountain_Threshold, 101) < mountain_Range_Size_Minimum) { +// rVal[x][y] = rVal[x][y] - 25; +//// removed_Something = true; +// } + } + } + } +// if(!removed_Something){ +// break; +//// } else { +// removed_Something = false; +// } +// } + return rVal; + } + + /** + * Compresses the extremes of the map towards the center half. + * IE: + * values less than 25 -> Center 50 <- values greater than 75 + * @param data The heightmap to apply the compression filter to + * @return A new heightmap containing the compressed data + */ + int[][] compressionFilter(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(data[x][y] < 25){ + rVal[x][y] = (int)(data[x][y] * 3.0 / 2.0); + } else if(data[x][y] > 75){ + rVal[x][y] = (int)(data[x][y] / 3.0 * 2.0); + } else { + rVal[x][y] = data[x][y]; + } + } + } + return rVal; + } + + /** + * Applies a filter that pushes the terrain upwards by making all values "Three halves" of their original values. + * @param data The heightmap to apply the filter to + * @return A new array containing the data with filter applied + */ + int[][] threeHalvesFilter(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + rVal[x][y] = (int)(data[x][y] * 3.0 / 2.0); + if(rVal[x][y] > 100){ + rVal[x][y] = 100; + } + } + } + return rVal; + } + + int[][] two_third_filter(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + rVal[x][y] = (int)(data[x][y] * 2.0 / 3.0); + if(rVal[x][y] < 0){ + rVal[x][y] = 0; + } + } + } + return rVal; + } + + int[][] high_end_two_third_filter(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++) { + if (data[x][y] > 50) { + rVal[x][y] = (int) (data[x][y] * 2.0 / 3.0); + if (rVal[x][y] < 0) { + rVal[x][y] = 0; + } + } else { + rVal[x][y] = data[x][y]; + } + } + } + return rVal; + } + + int[][] reduce_mid_high_end_filter(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++) { + if (data[x][y] > 50 && data[x][y] < 75) { + rVal[x][y] = (int) (data[x][y] * 2.0 / 3.0); + if (rVal[x][y] < 0) { + rVal[x][y] = 0; + } + } else { + rVal[x][y] = data[x][y]; + } + } + } + return rVal; + } + + /** + * Applies a small radius vertical, horizontal, and radial smoothing kernel to the data + * @param elevationMap The heightmap to apply the filter to + * @return A new array containing the data with filter applied + */ + int[][] smallKernelSmooth(int elevationMap[][]){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + int kernelOffsetX[] = { + -1,0,1, + -1,0,1, + -1,0,1 + }; + int kernelOffsetY[] = { + -1,-1,-1, + 0,0,0, + 1,1,1 + }; + int kernelModifier[] = { + 1,2,1, + 2,4,2, + 1,2,1 + }; + for(int x = 1; x < continentPhaseDimension - 1; x++){ + for(int y = 1; y < continentPhaseDimension - 1; y++){ + int sum = 0; + for(int i = 0; i < 9; i++){ + sum = sum + elevationMap[x+kernelOffsetX[i]][y+kernelOffsetY[i]] * kernelModifier[i]; + } + sum = (int)(sum/13.0); + if(sum > 100){ + sum = 100; + } + rVal[x][y] = sum; + } + } + return rVal; + } + + /** + * Applies a small horizontal, vertical, and radial sharpen filter to the data + * @param elevationMap The heightmap to apply the filter to + * @return A new array containing the data with filter applied + */ + int[][] smallKernelSharpen(int elevationMap[][]){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + int kernelOffsetX[] = { + -1,0,1, + -1,0,1, + -1,0,1 + }; + int kernelOffsetY[] = { + -1,-1,-1, + 0,0,0, + 1,1,1 + }; + int kernelModifier[] = { + 0,-1,0, + -1,5,-1, + 0,-1,0 + }; + for(int x = 1; x < continentPhaseDimension - 1; x++){ + for(int y = 1; y < continentPhaseDimension - 1; y++){ + int sum = 0; + for(int i = 0; i < 9; i++){ + sum = sum + elevationMap[x+kernelOffsetX[i]][y+kernelOffsetY[i]] * kernelModifier[i]; + } +// sum = (int)(sum/13.0); + if(sum > 100){ + sum = 100; + } + if(sum < 0){ + sum = 0; + } + rVal[x][y] = sum; + } + } + return rVal; + } + + + /** + * Applies a vertical, horizontal, and radial smoothing kernel to the heightmap data. + * @param elevationMap The heightmap to apply the filter to + * @return A new array containing the data with filter applied + */ + int[][] smoothTerrainFurther(int elevationMap[][]){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + int kernelOffsetX[] = {-2,-1,0,1,2, + -2,-1,0,1,2, + -2,-1,0,1,2, + -2,-1,0,1,2, + -2,-1,0,1,2, + }; + int kernelOffsetY[] = {-2,-2,-2,-2,-2, + -1,-1,-1,-1,-1, + 0,0,0,0,0, + 1,1,1,1,1, + 2,2,2,2,2}; + int kernelModifier[] = { + 1,4,6,4,1, + 4,16,24,16,4, + 6,24,36,24,6, + 4,16,24,16,4, + 1,4,6,4,1 + }; + for(int x = 2; x < continentPhaseDimension - 2; x++){ + for(int y = 2; y < continentPhaseDimension - 2; y++){ + int sum = 0; + for(int i = 0; i < 25; i++){ + sum = sum + elevationMap[x+kernelOffsetX[i]][y+kernelOffsetY[i]] * kernelModifier[i]; + } + sum = (int)(sum/256.0); + if(sum > 100){ + sum = 100; + } + rVal[x][y] = sum; + } + } + return rVal; + } + + /** + * Applies a filter that makes the land height scale exponentially + * IE Terrain that looks like: + * ^ + * / \ + * / \ + * Becomes + * ^ + * | | + * __ __ + * @param data The data to apply the filter to + * @return A new array containing the data with the filter applied + */ + int[][] landExponentialFilter(int[][] data){ + int rVal[][] = new int[continentPhaseDimension][continentPhaseDimension]; + for(int x = 0; x < continentPhaseDimension; x++){ + for(int y = 0; y < continentPhaseDimension; y++){ + if(data[x][y] > TerrainGenerator.OCEAN_THRESHOLD && data[x][y] < 90){ + //This is the total height that can be above land + //eg if ocean level is 25, and the maximum possible height is 100, the maximum height relative to sea level would be 75 + int maxHeightAboveLand = MAX_HEIGHT - OCEAN_THRESHOLD; + //This is the current height above sea level + int currentHeightAboveLand = data[x][y] - OCEAN_THRESHOLD; + //This is the percentage of the total height above sea level that the current height is + //In other words if max is 75 and current is 25, it would be 33% of the total height that it COULD be, relative to sea level + float percentageAboveLand = (float)currentHeightAboveLand / (float)currentHeightAboveLand; + //The weight for the exponential component of the calculation + float exponentialComponentWeight = 0.2f; + //The weight for the existing data + float existingComponentWeight = 0.8f; + //calculate rVal and make sure stays above sealevel as it's terrain after all + rVal[x][y] = (int)(data[x][y] * Math.exp(-(1.0f - percentageAboveLand)) * exponentialComponentWeight + data[x][y] * existingComponentWeight); + if(rVal[x][y] < TerrainGenerator.OCEAN_THRESHOLD){ + rVal[x][y] = TerrainGenerator.OCEAN_THRESHOLD + 1; + } + //Because of how exponentials work, this check should never pass + if(Math.exp(-(1.0 - percentageAboveLand)) > 1.0){ + System.out.println("WARNING!!"); + } + } else { + rVal[x][y] = data[x][y]; + } + } + } + return rVal; + } + + int[][] generate_Interpolation(int[][] data){ + /* + Looks at 3x3 kernel + */ + int kernel_offset_x[] = {-1,0,1,-1,0,1,-1,0,1}; + int kernel_offset_y[] = {1,1,1,0,0,0,-1,-1,-1}; + int rVal[][] = new int[100][100]; + return rVal; + } + + /** + * Doubles the dimensions of the map so that the new space can be interpolated linearly using existing values + * @param raw The original input map + * @return The new, interpolated output map + */ + int[][] interpolateElevationRaws(int[][] raw){ + int rVal[][] = new int[continentPhaseDimension * 2][continentPhaseDimension * 2]; + //perform interpolation + for(int x = 0; x < continentPhaseDimension - 1; x++){ + for(int y = 0; y < continentPhaseDimension - 1; y++){ + rVal[x*2][y*2] = raw[x][y]; + rVal[x*2+1][y*2] = (raw[x][y] + raw[x+1][y])/2; + rVal[x*2][y*2+1] = (raw[x][y] + raw[x][y+1])/2; + rVal[x*2+1][y*2+1] = (raw[x][y] + raw[x+1][y+1])/2; + } + } + //double dimension to account for new array size + continentPhaseDimension = continentPhaseDimension * 2; + return rVal; + } + + int[][] load_Data(String path){ + int rVal[][] = null; + try (BufferedReader br = new BufferedReader(new FileReader(path));){ + String line; + line = br.readLine(); + int dim_x = Integer.parseInt(line); + interpolationPhaseDimension = dim_x; + line = br.readLine(); + int dim_y = Integer.parseInt(line); + rVal = new int[dim_x][dim_y]; + int incrementer_x = 0; + int incrementer_y = 0; + while ((line = br.readLine()) != null) { + incrementer_y = 0; + while(line != ""){ + rVal[incrementer_x][incrementer_y] = Integer.parseInt(Utilities.string_To_First_Space(line)); + if(line.contains(" ")){ + line = line.substring(Utilities.get_Position_Of_Next_Instance_Of_Char_In_String(line, ' ') + 1); + } else { + line = ""; + } + incrementer_y++; + } + incrementer_x++; + } + } catch (FileNotFoundException ex) { + } catch (IOException ex) { + } + return rVal; + } + + public void increment_Current_Continent(){ + current_Continent++; + if (current_Continent > numberContinents - 1) { + current_Continent = 0; + } + while(continents.get(current_Continent).size < 50){ + current_Continent++; + if(current_Continent > numberContinents - 1){ + current_Continent = 0; } } } /** - * Applies a smooth kernel to the terrain data + * Increments the display mode of the built in data renderer */ - 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]; - } + public void incrementDisplayToggle(){ + displayToggle++; + if(displayToggle > max_Display_Toggle){ + displayToggle = 0; } } - - /** - * 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(); - } - } + + //the interpolation ratio applied to the statically generated terrain + public void setInterpolationRatio(int interpRatio){ + interpolationRatio = interpRatio; } - - - /** - * 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 { - } - } - } - } - } - } - } + + //the interpolation ratio applied to the dynamically generated terrain per chunk + public void setDynamicInterpolationRatio(int dynInterpRatio){ + dynamicInterpRatio = dynInterpRatio; } - - - - //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]; - } - } + + //Sets the vertical interpolation ratio + public void setVerticalInterpolationRatio(int vertInterpRatio){ + verticalInterpolationRatio = vertInterpRatio; } - - + /** - * A worker thread for simulating terrain moving due to force vector field + * Sets the random seed for the terrain generator + * @param seed The seed to set */ - 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(); - } - + public void setRandomSeed(long seed){ + Utilities.seed_Random_Functions(seed); } } diff --git a/src/main/java/electrosphere/server/terrain/generation/Vector.java b/src/main/java/electrosphere/server/terrain/generation/Vector.java index c8b21d2c..457cc1a4 100644 --- a/src/main/java/electrosphere/server/terrain/generation/Vector.java +++ b/src/main/java/electrosphere/server/terrain/generation/Vector.java @@ -6,4 +6,11 @@ package electrosphere.server.terrain.generation; class Vector { public int x; public int y; + protected Vector(){ + + } + protected Vector(int x, int y){ + this.x = x; + this.y = y; + } } diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java index d7bda5fc..90f345a6 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java @@ -2,7 +2,7 @@ package electrosphere.server.terrain.manager; import com.google.gson.Gson; import electrosphere.game.terrain.processing.TerrainInterpolator; -import electrosphere.server.terrain.generation.TerrainGen; +import electrosphere.server.terrain.generation.TerrainGenerator; import electrosphere.server.terrain.models.ModificationList; import electrosphere.server.terrain.models.TerrainModel; import electrosphere.server.terrain.models.TerrainModification; @@ -85,7 +85,7 @@ public class ServerTerrainManager { } public void generate(){ - TerrainGen terrainGen = new TerrainGen(); + TerrainGenerator terrainGen = new TerrainGenerator(); terrainGen.setInterpolationRatio(worldSizeDiscrete/200); terrainGen.setVerticalInterpolationRatio(verticalInterpolationRatio); terrainGen.setDynamicInterpolationRatio(dynamicInterpolationRatio); @@ -343,12 +343,9 @@ public class ServerTerrainManager { return heightmapCache.get(key); } else { float[][] macroValues = model.getRad5MacroValuesAtPosition(worldX, worldZ); - long[][] randomizer = model.getRad5RandomizerValuesAtPosition(worldX, worldZ); float[][] heightmap = TerrainInterpolator.getBicubicInterpolatedChunk( macroValues, - randomizer, - model.getDynamicInterpolationRatio(), - model.getRandomDampener() + model.getDynamicInterpolationRatio() ); heightmapCache.put(key,heightmap); return heightmap; @@ -375,8 +372,4 @@ public class ServerTerrainManager { } } - public long getRandomizerAtPoint(int worldX, int worldY){ - return model.getRad5RandomizerValuesAtPosition(worldX, worldY)[2][2]; - } - } diff --git a/src/main/java/electrosphere/server/terrain/models/TerrainModel.java b/src/main/java/electrosphere/server/terrain/models/TerrainModel.java index fad22b2c..254c3b8e 100644 --- a/src/main/java/electrosphere/server/terrain/models/TerrainModel.java +++ b/src/main/java/electrosphere/server/terrain/models/TerrainModel.java @@ -13,7 +13,6 @@ public class TerrainModel { int discreteArrayDimension; float[][] elevation; - long[][] chunkRandomizer; float realMountainThreshold; float realOceanThreshold; @@ -28,7 +27,6 @@ public class TerrainModel { public TerrainModel( int dimension, float[][] elevation, - long[][] chunkRandomizer, float realOceanThreshold, float realMountainThreshold, int dynamicInterpolationRatio @@ -37,7 +35,6 @@ public class TerrainModel { this.dynamicInterpolationRatio = dynamicInterpolationRatio; this.discreteArrayDimension = dimension; this.elevation = elevation; - this.chunkRandomizer = chunkRandomizer; this.realMountainThreshold = realMountainThreshold; this.realOceanThreshold = realOceanThreshold; this.modifications = new HashMap(); @@ -66,7 +63,6 @@ public class TerrainModel { */ public float[][] getElevationForChunk(int x, int y){ - Random rand = new Random(chunkRandomizer[x][y]); //this is what we intend to return from the function float[][] rVal = new float[dynamicInterpolationRatio][dynamicInterpolationRatio]; @@ -308,53 +304,6 @@ public class TerrainModel { */ - public long[][] getRandomizerValuesAtPosition(int x, int y){ - long[][] rVal = new long[3][3]; - rVal[1][1] = chunkRandomizer[x][y]; - if(x - 1 >= 0){ - rVal[0][1] = chunkRandomizer[x-1][y]; - if(y - 1 >= 0){ - rVal[0][0] = chunkRandomizer[x-1][y-1]; - } - if(y + 1 < discreteArrayDimension){ - rVal[0][2] = chunkRandomizer[x-1][y+1]; - } - } - if(x + 1 < discreteArrayDimension){ - rVal[2][1] = chunkRandomizer[x+1][y]; - if(y - 1 >= 0){ - rVal[2][0] = chunkRandomizer[x+1][y-1]; - } - if(y + 1 < discreteArrayDimension){ - rVal[2][2] = chunkRandomizer[x+1][y+1]; - } - } - if(y - 1 >= 0){ - rVal[1][0] = chunkRandomizer[x][y-1]; - } - if(y + 1 < discreteArrayDimension){ - rVal[1][2] = chunkRandomizer[x][y+1]; - } - return rVal; - } - - - public long[][] getRad5RandomizerValuesAtPosition(int x, int y){ - - long[][] rVal = new long[5][5]; - for(int i = -2; i < 3; i++){ - for(int j = -2; j < 3; j++){ - if(x + i >= 0 && x + i < discreteArrayDimension && y + j >= 0 && y + j < discreteArrayDimension){ - rVal[i+2][j+2] = chunkRandomizer[x+i][y+j]; - } else { - rVal[i+2][j+2] = 0; - } - } - } - - return rVal; - } - public float getRandomDampener(){ return interpolationRandomDampener;