voxelImprovements #5

Merged
railgun merged 21 commits from voxelImprovements into master 2024-11-07 15:08:54 -05:00
28 changed files with 878 additions and 322 deletions

View File

@ -404,7 +404,7 @@ public class FluidCellManager {
* @return The chunk data at the specified points * @return The chunk data at the specified points
*/ */
ChunkData getChunkDataAtPoint(int worldX, int worldY, int worldZ){ ChunkData getChunkDataAtPoint(int worldX, int worldY, int worldZ){
return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ); return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ,ChunkData.NO_STRIDE);
} }

View File

@ -205,7 +205,7 @@ public class FoliageCell {
protected void generate(){ protected void generate(){
boolean shouldGenerate = false; boolean shouldGenerate = false;
//get foliage types supported //get foliage types supported
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition); ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition,ChunkData.NO_STRIDE);
if(data == null){ if(data == null){
return; return;
} }

View File

@ -77,9 +77,9 @@ public class FoliageChunk {
*/ */
public void initCells(){ public void initCells(){
Globals.profiler.beginCpuSample("FoliageChunk.initCells"); Globals.profiler.beginCpuSample("FoliageChunk.initCells");
this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos); this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos,ChunkData.NO_STRIDE);
// //evaluate top cells if chunk above this one exists // //evaluate top cells if chunk above this one exists
this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0)); this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0),ChunkData.NO_STRIDE);
this.updateCells(true); this.updateCells(true);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
@ -113,7 +113,7 @@ public class FoliageChunk {
* @return true if contains foliage voxel, false otherwise * @return true if contains foliage voxel, false otherwise
*/ */
private boolean checkContainsFoliageVoxel(){ private boolean checkContainsFoliageVoxel(){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos()); ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos(),ChunkData.NO_STRIDE);
if(data == null){ if(data == null){
return false; return false;
} }

View File

@ -12,6 +12,11 @@ import electrosphere.server.terrain.manager.ServerTerrainChunk;
*/ */
public class ChunkData { public class ChunkData {
/**
* No stride
*/
public static final int NO_STRIDE = 0;
//The size of a chunk in virtual data //The size of a chunk in virtual data
public static final int CHUNK_SIZE = ServerTerrainChunk.CHUNK_DIMENSION; public static final int CHUNK_SIZE = ServerTerrainChunk.CHUNK_DIMENSION;
//The size of the data passed into marching cubes/transvoxel algorithm to get a fully connected and seamless chunk //The size of the data passed into marching cubes/transvoxel algorithm to get a fully connected and seamless chunk
@ -39,16 +44,23 @@ public class ChunkData {
*/ */
int worldZ; int worldZ;
/**
* The stride of the data
*/
int stride;
/** /**
* Creates a chunk data * Creates a chunk data
* @param worldX The word x coordinate * @param worldX The word x coordinate
* @param worldY The word y coordinate * @param worldY The word y coordinate
* @param worldZ The word z coordinate * @param worldZ The word z coordinate
* @param stride The stride of the data
*/ */
public ChunkData(int worldX, int worldY, int worldZ){ public ChunkData(int worldX, int worldY, int worldZ, int stride){
this.worldX = worldX; this.worldX = worldX;
this.worldY = worldY; this.worldY = worldY;
this.worldZ = worldZ; this.worldZ = worldZ;
this.stride = stride;
} }
@ -211,5 +223,13 @@ public class ChunkData {
return new Vector3i(worldX,worldY,worldZ); return new Vector3i(worldX,worldY,worldZ);
} }
/**
* Gets the stride of the data
* @return The stride of the data
*/
public int getStride(){
return stride;
}
} }

View File

@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
import org.joml.Vector3i; import org.joml.Vector3i;
@ -16,19 +17,50 @@ import io.github.studiorailgun.HashUtils;
*/ */
public class ClientTerrainCache { public class ClientTerrainCache {
//cache capacity /**
* Cache capacity
*/
int cacheSize; int cacheSize;
//the map of chunk key -> chunk data
Map<Long,ChunkData> cacheMap = new ConcurrentHashMap<Long,ChunkData>(); /**
//the list of keys in the cache * The map of full res chunk key -> chunk data
*/
Map<Long,ChunkData> cacheMapFullRes = new ConcurrentHashMap<Long,ChunkData>();
/**
* The map of half res chunk key -> chunk data
*/
Map<Long,ChunkData> cacheMapHalfRes = new ConcurrentHashMap<Long,ChunkData>();
/**
* The map of quarter res chunk key -> chunk data
*/
Map<Long,ChunkData> cacheMapQuarterRes = new ConcurrentHashMap<Long,ChunkData>();
/**
* The map of eighth res chunk key -> chunk data
*/
Map<Long,ChunkData> cacheMapEighthRes = new ConcurrentHashMap<Long,ChunkData>();
/**
* The map of sixteenth res chunk key -> chunk data
*/
Map<Long,ChunkData> cacheMapSixteenthRes = new ConcurrentHashMap<Long,ChunkData>();
/**
* The list of keys in the cache
*/
List<Long> cacheList = new CopyOnWriteArrayList<Long>(); List<Long> cacheList = new CopyOnWriteArrayList<Long>();
//A map of chunk to its world position
/**
* A map of chunk to its world position
*/
Map<ChunkData,Vector3i> chunkPositionMap = new ConcurrentHashMap<ChunkData,Vector3i>(); Map<ChunkData,Vector3i> chunkPositionMap = new ConcurrentHashMap<ChunkData,Vector3i>();
/** /**
* The map tracking chunks that have been requested * The lock on the terrain cache
*/ */
Map<Long,Boolean> requestedChunks = new ConcurrentHashMap<Long,Boolean>(); Semaphore lock = new Semaphore(1);
/** /**
* Constructor * Constructor
@ -46,23 +78,27 @@ public class ClientTerrainCache {
* @param chunkData The chunk data to add at the specified positions * @param chunkData The chunk data to add at the specified positions
*/ */
public void addChunkDataToCache(int worldX, int worldY, int worldZ, ChunkData chunkData){ public void addChunkDataToCache(int worldX, int worldY, int worldZ, ChunkData chunkData){
cacheMap.put(getKey(worldX,worldY,worldZ),chunkData); lock.acquireUninterruptibly();
Map<Long,ChunkData> cache = this.getCache(chunkData.getStride());
cache.put(getKey(worldX,worldY,worldZ),chunkData);
chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ)); chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ));
while(cacheList.size() > cacheSize){ while(cacheList.size() > cacheSize){
Long currentChunk = cacheList.remove(0); Long currentChunk = cacheList.remove(0);
ChunkData data = cacheMap.remove(currentChunk); cache.remove(currentChunk);
Vector3i worldPos = data.getWorldPos();
requestedChunks.remove(getKey(worldPos.x,worldPos.y,worldPos.z));
} }
lock.release();
} }
/** /**
* Evicts all chunks from the cache * Evicts all chunks from the cache
*/ */
public void evictAll(){ public void evictAll(){
lock.acquireUninterruptibly();
this.cacheList.clear(); this.cacheList.clear();
this.cacheMap.clear(); this.cacheMapFullRes.clear();
this.cacheMapHalfRes.clear();
this.chunkPositionMap.clear(); this.chunkPositionMap.clear();
lock.release();
} }
@ -82,10 +118,14 @@ public class ClientTerrainCache {
* @param worldX The x world position * @param worldX The x world position
* @param worldY The y world position * @param worldY The y world position
* @param worldZ The z world position * @param worldZ The z world position
* @param stride The stride of the data
* @return True if the cache contains chunk data at the specified point, false otherwise * @return True if the cache contains chunk data at the specified point, false otherwise
*/ */
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){
return cacheMap.containsKey(getKey(worldX,worldY,worldZ)); lock.acquireUninterruptibly();
boolean rVal = this.getCache(stride).containsKey(getKey(worldX,worldY,worldZ));
lock.release();
return rVal;
} }
@ -97,10 +137,14 @@ public class ClientTerrainCache {
* @param worldX The x world position * @param worldX The x world position
* @param worldY The y world position * @param worldY The y world position
* @param worldZ The z world position * @param worldZ The z world position
* @param stride The stride of the data
* @return The chunk data if it exists, null otherwise * @return The chunk data if it exists, null otherwise
*/ */
public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ){ public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ, int stride){
return cacheMap.get(getKey(worldX,worldY,worldZ)); lock.acquireUninterruptibly();
ChunkData rVal = this.getCache(stride).get(getKey(worldX,worldY,worldZ));
lock.release();
return rVal;
} }
/** /**
@ -108,7 +152,7 @@ public class ClientTerrainCache {
* @return The list of all chunks in the cache * @return The list of all chunks in the cache
*/ */
public Collection<ChunkData> getAllChunks(){ public Collection<ChunkData> getAllChunks(){
return this.cacheMap.values(); return this.cacheMapFullRes.values();
} }
/** /**
@ -119,34 +163,33 @@ public class ClientTerrainCache {
public Vector3i getChunkPosition(ChunkData chunk){ public Vector3i getChunkPosition(ChunkData chunk){
return chunkPositionMap.get(chunk); return chunkPositionMap.get(chunk);
} }
/**
* Gets the number of cells that have been requested
* @return The number of cells that have been requested
*/
public int getRequestedCellCount(){
return this.requestedChunks.size();
}
/**
* Checks if a chunk has been requested or not
* @param worldX The x coordinate in the world
* @param worldY The y coordinate in the world
* @param worldZ The z coordinate in the world
* @return true if it has been requested, false otherwise
*/
public boolean hasRequested(int worldX, int worldY, int worldZ){
return this.requestedChunks.containsKey(getKey(worldX, worldY, worldZ));
}
/**
* Marks a chunk as requested
* @param worldX The x coordinate in the world
* @param worldY The y coordinate in the world
* @param worldZ The z coordinate in the world
*/
public void markAsRequested(int worldX, int worldY, int worldZ){
this.requestedChunks.put(getKey(worldX, worldY, worldZ),true);
}
/**
* Gets the cache
* @param stride The stride of the data
* @return The cache to use
*/
public Map<Long,ChunkData> getCache(int stride){
switch(stride){
case 0: {
return cacheMapFullRes;
}
case 1: {
return cacheMapHalfRes;
}
case 2: {
return cacheMapQuarterRes;
}
case 3: {
return cacheMapEighthRes;
}
case 4: {
return cacheMapEighthRes;
}
default: {
throw new Error("Invalid stride probided! " + stride);
}
}
}
} }

