269 lines
10 KiB
Java
269 lines
10 KiB
Java
package electrosphere.client.terrain.manager;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.nio.IntBuffer;
|
|
import java.util.Collection;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.Semaphore;
|
|
|
|
import org.joml.Vector3i;
|
|
|
|
import electrosphere.client.scene.ClientWorldData;
|
|
import electrosphere.client.terrain.cache.ChunkData;
|
|
import electrosphere.client.terrain.cache.ClientTerrainCache;
|
|
import electrosphere.client.terrain.cells.ClientDrawCellManager;
|
|
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
|
|
import electrosphere.engine.Globals;
|
|
import electrosphere.entity.types.terrain.TerrainChunkData;
|
|
import electrosphere.logger.LoggerInterface;
|
|
import electrosphere.net.parser.net.message.TerrainMessage;
|
|
import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
|
|
import electrosphere.renderer.model.Model;
|
|
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
|
import electrosphere.server.terrain.manager.ServerTerrainManager;
|
|
|
|
/**
|
|
* Manages terrain storage and access on the client
|
|
*/
|
|
public class ClientTerrainManager {
|
|
|
|
//queues messages from server
|
|
List<TerrainMessage> messageQueue = new CopyOnWriteArrayList<TerrainMessage>();
|
|
|
|
/**
|
|
* Locks the terrain manager (eg if adding message from network)
|
|
*/
|
|
static Semaphore lock = new Semaphore(1);
|
|
|
|
//The interpolation ratio of terrain
|
|
public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO;
|
|
|
|
//caches chunks from server
|
|
static final int CACHE_SIZE = 1500 + (int)(ClientDrawCellManager.FULL_RES_DIST * 10) + (int)(ClientDrawCellManager.HALF_RES_DIST * 10);
|
|
|
|
/**
|
|
* Size of the cache in bytes
|
|
*/
|
|
static final int CACHE_SIZE_IN_MB = (CACHE_SIZE * ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION * 2 * 4) / 1024 / 1024;
|
|
|
|
//used for caching the macro values
|
|
ClientTerrainCache terrainCache;
|
|
|
|
//The world data for the client
|
|
ClientWorldData clientWorldData;
|
|
|
|
//The queue of terrain chunk data to be buffered to gpu
|
|
static List<TerrainChunkGenQueueItem> terrainChunkGenerationQueue = new CopyOnWriteArrayList<TerrainChunkGenQueueItem>();
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public ClientTerrainManager(){
|
|
terrainCache = new ClientTerrainCache(CACHE_SIZE);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles messages that have been received from the server
|
|
*/
|
|
public void handleMessages(){
|
|
Globals.profiler.beginCpuSample("ClientTerrainManager.handleMessages");
|
|
lock.acquireUninterruptibly();
|
|
List<TerrainMessage> bouncedMessages = new LinkedList<TerrainMessage>();
|
|
for(TerrainMessage message : messageQueue){
|
|
messageQueue.remove(message);
|
|
switch(message.getMessageSubtype()){
|
|
case SENDCHUNKDATA: {
|
|
int[][][] values = new int[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE];
|
|
float[][][] weights = new float[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE];
|
|
ByteBuffer buffer = ByteBuffer.wrap(message.getchunkData());
|
|
FloatBuffer floatBuffer = buffer.asFloatBuffer();
|
|
for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
|
|
for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){
|
|
for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
|
|
weights[x][y][z] = floatBuffer.get();
|
|
}
|
|
}
|
|
}
|
|
IntBuffer intView = buffer.asIntBuffer();
|
|
intView.position(floatBuffer.position());
|
|
for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
|
|
for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){
|
|
for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
|
|
values[x][y][z] = intView.get();
|
|
}
|
|
}
|
|
}
|
|
ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ());
|
|
data.setVoxelType(values);
|
|
data.setVoxelWeight(weights);
|
|
terrainCache.addChunkDataToCache(
|
|
message.getworldX(), message.getworldY(), message.getworldZ(),
|
|
data
|
|
);
|
|
} break;
|
|
default:
|
|
LoggerInterface.loggerEngine.WARNING("ClientTerrainManager: unhandled network message of type" + message.getMessageSubtype());
|
|
break;
|
|
}
|
|
}
|
|
for(TerrainMessage message : bouncedMessages){
|
|
messageQueue.add(message);
|
|
}
|
|
lock.release();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
/**
|
|
* Evicts all cached terrain
|
|
*/
|
|
public void evictAll(){
|
|
this.terrainCache.evictAll();
|
|
}
|
|
|
|
/**
|
|
* Attaches a terrain message to the queue of messages that this manager needs to process
|
|
* @param message The message
|
|
*/
|
|
public void attachTerrainMessage(TerrainMessage message){
|
|
lock.acquireUninterruptibly();
|
|
messageQueue.add(message);
|
|
lock.release();
|
|
}
|
|
|
|
/**
|
|
* Checks if the terrain cache contains chunk data at a given world position
|
|
* @param worldX the x position
|
|
* @param worldY the y position
|
|
* @param worldZ the z position
|
|
* @return true if the data exists, false otherwise
|
|
*/
|
|
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
|
|
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ);
|
|
}
|
|
|
|
/**
|
|
* Checks if the terrain cache contains chunk data at a given world position
|
|
* @param worldPos The vector containing the world-space position
|
|
* @return true if the data exists, false otherwise
|
|
*/
|
|
public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){
|
|
return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z);
|
|
}
|
|
|
|
/**
|
|
* Requests a chunk from the server
|
|
* @param worldX the world x coordinate of the chunk
|
|
* @param worldY the world y coordinate of the chunk
|
|
* @param worldZ the world z coordinate of the chunk
|
|
*/
|
|
public void requestChunk(int worldX, int worldY, int worldZ){
|
|
if(!this.terrainCache.hasRequested(worldX, worldY, worldZ)){
|
|
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
|
|
worldX,
|
|
worldY,
|
|
worldZ
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the cache contains chunk data at a real-space coordinate
|
|
* @param x the x coordinate
|
|
* @param y the y coordinate
|
|
* @param z the z coordinate
|
|
* @return true if the cache contains the chunk data at the coordinate, false otherwise
|
|
*/
|
|
public boolean containsChunkDataAtRealPoint(double x, double y, double z){
|
|
assert clientWorldData != null;
|
|
return terrainCache.containsChunkDataAtWorldPoint(
|
|
clientWorldData.convertRealToChunkSpace(x),
|
|
clientWorldData.convertRealToChunkSpace(y),
|
|
clientWorldData.convertRealToChunkSpace(z)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Queues a terrain chunk to be pushed to GPU based on chunk data
|
|
* @param data The chunk data (triangles, normals, etc)
|
|
* @return The model path that is promised to eventually reflect the terrain model when it makes it to gpu
|
|
*/
|
|
public static String queueTerrainGridGeneration(TerrainChunkData data, VoxelTextureAtlas atlas){
|
|
String promisedHash = "";
|
|
UUID newUUID = UUID.randomUUID();
|
|
promisedHash = newUUID.toString();
|
|
TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash, atlas);
|
|
lock.acquireUninterruptibly();
|
|
terrainChunkGenerationQueue.add(queueItem);
|
|
lock.release();
|
|
return promisedHash;
|
|
}
|
|
|
|
/**
|
|
* Pushes all terrain data in queue to the gpu and registers the resulting models
|
|
*/
|
|
public static void generateTerrainChunkGeometry(){
|
|
Globals.profiler.beginCpuSample("ClientTerrainManager.generateTerrainChunkGeometry");
|
|
lock.acquireUninterruptibly();
|
|
for(TerrainChunkGenQueueItem queueItem : terrainChunkGenerationQueue){
|
|
Model terrainModel = TerrainChunkModelGeneration.generateTerrainModel(queueItem.getData(), queueItem.getAtlas());
|
|
Globals.assetManager.registerModelToSpecificString(terrainModel, queueItem.getPromisedHash());
|
|
}
|
|
terrainChunkGenerationQueue.clear();
|
|
lock.release();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
/**
|
|
* Gets all chunks in the terrain cache
|
|
* @return The collection of all chunk data objects
|
|
*/
|
|
public Collection<ChunkData> getAllChunks(){
|
|
return terrainCache.getAllChunks();
|
|
}
|
|
|
|
/**
|
|
* Gets the world position of a given chunk
|
|
* @param chunk The chunk
|
|
* @return The world position of the chunk
|
|
*/
|
|
public Vector3i getPositionOfChunk(ChunkData chunk){
|
|
return terrainCache.getChunkPosition(chunk);
|
|
}
|
|
|
|
/**
|
|
* Gets the number of chunks that have been requested
|
|
* @return The number of chunks
|
|
*/
|
|
public int getRequestedCellCount(){
|
|
return this.terrainCache.getRequestedCellCount();
|
|
}
|
|
|
|
}
|