Renderer/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java
2024-09-04 00:42:06 -04:00

413 lines
15 KiB
Java

package electrosphere.client.fluid.cells;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.renderer.shader.ShaderProgram;
/**
*
* @author satellite
*/
public class FluidCellManager {
//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<FluidCell> cells;
Map<String,FluidCell> keyCellMap = new HashMap<String,FluidCell>();
Set<String> hasNotRequested;
Set<String> hasRequested;
Set<String> drawable;
Set<String> undrawable;
Set<String> updateable;
ShaderProgram program;
// int drawRadius = 5;
int drawStepdownInterval = 3;
int drawStepdownValue = 25;
double drawRadius = 50;
int physicsRadius = 3;
int worldBoundDiscreteMin = 0;
int worldBoundDiscreteMax = 0;
//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 FluidCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){
if(Globals.clientWorldData != null){
worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f);
}
cells = new HashSet<FluidCell>();
hasNotRequested = new HashSet<String>();
drawable = new HashSet<String>();
undrawable = new HashSet<String>();
updateable = new HashSet<String>();
hasRequested = new HashSet<String>();
cellX = discreteX;
cellY = discreteY;
cellZ = discreteZ;
program = Globals.terrainShaderProgram;
// drawRadius = Globals.userSettings.getGraphicsPerformanceLODChunkRadius();
drawStepdownInterval = Globals.userSettings.getGameplayPhysicsCellRadius();
physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius();
invalidateAllCells();
update = true;
}
FluidCellManager(){
}
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.constructRequestFluidDataMessage(
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)){
FluidCell cell = FluidCell.generateFluidCell(
worldPos,
Globals.clientFluidManager.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).destroy();
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
*/
private void calculateDeltas(){
//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<FluidCell> cellsToRemove = new HashSet<FluidCell>();
for(FluidCell cell : cells){
Vector3d realPos = cell.getRealPos();
if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawRadius){
cellsToRemove.add(cell);
}
}
for(FluidCell 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(){
calculateDeltas();
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.clientFluidManager != null){
return Globals.clientFluidManager.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]));
}
/**
* Marks a data cell as updateable (can be regenerated with a new model because the underlying data has changed)
* @param chunkX The chunk x coordinate
* @param chunkY The chunk y coordinate
* @param chunkZ The chunk z coordinate
*/
public void markUpdateable(int chunkX, int chunkY, int chunkZ){
updateable.add(getCellKey(chunkX, chunkY, chunkZ));
}
// public
}