View File

@ -25,6 +25,11 @@ public class ClientDrawCellManager {
*/ */
static final int UPDATE_ATTEMPTS_PER_FRAME = 3; static final int UPDATE_ATTEMPTS_PER_FRAME = 3;
/**
* The number of generation attempts before a cell is marked as having not requested its data
*/
static final int FAILED_GENERATION_ATTEMPT_THRESHOLD = 250;
/** /**
* The distance to draw at full resolution * The distance to draw at full resolution
*/ */
@ -35,6 +40,51 @@ public class ClientDrawCellManager {
*/ */
public static final double HALF_RES_DIST = 16 * ServerTerrainChunk.CHUNK_DIMENSION; public static final double HALF_RES_DIST = 16 * ServerTerrainChunk.CHUNK_DIMENSION;
/**
* The distance for quarter resolution
*/
public static final double QUARTER_RES_DIST = 20 * ServerTerrainChunk.CHUNK_DIMENSION;
/**
* The distance for eighth resolution
*/
public static final double EIGHTH_RES_DIST = 32 * ServerTerrainChunk.CHUNK_DIMENSION;
/**
* The distance for sixteenth resolution
*/
public static final double SIXTEENTH_RES_DIST = 128 * ServerTerrainChunk.CHUNK_DIMENSION;
/**
* Lod value for a full res chunk
*/
public static final int FULL_RES_LOD = 0;
/**
* Lod value for a half res chunk
*/
public static final int HALF_RES_LOD = 1;
/**
* Lod value for a quarter res chunk
*/
public static final int QUARTER_RES_LOD = 2;
/**
* Lod value for a eighth res chunk
*/
public static final int EIGHTH_RES_LOD = 3;
/**
* Lod value for a sixteenth res chunk
*/
public static final int SIXTEENTH_RES_LOD = 4;
/**
* Lod value for evaluating all lod levels
*/
public static final int ALL_RES_LOD = 5;
/** /**
* The octree holding all the chunks to evaluate * The octree holding all the chunks to evaluate
*/ */
@ -80,6 +130,11 @@ public class ClientDrawCellManager {
*/ */
int generated = 0; int generated = 0;
/**
* Tracks whether the cell manager has initialized or not
*/
boolean initialized = false;
/** /**
* Constructor * Constructor
* @param voxelTextureAtlas The voxel texture atlas * @param voxelTextureAtlas The voxel texture atlas
@ -100,12 +155,24 @@ public class ClientDrawCellManager {
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
//the sets to iterate through //the sets to iterate through
updatedLastFrame = true; updatedLastFrame = true;
int attempts = 0;
validCellCount = 0; validCellCount = 0;
while(updatedLastFrame && attempts < UPDATE_ATTEMPTS_PER_FRAME){ //update all full res cells
FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot(); FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot();
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos); updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, HALF_RES_LOD);
attempts++; if(!updatedLastFrame && !this.initialized){
this.initialized = true;
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, QUARTER_RES_LOD);
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, EIGHTH_RES_LOD);
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, SIXTEENTH_RES_LOD);
}
if(!updatedLastFrame){
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, ALL_RES_LOD);
} }
} }
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
@ -115,9 +182,10 @@ public class ClientDrawCellManager {
* Recursively update child nodes * Recursively update child nodes
* @param node The root node * @param node The root node
* @param playerPos The player's position * @param playerPos The player's position
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if there is work remaining to be done, false otherwise * @return true if there is work remaining to be done, false otherwise
*/ */
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos){ private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos, int minLeafLod){
Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity); Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
boolean updated = false; boolean updated = false;
if(this.shouldSplit(playerPos, node)){ if(this.shouldSplit(playerPos, node)){
@ -161,19 +229,34 @@ public class ClientDrawCellManager {
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
updated = true; updated = true;
} else if(shouldRequest(playerPos, node)){ } else if(shouldRequest(playerPos, node, minLeafLod)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request"); Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
DrawCell cell = node.getData(); DrawCell cell = node.getData();
this.requestChunks(node); List<DrawCellFace> highResFaces = this.solveHighResFace(node);
cell.setHasRequested(true);
//actually send requests
if(this.requestChunks(node, highResFaces)){
cell.setHasRequested(true);
}
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
updated = true; updated = true;
} else if(shouldGenerate(playerPos, node)){ } else if(shouldGenerate(playerPos, node, minLeafLod)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.generate"); Globals.profiler.beginCpuSample("ClientDrawCellManager.generate");
int lodLevel = this.getLODLevel(playerRealPos, node); int lodLevel = this.getLODLevel(playerRealPos, node);
List<DrawCellFace> highResFaces = this.solveHighResFace(node); List<DrawCellFace> highResFaces = this.solveHighResFace(node);
if(containsDataToGenerate(node,highResFaces)){ if(containsDataToGenerate(node,highResFaces)){
node.getData().generateDrawableEntity(textureAtlas, lodLevel, highResFaces); node.getData().generateDrawableEntity(textureAtlas, lodLevel, highResFaces);
if(node.getData().getFailedGenerationAttempts() > FAILED_GENERATION_ATTEMPT_THRESHOLD){
node.getData().setHasRequested(false);
}
} else if(node.getData() != null){
node.getData().setFailedGenerationAttempts(node.getData().getFailedGenerationAttempts() + 1);
if(node.getData().getFailedGenerationAttempts() > FAILED_GENERATION_ATTEMPT_THRESHOLD){
node.getData().setHasRequested(false);
}
} }
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
updated = true; updated = true;
@ -181,7 +264,7 @@ public class ClientDrawCellManager {
this.validCellCount++; this.validCellCount++;
List<FloatingChunkTreeNode<DrawCell>> children = new LinkedList<FloatingChunkTreeNode<DrawCell>>(node.getChildren()); List<FloatingChunkTreeNode<DrawCell>> children = new LinkedList<FloatingChunkTreeNode<DrawCell>>(node.getChildren());
for(FloatingChunkTreeNode<DrawCell> child : children){ for(FloatingChunkTreeNode<DrawCell> child : children){
boolean childUpdate = recursivelyUpdateCells(child, playerPos); boolean childUpdate = recursivelyUpdateCells(child, playerPos, minLeafLod);
if(childUpdate == true){ if(childUpdate == true){
updated = true; updated = true;
} }
@ -216,7 +299,22 @@ public class ClientDrawCellManager {
node.canSplit() && node.canSplit() &&
( (
( (
node.getLevel() < this.chunkTree.getMaxLevel() - 1 && node.getLevel() < this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD &&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD &&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
this.getMinDistance(pos, node) <= QUARTER_RES_DIST
)
||
(
node.getLevel() < this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
this.getMinDistance(pos, node) <= HALF_RES_DIST this.getMinDistance(pos, node) <= HALF_RES_DIST
) )
|| ||
@ -355,13 +453,28 @@ public class ClientDrawCellManager {
!node.isLeaf() && !node.isLeaf() &&
( (
( (
node.getLevel() == this.chunkTree.getMaxLevel() - 1 && node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
this.getMinDistance(pos, node) > FULL_RES_DIST this.getMinDistance(pos, node) > FULL_RES_DIST
) )
|| ||
( (
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
this.getMinDistance(pos, node) > HALF_RES_DIST this.getMinDistance(pos, node) > HALF_RES_DIST
) )
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD &&
this.getMinDistance(pos, node) > QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD &&
this.getMinDistance(pos, node) > EIGHTH_RES_DIST
)
||
(
this.getMinDistance(pos, node) > SIXTEENTH_RES_DIST
)
) )
; ;
} }
@ -370,13 +483,15 @@ public class ClientDrawCellManager {
* Checks if this cell should request chunk data * Checks if this cell should request chunk data
* @param pos the player's position * @param pos the player's position
* @param node the node * @param node the node
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if should request chunk data, false otherwise * @return true if should request chunk data, false otherwise
*/ */
public boolean shouldRequest(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){ public boolean shouldRequest(Vector3d pos, FloatingChunkTreeNode<DrawCell> node, int minLeafLod){
return return
node.isLeaf() && node.isLeaf() &&
node.getData() != null && node.getData() != null &&
!node.getData().hasRequested() && !node.getData().hasRequested() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod &&
( (
( (
node.getLevel() == this.chunkTree.getMaxLevel() node.getLevel() == this.chunkTree.getMaxLevel()
@ -385,9 +500,27 @@ public class ClientDrawCellManager {
) )
|| ||
( (
node.getLevel() == this.chunkTree.getMaxLevel() - 1 node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD
&& &&
this.getMinDistance(pos, node) <= HALF_RES_DIST this.getMinDistance(pos, node) <= QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD
&&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
) )
) )
; ;
@ -397,24 +530,44 @@ public class ClientDrawCellManager {
* Checks if this cell should generate * Checks if this cell should generate
* @param pos the player's position * @param pos the player's position
* @param node the node * @param node the node
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @return true if should generate, false otherwise * @return true if should generate, false otherwise
*/ */
public boolean shouldGenerate(Vector3d pos, FloatingChunkTreeNode<DrawCell> node){ public boolean shouldGenerate(Vector3d pos, FloatingChunkTreeNode<DrawCell> node, int minLeafLod){
return return
node.isLeaf() && node.isLeaf() &&
node.getData() != null && node.getData() != null &&
!node.getData().hasGenerated() && !node.getData().hasGenerated() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod &&
( (
( (
node.getLevel() == this.chunkTree.getMaxLevel() node.getLevel() == this.chunkTree.getMaxLevel()
// && // &&
// this.getMinDistance(pos, node) <= FULL_RES_DIST // this.getMinDistance(pos, node) <= FULL_RES_DIST
) )
|| ||
( (
node.getLevel() == this.chunkTree.getMaxLevel() - 1 node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD
&& &&
this.getMinDistance(pos, node) <= HALF_RES_DIST this.getMinDistance(pos, node) <= QUARTER_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST
)
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - SIXTEENTH_RES_LOD
&&
this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
) )
) )
; ;
@ -490,31 +643,19 @@ public class ClientDrawCellManager {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
/**
* 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;
}
/** /**
* Requests all chunks for a given draw cell * Requests all chunks for a given draw cell
* @param cell The cell * @param cell The cell
* @return true if all cells were successfully requested, false otherwise
*/ */
private void requestChunks(WorldOctTree.FloatingChunkTreeNode<DrawCell> node){ private boolean requestChunks(WorldOctTree.FloatingChunkTreeNode<DrawCell> node, List<DrawCellFace> highResFaces){
DrawCell cell = node.getData(); DrawCell cell = node.getData();
int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1; int lod = this.chunkTree.getMaxLevel() - node.getLevel();
for(int i = 0; i < 2 * lodMultiplitier; i++){ int spacingFactor = (int)Math.pow(2,lod);
for(int j = 0; j < 2 * lodMultiplitier; j++){ for(int i = 0; i < 2; i++){
for(int k = 0; k < 2 * lodMultiplitier; k++){ for(int j = 0; j < 2; j++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k); for(int k = 0; k < 2; k++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i*spacingFactor,j*spacingFactor,k*spacingFactor);
if( if(
posToCheck.x >= 0 && posToCheck.x >= 0 &&
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -522,15 +663,65 @@ public class ClientDrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 && posToCheck.z >= 0 &&
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z, lod)
){ ){
//client should request chunk data from server for each chunk necessary to create the model //client should request chunk data from server for each chunk necessary to create the model
LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck); LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck);
Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z); if(!Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, lod)){
return false;
}
} }
} }
} }
} }
int highResLod = this.chunkTree.getMaxLevel() - (node.getLevel() + 1);
int highResSpacingFactor = (int)Math.pow(2,highResLod);
if(highResFaces != null){
for(DrawCellFace highResFace : highResFaces){
//x & y are in face-space
for(int x = 0; x < 3; x++){
for(int y = 0; y < 3; y++){
Vector3i posToCheck = null;
//implicitly performing transforms to adapt from face-space to world space
switch(highResFace){
case X_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(spacingFactor,x*highResSpacingFactor,y*highResSpacingFactor);
} break;
case X_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(0,x*highResSpacingFactor,y*highResSpacingFactor);
} break;
case Y_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,spacingFactor,y*highResSpacingFactor);
} break;
case Y_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,0,y*highResSpacingFactor);
} break;
case Z_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,spacingFactor);
} break;
case Z_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,0);
} break;
}
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, highResLod)
){
LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck);
if(!Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, highResLod)){
return false;
}
}
}
}
}
}
return true;
} }
/** /**
@ -541,11 +732,12 @@ public class ClientDrawCellManager {
*/ */
private boolean containsDataToGenerate(WorldOctTree.FloatingChunkTreeNode<DrawCell> node, List<DrawCellFace> highResFaces){ private boolean containsDataToGenerate(WorldOctTree.FloatingChunkTreeNode<DrawCell> node, List<DrawCellFace> highResFaces){
DrawCell cell = node.getData(); DrawCell cell = node.getData();
int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1; int lod = this.chunkTree.getMaxLevel() - node.getLevel();
for(int i = 0; i < 2 * lodMultiplitier; i++){ int spacingFactor = (int)Math.pow(2,lod);
for(int j = 0; j < 2 * lodMultiplitier; j++){ for(int i = 0; i < 2; i++){
for(int k = 0; k < 2 * lodMultiplitier; k++){ for(int j = 0; j < 2; j++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k); for(int k = 0; k < 2; k++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i*spacingFactor,j*spacingFactor,k*spacingFactor);
if( if(
posToCheck.x >= 0 && posToCheck.x >= 0 &&
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -553,38 +745,40 @@ public class ClientDrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 && posToCheck.z >= 0 &&
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z, lod)
){ ){
return false; return false;
} }
} }
} }
} }
int highResLod = this.chunkTree.getMaxLevel() - (node.getLevel() + 1);
int highResSpacingFactor = (int)Math.pow(2,highResLod);
if(highResFaces != null){ if(highResFaces != null){
for(DrawCellFace highResFace : highResFaces){ for(DrawCellFace highResFace : highResFaces){
//x & y are in face-space //x & y are in face-space
for(int x = 0; x < 2 * lodMultiplitier; x++){ for(int x = 0; x < 3; x++){
for(int y = 0; y < 2 * lodMultiplitier; y++){ for(int y = 0; y < 3; y++){
Vector3i posToCheck = null; Vector3i posToCheck = null;
//implicitly performing transforms to adapt from face-space to world space //implicitly performing transforms to adapt from face-space to world space
switch(highResFace){ switch(highResFace){
case X_POSITIVE: { case X_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(lodMultiplitier,x,y); posToCheck = new Vector3i(cell.getWorldPos()).add(spacingFactor,x*highResSpacingFactor,y*highResSpacingFactor);
} break; } break;
case X_NEGATIVE: { case X_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(-1,x,y); posToCheck = new Vector3i(cell.getWorldPos()).add(0,x*highResSpacingFactor,y*highResSpacingFactor);
} break; } break;
case Y_POSITIVE: { case Y_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,lodMultiplitier,y); posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,spacingFactor,y*highResSpacingFactor);
} break; } break;
case Y_NEGATIVE: { case Y_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,-1,y); posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,0,y*highResSpacingFactor);
} break; } break;
case Z_POSITIVE: { case Z_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,lodMultiplitier); posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,spacingFactor);
} break; } break;
case Z_NEGATIVE: { case Z_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,-1); posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,0);
} break; } break;
} }
if( if(
@ -594,7 +788,7 @@ public class ClientDrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 && posToCheck.z >= 0 &&
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z, highResLod)
){ ){
return false; return false;
} }
@ -685,6 +879,14 @@ public class ClientDrawCellManager {
return generated; return generated;
} }
/**
* Gets whether the client draw cell manager has initialized or not
* @return true if it has initialized, false otherwise
*/
public boolean isInitialized(){
return this.initialized;
}
} }

