Renderer/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
austin d6c476b4a3
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
true terrain regen, gen algo tweaks
2024-10-29 14:58:06 -04:00

313 lines
10 KiB
Java

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;
import electrosphere.server.terrain.models.TerrainModification;
import electrosphere.util.FileUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.joml.Vector3i;
/**
* Provides an interface for the server to query information about terrain
*/
public class ServerTerrainManager {
/**
* Full world discrete size
*/
public static final int WORLD_SIZE_DISCRETE = 1000;
/**
* Vertical interp ratio
*/
public static final int VERTICAL_INTERPOLATION_RATIO = 50;
//the interpolation width of a server terrain manager is hard coded at the moment to make sure it's divisible by the sub chunk calculations
public static final int SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO = 128;
/**
* The dampening ratio for ground noise
*/
public static final float SERVER_TERRAIN_MANAGER_DAMPENER = 1.0f;
/**
* The parent world data
*/
ServerWorldData parent;
//the seed for terrain generation
long seed;
//The model of the terrain this manager is managing
TerrainModel model;
//In memory cache of chunk data
//Basic idea is we associate string that contains chunk x&y&z with elevation
//While we incur a penalty with converting ints -> string, think this will
//offset regenerating the array every time we want a new one
int cacheSize = 500;
Map<String, ServerTerrainChunk> chunkCache;
List<String> chunkCacheContents;
//The map of chunk position <-> file on disk containing chunk data
ChunkDiskMap chunkDiskMap = null;
//The generation algorithm for this terrain manager
ChunkGenerator chunkGenerator;
/**
* Constructor
*/
public ServerTerrainManager(
ServerWorldData parent,
long seed,
ChunkGenerator chunkGenerator
){
this.parent = parent;
this.chunkCache = new ConcurrentHashMap<String, ServerTerrainChunk>();
this.chunkCacheContents = new CopyOnWriteArrayList<String>();
this.seed = seed;
this.chunkGenerator = chunkGenerator;
}
ServerTerrainManager(){
}
/**
* Generates a terrain model for the manager
*/
public void generate(){
TerrainGenerator terrainGen = new TerrainGenerator();
terrainGen.setInterpolationRatio(parent.getWorldSizeDiscrete()/200);
terrainGen.setVerticalInterpolationRatio(parent.getWorldSizeDiscrete());
terrainGen.setRandomSeed(seed);
model = terrainGen.generateModel();
this.chunkGenerator.setModel(model);
model.setInterpolationRandomDampener(SERVER_TERRAIN_MANAGER_DAMPENER);
this.chunkDiskMap = new ChunkDiskMap();
}
/**
* Saves the terrain model backing this manager to a save file
* @param saveName The name of the save
*/
public void save(String saveName){
if(model != null){
ByteBuffer buffer = ByteBuffer.allocate(model.getElevation().length * model.getElevation()[0].length * 4);
FloatBuffer floatView = buffer.asFloatBuffer();
for(int x = 0; x < model.getElevation().length; x++){
floatView.put(model.getElevation()[x]);
}
if(floatView.position() > 0){
floatView.flip();
}
FileUtils.serializeObjectToSavePath(saveName, "./terrain.json", model);
FileUtils.saveBinaryToSavePath(saveName, "./terrain.dat", buffer.array());
}
//for each chunk, save via disk map
for(String chunkKey : chunkCacheContents){
ServerTerrainChunk chunk = chunkCache.get(chunkKey);
chunkDiskMap.saveToDisk(chunk);
}
//save disk map itself
if(chunkDiskMap != null){
chunkDiskMap.save();
}
}
/**
* Loads a terrain manager from a save file
* @param saveName The name of the save
*/
public void load(String saveName){
//load terrain model
if(FileUtils.getSaveFile(saveName, "./terrain.json").exists()){
model = FileUtils.loadObjectFromSavePath(saveName, "./terrain.json", TerrainModel.class);
chunkGenerator.setModel(model);
byte[] data = FileUtils.loadBinaryFromSavePath(saveName, "./terrain.dat");
ByteBuffer buffer = ByteBuffer.wrap(data);
FloatBuffer floatView = buffer.asFloatBuffer();
float[][] elevation = new float[parent.getWorldSizeDiscrete()][parent.getWorldSizeDiscrete()];
for(int x = 0; x < parent.getWorldSizeDiscrete(); x++){
for(int y = 0; y < parent.getWorldSizeDiscrete(); y++){
elevation[x][y] = floatView.get();
}
}
model.setElevationArray(elevation);
}
//load chunk disk map
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);
}
/**
* Evicts all cached terrain
*/
public void evictAll(){
this.chunkCache.clear();
this.chunkCacheContents.clear();
}
public float[][] getTerrainAtChunk(int x, int y){
return model.getElevationForChunk(x, y);
}
public double getHeightAtPosition(double x, double y, double z){
return y;
}
public float getDiscreteValue(int x, int y){
if(model != null){
return model.getElevation()[x][y];
} else {
return 0;
}
}
public int getDynamicInterpolationRatio(){
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
if(model != null){
return model.getDynamicInterpolationRatio();
} else {
//THIS FIRES IF THERE IS AN ARENA WORLD RUNNING
return 0;
}
}
public float getRandomDampener(){
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
if(model != null){
return model.getRandomDampener();
} else {
//THIS FIRES IF THERE IS AN ARENA WORLD RUNNING
return 0.0f;
}
}
/**
* Gets the terrain model backing this terrain manager
* @return The terrain model
*/
public TerrainModel getModel() {
return model;
}
/**
* Gets the key for a given world position
* @param worldX The x component
* @param worldY The y component
* @param worldZ The z component
* @return The key
*/
public String getKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ;
}
/**
* Gets a server terrain chunk
* @param worldX The world x position
* @param worldY The world y position
* @param worldZ The world z position
* @return The ServerTerrainChunk
*/
public ServerTerrainChunk getChunk(int worldX, int worldY, int worldZ){
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
String key = getKey(worldX,worldY,worldZ);
ServerTerrainChunk returnedChunk = null;
if(chunkCache.containsKey(key)){
chunkCacheContents.remove(key);
chunkCacheContents.add(0, key);
returnedChunk = chunkCache.get(key);
return returnedChunk;
} else {
if(chunkCacheContents.size() >= cacheSize){
String oldChunk = chunkCacheContents.remove(chunkCacheContents.size() - 1);
chunkCache.remove(oldChunk);
}
//pull from disk if it exists
if(chunkDiskMap != null){
if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
returnedChunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ);
}
}
//generate if it does not exist
if(returnedChunk == null){
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
}
chunkCache.put(key, returnedChunk);
chunkCacheContents.add(key);
return returnedChunk;
}
}
/**
* Saves a given position's chunk to disk.
* Uses the current global save name
* @param position The position to save
*/
public void savePositionToDisk(Vector3i position){
if(chunkDiskMap != null){
chunkDiskMap.saveToDisk(getChunk(position.x, position.y, position.z));
}
}
/**
* Applies a deform to terrain at a given location
* @param worldPos The world coordinates of the chunk to modify
* @param voxelPos The voxel coordinates of the voxel to modify
* @param weight The weight to set it to
* @param value The value to set it to
*/
public void deformTerrainAtLocationToValue(Vector3i worldPos, Vector3i voxelPos, float weight, int value){
TerrainModification modification = new TerrainModification(worldPos,voxelPos,weight,value);
//could be null if, for instance, arena mode
if(model != null){
model.addModification(modification);
}
String key = getKey(worldPos.x,worldPos.y,worldPos.z);
if(chunkCache.containsKey(key)){
ServerTerrainChunk chunk = chunkCache.get(key);
chunk.addModification(modification);
}
}
/**
* Sets the parent world data of this manager
* @param serverWorldData The parent world data
*/
public void setParent(ServerWorldData serverWorldData){
this.parent = serverWorldData;
}
/**
* Gets the chunk generator of the terrain manager
* @return The chunk generator
*/
public ChunkGenerator getChunkGenerator(){
return chunkGenerator;
}
}