package electrosphere.client.terrain.cells; import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.engine.Globals; import electrosphere.entity.EntityUtils; import electrosphere.game.terrain.processing.TerrainInterpolator; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.ShaderProgram; import electrosphere.server.terrain.manager.ServerTerrainManager; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.joml.Vector2i; import org.joml.Vector3d; import org.joml.Vector3f; import org.joml.Vector3i; /** * * @author satellite */ public class DrawCellManager { //the center of this cell manager's array in cell space int cellX; int cellY; int cellZ; //the dimensions of the world that this cell manager can handles int cellWidth; //the width of a minicell in this manager int miniCellWidth; //all currently displaying mini cells Set cells; Map keyCellMap = new HashMap(); Set hasNotRequested; Set hasRequested; Set drawable; Set undrawable; Set updateable; ShaderProgram program; // int drawRadius = 5; int drawStepdownInterval = 3; int drawStepdownValue = 25; double drawRadius = 200; int physicsRadius = 3; int worldBoundDiscreteMin = 0; int worldBoundDiscreteMax = 0; //client terrain manager // ClientTerrainManager clientTerrainManager; //ready to start updating? boolean update = false; //controls whether we try to generate the drawable entities //we want this to be false when in server-only mode boolean generateDrawables = false; /** * DrawCellManager constructor * @param commonWorldData The common world data * @param clientTerrainManager The client terrain manager * @param discreteX The initial discrete position X coordinate * @param discreteY The initial discrete position Y coordinate */ public DrawCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){ worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f); cells = new HashSet(); hasNotRequested = new HashSet(); drawable = new HashSet(); undrawable = new HashSet(); updateable = new HashSet(); hasRequested = new HashSet(); cellX = discreteX; cellY = discreteY; cellZ = discreteZ; program = Globals.terrainShaderProgram; // drawRadius = Globals.userSettings.getGraphicsPerformanceLODChunkRadius(); drawStepdownInterval = Globals.userSettings.getGameplayPhysicsCellRadius(); physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius(); invalidateAllCells(); update = true; } DrawCellManager(){ } public void setCell(Vector3i cellPos){ cellX = cellPos.x; cellY = cellPos.y; cellZ = cellPos.z; } void updateUnrequestedCell(){ if(hasNotRequested.size() > 0){ String targetKey = hasNotRequested.iterator().next(); hasNotRequested.remove(targetKey); Vector3i worldPos = getVectorFromKey(targetKey); // Vector3i vector = getVectorFromKey(targetKey); // int currentCellX = cellX - drawRadius + vector.x; // int currentCellY = cellY - drawRadius + vector.y; // int currentCellZ = cellZ - drawRadius + vector.z; if( worldPos.x >= 0 && worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.y >= 0 && worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.z >= 0 && worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ // if(!hasRequested.contains(targetKey)){ //client should request chunk data from server Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage( worldPos.x, worldPos.y, worldPos.z )); undrawable.add(targetKey); hasRequested.add(targetKey); // } } } } /** * Makes one of the undrawable cells drawable */ void makeCellDrawable(){ if(undrawable.size() > 0){ String targetKey = undrawable.iterator().next(); Vector3i worldPos = getVectorFromKey(targetKey); if( worldPos.x >= 0 && worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.y >= 0 && worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.z >= 0 && worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ if(containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z)){ DrawCell cell = DrawCell.generateTerrainCell( worldPos, Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z), program ); cells.add(cell); keyCellMap.put(targetKey,cell); // undrawable.add(targetKey); undrawable.remove(targetKey); drawable.add(targetKey); //make drawable entity keyCellMap.get(targetKey).generateDrawableEntity(); } } } } /** * Updates a cell that can be updated */ void updateCellModel(){ if(updateable.size() > 0){ String targetKey = updateable.iterator().next(); updateable.remove(targetKey); Vector3i worldPos = getVectorFromKey(targetKey); if( worldPos.x >= 0 && worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.y >= 0 && worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.z >= 0 && worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ // if(Math.abs(drawRadius + 1 - targetX) < physicsRadius && Math.abs(drawRadius + 1 - targetY) < physicsRadius){ // needsPhysics[targetX][targetY] = true; // } // int dist = (int)Math.sqrt((targetX - drawRadius)*(targetX - drawRadius) + (targetY - drawRadius) * (targetY - drawRadius)); //Math.abs(targetX - drawRadius) * Math.abs(targetY - drawRadius); // int stride = Math.min(commonWorldData.getDynamicInterpolationRatio()/2, Math.max(1, dist / drawStepdownInterval * drawStepdownValue)); // while(commonWorldData.getDynamicInterpolationRatio() % stride != 0){ // stride = stride + 1; // } keyCellMap.get(targetKey).generateDrawableEntity(); } drawable.add(targetKey); } } public boolean containsUnrequestedCell(){ return hasNotRequested.size() > 0; } public boolean containsUndrawableCell(){ return undrawable.size() > 0; } public boolean containsUpdateableCell(){ return updateable.size() > 0; } public int transformRealSpaceToCellSpace(double input){ return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio()); } /** * Clears the valid set and adds all keys to invalid set */ public void invalidateAllCells(){ drawable.clear(); hasNotRequested.clear(); clearOutOfBoundsCells(); queueNewCells(); } /** * Calculates whether the position of the player has changed and if so, invalidates and cleans up cells accordingly * @param position The position of the player entity on current frame */ public void calculateDeltas(Vector3d position){ //check if any not requested cells no longer need to be requested clearOutOfBoundsCells(); //check if any cells should be added queueNewCells(); } /** * Clears all cells outside of draw radius */ private void clearOutOfBoundsCells(){ Set cellsToRemove = new HashSet(); for(DrawCell cell : cells){ Vector3d realPos = cell.getRealPos(); if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawRadius){ cellsToRemove.add(cell); } } for(DrawCell cell : cellsToRemove){ cells.remove(cell); String key = getCellKey(cell.worldPos.x, cell.worldPos.y, cell.worldPos.z); hasNotRequested.remove(key); drawable.remove(key); undrawable.remove(key); updateable.remove(key); keyCellMap.remove(key); hasRequested.remove(key); } } /** * Queues new cells that are in bounds but not currently accounted for */ private void queueNewCells(){ if(Globals.playerEntity != null && Globals.clientWorldData != null){ Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); for(int x = -(int)drawRadius; x < drawRadius; x = x + ChunkData.CHUNK_SIZE){ for(int y = -(int)drawRadius; y < drawRadius; y = y + ChunkData.CHUNK_SIZE){ for(int z = -(int)drawRadius; z < drawRadius; z = z + ChunkData.CHUNK_SIZE){ Vector3d newPos = new Vector3d(playerPos.x + x, playerPos.y + y, playerPos.z + z); Vector3i worldPos = new Vector3i( Globals.clientWorldData.convertRealToChunkSpace(newPos.x), Globals.clientWorldData.convertRealToChunkSpace(newPos.y), Globals.clientWorldData.convertRealToChunkSpace(newPos.z) ); Vector3d chunkRealSpace = new Vector3d( Globals.clientWorldData.convertChunkToRealSpace(worldPos.x), Globals.clientWorldData.convertChunkToRealSpace(worldPos.y), Globals.clientWorldData.convertChunkToRealSpace(worldPos.z) ); if( playerPos.distance(chunkRealSpace) < drawRadius && worldPos.x >= 0 && worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.y >= 0 && worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.z >= 0 && worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ String key = getCellKey( Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.x), Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.y), Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.z) ); if(!keyCellMap.containsKey(key) && !hasNotRequested.contains(key) && !undrawable.contains(key) && !drawable.contains(key) && !hasRequested.contains(key)){ hasNotRequested.add(key); } } } } } } } /** * Updates cells that need updating in this manager */ public void update(){ if(update){ if(containsUnrequestedCell() && !containsUndrawableCell()){ updateUnrequestedCell(); } else if(containsUndrawableCell()){ makeCellDrawable(); } else if(containsUpdateableCell()){ updateCellModel(); } } } /** * Splits a cell key into its constituent coordinates in array format. * @param cellKey The cell key to split * @return The coordinates in array format */ // private int[] splitKeyToCoordinates(String cellKey){ // int[] rVal = new int[3]; // String[] components = cellKey.split("_"); // for(int i = 0; i < 3; i++){ // rVal[i] = Integer.parseInt(components[i]); // } // return rVal; // } public boolean coordsInPhysicsSpace(int worldX, int worldY){ return worldX <= cellX + physicsRadius && worldX >= cellX - physicsRadius && worldY <= cellY + physicsRadius && worldY >= cellY - physicsRadius; } public void setGenerateDrawables(boolean generate){ this.generateDrawables = generate; } boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ if(Globals.clientTerrainManager != null){ return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ); } return true; } /** * Gets the chunk data at a given point * @param worldX The world position x component of the cell * @param worldY The world position y component of the cell * @param worldZ The world position z component of the cell * @return The chunk data at the specified points */ ChunkData getChunkDataAtPoint(int worldX, int worldY, int worldZ){ return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ); } /** * Gets a unique key for the cell * @param worldX The world position x component of the cell * @param worldY The world position y component of the cell * @param worldZ The world position z component of the cell * @return The key */ private String getCellKey(int worldX, int worldY, int worldZ){ return worldX + "_" + worldY + "_" + worldZ; } /** * Parses a vector3i from the cell key * @param key The cell key * @return The vector3i containing the components of the cell key */ private Vector3i getVectorFromKey(String key){ String[] keyComponents = key.split("_"); return new Vector3i(Integer.parseInt(keyComponents[0]),Integer.parseInt(keyComponents[1]),Integer.parseInt(keyComponents[2])); } // public }