View File

@ -87,6 +87,11 @@ public class DrawCell {
* True if there are multiple types of voxels in this chunk * True if there are multiple types of voxels in this chunk
*/ */
boolean multiVoxelType = false; boolean multiVoxelType = false;
/**
* Number of failed generation attempts
*/
int failedGenerationAttempts = 0;
/** /**
@ -116,7 +121,7 @@ public class DrawCell {
boolean success = this.fillInData(lod); boolean success = this.fillInData(lod);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
if(!success){ if(!success){
this.setHasRequested(false); this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
return; return;
} }
if(modelEntity != null){ if(modelEntity != null){
@ -129,7 +134,7 @@ public class DrawCell {
success = this.fillInFaceData(chunkData,face,lod); success = this.fillInFaceData(chunkData,face,lod);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
if(!success){ if(!success){
this.setHasRequested(false); this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
return; return;
} }
} }
@ -192,23 +197,39 @@ public class DrawCell {
for(int y = 0; y < ChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){ for(int y = 0; y < ChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){
for(int z = 0; z < ChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){ for(int z = 0; z < ChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + (x * spacingFactor) / ChunkData.CHUNK_SIZE, worldPos.x + (x / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.y + (y * spacingFactor) / ChunkData.CHUNK_SIZE, worldPos.y + (y / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE worldPos.z + (z / ChunkData.CHUNK_SIZE) * spacingFactor,
lod
); );
if(currentChunk == null){ if(currentChunk == null){
return false; Vector3i posToCheck = new Vector3i(
worldPos.x + (x / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.y + (y / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.z + (z / ChunkData.CHUNK_SIZE) * spacingFactor
);
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()
){
return false;
}
} else {
weights[x][y][z] = currentChunk.getWeight(
x % ChunkData.CHUNK_SIZE,
y % ChunkData.CHUNK_SIZE,
z % ChunkData.CHUNK_SIZE
);
types[x][y][z] = currentChunk.getType(
x % ChunkData.CHUNK_SIZE,
y % ChunkData.CHUNK_SIZE,
z % ChunkData.CHUNK_SIZE
);
} }
weights[x][y][z] = currentChunk.getWeight(
(x * spacingFactor) % ChunkData.CHUNK_SIZE,
(y * spacingFactor) % ChunkData.CHUNK_SIZE,
(z * spacingFactor) % ChunkData.CHUNK_SIZE
);
types[x][y][z] = currentChunk.getType(
(x * spacingFactor) % ChunkData.CHUNK_SIZE,
(y * spacingFactor) % ChunkData.CHUNK_SIZE,
(z * spacingFactor) % ChunkData.CHUNK_SIZE
);
//checks to see if there is only one type of voxel in this chunk //checks to see if there is only one type of voxel in this chunk
if(!this.multiVoxelType){ if(!this.multiVoxelType){
@ -233,26 +254,30 @@ public class DrawCell {
*/ */
private boolean fillInFaceData(TransvoxelChunkData chunkData, DrawCellFace higherLODFace, int lod){ private boolean fillInFaceData(TransvoxelChunkData chunkData, DrawCellFace higherLODFace, int lod){
int mainSpacing = (int)Math.pow(2,lod); int mainSpacing = (int)Math.pow(2,lod);
int higherResSpacing = (int)Math.pow(2,lod - 1); int higherLOD = lod - 1;
int higherResSpacing = (int)Math.pow(2,higherLOD);
float[][] faceWeights = new float[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS]; float[][] faceWeights = new float[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS];
int[][] faceTypes = new int[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS]; int[][] faceTypes = new int[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS];
//allocate face array //allocate face array
for(int x = 0; x < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; x++){ for(int x = 0; x < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; x++){
for(int y = 0; y < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; y++){ for(int y = 0; y < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; y++){
int worldCoordOffset1 = (x * higherResSpacing) / ChunkData.CHUNK_SIZE; int worldCoordOffset1 = x / ChunkData.CHUNK_SIZE * higherResSpacing;
int worldCoordOffset2 = (y * higherResSpacing) / ChunkData.CHUNK_SIZE; int worldCoordOffset2 = y / ChunkData.CHUNK_SIZE * higherResSpacing;
//solve coordinates relative to the face //solve coordinates relative to the face
int localCoord1 = (x * higherResSpacing) % ChunkData.CHUNK_SIZE; int localCoord1 = x % ChunkData.CHUNK_SIZE;
int localCoord2 = (y * higherResSpacing) % ChunkData.CHUNK_SIZE; int localCoord2 = y % ChunkData.CHUNK_SIZE;
//implicitly performing transforms to adapt from face-space to world & local space //implicitly performing transforms to adapt from face-space to world & local space
switch(higherLODFace){ switch(higherLODFace){
case X_POSITIVE: { case X_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + (17 * mainSpacing) / ChunkData.CHUNK_SIZE, new Vector3i(
worldPos.y + worldCoordOffset1, worldPos.x + mainSpacing,
worldPos.z + worldCoordOffset2 worldPos.y + worldCoordOffset1,
)); worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){ if(currentChunk == null){
return false; return false;
} }
@ -268,11 +293,14 @@ public class DrawCell {
); );
} break; } break;
case X_NEGATIVE: { case X_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x, new Vector3i(
worldPos.y + worldCoordOffset1, worldPos.x,
worldPos.z + worldCoordOffset2 worldPos.y + worldCoordOffset1,
)); worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){ if(currentChunk == null){
return false; return false;
} }
@ -288,11 +316,14 @@ public class DrawCell {
); );
} break; } break;
case Y_POSITIVE: { case Y_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + worldCoordOffset1, new Vector3i(
worldPos.y + (17 * mainSpacing) / ChunkData.CHUNK_SIZE, worldPos.x + worldCoordOffset1,
worldPos.z + worldCoordOffset2 worldPos.y + mainSpacing,
)); worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){ if(currentChunk == null){
return false; return false;
} }
@ -308,11 +339,14 @@ public class DrawCell {
); );
} break; } break;
case Y_NEGATIVE: { case Y_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + worldCoordOffset1, new Vector3i(
worldPos.y, worldPos.x + worldCoordOffset1,
worldPos.z + worldCoordOffset2 worldPos.y,
)); worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){ if(currentChunk == null){
return false; return false;
} }
@ -328,11 +362,14 @@ public class DrawCell {
); );
} break; } break;
case Z_POSITIVE: { case Z_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + worldCoordOffset1, new Vector3i(
worldPos.y + worldCoordOffset2, worldPos.x + worldCoordOffset1,
worldPos.z + (17 * mainSpacing) / ChunkData.CHUNK_SIZE worldPos.y + worldCoordOffset2,
)); worldPos.z + mainSpacing
),
higherLOD
);
if(currentChunk == null){ if(currentChunk == null){
return false; return false;
} }
@ -348,11 +385,14 @@ public class DrawCell {
); );
} break; } break;
case Z_NEGATIVE: { case Z_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i( ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + worldCoordOffset1, new Vector3i(
worldPos.y + worldCoordOffset2, worldPos.x + worldCoordOffset1,
worldPos.z worldPos.y + worldCoordOffset2,
)); worldPos.z
),
higherLOD
);
if(currentChunk == null){ if(currentChunk == null){
return false; return false;
} }
@ -443,6 +483,9 @@ public class DrawCell {
*/ */
public void setHasRequested(boolean hasRequested) { public void setHasRequested(boolean hasRequested) {
this.hasRequested = hasRequested; this.hasRequested = hasRequested;
if(!this.hasRequested){
this.failedGenerationAttempts = 0;
}
} }
/** /**
@ -469,6 +512,22 @@ public class DrawCell {
return this.multiVoxelType; return this.multiVoxelType;
} }
/**
* Gets the number of failed generation attempts
* @return The number of failed generation attempts
*/
public int getFailedGenerationAttempts(){
return failedGenerationAttempts;
}
/**
* Sets the number of failed generation attempts
* @param attempts The number of failed generation attempts
*/
public void setFailedGenerationAttempts(int attempts){
this.failedGenerationAttempts = this.failedGenerationAttempts + attempts;
}
} }

