Terrain generator updates + Foliage work

This commit is contained in:
austin 2023-06-20 15:54:09 -04:00
parent c69caeeab9
commit 3948f1f921
27 changed files with 2720 additions and 2202 deletions

View File

@ -17,6 +17,13 @@
}
],
"modelPath" : "Models/falloak1.fbx"
},
{
"name" : "Green Grass",
"tokens" : [
"AMBIENT"
],
"modelPath" : "Models/falloak1.fbx"
}
]

View File

@ -0,0 +1,19 @@
{
"types" : [
{
"id" : 0,
"name" : "air"
},
{
"id" : 1,
"name" : "dirt"
},
{
"id" : 2,
"name" : "grass",
"ambientFoliage" : [
"Grass"
]
}
]
}

20
docs/TerrainEditing.txt Normal file
View File

@ -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

View File

@ -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<FoliageCell> activeCells = new HashSet<FoliageCell>();
//map of position-based key to foliage cell at the position
Map<String,FoliageCell> locationCellMap = new HashMap<String,FoliageCell>();
//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
}
}

View File

@ -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<Entity> containedEntities;
protected Set<Entity> containedEntities;
}

View File

@ -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))),

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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){

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<FoliageType> foliageList;
/**
* Gets the list of all foliage types
* @return The list of all foliage types
*/
public List<FoliageType> 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)){

View File

@ -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<VoxelType> types;
/**
* Gets all voxel types
* @return The set of all voxel types
*/
public Set<VoxelType> 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;
}
}

View File

@ -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<String> 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<String> getAmbientFoliage(){
return ambientFoliage;
}
}

View File

@ -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;
}
}

View File

@ -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(

View File

@ -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(){
}
}

View File

@ -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;

View File

@ -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++){

View File

@ -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<Hotspot> spots = new ArrayList<Hotspot>();
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<Hotspot> to_Remove = new ArrayList<Hotspot>();
Iterator<Hotspot> 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<Hotspot> 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();
}
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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<String,ModificationList>();
@ -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;