Merge pull request 'voxelImprovements' (#5) from voxelImprovements into master
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

Reviewed-on: https://git.austinwhoover.com/studiorailgun/Renderer/pulls/5
This commit is contained in:
railgun 2024-11-07 15:08:54 -05:00
commit afabf5595e
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
*/
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(){
boolean shouldGenerate = false;
//get foliage types supported
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition);
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosition,ChunkData.NO_STRIDE);
if(data == null){
return;
}

View File

@ -77,9 +77,9 @@ public class FoliageChunk {
*/
public void 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
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);
Globals.profiler.endCpuSample();
}
@ -113,7 +113,7 @@ public class FoliageChunk {
* @return true if contains foliage voxel, false otherwise
*/
private boolean checkContainsFoliageVoxel(){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos());
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos(),ChunkData.NO_STRIDE);
if(data == null){
return false;
}

View File

@ -12,6 +12,11 @@ import electrosphere.server.terrain.manager.ServerTerrainChunk;
*/
public class ChunkData {
/**
* No stride
*/
public static final int NO_STRIDE = 0;
//The size of a chunk in virtual data
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
@ -39,16 +44,23 @@ public class ChunkData {
*/
int worldZ;
/**
* The stride of the data
*/
int stride;
/**
* Creates a chunk data
* @param worldX The word x coordinate
* @param worldY The word y 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.worldY = worldY;
this.worldZ = worldZ;
this.stride = stride;
}
@ -211,5 +223,13 @@ public class ChunkData {
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.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
import org.joml.Vector3i;
@ -16,19 +17,50 @@ import io.github.studiorailgun.HashUtils;
*/
public class ClientTerrainCache {
//cache capacity
/**
* Cache capacity
*/
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>();
//A map of chunk to its world position
/**
* A map of chunk to its world position
*/
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
@ -46,23 +78,27 @@ public class ClientTerrainCache {
* @param chunkData The chunk data to add at the specified positions
*/
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));
while(cacheList.size() > cacheSize){
Long currentChunk = cacheList.remove(0);
ChunkData data = cacheMap.remove(currentChunk);
Vector3i worldPos = data.getWorldPos();
requestedChunks.remove(getKey(worldPos.x,worldPos.y,worldPos.z));
cache.remove(currentChunk);
}
lock.release();
}
/**
* Evicts all chunks from the cache
*/
public void evictAll(){
lock.acquireUninterruptibly();
this.cacheList.clear();
this.cacheMap.clear();
this.cacheMapFullRes.clear();
this.cacheMapHalfRes.clear();
this.chunkPositionMap.clear();
lock.release();
}
@ -82,10 +118,14 @@ public class ClientTerrainCache {
* @param worldX The x world position
* @param worldY The y 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
*/
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return cacheMap.containsKey(getKey(worldX,worldY,worldZ));
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){
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 worldY The y world position
* @param worldZ The z world position
* @param stride The stride of the data
* @return The chunk data if it exists, null otherwise
*/
public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ){
return cacheMap.get(getKey(worldX,worldY,worldZ));
public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ, int stride){
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
*/
public Collection<ChunkData> getAllChunks(){
return this.cacheMap.values();
return this.cacheMapFullRes.values();
}
/**
@ -119,34 +163,33 @@ public class ClientTerrainCache {
public Vector3i getChunkPosition(ChunkData 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;
/**
* 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
*/
@ -35,6 +40,51 @@ public class ClientDrawCellManager {
*/
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
*/
@ -80,6 +130,11 @@ public class ClientDrawCellManager {
*/
int generated = 0;
/**
* Tracks whether the cell manager has initialized or not
*/
boolean initialized = false;
/**
* Constructor
* @param voxelTextureAtlas The voxel texture atlas
@ -100,12 +155,24 @@ public class ClientDrawCellManager {
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
//the sets to iterate through
updatedLastFrame = true;
int attempts = 0;
validCellCount = 0;
while(updatedLastFrame && attempts < UPDATE_ATTEMPTS_PER_FRAME){
FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot();
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos);
attempts++;
//update all full res cells
FloatingChunkTreeNode<DrawCell> rootNode = this.chunkTree.getRoot();
updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos, HALF_RES_LOD);
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();
@ -115,9 +182,10 @@ public class ClientDrawCellManager {
* Recursively update child nodes
* @param node The root node
* @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
*/
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos){
private boolean recursivelyUpdateCells(FloatingChunkTreeNode<DrawCell> node, Vector3d playerPos, int minLeafLod){
Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
boolean updated = false;
if(this.shouldSplit(playerPos, node)){
@ -161,19 +229,34 @@ public class ClientDrawCellManager {
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldRequest(playerPos, node)){
} else if(shouldRequest(playerPos, node, minLeafLod)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
//calculate what to request
DrawCell cell = node.getData();
this.requestChunks(node);
cell.setHasRequested(true);
List<DrawCellFace> highResFaces = this.solveHighResFace(node);
//actually send requests
if(this.requestChunks(node, highResFaces)){
cell.setHasRequested(true);
}
Globals.profiler.endCpuSample();
updated = true;
} else if(shouldGenerate(playerPos, node)){
} else if(shouldGenerate(playerPos, node, minLeafLod)){
Globals.profiler.beginCpuSample("ClientDrawCellManager.generate");
int lodLevel = this.getLODLevel(playerRealPos, node);
List<DrawCellFace> highResFaces = this.solveHighResFace(node);
if(containsDataToGenerate(node,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();
updated = true;
@ -181,7 +264,7 @@ public class ClientDrawCellManager {
this.validCellCount++;
List<FloatingChunkTreeNode<DrawCell>> children = new LinkedList<FloatingChunkTreeNode<DrawCell>>(node.getChildren());
for(FloatingChunkTreeNode<DrawCell> child : children){
boolean childUpdate = recursivelyUpdateCells(child, playerPos);
boolean childUpdate = recursivelyUpdateCells(child, playerPos, minLeafLod);
if(childUpdate == true){
updated = true;
}
@ -216,7 +299,22 @@ public class ClientDrawCellManager {
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
)
||
@ -355,13 +453,28 @@ public class ClientDrawCellManager {
!node.isLeaf() &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel() - 1 &&
node.getLevel() == this.chunkTree.getMaxLevel() - HALF_RES_LOD &&
this.getMinDistance(pos, node) > FULL_RES_DIST
)
||
||
(
node.getLevel() == this.chunkTree.getMaxLevel() - QUARTER_RES_LOD &&
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
* @param pos the player's position
* @param node the node
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @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
node.isLeaf() &&
node.getData() != null &&
!node.getData().hasRequested() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod &&
(
(
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
* @param pos the player's position
* @param node the node
* @param minLeafLod The minimum LOD required to evaluate a leaf
* @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
node.isLeaf() &&
node.getData() != null &&
!node.getData().hasGenerated() &&
(this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod &&
(
(
node.getLevel() == this.chunkTree.getMaxLevel()
// &&
// 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");
}
/**
* 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
* @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();
int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1;
for(int i = 0; i < 2 * lodMultiplitier; i++){
for(int j = 0; j < 2 * lodMultiplitier; j++){
for(int k = 0; k < 2 * lodMultiplitier; k++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k);
int lod = this.chunkTree.getMaxLevel() - node.getLevel();
int spacingFactor = (int)Math.pow(2,lod);
for(int i = 0; i < 2; i++){
for(int j = 0; j < 2; j++){
for(int k = 0; k < 2; k++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i*spacingFactor,j*spacingFactor,k*spacingFactor);
if(
posToCheck.x >= 0 &&
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -522,15 +663,65 @@ public class ClientDrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 &&
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
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){
DrawCell cell = node.getData();
int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1;
for(int i = 0; i < 2 * lodMultiplitier; i++){
for(int j = 0; j < 2 * lodMultiplitier; j++){
for(int k = 0; k < 2 * lodMultiplitier; k++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k);
int lod = this.chunkTree.getMaxLevel() - node.getLevel();
int spacingFactor = (int)Math.pow(2,lod);
for(int i = 0; i < 2; i++){
for(int j = 0; j < 2; j++){
for(int k = 0; k < 2; k++){
Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i*spacingFactor,j*spacingFactor,k*spacingFactor);
if(
posToCheck.x >= 0 &&
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
@ -553,38 +745,40 @@ public class ClientDrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 &&
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;
}
}
}
}
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 < 2 * lodMultiplitier; x++){
for(int y = 0; y < 2 * lodMultiplitier; y++){
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(lodMultiplitier,x,y);
posToCheck = new Vector3i(cell.getWorldPos()).add(spacingFactor,x*highResSpacingFactor,y*highResSpacingFactor);
} break;
case X_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(-1,x,y);
posToCheck = new Vector3i(cell.getWorldPos()).add(0,x*highResSpacingFactor,y*highResSpacingFactor);
} break;
case Y_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,lodMultiplitier,y);
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,spacingFactor,y*highResSpacingFactor);
} break;
case Y_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,-1,y);
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,0,y*highResSpacingFactor);
} break;
case Z_POSITIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,lodMultiplitier);
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,spacingFactor);
} break;
case Z_NEGATIVE: {
posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,-1);
posToCheck = new Vector3i(cell.getWorldPos()).add(x*highResSpacingFactor,y*highResSpacingFactor,0);
} break;
}
if(
@ -594,7 +788,7 @@ public class ClientDrawCellManager {
posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
posToCheck.z >= 0 &&
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;
}
@ -685,6 +879,14 @@ public class ClientDrawCellManager {
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
*/
boolean multiVoxelType = false;
/**
* Number of failed generation attempts
*/
int failedGenerationAttempts = 0;
/**
@ -116,7 +121,7 @@ public class DrawCell {
boolean success = this.fillInData(lod);
Globals.profiler.endCpuSample();
if(!success){
this.setHasRequested(false);
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
return;
}
if(modelEntity != null){
@ -129,7 +134,7 @@ public class DrawCell {
success = this.fillInFaceData(chunkData,face,lod);
Globals.profiler.endCpuSample();
if(!success){
this.setHasRequested(false);
this.setFailedGenerationAttempts(this.getFailedGenerationAttempts() + 1);
return;
}
}
@ -192,23 +197,39 @@ public class DrawCell {
for(int y = 0; y < ChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){
for(int z = 0; z < ChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
worldPos.x + (x * spacingFactor) / ChunkData.CHUNK_SIZE,
worldPos.y + (y * spacingFactor) / ChunkData.CHUNK_SIZE,
worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE
worldPos.x + (x / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.y + (y / ChunkData.CHUNK_SIZE) * spacingFactor,
worldPos.z + (z / ChunkData.CHUNK_SIZE) * spacingFactor,
lod
);
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
if(!this.multiVoxelType){
@ -233,26 +254,30 @@ public class DrawCell {
*/
private boolean fillInFaceData(TransvoxelChunkData chunkData, DrawCellFace higherLODFace, int 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];
int[][] faceTypes = new int[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS];
//allocate face array
for(int x = 0; x < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; x++){
for(int y = 0; y < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; y++){
int worldCoordOffset1 = (x * higherResSpacing) / ChunkData.CHUNK_SIZE;
int worldCoordOffset2 = (y * higherResSpacing) / ChunkData.CHUNK_SIZE;
int worldCoordOffset1 = x / ChunkData.CHUNK_SIZE * higherResSpacing;
int worldCoordOffset2 = y / ChunkData.CHUNK_SIZE * higherResSpacing;
//solve coordinates relative to the face
int localCoord1 = (x * higherResSpacing) % ChunkData.CHUNK_SIZE;
int localCoord2 = (y * higherResSpacing) % ChunkData.CHUNK_SIZE;
int localCoord1 = x % ChunkData.CHUNK_SIZE;
int localCoord2 = y % ChunkData.CHUNK_SIZE;
//implicitly performing transforms to adapt from face-space to world & local space
switch(higherLODFace){
case X_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + (17 * mainSpacing) / ChunkData.CHUNK_SIZE,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
));
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + mainSpacing,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){
return false;
}
@ -268,11 +293,14 @@ public class DrawCell {
);
} break;
case X_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
));
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x,
worldPos.y + worldCoordOffset1,
worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){
return false;
}
@ -288,11 +316,14 @@ public class DrawCell {
);
} break;
case Y_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + (17 * mainSpacing) / ChunkData.CHUNK_SIZE,
worldPos.z + worldCoordOffset2
));
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + mainSpacing,
worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){
return false;
}
@ -308,11 +339,14 @@ public class DrawCell {
);
} break;
case Y_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y,
worldPos.z + worldCoordOffset2
));
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y,
worldPos.z + worldCoordOffset2
),
higherLOD
);
if(currentChunk == null){
return false;
}
@ -328,11 +362,14 @@ public class DrawCell {
);
} break;
case Z_POSITIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z + (17 * mainSpacing) / ChunkData.CHUNK_SIZE
));
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z + mainSpacing
),
higherLOD
);
if(currentChunk == null){
return false;
}
@ -348,11 +385,14 @@ public class DrawCell {
);
} break;
case Z_NEGATIVE: {
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z
));
ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
new Vector3i(
worldPos.x + worldCoordOffset1,
worldPos.y + worldCoordOffset2,
worldPos.z
),
higherLOD
);
if(currentChunk == null){
return false;
}
@ -443,6 +483,9 @@ public class DrawCell {
*/
public void setHasRequested(boolean hasRequested) {
this.hasRequested = hasRequested;
if(!this.hasRequested){
this.failedGenerationAttempts = 0;
}
}
/**
@ -469,6 +512,22 @@ public class DrawCell {
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.z >= 0 &&
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)){
//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){
if(Globals.clientTerrainManager != null){
return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ);
return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ,ChunkData.NO_STRIDE);
}
return true;
}
@ -442,7 +442,7 @@ public class DrawCellManager {
* @return The chunk data at the specified points
*/
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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
@ -25,6 +27,7 @@ import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
import electrosphere.renderer.model.Model;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.manager.ServerTerrainManager;
import io.github.studiorailgun.HashUtils;
/**
* 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)
*/
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
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
static List<TerrainChunkGenQueueItem> terrainChunkGenerationQueue = new CopyOnWriteArrayList<TerrainChunkGenQueueItem>();
/**
* Tracks what outgoing requests are currently active
*/
Map<Long,Integer> requestedMap = new ConcurrentHashMap<Long,Integer>();
/**
* 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.setVoxelWeight(weights);
terrainCache.addChunkDataToCache(
@ -106,6 +124,37 @@ public class ClientTerrainManager {
data
);
} 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:
LoggerInterface.loggerEngine.WARNING("ClientTerrainManager: unhandled network message of type" + message.getMessageSubtype());
break;
@ -114,6 +163,15 @@ public class ClientTerrainManager {
for(TerrainMessage message : bouncedMessages){
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();
Globals.profiler.endCpuSample();
}
@ -140,19 +198,21 @@ public class ClientTerrainManager {
* @param worldX the x position
* @param worldY the y position
* @param worldZ the z position
* @param stride The stride of the data
* @return true if the data exists, false otherwise
*/
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ);
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ, stride);
}
/**
* Checks if the terrain cache contains chunk data at a given world 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
*/
public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){
return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z);
public boolean containsChunkDataAtWorldPoint(Vector3i worldPos, int stride){
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 worldY the world y 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){
if(!this.terrainCache.hasRequested(worldX, worldY, worldZ)){
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
public boolean requestChunk(int worldX, int worldY, int worldZ, int stride){
boolean rVal = false;
lock.acquireUninterruptibly();
if(this.requestedMap.size() < MAX_CONCURRENT_REQUESTS && !this.requestedMap.containsKey(this.getRequestKey(worldX, worldY, worldZ, stride))){
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestReducedChunkDataMessage(
worldX,
worldY,
worldZ
worldZ,
stride
));
this.requestedMap.put(this.getRequestKey(worldX, worldY, worldZ, stride), 0);
rVal = true;
}
}
/**
* 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)
);
lock.release();
return rVal;
}
/**
@ -192,19 +245,21 @@ public class ClientTerrainManager {
* @param worldX The x component of the world coordinate
* @param worldY The y 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
*/
public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ);
public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ, stride);
}
/**
* Gets the chunk data at a given world position
* @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
*/
public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos){
return terrainCache.getSubChunkDataAtPoint(worldPos.x, worldPos.y, worldPos.z);
public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos, int stride){
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
* @return The number of chunks
* Gets the key for a given request
* @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(){
return this.terrainCache.getRequestedCellCount();
private Long getRequestKey(int worldX, int worldY, int worldZ, int stride){
return (long)HashUtils.cantorHash(worldY, worldZ, worldZ);
}
}

View File

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

View File

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

View File

@ -245,7 +245,7 @@ public class ClientLoading {
EntityCreationUtils.makeEntityDrawable(skybox, "Models/environment/skyboxSphere.fbx");
DrawableUtils.disableCulling(skybox);
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");
//cloud ring pseudo skybox
@ -297,10 +297,14 @@ public class ClientLoading {
Globals.clientSimulation.setLoadingTerrain(true);
//wait for all the terrain data to arrive
int i = 0;
while(blockForInit && Globals.clientDrawCellManager.updatedLastFrame() && Globals.threadManager.shouldKeepRunning()){
while(
blockForInit &&
!Globals.clientDrawCellManager.isInitialized() &&
Globals.threadManager.shouldKeepRunning()
){
i++;
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 {
TimeUnit.MILLISECONDS.sleep(10);

View File

@ -20,6 +20,11 @@ public class TerrainChunkData {
//texture ratio vector
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
* @param vertices
@ -27,14 +32,16 @@ public class TerrainChunkData {
* @param faceElements
* @param uvs
* @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.normals = normals;
this.faceElements = faceElements;
this.uvs = uvs;
this.textureSamplers = textureSamplers;
this.textureRatioVectors = textureRatioVectors;
this.lod = lod;
}
/**
@ -85,4 +92,12 @@ public class TerrainChunkData {
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
* @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());
Globals.clientTerrainManager.attachTerrainMessage(message);
} 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: {
//
//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
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(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.NO_STRIDE);
if(data != null){
data.updatePosition(
message.getvoxelX(),
@ -94,8 +98,8 @@ public class TerrainProtocol implements ClientProtocolTemplate<TerrainMessage> {
//
//mark all relevant drawcells as updateable
for(Vector3i worldPosToUpdate : positionsToUpdate){
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(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.NO_STRIDE);
if(data != null){
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 electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
@ -32,6 +33,12 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
);
return null;
}
case REQUESTREDUCEDCHUNKDATA: {
sendWorldSubChunkAsyncStrided(connectionHandler,
message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution()
);
return null;
}
default: {
} break;
}
@ -185,7 +192,65 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
};
//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();
}

View File

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

View File

@ -453,7 +453,7 @@ public class RenderingEngine {
//
// Projection and View matrix creation
//
Globals.projectionMatrix = new Matrix4f();
Globals.projectionMatrix = new Matrix4d();
Globals.viewMatrix = new Matrix4f();
verticalFOV = (float)(Globals.verticalFOV * Math.PI /180.0f);
//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
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData);
TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData, 0);
return rVal;
}
@ -892,14 +892,15 @@ public class TerrainChunkModelGeneration {
//bounding sphere logic
int distance = ServerTerrainChunk.CHUNK_DIMENSION / 2 * (int)Math.pow(2,data.getLOD());
mesh.updateBoundingSphere(
ServerTerrainChunk.CHUNK_DIMENSION,
ServerTerrainChunk.CHUNK_DIMENSION,
ServerTerrainChunk.CHUNK_DIMENSION,
distance,
distance,
distance,
(float)Math.sqrt(
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION +
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION +
ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION
distance * distance +
distance * distance +
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+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
@ -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]
);
//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 {
@ -1694,7 +1694,7 @@ public class TransvoxelModelGeneration {
}
//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;
}

View File

@ -22,7 +22,7 @@ public class DefaultChunkGenerator implements ChunkGenerator {
}
@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.
//Hence, width should actually be chunk dimension + 1
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
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) {
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride) {
ServerTerrainChunk returnedChunk;
//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

View File

@ -1,6 +1,5 @@
package electrosphere.server.terrain.generation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -24,7 +23,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
/**
* 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
@ -65,36 +64,14 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
}
@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");
ServerTerrainChunk rVal = null;
float[][][] weights;
int[][][] values;
float[][][] weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];;
int[][][] values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
if(worldX == 0 || worldZ == 0){
//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 {
try {
//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
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());
}
//stride value
int strideValue = (int)Math.pow(2,stride);
//presolve heightfield
float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
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");
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
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;
values[x][y][z] = voxel.type;
}
}
Globals.profiler.endCpuSample();
}
} catch(Exception ex){
ex.printStackTrace();
}
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
Globals.profiler.endCpuSample();
return rVal;
@ -144,7 +139,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkX The chunk x pos
* @param chunkY The chunk y 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 surfaceBiome The surface biome of the chunk
* @return The value of the chunk
@ -152,7 +147,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
float[][] heightfield,
float surfaceHeight,
TerrainModel terrainModel,
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());
}
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldX);
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
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);
Globals.profiler.endCpuSample();
if(realY < surfaceHeight - 1){
return getSubsurfaceVoxel(
worldX,worldY, worldZ,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight,
@ -181,7 +175,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
);
} else if(realY > flooredSurfaceHeight) {
return getOverSurfaceVoxel(
worldX,worldY, worldZ,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight,
@ -190,7 +184,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
);
} else {
return getSurfaceVoxel(
worldX,worldY, worldZ,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight,

View File

@ -13,9 +13,10 @@ public interface ChunkGenerator {
* @param worldX The x component
* @param worldY The y component
* @param worldZ The z component
* @param stride The stride of the data
* @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

View File

@ -4,6 +4,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.diskmap.ChunkDiskMap;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
@ -51,6 +52,11 @@ public class ChunkGenerationThread implements Runnable {
* The world z coordinate
*/
int worldZ;
/**
* The stride of the data
*/
int stride;
/**
* 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 worldY The world y 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
*/
public ChunkGenerationThread(
@ -72,6 +79,7 @@ public class ChunkGenerationThread implements Runnable {
ServerChunkCache chunkCache,
ChunkGenerator chunkGenerator,
int worldX, int worldY, int worldZ,
int stride,
Consumer<ServerTerrainChunk> onLoad
){
this.chunkDiskMap = chunkDiskMap;
@ -80,6 +88,7 @@ public class ChunkGenerationThread implements Runnable {
this.worldX = worldX;
this.worldY = worldY;
this.worldZ = worldZ;
this.stride = stride;
this.onLoad = onLoad;
}
@ -87,37 +96,41 @@ public class ChunkGenerationThread implements Runnable {
public void run() {
ServerTerrainChunk chunk = null;
int i = 0;
while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
if(chunkCache.containsChunk(worldX,worldY,worldZ)){
chunk = chunkCache.get(worldX,worldY,worldZ);
} else {
//pull from disk if it exists
if(chunkDiskMap != null){
if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
chunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ);
try {
while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
if(chunkCache.containsChunk(worldX, worldY, worldZ, stride)){
chunk = chunkCache.get(worldX, worldY, worldZ, stride);
} else {
//pull from disk if it exists
if(chunkDiskMap != null){
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){
chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
}
if(chunk != null){
chunkCache.add(worldX, worldY, worldZ, chunk);
try {
TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
}
if(chunk == null){
try {
TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i >= MAX_TIME_TO_WAIT){
throw new Error("Failed to resolve chunk!");
}
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.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import io.github.studiorailgun.HashUtils;
/**
* Caches chunk data on the server
*/
@ -16,7 +19,7 @@ public class ServerChunkCache {
/**
* Number of chunks to cache
*/
static final int CACHE_SIZE = 5000;
static final int CACHE_SIZE = 2500;
/**
* The size of the cache
@ -24,19 +27,39 @@ public class ServerChunkCache {
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)
*/
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
*/
Map<String, Boolean> queuedChunkMap = new HashMap<String,Boolean>();
Map<Long, Boolean> queuedChunkMap = new HashMap<Long,Boolean>();
/**
* The lock for thread safety
@ -49,7 +72,7 @@ public class ServerChunkCache {
*/
public Collection<ServerTerrainChunk> getContents(){
lock.acquireUninterruptibly();
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(chunkCache.values());
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
lock.release();
return rVal;
}
@ -59,7 +82,8 @@ public class ServerChunkCache {
*/
public void clear(){
lock.acquireUninterruptibly();
chunkCache.clear();
cacheMapFullRes.clear();
cacheMapHalfRes.clear();
lock.release();
}
@ -68,15 +92,17 @@ public class ServerChunkCache {
* @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 chunk
*/
public ServerTerrainChunk get(int worldX, int worldY, int worldZ){
public ServerTerrainChunk get(int worldX, int worldY, int worldZ, int stride){
ServerTerrainChunk rVal = null;
String key = this.getKey(worldX, worldY, worldZ);
Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly();
queryRecencyQueue.remove(key);
queryRecencyQueue.add(0, key);
rVal = this.chunkCache.get(key);
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
rVal = cache.get(key);
lock.release();
return rVal;
}
@ -86,13 +112,15 @@ public class ServerChunkCache {
* @param worldX The world x coordinate of the chunk
* @param worldY The world y 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
*/
public void add(int worldX, int worldY, int worldZ, ServerTerrainChunk chunk){
String key = this.getKey(worldX, worldY, worldZ);
public void add(int worldX, int worldY, int worldZ, int stride, ServerTerrainChunk chunk){
Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly();
queryRecencyQueue.add(0, key);
this.chunkCache.put(key, chunk);
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
cache.put(key, chunk);
lock.release();
}
@ -101,12 +129,14 @@ public class ServerChunkCache {
* @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 true if the cache contains this chunk, false otherwise
*/
public boolean containsChunk(int worldX, int worldY, int worldZ){
String key = this.getKey(worldX,worldY,worldZ);
public boolean containsChunk(int worldX, int worldY, int worldZ, int stride){
Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
boolean rVal = this.chunkCache.containsKey(key);
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
boolean rVal = cache.containsKey(key);
lock.release();
return rVal;
}
@ -118,8 +148,8 @@ public class ServerChunkCache {
* @param worldZ The z component
* @return The key
*/
public String getKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ;
public long getKey(int worldX, int worldY, int worldZ){
return HashUtils.cantorHash(worldX, worldY, worldZ);
}
/**
@ -130,7 +160,7 @@ public class ServerChunkCache {
* @return true if the chunk is already queued, false otherwise
*/
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();
boolean rVal = this.queuedChunkMap.containsKey(key);
lock.release();
@ -144,7 +174,7 @@ public class ServerChunkCache {
* @param worldZ The world z position of the chunk
*/
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();
this.queuedChunkMap.put(key,true);
lock.release();
@ -155,12 +185,41 @@ public class ServerChunkCache {
* @param worldX The world x position of the chunk
* @param worldY The world y 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){
String key = this.getKey(worldX,worldY,worldZ);
public void unqueueChunk(int worldX, int worldY, int worldZ, int stride){
Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
this.queuedChunkMap.remove(key);
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;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.server.terrain.diskmap.ChunkDiskMap;
@ -231,8 +232,8 @@ public class ServerTerrainManager {
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
ServerTerrainChunk returnedChunk = null;
if(chunkCache.containsChunk(worldX,worldY,worldZ)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ);
if(chunkCache.containsChunk(worldX,worldY,worldZ,ChunkData.NO_STRIDE)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ, ChunkData.NO_STRIDE);
} else {
//pull from disk if it exists
if(chunkDiskMap != null){
@ -242,9 +243,9 @@ public class ServerTerrainManager {
}
//generate if it does not exist
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();
return returnedChunk;
@ -255,11 +256,12 @@ public class ServerTerrainManager {
* @param worldX The world x position
* @param worldY The world y 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
*/
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");
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();
}
@ -287,8 +289,8 @@ public class ServerTerrainManager {
if(model != null){
model.addModification(modification);
}
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z)){
ServerTerrainChunk chunk = chunkCache.get(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, ChunkData.NO_STRIDE);
chunk.addModification(modification);
}
}

View File

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