View File

@ -176,7 +176,7 @@ public class DrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 && posToCheck.z >= 0 &&
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z, ChunkData.NO_STRIDE)
){ ){
if(!requested.contains(requestKey)){ if(!requested.contains(requestKey)){
//client should request chunk data from server for each chunk necessary to create the model //client should request chunk data from server for each chunk necessary to create the model
@ -429,7 +429,7 @@ public class DrawCellManager {
*/ */
boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
if(Globals.clientTerrainManager != null){ if(Globals.clientTerrainManager != null){
return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ); return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ,ChunkData.NO_STRIDE);
} }
return true; return true;
} }
@ -442,7 +442,7 @@ public class DrawCellManager {
* @return The chunk data at the specified points * @return The chunk data at the specified points
*/ */
ChunkData getChunkDataAtPoint(int worldX, int worldY, int worldZ){ ChunkData getChunkDataAtPoint(int worldX, int worldY, int worldZ){
return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ); return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ,ChunkData.NO_STRIDE);
} }

View File

@ -6,7 +6,9 @@ import java.nio.IntBuffer;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@ -25,6 +27,7 @@ import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
import electrosphere.renderer.model.Model; import electrosphere.renderer.model.Model;
import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.manager.ServerTerrainManager; import electrosphere.server.terrain.manager.ServerTerrainManager;
import io.github.studiorailgun.HashUtils;
/** /**
* Manages terrain storage and access on the client * Manages terrain storage and access on the client
@ -38,6 +41,16 @@ public class ClientTerrainManager {
* Locks the terrain manager (eg if adding message from network) * Locks the terrain manager (eg if adding message from network)
*/ */
static Semaphore lock = new Semaphore(1); static Semaphore lock = new Semaphore(1);
/**
* Maximum concurrent terrain requests
*/
public static final int MAX_CONCURRENT_REQUESTS = 500;
/**
* Number of frames to wait before flagging a request as failed
*/
public static final int FAILED_REQUEST_THRESHOLD = 500;
//The interpolation ratio of terrain //The interpolation ratio of terrain
public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO; public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO;
@ -58,6 +71,11 @@ public class ClientTerrainManager {
//The queue of terrain chunk data to be buffered to gpu //The queue of terrain chunk data to be buffered to gpu
static List<TerrainChunkGenQueueItem> terrainChunkGenerationQueue = new CopyOnWriteArrayList<TerrainChunkGenQueueItem>(); static List<TerrainChunkGenQueueItem> terrainChunkGenerationQueue = new CopyOnWriteArrayList<TerrainChunkGenQueueItem>();
/**
* Tracks what outgoing requests are currently active
*/
Map<Long,Integer> requestedMap = new ConcurrentHashMap<Long,Integer>();
/** /**
* Constructor * Constructor
@ -98,7 +116,7 @@ public class ClientTerrainManager {
} }
} }
} }
ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ()); ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ(), ChunkData.NO_STRIDE);
data.setVoxelType(values); data.setVoxelType(values);
data.setVoxelWeight(weights); data.setVoxelWeight(weights);
terrainCache.addChunkDataToCache( terrainCache.addChunkDataToCache(
@ -106,6 +124,37 @@ public class ClientTerrainManager {
data data
); );
} break; } break;
case SENDREDUCEDCHUNKDATA: {
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(), message.getchunkResolution());
data.setVoxelType(values);
data.setVoxelWeight(weights);
terrainCache.addChunkDataToCache(
message.getworldX(), message.getworldY(), message.getworldZ(),
data
);
//remove from request map
this.requestedMap.remove(this.getRequestKey(message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution()));
} break;
default: default:
LoggerInterface.loggerEngine.WARNING("ClientTerrainManager: unhandled network message of type" + message.getMessageSubtype()); LoggerInterface.loggerEngine.WARNING("ClientTerrainManager: unhandled network message of type" + message.getMessageSubtype());
break; break;
@ -114,6 +163,15 @@ public class ClientTerrainManager {
for(TerrainMessage message : bouncedMessages){ for(TerrainMessage message : bouncedMessages){
messageQueue.add(message); messageQueue.add(message);
} }
//evaluate if any chunks have failed to request
for(Long key : this.requestedMap.keySet()){
int duration = this.requestedMap.get(key);
if(duration > FAILED_REQUEST_THRESHOLD){
this.requestedMap.remove(key);
} else {
this.requestedMap.put(key,duration + 1);
}
}
lock.release(); lock.release();
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
@ -140,19 +198,21 @@ public class ClientTerrainManager {
* @param worldX the x position * @param worldX the x position
* @param worldY the y position * @param worldY the y position
* @param worldZ the z position * @param worldZ the z position
* @param stride The stride of the data
* @return true if the data exists, false otherwise * @return true if the data exists, false otherwise
*/ */
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ); return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ, stride);
} }
/** /**
* Checks if the terrain cache contains chunk data at a given world position * Checks if the terrain cache contains chunk data at a given world position
* @param worldPos The vector containing the world-space position * @param worldPos The vector containing the world-space position
* @param stride The stride of the data
* @return true if the data exists, false otherwise * @return true if the data exists, false otherwise
*/ */
public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){ public boolean containsChunkDataAtWorldPoint(Vector3i worldPos, int stride){
return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z); return this.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, stride);
} }
/** /**
@ -160,31 +220,24 @@ public class ClientTerrainManager {
* @param worldX the world x coordinate of the chunk * @param worldX the world x coordinate of the chunk
* @param worldY the world y coordinate of the chunk * @param worldY the world y coordinate of the chunk
* @param worldZ the world z coordinate of the chunk * @param worldZ the world z coordinate of the chunk
* @param stride The stride of the data
* @return true if the request was successfully sent, false otherwise
*/ */
public void requestChunk(int worldX, int worldY, int worldZ){ public boolean requestChunk(int worldX, int worldY, int worldZ, int stride){
if(!this.terrainCache.hasRequested(worldX, worldY, worldZ)){ boolean rVal = false;
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage( lock.acquireUninterruptibly();
if(this.requestedMap.size() < MAX_CONCURRENT_REQUESTS && !this.requestedMap.containsKey(this.getRequestKey(worldX, worldY, worldZ, stride))){
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestReducedChunkDataMessage(
worldX, worldX,
worldY, worldY,
worldZ worldZ,
stride
)); ));
this.requestedMap.put(this.getRequestKey(worldX, worldY, worldZ, stride), 0);
rVal = true;
} }
} lock.release();
return rVal;
/**
* 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)
);
} }
/** /**
@ -192,19 +245,21 @@ public class ClientTerrainManager {
* @param worldX The x component of the world coordinate * @param worldX The x component of the world coordinate
* @param worldY The y component of the world coordinate * @param worldY The y component of the world coordinate
* @param worldZ The z component of the world coordinate * @param worldZ The z component of the world coordinate
* @param stride The stride of the data
* @return The chunk data if it exists, otherwise null * @return The chunk data if it exists, otherwise null
*/ */
public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ); return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ, stride);
} }
/** /**
* Gets the chunk data at a given world position * Gets the chunk data at a given world position
* @param worldPos The world position as a joml vector * @param worldPos The world position as a joml vector
* @param stride The stride of the data
* @return The chunk data if it exists, otherwise null * @return The chunk data if it exists, otherwise null
*/ */
public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos){ public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos, int stride){
return terrainCache.getSubChunkDataAtPoint(worldPos.x, worldPos.y, worldPos.z); return this.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, stride);
} }
@ -258,11 +313,15 @@ public class ClientTerrainManager {
} }
/** /**
* Gets the number of chunks that have been requested * Gets the key for a given request
* @return The number of chunks * @param worldX The world x coordinate
* @param worldY The world y coordinate
* @param worldZ The world z coordinate
* @param stride The stride of the data
* @return The key
*/ */
public int getRequestedCellCount(){ private Long getRequestKey(int worldX, int worldY, int worldZ, int stride){
return this.terrainCache.getRequestedCellCount(); return (long)HashUtils.cantorHash(worldY, worldZ, worldZ);
} }
} }

