687 lines
28 KiB
Java
687 lines
28 KiB
Java
package electrosphere.client.terrain.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.cells.DrawCell.DrawCellFace;
|
|
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;
|
|
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
|
|
|
/**
|
|
* Manages the graphical entities for the terrain chunks
|
|
*
|
|
*
|
|
*
|
|
Notes for integrating with transvoxel algo:
|
|
Different problems to tackle
|
|
|
|
For all chunks within minimum radius, check if they can be updated and update accordingly <--- we do this currently
|
|
|
|
|
|
For all chunks between minimum radius and first LOD radius, check if we can make a LOD chunk
|
|
If we can, make a lod chunk or see if it can be updated
|
|
This check will be:
|
|
For every position, check if all four positions required for LOD chunk are within lod radius
|
|
if yes, make lod chunk
|
|
|
|
If we cannot, create a fullres chunk
|
|
This check will be:
|
|
For every position, check if all four positions required for LOD chunk are within lod radius
|
|
if yes, make lod chunk
|
|
if they are outside the far bound, create lod chunk
|
|
if they are within the near bound, create a fullres chunk
|
|
|
|
|
|
|
|
*/
|
|
public class DrawCellManager {
|
|
|
|
|
|
//the center of this cell manager's array in cell space
|
|
int cellX;
|
|
int cellY;
|
|
int cellZ;
|
|
|
|
//all currently displaying mini cells
|
|
Set<DrawCell> cells = new HashSet<DrawCell>();
|
|
Map<String,DrawCell> keyCellMap = new HashMap<String,DrawCell>();
|
|
|
|
//status of all position keys
|
|
Set<String> hasNotRequested = new HashSet<String>();
|
|
Set<String> requested = new HashSet<String>();
|
|
Set<String> drawable = new HashSet<String>();
|
|
Set<String> undrawable = new HashSet<String>();
|
|
Set<String> updateable = new HashSet<String>();
|
|
|
|
//LOD level of all position keys
|
|
Map<String,Integer> positionLODLevel = new HashMap<String,Integer>();
|
|
|
|
//voxel atlas
|
|
VoxelTextureAtlas atlas;
|
|
|
|
//shader program for drawable cells
|
|
ShaderProgram program;
|
|
|
|
|
|
|
|
//the real-space radius for which we will construct draw cells inside of
|
|
//ie, we check if the draw cell's entity would be inside this radius. If it would, create the draw cell, otherwise don't
|
|
double drawFullModelRadius = 50;
|
|
|
|
//the radius we'll draw LODed chunks for
|
|
double drawLODRadius = drawFullModelRadius + ServerTerrainChunk.CHUNK_DIMENSION * (2*2 + 4*4 + 8*8 + 16*16);
|
|
|
|
|
|
//the number of possible LOD levels
|
|
//1,2,4,8,16
|
|
static final int NUMBER_OF_LOD_LEVELS = 5;
|
|
|
|
//the table of lod leve -> radius at which we will look for chunks within this log
|
|
double[] lodLevelRadiusTable = new double[5];
|
|
|
|
//the radius for which physics meshes are created when draw cells are created
|
|
int physicsRadius = 3;
|
|
|
|
//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){
|
|
cellX = discreteX;
|
|
cellY = discreteY;
|
|
cellZ = discreteZ;
|
|
|
|
program = Globals.terrainShaderProgram;
|
|
|
|
//the first lod level is set by user
|
|
lodLevelRadiusTable[0] = drawFullModelRadius;
|
|
//generate LOD radius table
|
|
for(int i = 1; i < NUMBER_OF_LOD_LEVELS; i++){
|
|
double sizeOfSingleModel = Math.pow(2,i) * ServerTerrainChunk.CHUNK_DIMENSION;
|
|
//size of the radius for this lod level should be three times the size of a model + the previous radius
|
|
//this guarantees we get at least one adapter chunk, one proper chunk, and also that the radius accounts for the previous lod level chunks
|
|
lodLevelRadiusTable[i] = lodLevelRadiusTable[i-1] + sizeOfSingleModel * 3;
|
|
}
|
|
|
|
physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius();
|
|
|
|
invalidateAllCells();
|
|
|
|
update = true;
|
|
}
|
|
|
|
/**
|
|
* Private constructor
|
|
*/
|
|
DrawCellManager(){
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the player's current position in cell-space
|
|
* @param cellPos The cell's position
|
|
*/
|
|
public void setPlayerCell(Vector3i cellPos){
|
|
cellX = cellPos.x;
|
|
cellY = cellPos.y;
|
|
cellZ = cellPos.z;
|
|
}
|
|
|
|
/**
|
|
* Update function that is called if a cell has not been requested
|
|
*/
|
|
void updateUnrequestedCell(){
|
|
if(hasNotRequested.size() > 0){
|
|
String targetKey = hasNotRequested.iterator().next();
|
|
hasNotRequested.remove(targetKey);
|
|
Vector3i worldPos = getVectorFromKey(targetKey);
|
|
|
|
//
|
|
//Because of the way marching cubes works, we need to request the adjacent chunks so we know how to properly blend between one chunk and the next
|
|
//The following loop-hell does this
|
|
//
|
|
for(int i = 0; i < 2; i++){
|
|
for(int j = 0; j < 2; j++){
|
|
for(int k = 0; k < 2; k++){
|
|
Vector3i posToCheck = new Vector3i(worldPos).add(i,j,k);
|
|
String requestKey = getCellKey(posToCheck.x,posToCheck.y,posToCheck.z);
|
|
if(
|
|
posToCheck.x >= 0 &&
|
|
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
|
|
posToCheck.y >= 0 &&
|
|
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
|
|
posToCheck.z >= 0 &&
|
|
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
|
|
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
|
|
){
|
|
if(!requested.contains(requestKey)){
|
|
//client should request chunk data from server for each chunk necessary to create the model
|
|
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
|
|
posToCheck.x,
|
|
posToCheck.y,
|
|
posToCheck.z
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
undrawable.add(targetKey);
|
|
requested.add(targetKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes one of the undrawable cells drawable
|
|
*/
|
|
void makeCellDrawable(){
|
|
if(undrawable.size() > 0){
|
|
String targetKey = undrawable.iterator().next();
|
|
Vector3i worldPos = getVectorFromKey(targetKey);
|
|
|
|
//
|
|
//Checks if all chunk data necessary to generate a mesh is present
|
|
boolean containsNecessaryChunks = true;
|
|
for(int i = 0; i < 2; i++){
|
|
for(int j = 0; j < 2; j++){
|
|
for(int k = 0; k < 2; k++){
|
|
Vector3i posToCheck = new Vector3i(worldPos).add(i,j,k);
|
|
if(worldPos.x >= 0 &&
|
|
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
|
|
posToCheck.y >= 0 &&
|
|
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
|
|
posToCheck.z >= 0 &&
|
|
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
|
|
!containsChunkDataAtWorldPoint(posToCheck.x,posToCheck.y,posToCheck.z)
|
|
){
|
|
containsNecessaryChunks = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
//if contains data for all chunks necessary to generate visuals
|
|
if(containsNecessaryChunks){
|
|
|
|
//update the status of the terrain key
|
|
undrawable.remove(targetKey);
|
|
drawable.add(targetKey);
|
|
|
|
//build the cell
|
|
DrawCell cell = DrawCell.generateTerrainCell(
|
|
worldPos
|
|
);
|
|
cells.add(cell);
|
|
keyCellMap.put(targetKey,cell);
|
|
DrawCellFace higherLODFace = null;
|
|
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
|
|
|
|
//evaluate for foliage
|
|
Globals.clientFoliageManager.evaluateChunk(worldPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
){
|
|
keyCellMap.get(targetKey).destroy();
|
|
DrawCellFace higherLODFace = null;
|
|
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
|
|
}
|
|
drawable.add(targetKey);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Checks if the manager contains a cell position that hasn't had its chunk data requested from the server yet
|
|
* @return true if there is an unrequested cell, false otherwise
|
|
*/
|
|
public boolean containsUnrequestedCell(){
|
|
return hasNotRequested.size() > 0;
|
|
}
|
|
|
|
/**
|
|
* Checks if the manager contains a cell who hasn't been made drawable yet
|
|
* @return true if there is an undrawable cell, false otherwise
|
|
*/
|
|
public boolean containsUndrawableCell(){
|
|
return undrawable.size() > 0;
|
|
}
|
|
|
|
/**
|
|
* Checks if the manager contains a cell who needs to be updated
|
|
* @return true if there is an updateable cell, false otherwise
|
|
*/
|
|
public boolean containsUpdateableCell(){
|
|
return updateable.size() > 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Transforms a real coordinate into a cell-space coordinate
|
|
* @param input the real coordinate
|
|
* @return the cell coordinate
|
|
*/
|
|
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<DrawCell> cellsToRemove = new HashSet<DrawCell>();
|
|
for(DrawCell cell : cells){
|
|
Vector3d realPos = cell.getRealPos();
|
|
if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawFullModelRadius){
|
|
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);
|
|
requested.remove(key);
|
|
cell.destroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)drawFullModelRadius; x < drawFullModelRadius; x = x + ChunkData.CHUNK_SIZE){
|
|
for(int y = -(int)drawFullModelRadius; y < drawFullModelRadius; y = y + ChunkData.CHUNK_SIZE){
|
|
for(int z = -(int)drawFullModelRadius; z < drawFullModelRadius; 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) < drawFullModelRadius &&
|
|
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) &&
|
|
!requested.contains(key)){
|
|
hasNotRequested.add(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates cells that need updating in this manager
|
|
*/
|
|
public void update(){
|
|
calculateDeltas();
|
|
if(containsUnrequestedCell() && !containsUndrawableCell()){
|
|
updateUnrequestedCell();
|
|
} else if(containsUndrawableCell()){
|
|
makeCellDrawable();
|
|
} else if(containsUpdateableCell()){
|
|
updateCellModel();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Controls whether the client generates drawable chunks or just physics chunks (ie if running a headless client)
|
|
* @param generate true to generate graphics, false otherwise
|
|
*/
|
|
public void setGenerateDrawables(boolean generate){
|
|
this.generateDrawables = generate;
|
|
}
|
|
|
|
/**
|
|
* Checks if the terrain cache has a chunk at a given world point
|
|
* @param worldX the x coordinate
|
|
* @param worldY the y coordinate
|
|
* @param worldZ the z coordinate
|
|
* @return true if the chunk data exists, false otherwise
|
|
*/
|
|
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]));
|
|
}
|
|
|
|
/**
|
|
* 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));
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the radius within which full-detail models are drawn
|
|
* @return the radius
|
|
*/
|
|
public double getDrawFullModelRadius(){
|
|
return drawFullModelRadius;
|
|
}
|
|
|
|
/**
|
|
* Initializes the voxel texture atlas
|
|
*/
|
|
public void attachTextureAtlas(VoxelTextureAtlas voxelTextureAtlas){
|
|
atlas = voxelTextureAtlas;
|
|
}
|
|
|
|
/**
|
|
* Checks if the draw cell at the given world position has generated physics
|
|
* @param worldPos The world position
|
|
* @return true if has generated physics, false otherwise
|
|
*/
|
|
public boolean generatedPhysics(Vector3i worldPos){
|
|
String key = this.getCellKey(worldPos.x, worldPos.y, worldPos.z);
|
|
if(!keyCellMap.containsKey(key)){
|
|
return false;
|
|
}
|
|
DrawCell cell = this.keyCellMap.get(key);
|
|
return cell.modelEntity != null;
|
|
}
|
|
|
|
|
|
/**
|
|
transvoxel algorithm spacing management
|
|
|
|
want to specify a radius and have it be a circle so we're not wasting resolution on far off corners
|
|
To benefit from the LOD, should have a series of for loops
|
|
First loop iterates over (-largest radius) -> (largest radius) at an interval of the width of the lowest level of detail chunk
|
|
We check all 8 corners of the chunk to see if they all fall within the largest lod concentric circle
|
|
There are a few cases to consider
|
|
- All fall outside the largest LOD radius (ignore)
|
|
- All but one fall outside thel argest LOD radius (ignore)
|
|
- All fall within the largest LOD circle (make LOD chunk)
|
|
- One is within the next LOD radius, others are within the current LOD radius (ignore for this pass)
|
|
- All are within a lower LOD radius (ignore)
|
|
|
|
Once we're done with the largest LOD radius, we go to a higher resolution radius and iterate for smaller bounds
|
|
|
|
In a middle LOD level, if all corners are outside, but the lower resolution didn't
|
|
already pick it up, still need to generate a chunk at the current resolution
|
|
|
|
Detection of this case is tricky
|
|
We could snap the currently considered position to the nearest low-resolution chunk and then bounds check that low resolution chunk for every
|
|
higher resolution position we're considering
|
|
This is probably a bad idea
|
|
|
|
We could recurse every time a chunk doesn't work for the current level of detail, then evaluate it for a higher level of detail at this closer value
|
|
|
|
*/
|
|
|
|
//the number of corners to consider for a valid chunk
|
|
static final int NUM_CORNERS_TO_CONSIDER = 8;
|
|
|
|
//offsets to get from current position to the actual corner to consider
|
|
int[] cornerOffsetsX = new int[]{0,1,0,1,0,1,0,1,};
|
|
int[] cornerOffsetsY = new int[]{0,0,0,0,1,1,1,1,};
|
|
int[] cornerOffsetsZ = new int[]{0,0,1,1,0,0,1,1,};
|
|
|
|
/**
|
|
* Recursive function that does chunk validity checking
|
|
* @param playerChunkPos
|
|
* @param minPoint
|
|
* @param maxPoint
|
|
* @param currentLOD
|
|
*/
|
|
public void assesChunkPositionsAtLOD(
|
|
Vector3i playerChunkPos,
|
|
Vector3i minPoint,
|
|
Vector3i maxPoint,
|
|
int currentLOD
|
|
){
|
|
Vector3i currentChunkPositionConsidered = new Vector3i(playerChunkPos); // The chunk position we're currently considering
|
|
double currentRadius = lodLevelRadiusTable[currentLOD];
|
|
double nextRadius = 0;
|
|
int increment = 1;
|
|
if(currentLOD > 0){
|
|
//only works when the LOD is greater than 0. When it's 0, there is no lower bound on chunk positions to generate
|
|
nextRadius = lodLevelRadiusTable[currentLOD-1];
|
|
increment = (int)Math.pow(2,currentLOD);
|
|
}
|
|
|
|
//iterate
|
|
for(int x = minPoint.x; x < maxPoint.x; x = x + increment){
|
|
for(int y = minPoint.y; y < maxPoint.x; y = y + increment){
|
|
for(int z = minPoint.z; z < maxPoint.x; z = z + increment){
|
|
//we have 8 corners to consider
|
|
//need to identify which case the current position is
|
|
int positionCase = 0;
|
|
for(int j = 0; j < NUM_CORNERS_TO_CONSIDER; j++){
|
|
currentChunkPositionConsidered.set(
|
|
x + cornerOffsetsX[j] * increment,
|
|
y + cornerOffsetsY[j] * increment,
|
|
z + cornerOffsetsZ[j] * increment
|
|
);
|
|
//figure out the case of this corner
|
|
double distance = currentChunkPositionConsidered.distance(playerChunkPos);
|
|
if(distance > currentRadius){
|
|
positionCase = 1;
|
|
break;
|
|
} else if(distance <= nextRadius){
|
|
positionCase = 2;
|
|
break;
|
|
}
|
|
}
|
|
switch(positionCase){
|
|
case 0: {
|
|
//fully within current band, generate chunk
|
|
} break;
|
|
case 1: {
|
|
//partially outside bound, ignore
|
|
} break;
|
|
case 2: {
|
|
//partially inside higher resolution bound, recurse
|
|
assesChunkPositionsAtLOD(
|
|
playerChunkPos,
|
|
new Vector3i(x,y,z),
|
|
new Vector3i(x+increment,y+increment,z+increment),
|
|
currentLOD
|
|
);
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assess all LOD chunk levels
|
|
*/
|
|
public void assesLODChunks(){
|
|
|
|
//pre-existing values
|
|
Vector3d playerPosition = EntityUtils.getPosition(Globals.playerEntity);
|
|
Vector3i playerChunkPosition = Globals.clientWorldData.convertRealToWorldSpace(playerPosition);
|
|
|
|
//variables used while iterating across chunk positions
|
|
double currentRadius = 0; //the current radius is in units of chunks, even though it's a double (it's discrete, not real)
|
|
double nextRadius = 0; //the next radius is in units of chunks, even though it's a double (it's discrete, not real)
|
|
int increment = 1; //the increment is in units of chunks, not real
|
|
//the offsets from the player's position to
|
|
//the nearest possible spot we could place a chunk of the current LOD at (in units of chunks)
|
|
Vector3i lowerOffsets = new Vector3i(0,0,0);
|
|
Vector3i currentChunkPositionConsidered = new Vector3i(playerChunkPosition); // The chunk position we're currently considering
|
|
|
|
//actual logic to search for valid chunks
|
|
for(int i = NUMBER_OF_LOD_LEVELS - 1; i >= 0; i--){
|
|
currentRadius = lodLevelRadiusTable[i];
|
|
nextRadius = 0;
|
|
increment = 1;
|
|
if(i > 0){
|
|
//only works when the LOD is greater than 0. When it's 0, there is no lower bound on chunk positions to generate
|
|
nextRadius = lodLevelRadiusTable[i-1];
|
|
increment = (int)Math.pow(2,i);
|
|
}
|
|
//calculate offsets to get from player's current chunk position to the lower resolution grid for the current LOD
|
|
lowerOffsets.x = playerChunkPosition.x % ((int)increment);
|
|
lowerOffsets.y = playerChunkPosition.y % ((int)increment);
|
|
lowerOffsets.z = playerChunkPosition.z % ((int)increment);
|
|
|
|
//iterate
|
|
for(int x = -(int)currentRadius - lowerOffsets.x; x < currentRadius; x = x + increment){
|
|
for(int y = -(int)currentRadius - lowerOffsets.y; y < currentRadius; y = y + increment){
|
|
for(int z = -(int)currentRadius - lowerOffsets.z; z < currentRadius; z = z + increment){
|
|
//we have 8 corners to consider
|
|
//need to identify which case the current position is
|
|
int positionCase = 0;
|
|
for(int j = 0; j < NUM_CORNERS_TO_CONSIDER; j++){
|
|
currentChunkPositionConsidered.set(
|
|
x + cornerOffsetsX[j] * increment,
|
|
y + cornerOffsetsY[j] * increment,
|
|
z + cornerOffsetsZ[j] * increment
|
|
);
|
|
//figure out the case of this corner
|
|
double distance = currentChunkPositionConsidered.distance(playerChunkPosition);
|
|
if(distance > currentRadius){
|
|
positionCase = 1;
|
|
break;
|
|
} else if(distance <= nextRadius){
|
|
positionCase = 2;
|
|
break;
|
|
}
|
|
}
|
|
switch(positionCase){
|
|
case 0: {
|
|
//fully within current band, generate chunk
|
|
} break;
|
|
case 1: {
|
|
//partially outside bound, ignore
|
|
} break;
|
|
case 2: {
|
|
//partially inside higher resolution bound, recurse
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|