322 lines
11 KiB
Java
322 lines
11 KiB
Java
package electrosphere.server.terrain.manager;
|
|
|
|
import electrosphere.client.terrain.cache.ChunkData;
|
|
import electrosphere.engine.Globals;
|
|
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 electrosphere.util.annotation.Exclude;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.function.Consumer;
|
|
|
|
import org.joml.Vector3i;
|
|
|
|
/**
|
|
* Provides an interface for the server to query information about terrain
|
|
*/
|
|
public class ServerTerrainManager {
|
|
|
|
/**
|
|
* The number of threads for chunk generation
|
|
*/
|
|
public static final int GENERATION_THREAD_POOL_SIZE = 1;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* The cache of chunks
|
|
*/
|
|
@Exclude
|
|
ServerChunkCache chunkCache = new ServerChunkCache();
|
|
|
|
//The map of chunk position <-> file on disk containing chunk data
|
|
ChunkDiskMap chunkDiskMap = null;
|
|
|
|
//The generation algorithm for this terrain manager
|
|
@Exclude
|
|
ChunkGenerator chunkGenerator;
|
|
|
|
/**
|
|
* The threadpool for chunk generation
|
|
*/
|
|
@Exclude
|
|
ExecutorService chunkExecutorService = Executors.newFixedThreadPool(GENERATION_THREAD_POOL_SIZE);
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public ServerTerrainManager(
|
|
ServerWorldData parent,
|
|
long seed,
|
|
ChunkGenerator chunkGenerator
|
|
){
|
|
this.parent = parent;
|
|
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(ServerTerrainChunk chunk : this.chunkCache.getContents()){
|
|
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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Performs logic once a server chunk is available
|
|
* @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){
|
|
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
|
|
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
|
|
ServerTerrainChunk returnedChunk = null;
|
|
if(chunkCache.containsChunk(worldX,worldY,worldZ,ChunkData.NO_STRIDE)){
|
|
returnedChunk = chunkCache.get(worldX,worldY,worldZ, ChunkData.NO_STRIDE);
|
|
} else {
|
|
//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, ChunkData.NO_STRIDE);
|
|
}
|
|
this.chunkCache.add(worldX, worldY, worldZ, ChunkData.NO_STRIDE, returnedChunk);
|
|
}
|
|
Globals.profiler.endCpuSample();
|
|
return returnedChunk;
|
|
}
|
|
|
|
/**
|
|
* Performs logic once a server chunk is available
|
|
* @param worldX The world x position
|
|
* @param worldY The world y position
|
|
* @param worldZ The world z position
|
|
* @param stride The stride of the data
|
|
* @param onLoad The logic to run once the chunk is available
|
|
*/
|
|
public void getChunkAsync(int worldX, int worldY, int worldZ, int stride, Consumer<ServerTerrainChunk> onLoad){
|
|
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync");
|
|
this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, stride, onLoad));
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z,ChunkData.NO_STRIDE)){
|
|
ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z, ChunkData.NO_STRIDE);
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Closes the generation threadpool
|
|
*/
|
|
public void closeThreads(){
|
|
this.chunkExecutorService.shutdownNow();
|
|
}
|
|
|
|
}
|