View File

@ -36,8 +36,8 @@ public class ClientVoxelSampler {
int voxelId = 0; int voxelId = 0;
Vector3i chunkSpacePos = Globals.clientWorldData.convertRealToWorldSpace(realPos); Vector3i chunkSpacePos = Globals.clientWorldData.convertRealToWorldSpace(realPos);
Vector3i voxelSpacePos = Globals.clientWorldData.convertRealToVoxelSpace(realPos); Vector3i voxelSpacePos = Globals.clientWorldData.convertRealToVoxelSpace(realPos);
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(chunkSpacePos)){ if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(chunkSpacePos, ChunkData.NO_STRIDE)){
ChunkData chunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos); ChunkData chunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos, ChunkData.NO_STRIDE);
voxelId = chunkData.getType(voxelSpacePos); voxelId = chunkData.getType(voxelSpacePos);
} else { } else {
return INVALID_POSITION; return INVALID_POSITION;

View File

@ -4,6 +4,7 @@ import java.lang.management.ManagementFactory;
import java.util.ArrayList; import java.util.ArrayList;
import org.joml.Matrix4d;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
@ -257,7 +258,7 @@ public class Globals {
//matrices for drawing models //matrices for drawing models
public static Matrix4f viewMatrix = new Matrix4f(); public static Matrix4f viewMatrix = new Matrix4f();
public static Matrix4f projectionMatrix; public static Matrix4d projectionMatrix;
public static Matrix4f lightDepthMatrix = new Matrix4f(); public static Matrix4f lightDepthMatrix = new Matrix4f();
//locations for shadow map specific variables //locations for shadow map specific variables

View File

@ -245,7 +245,7 @@ public class ClientLoading {
EntityCreationUtils.makeEntityDrawable(skybox, "Models/environment/skyboxSphere.fbx"); EntityCreationUtils.makeEntityDrawable(skybox, "Models/environment/skyboxSphere.fbx");
DrawableUtils.disableCulling(skybox); DrawableUtils.disableCulling(skybox);
EntityUtils.getRotation(skybox).rotateX((float)(-Math.PI/2.0f)); EntityUtils.getRotation(skybox).rotateX((float)(-Math.PI/2.0f));
EntityUtils.getScale(skybox).mul(200000.0f); EntityUtils.getScale(skybox).mul(600000.0f);
Globals.assetManager.queueOverrideMeshShader("Models/environment/skyboxSphere.fbx", "Sphere", "Shaders/entities/skysphere/skysphere.vs", "Shaders/entities/skysphere/skysphere.fs"); Globals.assetManager.queueOverrideMeshShader("Models/environment/skyboxSphere.fbx", "Sphere", "Shaders/entities/skysphere/skysphere.vs", "Shaders/entities/skysphere/skysphere.fs");
//cloud ring pseudo skybox //cloud ring pseudo skybox
@ -297,10 +297,14 @@ public class ClientLoading {
Globals.clientSimulation.setLoadingTerrain(true); Globals.clientSimulation.setLoadingTerrain(true);
//wait for all the terrain data to arrive //wait for all the terrain data to arrive
int i = 0; int i = 0;
while(blockForInit && Globals.clientDrawCellManager.updatedLastFrame() && Globals.threadManager.shouldKeepRunning()){ while(
blockForInit &&
!Globals.clientDrawCellManager.isInitialized() &&
Globals.threadManager.shouldKeepRunning()
){
i++; i++;
if(i % DRAW_CELL_UPDATE_RATE == 0){ if(i % DRAW_CELL_UPDATE_RATE == 0){
WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND TERRAIN (" + Globals.clientTerrainManager.getAllChunks().size() + "/" + Globals.clientTerrainManager.getRequestedCellCount() + ")"); WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND TERRAIN (" + Globals.clientTerrainManager.getAllChunks().size() + ")");
} }
try { try {
TimeUnit.MILLISECONDS.sleep(10); TimeUnit.MILLISECONDS.sleep(10);

View File

@ -20,6 +20,11 @@ public class TerrainChunkData {
//texture ratio vector //texture ratio vector
List<Float> textureRatioVectors; //HOW MUCH of each texture in the atlas to sample List<Float> textureRatioVectors; //HOW MUCH of each texture in the atlas to sample
/**
* The LOD of the model
*/
int lod;
/** /**
* Creates an object to hold data required to generate a chunk * Creates an object to hold data required to generate a chunk
* @param vertices * @param vertices
@ -27,14 +32,16 @@ public class TerrainChunkData {
* @param faceElements * @param faceElements
* @param uvs * @param uvs
* @param textureSamplers * @param textureSamplers
* @param lod The LOD of the model
*/ */
public TerrainChunkData(List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs, List<Float> textureSamplers, List<Float> textureRatioVectors){ public TerrainChunkData(List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs, List<Float> textureSamplers, List<Float> textureRatioVectors, int lod){
this.vertices = vertices; this.vertices = vertices;
this.normals = normals; this.normals = normals;
this.faceElements = faceElements; this.faceElements = faceElements;
this.uvs = uvs; this.uvs = uvs;
this.textureSamplers = textureSamplers; this.textureSamplers = textureSamplers;
this.textureRatioVectors = textureRatioVectors; this.textureRatioVectors = textureRatioVectors;
this.lod = lod;
} }
/** /**
@ -85,4 +92,12 @@ public class TerrainChunkData {
return textureRatioVectors; return textureRatioVectors;
} }
/**
* Gets the LOD of the model
* @return The LOD
*/
public int getLOD(){
return lod;
}
} }

View File

@ -130,6 +130,17 @@ public class Logger {
} }
} }
/**
* Logs an error message.
* This should be used every time we throw any kind of error in the engine
* @param e The exception to report
*/
public void ERROR(Error e){
if(level == LogLevel.LOOP_DEBUG || level == LogLevel.DEBUG || level == LogLevel.INFO || level == LogLevel.WARNING || level == LogLevel.ERROR){
e.printStackTrace();
}
}
/** /**
* Prints a message at the specified logging level * Prints a message at the specified logging level
* @param level The logging level * @param level The logging level

View File

@ -47,6 +47,10 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
LoggerInterface.loggerNetworking.DEBUG("(Client) Received terrain at " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ()); LoggerInterface.loggerNetworking.DEBUG("(Client) Received terrain at " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ());
Globals.clientTerrainManager.attachTerrainMessage(message); Globals.clientTerrainManager.attachTerrainMessage(message);
} break; } break;
case SENDREDUCEDCHUNKDATA: {
LoggerInterface.loggerNetworking.DEBUG("(Client) Received terrain at " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ() + " " + message.getchunkResolution());
Globals.clientTerrainManager.attachTerrainMessage(message);
} break;
case UPDATEVOXEL: { case UPDATEVOXEL: {
// //
//find what all drawcells might be updated by this voxel update //find what all drawcells might be updated by this voxel update
@ -79,8 +83,8 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
} }
// //
//update the terrain cache //update the terrain cache
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z)){ if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z); ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z, ChunkData.NO_STRIDE);
if(data != null){ if(data != null){
data.updatePosition( data.updatePosition(
message.getvoxelX(), message.getvoxelX(),
@ -94,8 +98,8 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
// //
//mark all relevant drawcells as updateable //mark all relevant drawcells as updateable
for(Vector3i worldPosToUpdate : positionsToUpdate){ for(Vector3i worldPosToUpdate : positionsToUpdate){
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z)){ if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z, ChunkData.NO_STRIDE)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z); ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z, ChunkData.NO_STRIDE);
if(data != null){ if(data != null){
Globals.clientDrawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z); Globals.clientDrawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
} }

View File

@ -7,6 +7,7 @@ import java.util.function.Consumer;
import org.joml.Vector3d; import org.joml.Vector3d;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.net.parser.net.message.TerrainMessage;
@ -32,6 +33,12 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
); );
return null; return null;
} }
case REQUESTREDUCEDCHUNKDATA: {
sendWorldSubChunkAsyncStrided(connectionHandler,
message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution()
);
return null;
}
default: { default: {
} break; } break;
} }
@ -185,7 +192,65 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
}; };
//request chunk //request chunk
realm.getServerWorldData().getServerTerrainManager().getChunkAsync(worldX, worldY, worldZ, onLoad); realm.getServerWorldData().getServerTerrainManager().getChunkAsync(worldX, worldY, worldZ, ChunkData.NO_STRIDE, onLoad);
Globals.profiler.endCpuSample();
}
/**
* Sends a subchunk to the client
* @param connectionHandler The connection handler
* @param worldX the world x
* @param worldY the world y
* @param worldZ the world z
* @param stride The stride of the data
*/
static void sendWorldSubChunkAsyncStrided(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ, int stride){
Globals.profiler.beginAggregateCpuSample("TerrainProtocol(server).sendWorldSubChunk");
// System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY());
Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
if(realm.getServerWorldData().getServerTerrainManager() == null){
return;
}
Consumer<ServerTerrainChunk> onLoad = (ServerTerrainChunk chunk) -> {
//The length along each access of the chunk data. Typically, should be at least 17.
//Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate
//chunk data that connects seamlessly to the next chunk.
int xWidth = chunk.getWeights().length;
int yWidth = chunk.getWeights()[0].length;
int zWidth = chunk.getWeights()[0][0].length;
ByteBuffer buffer = ByteBuffer.allocate(xWidth*yWidth*zWidth*(4+4));
FloatBuffer floatView = buffer.asFloatBuffer();
for(int x = 0; x < xWidth; x++){
for(int y = 0; y < yWidth; y++){
for(int z = 0; z < zWidth; z++){
floatView.put(chunk.getWeights()[x][y][z]);
}
}
}
IntBuffer intView = buffer.asIntBuffer();
intView.position(floatView.position());
for(int x = 0; x < xWidth; x++){
for(int y = 0; y < yWidth; y++){
for(int z = 0; z < zWidth; z++){
intView.put(chunk.getValues()[x][y][z]);
}
}
}
// System.out.println("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ);
LoggerInterface.loggerNetworking.DEBUG("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ);
connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructSendReducedChunkDataMessage(worldX, worldY, worldZ, stride, buffer.array()));
};
//request chunk
realm.getServerWorldData().getServerTerrainManager().getChunkAsync(worldX, worldY, worldZ, stride, onLoad);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }

View File

@ -1,6 +1,7 @@
package electrosphere.renderer; package electrosphere.renderer;
import org.joml.FrustumIntersection; import org.joml.FrustumIntersection;
import org.joml.Matrix4d;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import electrosphere.renderer.actor.instance.InstanceData; import electrosphere.renderer.actor.instance.InstanceData;
@ -166,7 +167,7 @@ public class RenderPipelineState {
* @param projectionMatrix the projection matrix * @param projectionMatrix the projection matrix
* @param viewMatrix the view matrix * @param viewMatrix the view matrix
*/ */
public void updateFrustumIntersection(Matrix4f projectionMatrix, Matrix4f viewMatrix){ public void updateFrustumIntersection(Matrix4d projectionMatrix, Matrix4f viewMatrix){
Matrix4f projectionViewMatrix = new Matrix4f(); Matrix4f projectionViewMatrix = new Matrix4f();
projectionViewMatrix.set(projectionMatrix); projectionViewMatrix.set(projectionMatrix);
projectionViewMatrix.mul(viewMatrix); projectionViewMatrix.mul(viewMatrix);

View File

@ -453,7 +453,7 @@ public class RenderingEngine {
// //
// Projection and View matrix creation // Projection and View matrix creation
// //
Globals.projectionMatrix = new Matrix4f(); Globals.projectionMatrix = new Matrix4d();
Globals.viewMatrix = new Matrix4f(); Globals.viewMatrix = new Matrix4f();
verticalFOV = (float)(Globals.verticalFOV * Math.PI /180.0f); verticalFOV = (float)(Globals.verticalFOV * Math.PI /180.0f);
//set local aspect ratio and global aspect ratio at the same time //set local aspect ratio and global aspect ratio at the same time

View File

@ -763,7 +763,7 @@ public class TerrainChunkModelGeneration {
} }
//List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs //List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData); TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData, 0);
return rVal; return rVal;
} }
@ -892,14 +892,15 @@ public class TerrainChunkModelGeneration {
//bounding sphere logic //bounding sphere logic
int distance = ServerTerrainChunk.CHUNK_DIMENSION / 2 * (int)Math.pow(2,data.getLOD());
mesh.updateBoundingSphere( mesh.updateBoundingSphere(
ServerTerrainChunk.CHUNK_DIMENSION, distance,
ServerTerrainChunk.CHUNK_DIMENSION, distance,
ServerTerrainChunk.CHUNK_DIMENSION, distance,
(float)Math.sqrt( (float)Math.sqrt(
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION + distance * distance +
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION + distance * distance +
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION distance * distance
)); ));

View File

@ -1335,7 +1335,7 @@ public class TransvoxelModelGeneration {
chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0],
chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0] chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0]
); );
polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert, false); polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert, true);
// //
//Generate the normal cell with half width //Generate the normal cell with half width
@ -1349,7 +1349,7 @@ public class TransvoxelModelGeneration {
chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0] chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0]
); );
//polygonize the current gridcell //polygonize the current gridcell
polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert, false); polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert, true);
} }
} }
} else { } else {
@ -1694,7 +1694,7 @@ public class TransvoxelModelGeneration {
} }
//List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs //List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData); TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData, chunkData.levelOfDetail);
return rVal; return rVal;
} }

