diff --git a/assets/Data/game/biomes.json b/assets/Data/game/biomes.json
index ab93f97f..fd8e83b8 100644
--- a/assets/Data/game/biomes.json
+++ b/assets/Data/game/biomes.json
@@ -1,20 +1,5 @@
{
"biomes": [
- {
- "id": "sky",
- "displayName": "Sky",
- "isAerial": true,
- "isSurface": false,
- "isSubterranean": false,
- "regions": [
- {
- "frequency": 1.0,
- "baseFloorVoxel": 0,
- "floorVariants": [],
- "foliageDescription": []
- }
- ]
- },
{
"id": "plains",
"displayName": "Plains",
@@ -36,7 +21,24 @@
"foliageDescription": [
]
}
- ]
+ ],
+ "heightGenerator": "plains"
+ },
+ {
+ "id": "sky",
+ "displayName": "Sky",
+ "isAerial": true,
+ "isSurface": false,
+ "isSubterranean": false,
+ "regions": [
+ {
+ "frequency": 1.0,
+ "baseFloorVoxel": 0,
+ "floorVariants": [],
+ "foliageDescription": []
+ }
+ ],
+ "heightGenerator": "empty"
}
],
"files": [
diff --git a/pom.xml b/pom.xml
index 23653a4a..c262b5ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -294,12 +294,20 @@
${lwjgl.version}
- org.lwjgl
+ org.lwjgl
lwjgl-yoga
${lwjgl.version}
${lwjgl.natives}
+
+
+
+ io.github.studiorailgun
+ MathUtils
+ 1.0.1
+
+
diff --git a/src/main/java/electrosphere/game/data/biome/BiomeData.java b/src/main/java/electrosphere/game/data/biome/BiomeData.java
index 4eb5d50a..75bf5da1 100644
--- a/src/main/java/electrosphere/game/data/biome/BiomeData.java
+++ b/src/main/java/electrosphere/game/data/biome/BiomeData.java
@@ -37,6 +37,11 @@ public class BiomeData {
*/
Boolean isSubterranean;
+ /**
+ * Tag for the heightmap generator to source from
+ */
+ String heightGenerator;
+
/**
@@ -86,5 +91,13 @@ public class BiomeData {
public Boolean isSubterranean(){
return isSubterranean;
}
+
+ /**
+ * Gets the tag for the heightmap generator to source from
+ * @return The tag for the heightmap generator to source from
+ */
+ public String getHeightGenerator(){
+ return heightGenerator;
+ }
}
diff --git a/src/main/java/electrosphere/game/data/biome/BiomeTypeMap.java b/src/main/java/electrosphere/game/data/biome/BiomeTypeMap.java
index 283ac246..f4a4baa2 100644
--- a/src/main/java/electrosphere/game/data/biome/BiomeTypeMap.java
+++ b/src/main/java/electrosphere/game/data/biome/BiomeTypeMap.java
@@ -19,6 +19,11 @@ public class BiomeTypeMap {
*/
Map idBiomeMap = new HashMap();
+ /**
+ * The map of index -> biome data
+ */
+ Map indexBiomeMap = new HashMap();
+
/**
* The list of surface biomes
*/
@@ -51,6 +56,7 @@ public class BiomeTypeMap {
if(biome.isSubterranean()){
this.subterraneanBiomes.add(biome);
}
+ indexBiomeMap.put(indexBiomeMap.size(),biome);
}
/**
@@ -138,4 +144,13 @@ public class BiomeTypeMap {
return this.subterraneanBiomes;
}
+ /**
+ * Gets the biome by its index
+ * @param index The index
+ * @return The biome if the index exists, null otherwise
+ */
+ public BiomeData getBiomeByIndex(int index){
+ return indexBiomeMap.get(index);
+ }
+
}
diff --git a/src/main/java/electrosphere/game/server/world/ServerWorldData.java b/src/main/java/electrosphere/game/server/world/ServerWorldData.java
index 7b60192a..c1440419 100644
--- a/src/main/java/electrosphere/game/server/world/ServerWorldData.java
+++ b/src/main/java/electrosphere/game/server/world/ServerWorldData.java
@@ -150,7 +150,13 @@ public class ServerWorldData {
serverWorldData = ServerWorldData.createFixedWorldData(new Vector3d(0),new Vector3d(16 * 4 * 4));
serverWorldData.worldSizeDiscrete = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
serverWorldData.worldSizeDiscreteVertical = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
- serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new TestGenerationChunkGenerator());
+
+ //test terrain gen
+ {
+ TestGenerationChunkGenerator chunkGen = new TestGenerationChunkGenerator(serverWorldData);
+ serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, chunkGen);
+ serverTerrainManager.genTestData(chunkGen);
+ }
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
serverWorldData.setManagers(serverTerrainManager, serverFluidManager);
return serverWorldData;
@@ -184,6 +190,16 @@ public class ServerWorldData {
public float convertChunkToRealSpace(int chunk){
return chunk * ServerTerrainChunk.CHUNK_DIMENSION;
}
+
+ /**
+ * Converts a chunk space coordinate to a real space coordinate
+ * @param chunk The position within the chunk
+ * @param worldPos The world pos of the chunk
+ * @return The real pos
+ */
+ public double convertVoxelToRealSpace(int chunk, int worldPos){
+ return chunk + this.convertWorldToReal(worldPos);
+ }
public double getRelativeLocation(double real, int world){
return real - (world * dynamicInterpolationRatio);
diff --git a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java
index 767eda01..d6b90be9 100644
--- a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java
+++ b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java
@@ -1,7 +1,15 @@
package electrosphere.server.terrain.generation;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import electrosphere.game.data.biome.BiomeData;
+import electrosphere.game.server.world.ServerWorldData;
+import electrosphere.server.terrain.generation.heightmap.EmptySkyGen;
+import electrosphere.server.terrain.generation.heightmap.HeightmapGenerator;
+import electrosphere.server.terrain.generation.heightmap.HillsGen;
+import electrosphere.server.terrain.generation.heightmap.PlainsGen;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.models.TerrainModel;
@@ -21,6 +29,35 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
*/
TerrainModel terrainModel;
+ /**
+ * The server world data
+ */
+ ServerWorldData serverWorldData;
+
+ /**
+ * Map of generator tag to the generator
+ */
+ Map tagGeneratorMap = new HashMap();
+
+ /**
+ * Constructor
+ */
+ public TestGenerationChunkGenerator(ServerWorldData serverWorldData){
+ this.serverWorldData = serverWorldData;
+ registerGenerator(new EmptySkyGen());
+ registerGenerator(new HillsGen());
+ registerGenerator(new PlainsGen());
+
+ }
+
+ /**
+ * Registers a heightmap generator
+ * @param generator The heightmap generator
+ */
+ private void registerGenerator(HeightmapGenerator generator){
+ tagGeneratorMap.put(generator.getTag(),generator);
+ }
+
@Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) {
ServerTerrainChunk rVal = null;
@@ -51,11 +88,15 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
//actual generation algo
weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
+
+ //biome of the current chunk
+ BiomeData biome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ);
+
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
- weights[x][y][z] = this.getChunkWeight(worldX, worldY, worldZ, x, y, z, this.terrainModel);
- values[x][y][z] = this.getChunkValue(worldX, worldY, worldZ, x, y, z, this.terrainModel);
+ weights[x][y][z] = this.getChunkWeight(worldX, worldY, worldZ, x, y, z, this.terrainModel, biome);
+ values[x][y][z] = this.getChunkValue(worldX, worldY, worldZ, x, y, z, this.terrainModel, biome);
}
}
}
@@ -80,10 +121,30 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkY The chunk y pos
* @param chunkZ The chunk z pos
* @param terrainModel The terrain model
+ * @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk
*/
- private int getChunkValue(int worldX, int worldY, int worldZ, int chunkX, int chunkY, int chunkZ, TerrainModel terrainModel){
- return 1;
+ private int getChunkValue(
+ int worldX, int worldY, int worldZ,
+ int chunkX, int chunkY, int chunkZ,
+ TerrainModel terrainModel,
+ BiomeData surfaceBiome
+ ){
+ HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceBiome.getHeightGenerator());
+ if(heightmapGen == null){
+ throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceBiome.getHeightGenerator());
+ }
+
+ double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
+ double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
+ double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
+
+ float surfaceHeight = heightmapGen.getHeight(terrainModel.getSeed(), realX, realZ);
+ if(realY <= surfaceHeight){
+ return 1;
+ } else {
+ return 0;
+ }
}
/**
@@ -95,10 +156,33 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkY The chunk y pos
* @param chunkZ The chunk z pos
* @param terrainModel The terrain model
+ * @param surfaceBiome The surface biome of the chunk
* @return The weight of the chunk
*/
- private float getChunkWeight(int worldX, int worldY, int worldZ, int chunkX, int chunkY, int chunkZ, TerrainModel terrainModel){
- return 0.1f;
+ private float getChunkWeight(
+ int worldX, int worldY, int worldZ,
+ int chunkX, int chunkY, int chunkZ,
+ TerrainModel terrainModel,
+ BiomeData surfaceBiome
+ ){
+ HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceBiome.getHeightGenerator());
+ if(heightmapGen == null){
+ throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceBiome.getHeightGenerator());
+ }
+
+ double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
+ double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
+ double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
+
+ float surfaceHeight = heightmapGen.getHeight(terrainModel.getSeed(), realX, realZ);
+ double flooredSurfaceHeight = Math.floor(surfaceHeight);
+ if(realY < flooredSurfaceHeight){
+ return 1;
+ } else if(realY > flooredSurfaceHeight) {
+ return -1;
+ } else {
+ return (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1;
+ }
}
}
diff --git a/src/main/java/electrosphere/server/terrain/generation/heightmap/EmptySkyGen.java b/src/main/java/electrosphere/server/terrain/generation/heightmap/EmptySkyGen.java
new file mode 100644
index 00000000..a30955d3
--- /dev/null
+++ b/src/main/java/electrosphere/server/terrain/generation/heightmap/EmptySkyGen.java
@@ -0,0 +1,15 @@
+package electrosphere.server.terrain.generation.heightmap;
+
+public class EmptySkyGen implements HeightmapGenerator {
+
+ @Override
+ public float getHeight(long SEED, double x, double y) {
+ return 0;
+ }
+
+ @Override
+ public String getTag() {
+ return "empty";
+ }
+
+}
diff --git a/src/main/java/electrosphere/server/terrain/generation/heightmap/HeightmapGenerator.java b/src/main/java/electrosphere/server/terrain/generation/heightmap/HeightmapGenerator.java
new file mode 100644
index 00000000..4989ff10
--- /dev/null
+++ b/src/main/java/electrosphere/server/terrain/generation/heightmap/HeightmapGenerator.java
@@ -0,0 +1,23 @@
+package electrosphere.server.terrain.generation.heightmap;
+
+/**
+ * Generates height values for terrain
+ */
+public interface HeightmapGenerator {
+
+ /**
+ * Gets the height of a given position, given a SEED value
+ * @param SEED The seed for the terrain
+ * @param x The x value
+ * @param y The y value
+ * @return The height
+ */
+ public float getHeight(long SEED, double x, double y);
+
+ /**
+ * Gets the tag associated with this generator
+ * @return The tag
+ */
+ public String getTag();
+
+}
diff --git a/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java b/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java
new file mode 100644
index 00000000..c16256a0
--- /dev/null
+++ b/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java
@@ -0,0 +1,84 @@
+package electrosphere.server.terrain.generation.heightmap;
+
+import electrosphere.util.noise.OpenSimplex2S;
+
+/**
+ * Generates hilly heightmaps
+ */
+public class HillsGen implements HeightmapGenerator {
+
+ /**
+ * Offset from baseline to place the noisemap at
+ */
+ static final float HEIGHT_OFFSET = 10;
+
+ /**
+ * The different scales of noise to sample from
+ */
+ static final double[][] GRAD_NOISE = new double[][]{
+ {0.01, 2.0},
+ {0.02, 2.0},
+ {0.05, 1.0},
+ {0.1, 1.0},
+ {0.3, 1.0},
+ };
+
+ //distance from origin to sample for gradient calculation
+ public static float GRADIENT_DIST = 0.01f;
+
+ //param for controlling how pointer the initial layers are
+ public static float GRAD_INFLUENCE_DROPOFF = 0.35f;
+
+
+ /**
+ * Gets the height at a given position for this generation approach
+ * @param SEED The seed
+ * @param x The x position
+ * @param y The y position
+ * @return The height
+ */
+ public float getHeight(long SEED, double x, double y){
+ return gradientHeight(SEED, x, y);
+ }
+
+
+ /**
+ * Applies a gradient approach to heightfield generation
+ * @param SEED The seed
+ * @param x The x value
+ * @param y The y value
+ * @return The elevation at x,y
+ */
+ static float gradientHeight(long SEED, double x, double y){
+ float rVal = 0;
+
+ float gradXAccum = 0;
+ float gradYAccum = 0;
+ for(int n = 0; n < GRAD_NOISE.length; n++){
+ //get noise samples
+ float noiseOrigin = (float)(OpenSimplex2S.noise2_ImproveX(SEED, x * GRAD_NOISE[n][0], y * GRAD_NOISE[n][0]) * GRAD_NOISE[n][1]);
+ float noiseX = (float)(OpenSimplex2S.noise2_ImproveX(SEED, x * GRAD_NOISE[n][0] + GRADIENT_DIST, y * GRAD_NOISE[n][0]) * GRAD_NOISE[n][1]);
+ float noiseY = (float)(OpenSimplex2S.noise2_ImproveX(SEED, x * GRAD_NOISE[n][0], y * GRAD_NOISE[n][0] + GRADIENT_DIST) * GRAD_NOISE[n][1]);
+ //calculate gradient accumulation
+ float gradX = (noiseX - noiseOrigin) / GRADIENT_DIST;
+ float gradY = (noiseY - noiseOrigin) / GRADIENT_DIST;
+ gradXAccum = gradXAccum + gradX;
+ gradYAccum = gradYAccum + gradY;
+ //determine current noise's influence based on gradient
+ float gradientMagnitude = (float)Math.sqrt(gradXAccum * gradXAccum + gradYAccum * gradYAccum);
+ float influence = 1.0f / (1.0f + gradientMagnitude * GRAD_INFLUENCE_DROPOFF);
+
+ //add to height
+ rVal = rVal + (float)(OpenSimplex2S.noise2_ImproveX(SEED, x * GRAD_NOISE[n][0], y * GRAD_NOISE[n][0]) * GRAD_NOISE[n][1]) * influence;
+ }
+ rVal = rVal + HEIGHT_OFFSET;
+ return rVal;
+ }
+
+
+ @Override
+ public String getTag() {
+ return "hills";
+ }
+
+}
diff --git a/src/main/java/electrosphere/server/terrain/generation/heightmap/PlainsGen.java b/src/main/java/electrosphere/server/terrain/generation/heightmap/PlainsGen.java
new file mode 100644
index 00000000..29c902fb
--- /dev/null
+++ b/src/main/java/electrosphere/server/terrain/generation/heightmap/PlainsGen.java
@@ -0,0 +1,66 @@
+package electrosphere.server.terrain.generation.heightmap;
+
+import electrosphere.util.noise.OpenSimplex2S;
+
+/**
+ * Generator for plains
+ */
+public class PlainsGen implements HeightmapGenerator {
+
+ /**
+ * Offset from baseline to place the noisemap at
+ */
+ static final float HEIGHT_OFFSET = 10;
+
+ /**
+ * The scale to apply to the coordinates
+ */
+ static final float GEN_SCALE = 0.2f;
+
+ //the different scales of noise to sample from
+ static final double[][] NOISE_SCALES = new double[][]{
+ {0.01, 3.0},
+ {0.02, 2.0},
+ {0.05, 0.8},
+ {0.1, 0.3},
+ {0.3, 0.2},
+ };
+
+
+ /**
+ * Gets the height at a given position for this generation approach
+ * @param SEED The seed
+ * @param x The x position
+ * @param y The y position
+ * @return The height
+ */
+ public float getHeight(long SEED, double x, double y){
+ return sampleAllNoise(SEED, x, y);
+ }
+
+
+ /**
+ * Samples all noise values directly
+ * @param SEED The seed
+ * @param x The x value
+ * @param y The y value
+ * @return The elevation at x,y
+ */
+ static float sampleAllNoise(long SEED, double x, double y){
+ float rVal = 0;
+ double scaledX = x * GEN_SCALE;
+ double scaledY = y * GEN_SCALE;
+ for(int n = 0; n < NOISE_SCALES.length; n++){
+ rVal = rVal + (float)(OpenSimplex2S.noise2_ImproveX(SEED, scaledX * NOISE_SCALES[n][0], scaledY * NOISE_SCALES[n][0]) * NOISE_SCALES[n][1]);
+ }
+ rVal = rVal + HEIGHT_OFFSET;
+ return rVal;
+ }
+
+
+ @Override
+ public String getTag() {
+ return "plains";
+ }
+
+}
diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
index 49bc9ff8..c9bcf770 100644
--- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
+++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
@@ -2,6 +2,7 @@ package electrosphere.server.terrain.manager;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.server.terrain.diskmap.ChunkDiskMap;
+import electrosphere.server.terrain.generation.TestGenerationChunkGenerator;
import electrosphere.server.terrain.generation.continentphase.TerrainGenerator;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.models.TerrainModel;
@@ -151,6 +152,15 @@ public class ServerTerrainManager {
chunkDiskMap = new ChunkDiskMap();
chunkDiskMap.init(saveName);
}
+
+ /**
+ * Generates a test terrain model
+ * @param chunkGen The chunk generator
+ */
+ public void genTestData(TestGenerationChunkGenerator chunkGen){
+ this.model = TerrainModel.generateTestModel();
+ chunkGen.setModel(model);
+ }
public float[][] getTerrainAtChunk(int x, int y){
return model.getElevationForChunk(x, y);
diff --git a/src/main/java/electrosphere/server/terrain/models/TerrainModel.java b/src/main/java/electrosphere/server/terrain/models/TerrainModel.java
index 60bf3715..dc7bf8ba 100644
--- a/src/main/java/electrosphere/server/terrain/models/TerrainModel.java
+++ b/src/main/java/electrosphere/server/terrain/models/TerrainModel.java
@@ -5,12 +5,19 @@ import java.util.Map;
import electrosphere.engine.Globals;
import electrosphere.game.data.biome.BiomeData;
+import electrosphere.server.terrain.generation.TestGenerationChunkGenerator;
import electrosphere.util.annotation.Exclude;
/**
* The model of the terrain
*/
public class TerrainModel {
+
+
+ /**
+ * The scale of the macro data
+ */
+ static final int MACRO_DATA_SCALE = 32;
/**
@@ -33,6 +40,12 @@ public class TerrainModel {
*/
@Exclude
private float[][] elevation;
+
+ /**
+ * The macro level biome data
+ */
+ @Exclude
+ private short[][] biome;
/**
* The real coordinate mountain threshold
@@ -48,6 +61,11 @@ public class TerrainModel {
* The map of modifications applied to the model
*/
Map modifications;
+
+ /**
+ * The seed of the terrain
+ */
+ long seed = 0;
/**
* Private constructor
@@ -92,6 +110,18 @@ public class TerrainModel {
rVal.dynamicInterpolationRatio = dynamicInterpolationRatio;
return rVal;
}
+
+ /**
+ * Generates a test terrain model
+ * @return The test terrain model
+ */
+ public static TerrainModel generateTestModel(){
+ TerrainModel rVal = new TerrainModel();
+ rVal.discreteArrayDimension = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
+ rVal.dynamicInterpolationRatio = 1;
+ rVal.biome = new short[2][2];
+ return rVal;
+ }
/**
* Gets the macro elevation data for the terrain model
@@ -460,14 +490,26 @@ public class TerrainModel {
}
/**
- * Gets the biome for a given world position
+ * Gets the surface biome for a given world position
* @param worldX The world X
* @param worldY The world Y
* @param worldZ The world Z
* @return The biome
*/
- public BiomeData getBiome(int worldX, int worldY, int worldZ){
- return Globals.gameConfigCurrent.getBiomeMap().getTypes().iterator().next();
+ public BiomeData getSurfaceBiome(int worldX, int worldY, int worldZ){
+ int macroX = worldX / MACRO_DATA_SCALE;
+ int macroZ = worldZ / MACRO_DATA_SCALE;
+ int surfaceBiomeIndex = this.biome[macroX][macroZ];
+ BiomeData biome = Globals.gameConfigCurrent.getBiomeMap().getBiomeByIndex(surfaceBiomeIndex);
+ return biome;
+ }
+
+ /**
+ * Gets the seed of the terrain model
+ * @return The seed
+ */
+ public long getSeed(){
+ return seed;
}
}