package electrosphere.game.client.cells; import electrosphere.game.client.terrain.manager.ClientTerrainManager; import electrosphere.game.server.terrain.manager.ServerTerrainManager; import electrosphere.game.terrain.processing.TerrainInterpolator; import electrosphere.game.client.world.ClientWorldData; import electrosphere.game.collision.CommonWorldData; import electrosphere.main.Globals; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.ShaderProgram; import java.util.Arrays; import java.util.HashMap; import org.joml.Vector3d; import org.joml.Vector3f; /** * * @author satellite */ public class DrawCellManager { //the center of this cell manager's array in cell space int cellX; int cellY; //the dimensions of the world that this cell manager can handles int cellWidth; int cellHeight; //the width of a minicell in this manager int miniCellWidth; //all currently displaying mini cells DrawCell[][] cells; boolean[][] valid; boolean[][] drawable; boolean[][] updateable; boolean[][] hasRequested; boolean[][] needsPhysics; boolean[][] hasPhysics; ShaderProgram program; int drawRadius = 5; int drawStepdownInterval = 3; int drawStepdownValue = 25; int physicsRadius = 3; int worldBoundDiscreteMin = 0; int worldBoundDiscreteMax = 0; //metadata about the game world CommonWorldData commonWorldData; //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; public DrawCellManager(CommonWorldData commonWorldData, ClientTerrainManager clientTerrainManager, int discreteX, int discreteY){ this.commonWorldData = commonWorldData; worldBoundDiscreteMax = (int)(commonWorldData.getWorldBoundMin().x / commonWorldData.getDynamicInterpolationRatio() * 1.0f); // this.clientTerrainManager = clientTerrainManager; cells = new DrawCell[drawRadius * 2 + 1][drawRadius * 2 + 1]; valid = new boolean[drawRadius * 2 + 1][drawRadius * 2 + 1]; drawable = new boolean[drawRadius * 2 + 1][drawRadius * 2 + 1]; updateable = new boolean[drawRadius * 2 + 1][drawRadius * 2 + 1]; hasRequested = new boolean[drawRadius * 2 + 1][drawRadius * 2 + 1]; needsPhysics = new boolean[physicsRadius * 2 + 1][physicsRadius * 2 + 1]; hasPhysics = new boolean[physicsRadius * 2 + 1][physicsRadius * 2 + 1]; for(int x = 0; x < drawRadius * 2 + 1; x++){ for(int y = 0; y < drawRadius * 2 + 1; y++){ valid[x][y] = false; drawable[x][y] = false; updateable[x][y] = false; hasRequested[x][y] = false; } } for(int x = 0; x < physicsRadius * 2 + 1; x++){ for(int y = 0; y < physicsRadius * 2 + 1; y++){ needsPhysics[x][y] = true; hasPhysics[x][y] = false; } } cellX = discreteX; cellY = discreteY; program = Globals.terrainShaderProgram; drawRadius = Globals.userSettings.getGraphicsPerformanceLODChunkRadius(); drawStepdownInterval = Globals.userSettings.getGameplayPhysicsCellRadius(); physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius(); update = true; } DrawCellManager(){ } public int getCellX(){ return cellX; } public int getCellY(){ return cellY; } public void setCellX(int x){ cellX = x; } public void setCellY(int y){ cellY = y; } void updateInvalidCell(){ int targetX = 0; int targetY = 0; boolean found = false; for(int x = 0; x < drawRadius * 2 + 1; x++){ targetX = x; for(int y = 0; y < drawRadius * 2 + 1; y++){ targetY = y; if(!valid[x][y]){ found = true; break; } } if(found){ break; } } if(found){ int currentCellX = cellX - drawRadius + targetX; int currentCellY = cellY - drawRadius + targetY; if( currentCellX >= 0 && currentCellX < commonWorldData.getWorldDiscreteSize() && currentCellY >= 0 && currentCellY < commonWorldData.getWorldDiscreteSize() ){ if(containsHeightmapAtDiscretePoint(currentCellX, currentCellY)){ cells[targetX][targetY] = DrawCell.generateTerrainCell( currentCellX, currentCellY, getHeightmapAtPoint(currentCellX, currentCellY), getTextureMapAtPoint(currentCellX, currentCellY), commonWorldData.getDynamicInterpolationRatio(), program ); valid[targetX][targetY] = true; drawable[targetX][targetY] = false; updateable[targetX][targetY] = false; hasRequested[targetX][targetY] = false; // if(Math.abs(physicsRadius + 1 - targetX) < physicsRadius && Math.abs(physicsRadius + 1 - targetY) < physicsRadius){ // needsPhysics[targetX][targetY] = true; // } } else { if(hasRequested[targetX][targetY] == false){ //client should request macro values from server Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkMessage(currentCellX, currentCellY)); hasRequested[targetX][targetY] = true; } } } else { valid[targetX][targetY] = true; drawable[targetX][targetY] = false; updateable[targetX][targetY] = false; hasRequested[targetX][targetY] = false; } } } void makeCellDrawable(){ int targetX = 0; int targetY = 0; boolean found = false; for(int x = 0; x < drawRadius * 2 + 1; x++){ targetX = x; for(int y = 0; y < drawRadius * 2 + 1; y++){ targetY = y; if(valid[x][y] && !drawable[x][y]){ found = true; break; } } if(found){ break; } } if(found){ int currentCellX = cellX - drawRadius + targetX; int currentCellY = cellY - drawRadius + targetY; if( currentCellX >= 0 && currentCellX < commonWorldData.getWorldDiscreteSize() && currentCellY >= 0 && currentCellY < commonWorldData.getWorldDiscreteSize() ){ //physics radius calculation // if(Math.abs(physicsRadius + 1 - targetX) < physicsRadius && Math.abs(physicsRadius + 1 - targetY) < physicsRadius){ // needsPhysics[targetX][targetY] = true; // } //calculation for stride 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; } //make drawable entity cells[targetX][targetY].generateDrawableEntity(stride); } drawable[targetX][targetY] = true; } } void updateCellModel(){ int targetX = 0; int targetY = 0; boolean found = false; for(int x = 0; x < drawRadius * 2 + 1; x++){ targetX = x; for(int y = 0; y < drawRadius * 2 + 1; y++){ targetY = y; if(updateable[x][y]){ found = true; break; } } if(found){ break; } } if(found){ int currentCellX = cellX - drawRadius + targetX; int currentCellY = cellY - drawRadius + targetY; if( currentCellX >= 0 && currentCellX < commonWorldData.getWorldDiscreteSize() && currentCellY >= 0 && currentCellY < commonWorldData.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; } cells[targetX][targetY].generateDrawableEntity(stride); } updateable[targetX][targetY] = false; drawable[targetX][targetY] = true; } } public boolean containsInvalidCell(){ // if(DRAW_CELL_MANAGER_FLAG_UPDATE_DYNAMIC_TERRAIN){ for(int x = 0;x < drawRadius * 2 + 1; x++){ for(int y = 0; y < drawRadius * 2 + 1; y++){ if(!valid[x][y]){ return true; } } } // } else if(DRAW_CELL_MANAGER_FLAG_GENERATE_ARENA){ // return cells[0][0] == null || cells[1][0] == null || cells[0][1] == null | cells[1][1] == null; // } return false; } public boolean containsUndrawableCell(){ for(int x = 0;x < drawRadius * 2 + 1; x++){ for(int y = 0; y < drawRadius * 2 + 1; y++){ if(!drawable[x][y] && this.generateDrawables){ return true; } } } return false; } public boolean containsUpdateableCell(){ for(int x = 0;x < drawRadius * 2 + 1; x++){ for(int y = 0; y < drawRadius * 2 + 1; y++){ if(updateable[x][y] && this.generateDrawables){ return true; } } } return false; } public boolean containsPhysicsNeedingCell(){ for(int x = 0; x < physicsRadius * 2 + 1; x++){ for(int y = 0; y < physicsRadius * 2 + 1; y++){ if(needsPhysics[x][y]){ return true; } } } return false; } public void addPhysicsToCell(){ int targetX = 0; int targetY = 0; boolean found = false; for(int x = 0; x < physicsRadius * 2 + 1; x++){ targetX = x; for(int y = 0; y < physicsRadius * 2 + 1; y++){ targetY = y; // System.out.println(x + " <=>w " + y); if(needsPhysics[x][y]){ found = true; break; } } if(found){ break; } } if(found){ int currentCellX = cellX - physicsRadius + targetX; int currentCellY = cellY - physicsRadius + targetY; if( currentCellX >= 0 && currentCellX < commonWorldData.getWorldDiscreteSize() && currentCellY >= 0 && currentCellY < commonWorldData.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(clientWorldData.getDynamicInterpolationRatio()/2, Math.max(1, dist / drawStepdownInterval * drawStepdownValue)); // while(clientWorldData.getDynamicInterpolationRatio() % stride != 0){ // stride = stride + 1; // } // if(cells[targetX][targetY + drawRadius] != null){ // System.out.println(targetX + " - " + targetY); cells[targetX + drawRadius - physicsRadius][targetY + drawRadius - physicsRadius].generatePhysics(); // } else { // System.out.println("Current cell is null: " + currentCellX + " - " + currentCellY); // } } needsPhysics[targetX][targetY] = false; hasPhysics[targetX][targetY] = true; } } public void shiftChunksNegX(){ //retire old graphics for(int y = 0; y < drawRadius * 2 + 1; y++){ if(cells[drawRadius * 2][y] != null){ cells[drawRadius * 2][y].retireCell(); } } //shift draw array for(int x = drawRadius * 2; x > 0; x--){ for(int y = 0; y < drawRadius * 2 + 1; y++){ cells[x][y] = cells[x-1][y]; updateable[x][y] = true; hasRequested[x][y] = hasRequested[x-1][y]; } } //invalidate edge of draw array for(int y = 0; y < drawRadius * 2 + 1; y++){ valid[0][y] = false; hasRequested[0][y] = false; } //retire physics of cells for(int x = 0; x < physicsRadius * 2 + 1; x++){ if(hasPhysics[x][physicsRadius * 2]){ cells[x + drawRadius - physicsRadius][physicsRadius * 2 + drawRadius - physicsRadius].destroyPhysics(); } } //shift physics array for(int x = physicsRadius * 2; x > 0; x--){ for(int y = 0; y < physicsRadius * 2 + 1; y++){ needsPhysics[x][y] = needsPhysics[x-1][y]; hasPhysics[x][y] = hasPhysics[x-1][y]; } } //invalidate edge of physics array for(int y = 0; y < physicsRadius * 2 + 1; y++){ needsPhysics[0][y] = true; hasPhysics[0][y] = false; } } public void shiftChunksPosX(){ //retire old graphics for(int y = 0; y < drawRadius * 2 + 1; y++){ if(cells[0][y] != null){ cells[0][y].retireCell(); } } //shift draw array for(int x = 0; x < drawRadius * 2; x++){ for(int y = 0; y < drawRadius * 2 + 1; y++){ cells[x][y] = cells[x+1][y]; updateable[x][y] = true; hasRequested[x][y] = hasRequested[x+1][y]; } } //invalidate edge of draw array for(int y = 0; y < drawRadius * 2 + 1; y++){ valid[drawRadius * 2][y] = false; hasRequested[drawRadius * 2][y] = false; } //retire physics of cells for(int x = 0; x < physicsRadius * 2 + 1; x++){ if(hasPhysics[x][physicsRadius * 2]){ cells[x + drawRadius - physicsRadius][physicsRadius * 2 + drawRadius - physicsRadius].destroyPhysics(); } } //shift physics array for(int x = 0; x < physicsRadius * 2; x++){ for(int y = 0; y < physicsRadius * 2 + 1; y++){ needsPhysics[x][y] = needsPhysics[x+1][y]; hasPhysics[x][y] = hasPhysics[x+1][y]; } } //invalidate edge of physics array for(int y = 0; y < physicsRadius * 2 + 1; y++){ needsPhysics[physicsRadius * 2][y] = true; hasPhysics[physicsRadius * 2][y] = false; } } public void shiftChunksNegY(){ //retire cells for(int x = 0; x < drawRadius * 2 + 1; x++){ if(cells[x][drawRadius * 2] != null){ cells[x][drawRadius * 2].retireCell(); } } //shift draw array for(int x = 0; x < drawRadius * 2 + 1; x++){ for(int y = drawRadius * 2; y > 0; y--){ cells[x][y] = cells[x][y-1]; updateable[x][y] = true; hasRequested[x][y] = hasRequested[x][y-1]; } } //invalidate edge of draw array for(int x = 0; x < drawRadius * 2 + 1; x++){ valid[x][0] = false; hasRequested[x][0] = false; } //retire physics of cells for(int x = 0; x < physicsRadius * 2 + 1; x++){ if(hasPhysics[x][physicsRadius * 2]){ cells[x + drawRadius - physicsRadius][physicsRadius * 2 + drawRadius - physicsRadius].destroyPhysics(); } } //shift physics array for(int x = 0; x < physicsRadius * 2 + 1; x++){ for(int y = physicsRadius * 2; y > 0; y--){ needsPhysics[x][y] = needsPhysics[x][y-1]; hasPhysics[x][y] = hasPhysics[x][y-1]; } } //invalidate edge of physics array for(int x = 0; x < physicsRadius * 2 + 1; x++){ needsPhysics[x][0] = true; hasPhysics[x][0] = false; } } public void shiftChunksPosY(){ //retire old graphics for(int x = 0; x < drawRadius * 2 + 1; x++){ if(cells[x][0] != null){ cells[x][0].retireCell(); } } //shift draw array for(int x = 0; x < drawRadius * 2 + 1; x++){ for(int y = 0; y < drawRadius * 2; y++){ cells[x][y] = cells[x][y+1]; updateable[x][y] = true; hasRequested[x][y] = hasRequested[x][y+1]; } } //invalidate edge of draw array for(int x = 0; x < drawRadius * 2 + 1; x++){ valid[x][drawRadius * 2] = false; hasRequested[x][drawRadius * 2] = false; } //retire physics of cells for(int x = 0; x < physicsRadius * 2 + 1; x++){ if(hasPhysics[x][0]){ cells[x + drawRadius - physicsRadius][0 + drawRadius - physicsRadius].destroyPhysics(); } } //shift physics array for(int x = 0; x < physicsRadius * 2 + 1; x++){ for(int y = 0; y < physicsRadius * 2; y++){ needsPhysics[x][y] = needsPhysics[x][y+1]; hasPhysics[x][y] = hasPhysics[x][y+1]; } } //invalidate edge of physics array for(int x = 0; x < physicsRadius * 2 + 1; x++){ needsPhysics[x][physicsRadius * 2] = true; hasPhysics[x][physicsRadius * 2] = false; } } public int transformRealSpaceToCellSpace(double input){ return (int)(input / commonWorldData.getDynamicInterpolationRatio()); } public void invalidateAllCells(){ for(int x = 0; x < drawRadius * 2 + 1; x++){ for(int y = 0; y < drawRadius * 2 + 1; y++){ valid[x][y] = false; } } } public void calculateDeltas(Vector3d oldPosition, Vector3d newPosition){ // if(DRAW_CELL_MANAGER_FLAG_UPDATE_DYNAMIC_TERRAIN){ if(transformRealSpaceToCellSpace(newPosition.x()) < transformRealSpaceToCellSpace(oldPosition.x())){ shiftChunksNegX(); setCellX(transformRealSpaceToCellSpace(newPosition.x())); setCellY(transformRealSpaceToCellSpace(newPosition.z())); } else if(transformRealSpaceToCellSpace(newPosition.x()) > transformRealSpaceToCellSpace(oldPosition.x())){ shiftChunksPosX(); setCellX(transformRealSpaceToCellSpace(newPosition.x())); setCellY(transformRealSpaceToCellSpace(newPosition.z())); } if(transformRealSpaceToCellSpace(newPosition.z()) < transformRealSpaceToCellSpace(oldPosition.z())){ shiftChunksNegY(); setCellX(transformRealSpaceToCellSpace(newPosition.x())); setCellY(transformRealSpaceToCellSpace(newPosition.z())); } else if(transformRealSpaceToCellSpace(newPosition.z()) > transformRealSpaceToCellSpace(oldPosition.z())){ shiftChunksPosY(); setCellX(transformRealSpaceToCellSpace(newPosition.x())); setCellY(transformRealSpaceToCellSpace(newPosition.z())); } // } } public void update(){ if(update){ // if(DRAW_CELL_MANAGER_FLAG_UPDATE_DYNAMIC_TERRAIN){ if(containsInvalidCell()){ updateInvalidCell(); } else if(containsUndrawableCell()){ makeCellDrawable(); } else if(containsUpdateableCell()){ updateCellModel(); } else if(containsPhysicsNeedingCell()){ addPhysicsToCell(); } } // } else if(DRAW_CELL_MANAGER_FLAG_GENERATE_ARENA){ // if(cells[0][0] == null){ // int arenaChunkWidth = 100; // cells[0][0] = new DrawCell( // new float[arenaChunkWidth + 1][arenaChunkWidth + 1], // arenaChunkWidth + 1, // 0, // 0, // arenaChunkWidth, // program // ); // cells[0][0].generateDrawableEntity(1); // } else if(cells[0][1] == null){ // int arenaChunkWidth = 100; // cells[0][1] = new DrawCell( // new float[arenaChunkWidth + 1][arenaChunkWidth + 1], // arenaChunkWidth + 1, // 0, // 1, // arenaChunkWidth, // program // ); // cells[0][1].generateDrawableEntity(1); // } else if(cells[1][0] == null){ // int arenaChunkWidth = 100; // cells[1][0] = new DrawCell( // new float[arenaChunkWidth + 1][arenaChunkWidth + 1], // arenaChunkWidth + 1, // 1, // 0, // arenaChunkWidth, // program // ); // cells[1][0].generateDrawableEntity(1); // } else if(cells[1][1] == null){ // int arenaChunkWidth = 100; // cells[1][1] = new DrawCell( // new float[arenaChunkWidth + 1][arenaChunkWidth + 1], // arenaChunkWidth + 1, // 1, // 1, // arenaChunkWidth, // program // ); // cells[1][1].generateDrawableEntity(1); // } // } } // public float getElevationAtRealPoint(float realPointX, float realPointY){ //// if(DRAW_CELL_MANAGER_FLAG_UPDATE_DYNAMIC_TERRAIN){ // return terrainManager.getHeightAtPosition(realPointX, realPointY); //// } //// if(DRAW_CELL_MANAGER_FLAG_GENERATE_ARENA){ //// return 0; //// } //// return 0; // } public boolean canValidateCell(){ boolean rVal = false; 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 containsHeightmapAtDiscretePoint(int currentCellX, int currentCellY){ if(Globals.clientTerrainManager != null){ return Globals.clientTerrainManager.containsHeightmapAtDiscretePoint(currentCellX, currentCellY); } return true; } float[][] getHeightmapAtPoint(int currentCellX, int currentCellY){ if(Globals.clientTerrainManager != null){ return Globals.clientTerrainManager.getHeightmapAtPoint(currentCellX, currentCellY); } return Globals.serverTerrainManager.getChunk(currentCellX, currentCellY).getHeightMap(); } float[][] getTextureMapAtPoint(int currentCellX, int currentCellY){ if(Globals.clientTerrainManager != null){ return Globals.clientTerrainManager.getTextureMapAtPoint(currentCellX,currentCellY); } else { //hacky fix to +2 to this, I think the interpolation ratio was different for server/client data //now that we're merging/ambiguous within this class, it's out of bounds-ing unless I +2 //TODO: investigate float[][] rVal = new float[commonWorldData.getDynamicInterpolationRatio() + 2][commonWorldData.getDynamicInterpolationRatio() + 2]; rVal[1][1] = 1; rVal[2][1] = 1; rVal[3][1] = 1; rVal[4][1] = 1; rVal[5][1] = 1; rVal[5][2] = 1; rVal[6][1] = 1; rVal[6][2] = 1; return rVal; } } // public }