View File

@ -22,7 +22,7 @@ public class DefaultChunkGenerator implements ChunkGenerator {
} }
@Override @Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) { public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride) {
//Each chunk also needs custody of the next chunk's first values so that they can perfectly overlap. //Each chunk also needs custody of the next chunk's first values so that they can perfectly overlap.
//Hence, width should actually be chunk dimension + 1 //Hence, width should actually be chunk dimension + 1
float[][][] weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; float[][][] weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];

View File

@ -28,7 +28,7 @@ public class OverworldChunkGenerator implements ChunkGenerator {
} }
@Override @Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) { public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride) {
ServerTerrainChunk returnedChunk; ServerTerrainChunk returnedChunk;
//Each chunk also needs custody of the next chunk's first values so that they can perfectly overlap. //Each chunk also needs custody of the next chunk's first values so that they can perfectly overlap.
//Hence, width should actually be chunk dimension + 1 //Hence, width should actually be chunk dimension + 1

View File

@ -1,6 +1,5 @@
package electrosphere.server.terrain.generation; package electrosphere.server.terrain.generation;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -24,7 +23,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
/** /**
* The size of the realm for testing generation * The size of the realm for testing generation
*/ */
public static final int GENERATOR_REALM_SIZE = 32; public static final int GENERATOR_REALM_SIZE = 512;
/** /**
* The default biome index * The default biome index
@ -65,36 +64,14 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
} }
@Override @Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) { public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride) {
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.generateChunk"); Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.generateChunk");
ServerTerrainChunk rVal = null; ServerTerrainChunk rVal = null;
float[][][] weights; float[][][] weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];;
int[][][] values; int[][][] values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
if(worldX == 0 || worldZ == 0){ try {
//generate flat ground for the player to spawn on
weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
Arrays.fill(weights[x][y],-1f);
}
}
if(worldY == 0){
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
values[x][0][z] = 1;
weights[x][0][z] = 0.1f;
}
}
}
} else {
//actual generation algo //actual generation algo
weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
//biome of the current chunk //biome of the current chunk
BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ); BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ);
@ -105,11 +82,22 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag()); throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
} }
//stride value
int strideValue = (int)Math.pow(2,stride);
//presolve heightfield //presolve heightfield
float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
heightfield[x][z] = heightmapGen.getHeight(this.terrainModel.getSeed(), this.serverWorldData.convertVoxelToRealSpace(x, worldX), this.serverWorldData.convertVoxelToRealSpace(z, worldZ)); int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
heightfield[x][z] = heightmapGen.getHeight(
this.terrainModel.getSeed(),
this.serverWorldData.convertVoxelToRealSpace(finalChunkX, finalWorldX),
this.serverWorldData.convertVoxelToRealSpace(finalChunkZ, finalWorldZ)
);
} }
} }
@ -117,15 +105,22 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice"); Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){ for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
GeneratedVoxel voxel = this.getVoxel(worldX, worldY, worldZ, x, y, z, heightfield, this.terrainModel, surfaceBiome); int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
GeneratedVoxel voxel = this.getVoxel(finalWorldX, finalWorldY, finalWorldZ, finalChunkX, finalChunkY, finalChunkZ, heightfield[x][z], this.terrainModel, surfaceBiome);
weights[x][y][z] = voxel.weight; weights[x][y][z] = voxel.weight;
values[x][y][z] = voxel.type; values[x][y][z] = voxel.type;
} }
} }
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
} catch(Exception ex){
ex.printStackTrace();
} }
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values); rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
return rVal; return rVal;
@ -144,7 +139,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkX The chunk x pos * @param chunkX The chunk x pos
* @param chunkY The chunk y pos * @param chunkY The chunk y pos
* @param chunkZ The chunk z pos * @param chunkZ The chunk z pos
* @param heightfield The precomputed heightfield * @param surfaceHeight The height of the surface at x,z
* @param terrainModel The terrain model * @param terrainModel The terrain model
* @param surfaceBiome The surface biome of the chunk * @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk * @return The value of the chunk
@ -152,7 +147,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
private GeneratedVoxel getVoxel( private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ, int chunkX, int chunkY, int chunkZ,
float[][] heightfield, float surfaceHeight,
TerrainModel terrainModel, TerrainModel terrainModel,
BiomeData surfaceBiome BiomeData surfaceBiome
){ ){
@ -163,16 +158,15 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag()); throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
} }
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldX); double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY); double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldZ); double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
float surfaceHeight = heightfield[chunkX][chunkZ];
double flooredSurfaceHeight = Math.floor(surfaceHeight); double flooredSurfaceHeight = Math.floor(surfaceHeight);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
if(realY < surfaceHeight - 1){ if(realY < surfaceHeight - 1){
return getSubsurfaceVoxel( return getSubsurfaceVoxel(
worldX,worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfaceHeight, flooredSurfaceHeight,
@ -181,7 +175,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
); );
} else if(realY > flooredSurfaceHeight) { } else if(realY > flooredSurfaceHeight) {
return getOverSurfaceVoxel( return getOverSurfaceVoxel(
worldX,worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfaceHeight, flooredSurfaceHeight,
@ -190,7 +184,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
); );
} else { } else {
return getSurfaceVoxel( return getSurfaceVoxel(
worldX,worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfaceHeight, flooredSurfaceHeight,

View File

@ -13,9 +13,10 @@ public interface ChunkGenerator {
* @param worldX The x component * @param worldX The x component
* @param worldY The y component * @param worldY The y component
* @param worldZ The z component * @param worldZ The z component
* @param stride The stride of the data
* @return The chunk * @return The chunk
*/ */
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ); public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride);
/** /**
* Sets the terrain model for the generation algorithm * Sets the terrain model for the generation algorithm

View File

@ -4,6 +4,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.diskmap.ChunkDiskMap; import electrosphere.server.terrain.diskmap.ChunkDiskMap;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator; import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
@ -51,6 +52,11 @@ public class ChunkGenerationThread implements Runnable {
* The world z coordinate * The world z coordinate
*/ */
int worldZ; int worldZ;
/**
* The stride of the data
*/
int stride;
/** /**
* The work to do once the chunk is available * The work to do once the chunk is available
@ -65,6 +71,7 @@ public class ChunkGenerationThread implements Runnable {
* @param worldX The world x coordinate * @param worldX The world x coordinate
* @param worldY The world y coordinate * @param worldY The world y coordinate
* @param worldZ The world z coordinate * @param worldZ The world z coordinate
* @param stride The stride of the data
* @param onLoad The work to do once the chunk is available * @param onLoad The work to do once the chunk is available
*/ */
public ChunkGenerationThread( public ChunkGenerationThread(
@ -72,6 +79,7 @@ public class ChunkGenerationThread implements Runnable {
ServerChunkCache chunkCache, ServerChunkCache chunkCache,
ChunkGenerator chunkGenerator, ChunkGenerator chunkGenerator,
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int stride,
Consumer<ServerTerrainChunk> onLoad Consumer<ServerTerrainChunk> onLoad
){ ){
this.chunkDiskMap = chunkDiskMap; this.chunkDiskMap = chunkDiskMap;
@ -80,6 +88,7 @@ public class ChunkGenerationThread implements Runnable {
this.worldX = worldX; this.worldX = worldX;
this.worldY = worldY; this.worldY = worldY;
this.worldZ = worldZ; this.worldZ = worldZ;
this.stride = stride;
this.onLoad = onLoad; this.onLoad = onLoad;
} }
@ -87,37 +96,41 @@ public class ChunkGenerationThread implements Runnable {
public void run() { public void run() {
ServerTerrainChunk chunk = null; ServerTerrainChunk chunk = null;
int i = 0; int i = 0;
while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){ try {
if(chunkCache.containsChunk(worldX,worldY,worldZ)){ while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
chunk = chunkCache.get(worldX,worldY,worldZ); if(chunkCache.containsChunk(worldX, worldY, worldZ, stride)){
} else { chunk = chunkCache.get(worldX, worldY, worldZ, stride);
//pull from disk if it exists } else {
if(chunkDiskMap != null){ //pull from disk if it exists
if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){ if(chunkDiskMap != null){
chunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ); if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
chunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ);
}
}
//generate if it does not exist
if(chunk == null){
chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, stride);
}
if(chunk != null){
chunkCache.add(worldX, worldY, worldZ, stride, chunk);
} }
} }
//generate if it does not exist
if(chunk == null){ if(chunk == null){
chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ); try {
} TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
if(chunk != null){ } catch (InterruptedException e) {
chunkCache.add(worldX, worldY, worldZ, chunk); e.printStackTrace();
}
} }
i++;
} }
if(chunk == null){ if(i >= MAX_TIME_TO_WAIT){
try { throw new Error("Failed to resolve chunk!");
TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
i++; this.onLoad.accept(chunk);
} catch (Error e){
LoggerInterface.loggerEngine.ERROR(e);
} }
if(i >= MAX_TIME_TO_WAIT){
throw new Error("Failed to resolve chunk!");
}
this.onLoad.accept(chunk);
} }
} }

View File

@ -6,8 +6,11 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import io.github.studiorailgun.HashUtils;
/** /**
* Caches chunk data on the server * Caches chunk data on the server
*/ */
@ -16,7 +19,7 @@ public class ServerChunkCache {
/** /**
* Number of chunks to cache * Number of chunks to cache
*/ */
static final int CACHE_SIZE = 5000; static final int CACHE_SIZE = 2500;
/** /**
* The size of the cache * The size of the cache
@ -24,19 +27,39 @@ public class ServerChunkCache {
int cacheSize = CACHE_SIZE; int cacheSize = CACHE_SIZE;
/** /**
* The cached data * The map of full res chunk key -> chunk data
*/ */
Map<String, ServerTerrainChunk> chunkCache = new HashMap<String,ServerTerrainChunk>(); Map<Long,ServerTerrainChunk> cacheMapFullRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
/**
* The map of half res chunk key -> chunk data
*/
Map<Long,ServerTerrainChunk> cacheMapHalfRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
/**
* The map of quarter res chunk key -> chunk data
*/
Map<Long,ServerTerrainChunk> cacheMapQuarterRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
/**
* The map of eighth res chunk key -> chunk data
*/
Map<Long,ServerTerrainChunk> cacheMapEighthRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
/**
* The map of sixteenth res chunk key -> chunk data
*/
Map<Long,ServerTerrainChunk> cacheMapSixteenthRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
/** /**
* Tracks how recently a chunk has been queries for (used for evicting old chunks from cache) * Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
*/ */
List<String> queryRecencyQueue = new LinkedList<String>(); List<Long> queryRecencyQueue = new LinkedList<Long>();
/** /**
* Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk * Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk
*/ */
Map<String, Boolean> queuedChunkMap = new HashMap<String,Boolean>(); Map<Long, Boolean> queuedChunkMap = new HashMap<Long,Boolean>();
/** /**
* The lock for thread safety * The lock for thread safety
@ -49,7 +72,7 @@ public class ServerChunkCache {
*/ */
public Collection<ServerTerrainChunk> getContents(){ public Collection<ServerTerrainChunk> getContents(){
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(chunkCache.values()); Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
lock.release(); lock.release();
return rVal; return rVal;
} }
@ -59,7 +82,8 @@ public class ServerChunkCache {
*/ */
public void clear(){ public void clear(){
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
chunkCache.clear(); cacheMapFullRes.clear();
cacheMapHalfRes.clear();
lock.release(); lock.release();
} }
@ -68,15 +92,17 @@ public class ServerChunkCache {
* @param worldX The world x coordinate * @param worldX The world x coordinate
* @param worldY The world y coordinate * @param worldY The world y coordinate
* @param worldZ The world z coordinate * @param worldZ The world z coordinate
* @param stride The stride of the data
* @return The chunk * @return The chunk
*/ */
public ServerTerrainChunk get(int worldX, int worldY, int worldZ){ public ServerTerrainChunk get(int worldX, int worldY, int worldZ, int stride){
ServerTerrainChunk rVal = null; ServerTerrainChunk rVal = null;
String key = this.getKey(worldX, worldY, worldZ); Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
queryRecencyQueue.remove(key); queryRecencyQueue.remove(key);
queryRecencyQueue.add(0, key); queryRecencyQueue.add(0, key);
rVal = this.chunkCache.get(key); Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
rVal = cache.get(key);
lock.release(); lock.release();
return rVal; return rVal;
} }
@ -86,13 +112,15 @@ public class ServerChunkCache {
* @param worldX The world x coordinate of the chunk * @param worldX The world x coordinate of the chunk
* @param worldY The world y coordinate of the chunk * @param worldY The world y coordinate of the chunk
* @param worldZ The world z coordinate of the chunk * @param worldZ The world z coordinate of the chunk
* @param stride The stride of the data
* @param chunk The chunk itself * @param chunk The chunk itself
*/ */
public void add(int worldX, int worldY, int worldZ, ServerTerrainChunk chunk){ public void add(int worldX, int worldY, int worldZ, int stride, ServerTerrainChunk chunk){
String key = this.getKey(worldX, worldY, worldZ); Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
queryRecencyQueue.add(0, key); queryRecencyQueue.add(0, key);
this.chunkCache.put(key, chunk); Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
cache.put(key, chunk);
lock.release(); lock.release();
} }
@ -101,12 +129,14 @@ public class ServerChunkCache {
* @param worldX The world x coordinate * @param worldX The world x coordinate
* @param worldY The world y coordinate * @param worldY The world y coordinate
* @param worldZ The world z coordinate * @param worldZ The world z coordinate
* @param stride The stride of the data
* @return true if the cache contains this chunk, false otherwise * @return true if the cache contains this chunk, false otherwise
*/ */
public boolean containsChunk(int worldX, int worldY, int worldZ){ public boolean containsChunk(int worldX, int worldY, int worldZ, int stride){
String key = this.getKey(worldX,worldY,worldZ); Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
boolean rVal = this.chunkCache.containsKey(key); Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
boolean rVal = cache.containsKey(key);
lock.release(); lock.release();
return rVal; return rVal;
} }
@ -118,8 +148,8 @@ public class ServerChunkCache {
* @param worldZ The z component * @param worldZ The z component
* @return The key * @return The key
*/ */
public String getKey(int worldX, int worldY, int worldZ){ public long getKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ; return HashUtils.cantorHash(worldX, worldY, worldZ);
} }
/** /**
@ -130,7 +160,7 @@ public class ServerChunkCache {
* @return true if the chunk is already queued, false otherwise * @return true if the chunk is already queued, false otherwise
*/ */
public boolean chunkIsQueued(int worldX, int worldY, int worldZ){ public boolean chunkIsQueued(int worldX, int worldY, int worldZ){
String key = this.getKey(worldX,worldY,worldZ); Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
boolean rVal = this.queuedChunkMap.containsKey(key); boolean rVal = this.queuedChunkMap.containsKey(key);
lock.release(); lock.release();
@ -144,7 +174,7 @@ public class ServerChunkCache {
* @param worldZ The world z position of the chunk * @param worldZ The world z position of the chunk
*/ */
public void queueChunk(int worldX, int worldY, int worldZ){ public void queueChunk(int worldX, int worldY, int worldZ){
String key = this.getKey(worldX,worldY,worldZ); Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
this.queuedChunkMap.put(key,true); this.queuedChunkMap.put(key,true);
lock.release(); lock.release();
@ -155,12 +185,41 @@ public class ServerChunkCache {
* @param worldX The world x position of the chunk * @param worldX The world x position of the chunk
* @param worldY The world y position of the chunk * @param worldY The world y position of the chunk
* @param worldZ The world z position of the chunk * @param worldZ The world z position of the chunk
* @param stride The stride of the chunk
*/ */
public void unqueueChunk(int worldX, int worldY, int worldZ){ public void unqueueChunk(int worldX, int worldY, int worldZ, int stride){
String key = this.getKey(worldX,worldY,worldZ); Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
this.queuedChunkMap.remove(key); this.queuedChunkMap.remove(key);
lock.release(); lock.release();
} }
/**
* Gets the cache
* @param stride The stride of the data
* @return The cache to use
*/
public Map<Long,ServerTerrainChunk> getCache(int stride){
switch(stride){
case 0: {
return cacheMapFullRes;
}
case 1: {
return cacheMapHalfRes;
}
case 2: {
return cacheMapQuarterRes;
}
case 3: {
return cacheMapEighthRes;
}
case 4: {
return cacheMapSixteenthRes;
}
default: {
throw new Error("Invalid stride probided! " + stride);
}
}
}
} }

View File

@ -1,5 +1,6 @@
package electrosphere.server.terrain.manager; package electrosphere.server.terrain.manager;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.game.server.world.ServerWorldData; import electrosphere.game.server.world.ServerWorldData;
import electrosphere.server.terrain.diskmap.ChunkDiskMap; import electrosphere.server.terrain.diskmap.ChunkDiskMap;
@ -231,8 +232,8 @@ public class ServerTerrainManager {
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk"); Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING //THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
ServerTerrainChunk returnedChunk = null; ServerTerrainChunk returnedChunk = null;
if(chunkCache.containsChunk(worldX,worldY,worldZ)){ if(chunkCache.containsChunk(worldX,worldY,worldZ,ChunkData.NO_STRIDE)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ); returnedChunk = chunkCache.get(worldX,worldY,worldZ, ChunkData.NO_STRIDE);
} else { } else {
//pull from disk if it exists //pull from disk if it exists
if(chunkDiskMap != null){ if(chunkDiskMap != null){
@ -242,9 +243,9 @@ public class ServerTerrainManager {
} }
//generate if it does not exist //generate if it does not exist
if(returnedChunk == null){ if(returnedChunk == null){
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ); returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, ChunkData.NO_STRIDE);
} }
this.chunkCache.add(worldX, worldY, worldZ, returnedChunk); this.chunkCache.add(worldX, worldY, worldZ, ChunkData.NO_STRIDE, returnedChunk);
} }
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
return returnedChunk; return returnedChunk;
@ -255,11 +256,12 @@ public class ServerTerrainManager {
* @param worldX The world x position * @param worldX The world x position
* @param worldY The world y position * @param worldY The world y position
* @param worldZ The world z position * @param worldZ The world z position
* @param stride The stride of the data
* @param onLoad The logic to run once the chunk is available * @param onLoad The logic to run once the chunk is available
*/ */
public void getChunkAsync(int worldX, int worldY, int worldZ, Consumer<ServerTerrainChunk> onLoad){ public void getChunkAsync(int worldX, int worldY, int worldZ, int stride, Consumer<ServerTerrainChunk> onLoad){
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync"); Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync");
this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, onLoad)); this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, stride, onLoad));
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
@ -287,8 +289,8 @@ public class ServerTerrainManager {
if(model != null){ if(model != null){
model.addModification(modification); model.addModification(modification);
} }
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z)){ if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z,ChunkData.NO_STRIDE)){
ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z); ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z, ChunkData.NO_STRIDE);
chunk.addModification(modification); chunk.addModification(modification);
} }
} }

View File

@ -127,11 +127,13 @@ public class TerrainModel {
TerrainModel rVal = new TerrainModel(); TerrainModel rVal = new TerrainModel();
rVal.discreteArrayDimension = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE; rVal.discreteArrayDimension = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
rVal.dynamicInterpolationRatio = 1; rVal.dynamicInterpolationRatio = 1;
rVal.biome = new short[2][2]; int macroDataImageScale = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE / DEFAULT_MACRO_DATA_SCALE + 1;
rVal.biome[0][0] = TestGenerationChunkGenerator.DEFAULT_BIOME_INDEX; rVal.biome = new short[macroDataImageScale][macroDataImageScale];
rVal.biome[1][0] = TestGenerationChunkGenerator.DEFAULT_BIOME_INDEX; for(int x = 0; x < macroDataImageScale; x++){
rVal.biome[0][1] = TestGenerationChunkGenerator.DEFAULT_BIOME_INDEX; for(int z = 0; z < macroDataImageScale; z++){
rVal.biome[1][1] = TestGenerationChunkGenerator.DEFAULT_BIOME_INDEX; rVal.biome[x][z] = TestGenerationChunkGenerator.DEFAULT_BIOME_INDEX;
}
}
return rVal; return rVal;
} }